feat: add 23 new CLI tools and integration guides
New tools across 13 categories: - Email/Newsletter: beehiiv, klaviyo, postmark, brevo, activecampaign - Data Enrichment: clearbit, apollo - CRO/Testing: hotjar, optimizely - Analytics: plausible - Scheduling: calendly, savvycal - Forms: typeform - Messaging: intercom - Social: buffer - Video: wistia - Payments: paddle - Affiliate: partnerstack - Reviews: trustpilot, g2 - Push: onesignal - Webinar: demio, livestorm Each tool includes a zero-dependency CLI and integration guide. Registry and CLI README updated with all new entries. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+65
-19
@@ -36,29 +36,52 @@ Every CLI reads credentials from environment variables:
|
||||
|
||||
| CLI | Environment Variable |
|
||||
|-----|---------------------|
|
||||
| `ahrefs` | `AHREFS_API_KEY` |
|
||||
| `activecampaign` | `ACTIVECAMPAIGN_API_KEY`, `ACTIVECAMPAIGN_API_URL` |
|
||||
| `adobe-analytics` | `ADOBE_CLIENT_ID`, `ADOBE_ACCESS_TOKEN` |
|
||||
| `ahrefs` | `AHREFS_API_KEY` |
|
||||
| `amplitude` | `AMPLITUDE_API_KEY`, `AMPLITUDE_SECRET_KEY` |
|
||||
| `apollo` | `APOLLO_API_KEY` |
|
||||
| `beehiiv` | `BEEHIIV_API_KEY` |
|
||||
| `brevo` | `BREVO_API_KEY` |
|
||||
| `buffer` | `BUFFER_API_KEY` |
|
||||
| `calendly` | `CALENDLY_API_KEY` |
|
||||
| `clearbit` | `CLEARBIT_API_KEY` |
|
||||
| `customer-io` | `CUSTOMERIO_APP_KEY` (App API), `CUSTOMERIO_SITE_ID` + `CUSTOMERIO_API_KEY` (Track API) |
|
||||
| `dataforseo` | `DATAFORSEO_LOGIN`, `DATAFORSEO_PASSWORD` |
|
||||
| `demio` | `DEMIO_API_KEY`, `DEMIO_API_SECRET` |
|
||||
| `dub` | `DUB_API_KEY` |
|
||||
| `g2` | `G2_API_TOKEN` |
|
||||
| `ga4` | `GA4_ACCESS_TOKEN` |
|
||||
| `google-ads` | `GOOGLE_ADS_TOKEN`, `GOOGLE_ADS_DEVELOPER_TOKEN` |
|
||||
| `google-search-console` | `GSC_ACCESS_TOKEN` |
|
||||
| `hotjar` | `HOTJAR_CLIENT_ID`, `HOTJAR_CLIENT_SECRET` |
|
||||
| `intercom` | `INTERCOM_API_KEY` |
|
||||
| `keywords-everywhere` | `KEYWORDS_EVERYWHERE_API_KEY` |
|
||||
| `kit` | `KIT_API_KEY`, `KIT_API_SECRET` |
|
||||
| `klaviyo` | `KLAVIYO_API_KEY` |
|
||||
| `linkedin-ads` | `LINKEDIN_ACCESS_TOKEN` |
|
||||
| `livestorm` | `LIVESTORM_API_TOKEN` |
|
||||
| `mailchimp` | `MAILCHIMP_API_KEY` |
|
||||
| `mention-me` | `MENTIONME_API_KEY` |
|
||||
| `meta-ads` | `META_ACCESS_TOKEN` |
|
||||
| `mixpanel` | `MIXPANEL_TOKEN` (ingestion), `MIXPANEL_API_KEY` + `MIXPANEL_SECRET` (query) |
|
||||
| `onesignal` | `ONESIGNAL_REST_API_KEY`, `ONESIGNAL_APP_ID` |
|
||||
| `optimizely` | `OPTIMIZELY_API_KEY` |
|
||||
| `paddle` | `PADDLE_API_KEY` |
|
||||
| `partnerstack` | `PARTNERSTACK_PUBLIC_KEY`, `PARTNERSTACK_SECRET_KEY` |
|
||||
| `plausible` | `PLAUSIBLE_API_KEY` |
|
||||
| `postmark` | `POSTMARK_API_KEY` |
|
||||
| `resend` | `RESEND_API_KEY` |
|
||||
| `rewardful` | `REWARDFUL_API_KEY` |
|
||||
| `savvycal` | `SAVVYCAL_API_KEY` |
|
||||
| `segment` | `SEGMENT_WRITE_KEY` (tracking), `SEGMENT_ACCESS_TOKEN` (profile) |
|
||||
| `semrush` | `SEMRUSH_API_KEY` |
|
||||
| `sendgrid` | `SENDGRID_API_KEY` |
|
||||
| `tiktok-ads` | `TIKTOK_ACCESS_TOKEN` |
|
||||
| `tolt` | `TOLT_API_KEY` |
|
||||
| `trustpilot` | `TRUSTPILOT_API_KEY`, `TRUSTPILOT_API_SECRET`, `TRUSTPILOT_BUSINESS_UNIT_ID` |
|
||||
| `typeform` | `TYPEFORM_API_KEY` |
|
||||
| `wistia` | `WISTIA_API_KEY` |
|
||||
| `zapier` | `ZAPIER_API_KEY` |
|
||||
|
||||
## Command Pattern
|
||||
@@ -98,27 +121,50 @@ DOMAINS=$(rewardful affiliates list | jq -r '.data[].email')
|
||||
|
||||
| CLI | Category | Tool |
|
||||
|-----|----------|------|
|
||||
| `resend.js` | Email | [Resend](https://resend.com) |
|
||||
| `sendgrid.js` | Email | [SendGrid](https://sendgrid.com) |
|
||||
| `mailchimp.js` | Email | [Mailchimp](https://mailchimp.com) |
|
||||
| `kit.js` | Email | [Kit](https://kit.com) |
|
||||
| `customer-io.js` | Email | [Customer.io](https://customer.io) |
|
||||
| `ahrefs.js` | SEO | [Ahrefs](https://ahrefs.com) |
|
||||
| `semrush.js` | SEO | [SEMrush](https://semrush.com) |
|
||||
| `google-search-console.js` | SEO | [Google Search Console](https://search.google.com/search-console) |
|
||||
| `dataforseo.js` | SEO | [DataForSEO](https://dataforseo.com) |
|
||||
| `keywords-everywhere.js` | SEO | [Keywords Everywhere](https://keywordseverywhere.com) |
|
||||
| `ga4.js` | Analytics | [Google Analytics 4](https://analytics.google.com) |
|
||||
| `mixpanel.js` | Analytics | [Mixpanel](https://mixpanel.com) |
|
||||
| `amplitude.js` | Analytics | [Amplitude](https://amplitude.com) |
|
||||
| `segment.js` | Analytics | [Segment](https://segment.com) |
|
||||
| `activecampaign.js` | Email/CRM | [ActiveCampaign](https://activecampaign.com) |
|
||||
| `adobe-analytics.js` | Analytics | [Adobe Analytics](https://business.adobe.com/products/analytics) |
|
||||
| `rewardful.js` | Referral | [Rewardful](https://www.getrewardful.com) |
|
||||
| `tolt.js` | Referral | [Tolt](https://tolt.io) |
|
||||
| `mention-me.js` | Referral | [Mention Me](https://www.mention-me.com) |
|
||||
| `ahrefs.js` | SEO | [Ahrefs](https://ahrefs.com) |
|
||||
| `amplitude.js` | Analytics | [Amplitude](https://amplitude.com) |
|
||||
| `apollo.js` | Data Enrichment | [Apollo.io](https://apollo.io) |
|
||||
| `beehiiv.js` | Newsletter | [Beehiiv](https://beehiiv.com) |
|
||||
| `brevo.js` | Email/SMS | [Brevo](https://brevo.com) |
|
||||
| `buffer.js` | Social | [Buffer](https://buffer.com) |
|
||||
| `calendly.js` | Scheduling | [Calendly](https://calendly.com) |
|
||||
| `clearbit.js` | Data Enrichment | [Clearbit](https://clearbit.com) |
|
||||
| `customer-io.js` | Email | [Customer.io](https://customer.io) |
|
||||
| `dataforseo.js` | SEO | [DataForSEO](https://dataforseo.com) |
|
||||
| `demio.js` | Webinar | [Demio](https://demio.com) |
|
||||
| `dub.js` | Links | [Dub.co](https://dub.co) |
|
||||
| `g2.js` | Reviews | [G2](https://g2.com) |
|
||||
| `ga4.js` | Analytics | [Google Analytics 4](https://analytics.google.com) |
|
||||
| `google-ads.js` | Ads | [Google Ads](https://ads.google.com) |
|
||||
| `meta-ads.js` | Ads | [Meta Ads](https://www.facebook.com/business/ads) |
|
||||
| `google-search-console.js` | SEO | [Google Search Console](https://search.google.com/search-console) |
|
||||
| `hotjar.js` | CRO | [Hotjar](https://hotjar.com) |
|
||||
| `intercom.js` | Messaging | [Intercom](https://intercom.com) |
|
||||
| `keywords-everywhere.js` | SEO | [Keywords Everywhere](https://keywordseverywhere.com) |
|
||||
| `kit.js` | Email | [Kit](https://kit.com) |
|
||||
| `klaviyo.js` | Email/SMS | [Klaviyo](https://klaviyo.com) |
|
||||
| `linkedin-ads.js` | Ads | [LinkedIn Ads](https://business.linkedin.com/marketing-solutions/ads) |
|
||||
| `livestorm.js` | Webinar | [Livestorm](https://livestorm.co) |
|
||||
| `mailchimp.js` | Email | [Mailchimp](https://mailchimp.com) |
|
||||
| `mention-me.js` | Referral | [Mention Me](https://www.mention-me.com) |
|
||||
| `meta-ads.js` | Ads | [Meta Ads](https://www.facebook.com/business/ads) |
|
||||
| `mixpanel.js` | Analytics | [Mixpanel](https://mixpanel.com) |
|
||||
| `onesignal.js` | Push | [OneSignal](https://onesignal.com) |
|
||||
| `optimizely.js` | A/B Testing | [Optimizely](https://optimizely.com) |
|
||||
| `paddle.js` | Payments | [Paddle](https://paddle.com) |
|
||||
| `partnerstack.js` | Affiliate | [PartnerStack](https://partnerstack.com) |
|
||||
| `plausible.js` | Analytics | [Plausible](https://plausible.io) |
|
||||
| `postmark.js` | Email | [Postmark](https://postmarkapp.com) |
|
||||
| `resend.js` | Email | [Resend](https://resend.com) |
|
||||
| `rewardful.js` | Referral | [Rewardful](https://www.getrewardful.com) |
|
||||
| `savvycal.js` | Scheduling | [SavvyCal](https://savvycal.com) |
|
||||
| `segment.js` | Analytics | [Segment](https://segment.com) |
|
||||
| `semrush.js` | SEO | [SEMrush](https://semrush.com) |
|
||||
| `sendgrid.js` | Email | [SendGrid](https://sendgrid.com) |
|
||||
| `tiktok-ads.js` | Ads | [TikTok Ads](https://ads.tiktok.com) |
|
||||
| `tolt.js` | Referral | [Tolt](https://tolt.io) |
|
||||
| `trustpilot.js` | Reviews | [Trustpilot](https://trustpilot.com) |
|
||||
| `typeform.js` | Forms | [Typeform](https://typeform.com) |
|
||||
| `wistia.js` | Video | [Wistia](https://wistia.com) |
|
||||
| `zapier.js` | Automation | [Zapier](https://zapier.com) |
|
||||
|
||||
Executable
+432
@@ -0,0 +1,432 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.ACTIVECAMPAIGN_API_KEY
|
||||
const API_URL = process.env.ACTIVECAMPAIGN_API_URL
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'ACTIVECAMPAIGN_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!API_URL) {
|
||||
console.error(JSON.stringify({ error: 'ACTIVECAMPAIGN_API_URL environment variable required (e.g. https://yourname.api-us1.com)' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const BASE_URL = `${API_URL.replace(/\/$/, '')}/api/3`
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Api-Token': API_KEY,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const limit = args.limit ? Number(args.limit) : 20
|
||||
const offset = args.offset ? Number(args.offset) : 0
|
||||
|
||||
switch (cmd) {
|
||||
case 'contacts':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
params.set('offset', String(offset))
|
||||
if (args.email) params.set('email', args.email)
|
||||
if (args.search) params.set('search', args.search)
|
||||
if (args['list-id']) params.set('listid', args['list-id'])
|
||||
if (args.status) params.set('status', args.status)
|
||||
result = await api('GET', `/contacts?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/contacts/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const contact = { email }
|
||||
if (args['first-name']) contact.firstName = args['first-name']
|
||||
if (args['last-name']) contact.lastName = args['last-name']
|
||||
if (args.phone) contact.phone = args.phone
|
||||
result = await api('POST', '/contacts', { contact })
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const contact = {}
|
||||
if (args.email) contact.email = args.email
|
||||
if (args['first-name']) contact.firstName = args['first-name']
|
||||
if (args['last-name']) contact.lastName = args['last-name']
|
||||
if (args.phone) contact.phone = args.phone
|
||||
result = await api('PUT', `/contacts/${id}`, { contact })
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/contacts/${id}`)
|
||||
break
|
||||
}
|
||||
case 'sync': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const contact = { email }
|
||||
if (args['first-name']) contact.firstName = args['first-name']
|
||||
if (args['last-name']) contact.lastName = args['last-name']
|
||||
if (args.phone) contact.phone = args.phone
|
||||
result = await api('POST', '/contact/sync', { contact })
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown contacts subcommand. Use: list, get, create, update, delete, sync' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'lists':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
params.set('offset', String(offset))
|
||||
result = await api('GET', `/lists?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/lists/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
const list = { name }
|
||||
if (args['string-id']) list.stringid = args['string-id']
|
||||
if (args['sender-url']) list.sender_url = args['sender-url']
|
||||
if (args['sender-reminder']) list.sender_reminder = args['sender-reminder']
|
||||
result = await api('POST', '/lists', { list })
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/lists/${id}`)
|
||||
break
|
||||
}
|
||||
case 'subscribe': {
|
||||
const listId = args['list-id'] || args.id
|
||||
const contactId = args['contact-id']
|
||||
if (!listId) { result = { error: '--list-id required' }; break }
|
||||
if (!contactId) { result = { error: '--contact-id required' }; break }
|
||||
result = await api('POST', '/contactLists', {
|
||||
contactList: { list: listId, contact: contactId, status: 1 }
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'unsubscribe': {
|
||||
const listId = args['list-id'] || args.id
|
||||
const contactId = args['contact-id']
|
||||
if (!listId) { result = { error: '--list-id required' }; break }
|
||||
if (!contactId) { result = { error: '--contact-id required' }; break }
|
||||
result = await api('POST', '/contactLists', {
|
||||
contactList: { list: listId, contact: contactId, status: 2 }
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown lists subcommand. Use: list, get, create, delete, subscribe, unsubscribe' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'campaigns':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
params.set('offset', String(offset))
|
||||
result = await api('GET', `/campaigns?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/campaigns/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown campaigns subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'deals':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
params.set('offset', String(offset))
|
||||
if (args.search) params.set('search', args.search)
|
||||
if (args.stage) params.set('filters[stage]', args.stage)
|
||||
if (args.owner) params.set('filters[owner]', args.owner)
|
||||
result = await api('GET', `/deals?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/deals/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const title = args.title
|
||||
if (!title) { result = { error: '--title required' }; break }
|
||||
const deal = { title }
|
||||
if (args.value) deal.value = Number(args.value)
|
||||
if (args.currency) deal.currency = args.currency
|
||||
if (args.pipeline) deal.group = args.pipeline
|
||||
if (args.stage) deal.stage = args.stage
|
||||
if (args.owner) deal.owner = args.owner
|
||||
if (args['contact-id']) deal.contact = args['contact-id']
|
||||
result = await api('POST', '/deals', { deal })
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const deal = {}
|
||||
if (args.title) deal.title = args.title
|
||||
if (args.value) deal.value = Number(args.value)
|
||||
if (args.stage) deal.stage = args.stage
|
||||
if (args.owner) deal.owner = args.owner
|
||||
if (args.status) deal.status = Number(args.status)
|
||||
result = await api('PUT', `/deals/${id}`, { deal })
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/deals/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown deals subcommand. Use: list, get, create, update, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'automations':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
params.set('offset', String(offset))
|
||||
result = await api('GET', `/automations?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/automations/${id}`)
|
||||
break
|
||||
}
|
||||
case 'add-contact': {
|
||||
const automationId = args.id
|
||||
const contactEmail = args.email
|
||||
if (!automationId) { result = { error: '--id required (automation ID)' }; break }
|
||||
if (!contactEmail) { result = { error: '--email required' }; break }
|
||||
result = await api('POST', '/contactAutomations', {
|
||||
contactAutomation: { contact: contactEmail, automation: automationId }
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown automations subcommand. Use: list, get, add-contact' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'tags':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
params.set('offset', String(offset))
|
||||
if (args.search) params.set('search', args.search)
|
||||
result = await api('GET', `/tags?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/tags/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
result = await api('POST', '/tags', {
|
||||
tag: { tag: name, tagType: args.type || 'contact' }
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/tags/${id}`)
|
||||
break
|
||||
}
|
||||
case 'add-to-contact': {
|
||||
const tagId = args['tag-id']
|
||||
const contactId = args['contact-id']
|
||||
if (!tagId) { result = { error: '--tag-id required' }; break }
|
||||
if (!contactId) { result = { error: '--contact-id required' }; break }
|
||||
result = await api('POST', '/contactTags', {
|
||||
contactTag: { contact: contactId, tag: tagId }
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'remove-from-contact': {
|
||||
const contactTagId = args.id
|
||||
if (!contactTagId) { result = { error: '--id required (contactTag ID)' }; break }
|
||||
result = await api('DELETE', `/contactTags/${contactTagId}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown tags subcommand. Use: list, get, create, delete, add-to-contact, remove-from-contact' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'pipelines':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
params.set('offset', String(offset))
|
||||
result = await api('GET', `/dealGroups?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/dealGroups/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown pipelines subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'webhooks':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
params.set('offset', String(offset))
|
||||
result = await api('GET', `/webhooks?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/webhooks/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
const url = args.url
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
if (!url) { result = { error: '--url required' }; break }
|
||||
const events = args.events?.split(',') || ['subscribe']
|
||||
const sources = args.sources?.split(',') || ['public', 'admin', 'api', 'system']
|
||||
result = await api('POST', '/webhooks', {
|
||||
webhook: { name, url, events, sources }
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/webhooks/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown webhooks subcommand. Use: list, get, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'users':
|
||||
switch (sub) {
|
||||
case 'me':
|
||||
result = await api('GET', '/users/me')
|
||||
break
|
||||
case 'list':
|
||||
result = await api('GET', '/users')
|
||||
break
|
||||
default:
|
||||
result = { error: 'Unknown users subcommand. Use: me, list' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
contacts: 'contacts [list | get --id <id> | create --email <email> | update --id <id> | delete --id <id> | sync --email <email>]',
|
||||
lists: 'lists [list | get --id <id> | create --name <name> | delete --id <id> | subscribe --list-id <lid> --contact-id <cid> | unsubscribe --list-id <lid> --contact-id <cid>]',
|
||||
campaigns: 'campaigns [list | get --id <id>]',
|
||||
deals: 'deals [list | get --id <id> | create --title <title> | update --id <id> | delete --id <id>]',
|
||||
automations: 'automations [list | get --id <id> | add-contact --id <aid> --email <email>]',
|
||||
tags: 'tags [list | get --id <id> | create --name <name> | delete --id <id> | add-to-contact --tag-id <tid> --contact-id <cid>]',
|
||||
pipelines: 'pipelines [list | get --id <id>]',
|
||||
webhooks: 'webhooks [list | get --id <id> | create --name <name> --url <url> | delete --id <id>]',
|
||||
users: 'users [me | list]',
|
||||
options: '--limit <n> --offset <n> --search <query> --email <email>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.APOLLO_API_KEY
|
||||
const BASE_URL = 'https://api.apollo.io/api/v1'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'APOLLO_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'x-api-key': API_KEY,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const page = args.page ? Number(args.page) : 1
|
||||
const perPage = args['per-page'] ? Number(args['per-page']) : 25
|
||||
|
||||
switch (cmd) {
|
||||
case 'people':
|
||||
switch (sub) {
|
||||
case 'search': {
|
||||
const body = { page, per_page: perPage }
|
||||
if (args.titles) body.person_titles = args.titles.split(',')
|
||||
if (args.locations) body.person_locations = args.locations.split(',')
|
||||
if (args.seniorities) body.person_seniorities = args.seniorities.split(',')
|
||||
if (args['employee-ranges']) body.organization_num_employees_ranges = args['employee-ranges'].split(',').map(r => r.trim())
|
||||
if (args.keywords) body.q_keywords = args.keywords
|
||||
result = await api('POST', '/mixed_people/api_search', body)
|
||||
break
|
||||
}
|
||||
case 'enrich': {
|
||||
const body = {}
|
||||
if (args.email) body.email = args.email
|
||||
if (args['first-name']) body.first_name = args['first-name']
|
||||
if (args['last-name']) body.last_name = args['last-name']
|
||||
if (args.domain) body.domain = args.domain
|
||||
if (args.linkedin) body.linkedin_url = args.linkedin
|
||||
if (!args.email && !args.linkedin && !(args['first-name'] && args.domain)) {
|
||||
result = { error: '--email, --linkedin, or --first-name + --domain required' }
|
||||
break
|
||||
}
|
||||
result = await api('POST', '/people/match', body)
|
||||
break
|
||||
}
|
||||
case 'bulk-enrich': {
|
||||
const emails = args.emails?.split(',')
|
||||
if (!emails) { result = { error: '--emails required (comma-separated)' }; break }
|
||||
const details = emails.map(email => ({ email: email.trim() }))
|
||||
result = await api('POST', '/people/bulk_match', { details })
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown people subcommand. Use: search, enrich, bulk-enrich' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'organizations':
|
||||
switch (sub) {
|
||||
case 'search': {
|
||||
const body = { page, per_page: perPage }
|
||||
if (args.locations) body.organization_locations = args.locations.split(',')
|
||||
if (args['employee-ranges']) body.organization_num_employees_ranges = args['employee-ranges'].split(',').map(r => r.trim())
|
||||
if (args.keywords) body.q_keywords = args.keywords
|
||||
result = await api('POST', '/mixed_companies/search', body)
|
||||
break
|
||||
}
|
||||
case 'enrich': {
|
||||
const domain = args.domain
|
||||
if (!domain) { result = { error: '--domain required' }; break }
|
||||
result = await api('POST', '/organizations/enrich', { domain })
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown organizations subcommand. Use: search, enrich' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
people: {
|
||||
search: 'people search [--titles <t1,t2>] [--locations <l1,l2>] [--seniorities <s1,s2>] [--employee-ranges <1,100>] [--keywords <kw>] [--page <n>]',
|
||||
enrich: 'people enrich --email <email> | --first-name <name> --last-name <name> --domain <domain> | --linkedin <url>',
|
||||
'bulk-enrich': 'people bulk-enrich --emails <e1,e2,e3>',
|
||||
},
|
||||
organizations: {
|
||||
search: 'organizations search [--locations <l1,l2>] [--employee-ranges <1,100>] [--keywords <kw>] [--page <n>]',
|
||||
enrich: 'organizations enrich --domain <domain>',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+242
@@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.BEEHIIV_API_KEY
|
||||
const BASE_URL = 'https://api.beehiiv.com/v2'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'BEEHIIV_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const pubId = args.publication || args.pub
|
||||
const limit = args.limit ? Number(args.limit) : 10
|
||||
|
||||
switch (cmd) {
|
||||
case 'publications':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', '/publications')
|
||||
break
|
||||
case 'get': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
result = await api('GET', `/publications/${pubId}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown publications subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'subscriptions':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
if (args.email) params.set('email', args.email)
|
||||
if (args.status) params.set('status', args.status)
|
||||
if (args.tier) params.set('tier', args.tier)
|
||||
if (args.cursor) params.set('cursor', args.cursor)
|
||||
if (args.expand) params.set('expand[]', args.expand)
|
||||
result = await api('GET', `/publications/${pubId}/subscriptions?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
const subId = args.id
|
||||
if (!subId) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/publications/${pubId}/subscriptions/${subId}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const body = { email }
|
||||
if (args['reactivate-existing']) body.reactivate_existing = true
|
||||
if (args['send-welcome-email']) body.send_welcome_email = true
|
||||
if (args['utm-source']) body.utm_source = args['utm-source']
|
||||
if (args['utm-medium']) body.utm_medium = args['utm-medium']
|
||||
if (args['utm-campaign']) body.utm_campaign = args['utm-campaign']
|
||||
if (args.tier) body.tier = args.tier
|
||||
if (args['referring-site']) body.referring_site = args['referring-site']
|
||||
result = await api('POST', `/publications/${pubId}/subscriptions`, body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
const subId = args.id
|
||||
if (!subId) { result = { error: '--id required' }; break }
|
||||
const body = {}
|
||||
if (args.tier) body.tier = args.tier
|
||||
result = await api('PUT', `/publications/${pubId}/subscriptions/${subId}`, body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
const subId = args.id
|
||||
if (!subId) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/publications/${pubId}/subscriptions/${subId}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown subscriptions subcommand. Use: list, get, create, update, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'posts':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
if (args.status) params.set('status', args.status)
|
||||
if (args.cursor) params.set('cursor', args.cursor)
|
||||
result = await api('GET', `/publications/${pubId}/posts?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
const postId = args.id
|
||||
if (!postId) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/publications/${pubId}/posts/${postId}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
const title = args.title
|
||||
if (!title) { result = { error: '--title required' }; break }
|
||||
const body = { title }
|
||||
if (args.subtitle) body.subtitle = args.subtitle
|
||||
if (args.content) body.content = args.content
|
||||
if (args.status) body.status = args.status
|
||||
result = await api('POST', `/publications/${pubId}/posts`, body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
const postId = args.id
|
||||
if (!postId) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/publications/${pubId}/posts/${postId}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown posts subcommand. Use: list, get, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'segments':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
result = await api('GET', `/publications/${pubId}/segments`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
const segId = args.id
|
||||
if (!segId) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/publications/${pubId}/segments/${segId}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown segments subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'automations':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
result = await api('GET', `/publications/${pubId}/automations`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
const autoId = args.id
|
||||
if (!autoId) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/publications/${pubId}/automations/${autoId}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown automations subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'referral-program':
|
||||
switch (sub) {
|
||||
case 'get': {
|
||||
if (!pubId) { result = { error: '--publication required' }; break }
|
||||
result = await api('GET', `/publications/${pubId}/referral_program`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown referral-program subcommand. Use: get' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
publications: 'publications [list | get --publication <id>]',
|
||||
subscriptions: 'subscriptions [list | get --id <id> | create --email <email> | update --id <id> | delete --id <id>] --publication <id>',
|
||||
posts: 'posts [list | get --id <id> | create --title <title> | delete --id <id>] --publication <id>',
|
||||
segments: 'segments [list | get --id <id>] --publication <id>',
|
||||
automations: 'automations [list | get --id <id>] --publication <id>',
|
||||
'referral-program': 'referral-program [get] --publication <id>',
|
||||
options: '--publication <id> --limit <n> --email <email> --status <status> --tier <tier>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+365
@@ -0,0 +1,365 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.BREVO_API_KEY
|
||||
const BASE_URL = 'https://api.brevo.com/v3'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'BREVO_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'api-key': API_KEY,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const limit = args.limit ? Number(args.limit) : 50
|
||||
const offset = args.offset ? Number(args.offset) : 0
|
||||
|
||||
switch (cmd) {
|
||||
case 'account':
|
||||
switch (sub) {
|
||||
case 'get':
|
||||
result = await api('GET', '/account')
|
||||
break
|
||||
default:
|
||||
result = { error: 'Unknown account subcommand. Use: get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'contacts':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
params.set('offset', String(offset))
|
||||
if (args.sort) params.set('sort', args.sort)
|
||||
result = await api('GET', `/contacts?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id || args.email
|
||||
if (!id) { result = { error: '--id or --email required' }; break }
|
||||
result = await api('GET', `/contacts/${encodeURIComponent(id)}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const body = { email }
|
||||
if (args['first-name'] || args['last-name']) {
|
||||
body.attributes = {}
|
||||
if (args['first-name']) body.attributes.FIRSTNAME = args['first-name']
|
||||
if (args['last-name']) body.attributes.LASTNAME = args['last-name']
|
||||
}
|
||||
if (args['list-ids']) body.listIds = args['list-ids'].split(',').map(Number)
|
||||
result = await api('POST', '/contacts', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id || args.email
|
||||
if (!id) { result = { error: '--id or --email required' }; break }
|
||||
const body = {}
|
||||
if (args['first-name'] || args['last-name']) {
|
||||
body.attributes = {}
|
||||
if (args['first-name']) body.attributes.FIRSTNAME = args['first-name']
|
||||
if (args['last-name']) body.attributes.LASTNAME = args['last-name']
|
||||
}
|
||||
if (args['list-ids']) body.listIds = args['list-ids'].split(',').map(Number)
|
||||
if (args['unlink-list-ids']) body.unlinkListIds = args['unlink-list-ids'].split(',').map(Number)
|
||||
result = await api('PUT', `/contacts/${encodeURIComponent(id)}`, body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id || args.email
|
||||
if (!id) { result = { error: '--id or --email required' }; break }
|
||||
result = await api('DELETE', `/contacts/${encodeURIComponent(id)}`)
|
||||
break
|
||||
}
|
||||
case 'import': {
|
||||
const emails = args.emails?.split(',')
|
||||
if (!emails) { result = { error: '--emails required (comma-separated)' }; break }
|
||||
const body = {
|
||||
jsonBody: emails.map(e => ({ email: e.trim() })),
|
||||
}
|
||||
if (args['list-ids']) body.listIds = args['list-ids'].split(',').map(Number)
|
||||
result = await api('POST', '/contacts/import', body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown contacts subcommand. Use: list, get, create, update, delete, import' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'lists':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
params.set('offset', String(offset))
|
||||
if (args.sort) params.set('sort', args.sort)
|
||||
result = await api('GET', `/contacts/lists?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/contacts/lists/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
const body = { name, folderId: args.folder ? Number(args.folder) : 1 }
|
||||
result = await api('POST', '/contacts/lists', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const body = {}
|
||||
if (args.name) body.name = args.name
|
||||
if (args.folder) body.folderId = Number(args.folder)
|
||||
result = await api('PUT', `/contacts/lists/${id}`, body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/contacts/lists/${id}`)
|
||||
break
|
||||
}
|
||||
case 'contacts': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
params.set('offset', String(offset))
|
||||
result = await api('GET', `/contacts/lists/${id}/contacts?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'add-contacts': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const emails = args.emails?.split(',')
|
||||
if (!emails) { result = { error: '--emails required (comma-separated)' }; break }
|
||||
result = await api('POST', `/contacts/lists/${id}/contacts/add`, { emails })
|
||||
break
|
||||
}
|
||||
case 'remove-contacts': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const emails = args.emails?.split(',')
|
||||
if (!emails) { result = { error: '--emails required (comma-separated)' }; break }
|
||||
result = await api('POST', `/contacts/lists/${id}/contacts/remove`, { emails })
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown lists subcommand. Use: list, get, create, update, delete, contacts, add-contacts, remove-contacts' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'email':
|
||||
switch (sub) {
|
||||
case 'send': {
|
||||
const senderEmail = args.from
|
||||
const to = args.to
|
||||
const subject = args.subject
|
||||
if (!senderEmail) { result = { error: '--from required' }; break }
|
||||
if (!to) { result = { error: '--to required' }; break }
|
||||
if (!subject) { result = { error: '--subject required' }; break }
|
||||
const body = {
|
||||
sender: { email: senderEmail },
|
||||
to: to.split(',').map(e => ({ email: e.trim() })),
|
||||
subject,
|
||||
}
|
||||
if (args['sender-name']) body.sender.name = args['sender-name']
|
||||
if (args.html) body.htmlContent = args.html
|
||||
if (args.text) body.textContent = args.text
|
||||
if (!args.html && !args.text) body.textContent = ''
|
||||
if (args['reply-to']) body.replyTo = { email: args['reply-to'] }
|
||||
if (args.tags) body.tags = args.tags.split(',')
|
||||
result = await api('POST', '/smtp/email', body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown email subcommand. Use: send' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'campaigns':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
params.set('offset', String(offset))
|
||||
if (args.type) params.set('type', args.type)
|
||||
if (args.status) params.set('status', args.status)
|
||||
if (args.sort) params.set('sort', args.sort)
|
||||
result = await api('GET', `/emailCampaigns?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/emailCampaigns/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
const body = {
|
||||
name,
|
||||
sender: { email: args.from || '' },
|
||||
subject: args.subject || '',
|
||||
}
|
||||
if (args['sender-name']) body.sender.name = args['sender-name']
|
||||
if (args.html) body.htmlContent = args.html
|
||||
if (args['list-ids']) body.recipients = { listIds: args['list-ids'].split(',').map(Number) }
|
||||
result = await api('POST', '/emailCampaigns', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const body = {}
|
||||
if (args.name) body.name = args.name
|
||||
if (args.subject) body.subject = args.subject
|
||||
if (args.html) body.htmlContent = args.html
|
||||
result = await api('PUT', `/emailCampaigns/${id}`, body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/emailCampaigns/${id}`)
|
||||
break
|
||||
}
|
||||
case 'send-now': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('POST', `/emailCampaigns/${id}/sendNow`)
|
||||
break
|
||||
}
|
||||
case 'send-test': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const emails = args.emails?.split(',')
|
||||
if (!emails) { result = { error: '--emails required (comma-separated)' }; break }
|
||||
result = await api('POST', `/emailCampaigns/${id}/sendTest`, { emailTo: emails })
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown campaigns subcommand. Use: list, get, create, update, delete, send-now, send-test' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'sms':
|
||||
switch (sub) {
|
||||
case 'send': {
|
||||
const sender = args.from
|
||||
const recipient = args.to
|
||||
const content = args.content
|
||||
if (!sender) { result = { error: '--from required (sender name)' }; break }
|
||||
if (!recipient) { result = { error: '--to required (phone number)' }; break }
|
||||
if (!content) { result = { error: '--content required' }; break }
|
||||
result = await api('POST', '/transactionalSMS/sms', {
|
||||
sender,
|
||||
recipient,
|
||||
content,
|
||||
type: args.type || 'transactional',
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'campaigns': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
params.set('offset', String(offset))
|
||||
if (args.status) params.set('status', args.status)
|
||||
result = await api('GET', `/smsCampaigns?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown sms subcommand. Use: send, campaigns' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'senders':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', '/senders')
|
||||
break
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
const email = args.email
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
result = await api('POST', '/senders', { name, email })
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown senders subcommand. Use: list, create' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
account: 'account [get]',
|
||||
contacts: 'contacts [list | get --email <email> | create --email <email> | update --email <email> | delete --email <email> | import --emails <e1,e2>]',
|
||||
lists: 'lists [list | get --id <id> | create --name <name> | delete --id <id> | contacts --id <id> | add-contacts --id <id> --emails <e1,e2> | remove-contacts --id <id> --emails <e1,e2>]',
|
||||
email: 'email [send --from <from> --to <to> --subject <subj>]',
|
||||
campaigns: 'campaigns [list | get --id <id> | create --name <name> | send-now --id <id> | send-test --id <id> --emails <e1,e2>]',
|
||||
sms: 'sms [send --from <name> --to <phone> --content <msg> | campaigns]',
|
||||
senders: 'senders [list | create --name <name> --email <email>]',
|
||||
options: '--limit <n> --offset <n> --status <status>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+246
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.BUFFER_API_KEY
|
||||
const BASE_URL = 'https://api.bufferapp.com/1'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'BUFFER_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
if (body && method !== 'GET') {
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
}
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers,
|
||||
body: body ? new URLSearchParams(body).toString() : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
async function apiJson(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
|
||||
switch (cmd) {
|
||||
case 'user':
|
||||
switch (sub) {
|
||||
case 'info':
|
||||
result = await api('GET', '/user.json')
|
||||
break
|
||||
case 'deauthorize':
|
||||
result = await api('POST', '/user/deauthorize.json')
|
||||
break
|
||||
default:
|
||||
result = { error: 'Unknown user subcommand. Use: info, deauthorize' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'profiles':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', '/profiles.json')
|
||||
break
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/profiles/${id}.json`)
|
||||
break
|
||||
}
|
||||
case 'schedules': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (profile ID)' }; break }
|
||||
result = await api('GET', `/profiles/${id}/schedules.json`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown profiles subcommand. Use: list, get, schedules' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'updates':
|
||||
switch (sub) {
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (update ID)' }; break }
|
||||
result = await api('GET', `/updates/${id}.json`)
|
||||
break
|
||||
}
|
||||
case 'pending': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (profile ID)' }; break }
|
||||
const params = new URLSearchParams()
|
||||
if (args.page) params.set('page', args.page)
|
||||
if (args.count) params.set('count', args.count)
|
||||
if (args.since) params.set('since', args.since)
|
||||
const qs = params.toString() ? `?${params.toString()}` : ''
|
||||
result = await api('GET', `/profiles/${id}/updates/pending.json${qs}`)
|
||||
break
|
||||
}
|
||||
case 'sent': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (profile ID)' }; break }
|
||||
const params = new URLSearchParams()
|
||||
if (args.page) params.set('page', args.page)
|
||||
if (args.count) params.set('count', args.count)
|
||||
if (args.since) params.set('since', args.since)
|
||||
const qs = params.toString() ? `?${params.toString()}` : ''
|
||||
result = await api('GET', `/profiles/${id}/updates/sent.json${qs}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const profileIds = args['profile-ids']
|
||||
const text = args.text
|
||||
if (!profileIds) { result = { error: '--profile-ids required (comma-separated)' }; break }
|
||||
if (!text) { result = { error: '--text required' }; break }
|
||||
const body = { text }
|
||||
profileIds.split(',').forEach(id => {
|
||||
if (!body['profile_ids[]']) body['profile_ids[]'] = []
|
||||
})
|
||||
const formBody = new URLSearchParams()
|
||||
formBody.append('text', text)
|
||||
profileIds.split(',').forEach(id => formBody.append('profile_ids[]', id.trim()))
|
||||
if (args['scheduled-at']) formBody.append('scheduled_at', args['scheduled-at'])
|
||||
if (args.now) formBody.append('now', 'true')
|
||||
if (args.top) formBody.append('top', 'true')
|
||||
if (args.shorten) formBody.append('shorten', 'true')
|
||||
const res = await fetch(`${BASE_URL}/updates/create.json`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: formBody.toString(),
|
||||
})
|
||||
const resText = await res.text()
|
||||
try { result = JSON.parse(resText) } catch { result = { status: res.status, body: resText } }
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
const text = args.text
|
||||
if (!id) { result = { error: '--id required (update ID)' }; break }
|
||||
if (!text) { result = { error: '--text required' }; break }
|
||||
const body = { text }
|
||||
if (args['scheduled-at']) body.scheduled_at = args['scheduled-at']
|
||||
result = await api('POST', `/updates/${id}/update.json`, body)
|
||||
break
|
||||
}
|
||||
case 'share': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (update ID)' }; break }
|
||||
result = await api('POST', `/updates/${id}/share.json`)
|
||||
break
|
||||
}
|
||||
case 'destroy': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (update ID)' }; break }
|
||||
result = await api('POST', `/updates/${id}/destroy.json`)
|
||||
break
|
||||
}
|
||||
case 'reorder': {
|
||||
const id = args.id
|
||||
const order = args.order
|
||||
if (!id) { result = { error: '--id required (profile ID)' }; break }
|
||||
if (!order) { result = { error: '--order required (comma-separated update IDs)' }; break }
|
||||
const formBody = new URLSearchParams()
|
||||
order.split(',').forEach(uid => formBody.append('order[]', uid.trim()))
|
||||
const res = await fetch(`${BASE_URL}/profiles/${id}/updates/reorder.json`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: formBody.toString(),
|
||||
})
|
||||
const resText = await res.text()
|
||||
try { result = JSON.parse(resText) } catch { result = { status: res.status, body: resText } }
|
||||
break
|
||||
}
|
||||
case 'shuffle': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (profile ID)' }; break }
|
||||
result = await api('POST', `/profiles/${id}/updates/shuffle.json`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown updates subcommand. Use: get, pending, sent, create, update, share, destroy, reorder, shuffle' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'info':
|
||||
result = await api('GET', '/info/configuration.json')
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
user: 'user [info | deauthorize]',
|
||||
profiles: 'profiles [list | get --id <id> | schedules --id <id>]',
|
||||
updates: 'updates [get --id <id> | pending --id <profile-id> | sent --id <profile-id> | create --profile-ids <ids> --text <text> [--scheduled-at <time>] [--now] | update --id <id> --text <text> | share --id <id> | destroy --id <id> | reorder --id <profile-id> --order <id1,id2> | shuffle --id <profile-id>]',
|
||||
info: 'info',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+250
@@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.CALENDLY_API_KEY
|
||||
const BASE_URL = 'https://api.calendly.com'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'CALENDLY_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const count = args.count ? Number(args.count) : 20
|
||||
|
||||
switch (cmd) {
|
||||
case 'users':
|
||||
switch (sub) {
|
||||
case 'me':
|
||||
result = await api('GET', '/users/me')
|
||||
break
|
||||
default:
|
||||
result = { error: 'Unknown users subcommand. Use: me' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'event-types':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const user = args.user
|
||||
const org = args.organization
|
||||
if (!user && !org) { result = { error: '--user or --organization URI required' }; break }
|
||||
const params = new URLSearchParams()
|
||||
if (user) params.set('user', user)
|
||||
if (org) params.set('organization', org)
|
||||
if (args.active) params.set('active', args.active)
|
||||
params.set('count', String(count))
|
||||
if (args['page-token']) params.set('page_token', args['page-token'])
|
||||
result = await api('GET', `/event_types?${params}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const uuid = args.uuid
|
||||
if (!uuid) { result = { error: '--uuid required' }; break }
|
||||
result = await api('GET', `/event_types/${uuid}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown event-types subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'events':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const user = args.user
|
||||
const org = args.organization
|
||||
if (!user && !org) { result = { error: '--user or --organization URI required' }; break }
|
||||
const params = new URLSearchParams()
|
||||
if (user) params.set('user', user)
|
||||
if (org) params.set('organization', org)
|
||||
if (args['min-start']) params.set('min_start_time', args['min-start'])
|
||||
if (args['max-start']) params.set('max_start_time', args['max-start'])
|
||||
if (args.status) params.set('status', args.status)
|
||||
params.set('count', String(count))
|
||||
if (args['page-token']) params.set('page_token', args['page-token'])
|
||||
if (args.sort) params.set('sort', args.sort)
|
||||
result = await api('GET', `/scheduled_events?${params}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const uuid = args.uuid
|
||||
if (!uuid) { result = { error: '--uuid required' }; break }
|
||||
result = await api('GET', `/scheduled_events/${uuid}`)
|
||||
break
|
||||
}
|
||||
case 'cancel': {
|
||||
const uuid = args.uuid
|
||||
if (!uuid) { result = { error: '--uuid required' }; break }
|
||||
const body = {}
|
||||
if (args.reason) body.reason = args.reason
|
||||
result = await api('POST', `/scheduled_events/${uuid}/cancellation`, body)
|
||||
break
|
||||
}
|
||||
case 'invitees': {
|
||||
const uuid = args.uuid
|
||||
if (!uuid) { result = { error: '--uuid required (event UUID)' }; break }
|
||||
const params = new URLSearchParams()
|
||||
params.set('count', String(count))
|
||||
if (args['page-token']) params.set('page_token', args['page-token'])
|
||||
if (args.email) params.set('email', args.email)
|
||||
if (args.status) params.set('status', args.status)
|
||||
result = await api('GET', `/scheduled_events/${uuid}/invitees?${params}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown events subcommand. Use: list, get, cancel, invitees' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'availability':
|
||||
switch (sub) {
|
||||
case 'times': {
|
||||
const eventType = args['event-type']
|
||||
if (!eventType) { result = { error: '--event-type URI required' }; break }
|
||||
const startTime = args['start-time']
|
||||
const endTime = args['end-time']
|
||||
if (!startTime || !endTime) { result = { error: '--start-time and --end-time required (ISO 8601)' }; break }
|
||||
const params = new URLSearchParams({
|
||||
event_type: eventType,
|
||||
start_time: startTime,
|
||||
end_time: endTime,
|
||||
})
|
||||
result = await api('GET', `/event_type_available_times?${params}`)
|
||||
break
|
||||
}
|
||||
case 'busy': {
|
||||
const user = args.user
|
||||
if (!user) { result = { error: '--user URI required' }; break }
|
||||
const startTime = args['start-time']
|
||||
const endTime = args['end-time']
|
||||
if (!startTime || !endTime) { result = { error: '--start-time and --end-time required (ISO 8601)' }; break }
|
||||
const params = new URLSearchParams({
|
||||
user,
|
||||
start_time: startTime,
|
||||
end_time: endTime,
|
||||
})
|
||||
result = await api('GET', `/user_busy_times?${params}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown availability subcommand. Use: times, busy' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'webhooks':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const org = args.organization
|
||||
const scope = args.scope || 'organization'
|
||||
if (!org) { result = { error: '--organization URI required' }; break }
|
||||
const params = new URLSearchParams({
|
||||
organization: org,
|
||||
scope,
|
||||
})
|
||||
params.set('count', String(count))
|
||||
if (args['page-token']) params.set('page_token', args['page-token'])
|
||||
result = await api('GET', `/webhook_subscriptions?${params}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const url = args.url
|
||||
const events = args.events?.split(',')
|
||||
const org = args.organization
|
||||
const scope = args.scope || 'organization'
|
||||
if (!url || !events || !org) { result = { error: '--url, --events (comma-separated), and --organization required' }; break }
|
||||
const body = { url, events, organization: org, scope }
|
||||
if (args.user) body.user = args.user
|
||||
result = await api('POST', '/webhook_subscriptions', body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const uuid = args.uuid
|
||||
if (!uuid) { result = { error: '--uuid required' }; break }
|
||||
result = await api('DELETE', `/webhook_subscriptions/${uuid}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown webhooks subcommand. Use: list, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'org':
|
||||
switch (sub) {
|
||||
case 'members': {
|
||||
const org = args.organization
|
||||
if (!org) { result = { error: '--organization URI required' }; break }
|
||||
const params = new URLSearchParams({ organization: org })
|
||||
params.set('count', String(count))
|
||||
if (args['page-token']) params.set('page_token', args['page-token'])
|
||||
result = await api('GET', `/organization_memberships?${params}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown org subcommand. Use: members' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
users: 'users me',
|
||||
'event-types': 'event-types [list --user <uri> | get --uuid <id>]',
|
||||
events: 'events [list --user <uri> | get --uuid <id> | cancel --uuid <id> | invitees --uuid <id>]',
|
||||
availability: 'availability [times --event-type <uri> --start-time <iso> --end-time <iso> | busy --user <uri> --start-time <iso> --end-time <iso>]',
|
||||
webhooks: 'webhooks [list --organization <uri> | create --url <url> --events <e1,e2> --organization <uri> | delete --uuid <id>]',
|
||||
org: 'org [members --organization <uri>]',
|
||||
options: '--count <n> --page-token <token> --status <active|canceled>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+159
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.CLEARBIT_API_KEY
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'CLEARBIT_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, baseUrl, path, body) {
|
||||
const res = await fetch(`${baseUrl}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
|
||||
switch (cmd) {
|
||||
case 'person':
|
||||
switch (sub) {
|
||||
case 'find': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
result = await api('GET', 'https://person-stream.clearbit.com', `/v2/people/find?email=${encodeURIComponent(email)}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown person subcommand. Use: find' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'company':
|
||||
switch (sub) {
|
||||
case 'find': {
|
||||
const domain = args.domain
|
||||
if (!domain) { result = { error: '--domain required' }; break }
|
||||
result = await api('GET', 'https://company-stream.clearbit.com', `/v2/companies/find?domain=${encodeURIComponent(domain)}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown company subcommand. Use: find' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'combined':
|
||||
switch (sub) {
|
||||
case 'find': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
result = await api('GET', 'https://person-stream.clearbit.com', `/v2/combined/find?email=${encodeURIComponent(email)}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown combined subcommand. Use: find' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'reveal':
|
||||
switch (sub) {
|
||||
case 'find': {
|
||||
const ip = args.ip
|
||||
if (!ip) { result = { error: '--ip required' }; break }
|
||||
result = await api('GET', 'https://reveal.clearbit.com', `/v1/companies/find?ip=${encodeURIComponent(ip)}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown reveal subcommand. Use: find' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'name-to-domain':
|
||||
switch (sub) {
|
||||
case 'find': {
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
result = await api('GET', 'https://company.clearbit.com', `/v1/domains/find?name=${encodeURIComponent(name)}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown name-to-domain subcommand. Use: find' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'prospector':
|
||||
switch (sub) {
|
||||
case 'search': {
|
||||
const domain = args.domain
|
||||
if (!domain) { result = { error: '--domain required' }; break }
|
||||
const params = new URLSearchParams({ domain })
|
||||
if (args.role) params.set('role', args.role)
|
||||
if (args.seniority) params.set('seniority', args.seniority)
|
||||
if (args.title) params.set('title', args.title)
|
||||
if (args.page) params.set('page', args.page)
|
||||
if (args['page-size']) params.set('page_size', args['page-size'])
|
||||
result = await api('GET', 'https://prospector.clearbit.com', `/v1/people/search?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown prospector subcommand. Use: search' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
person: 'person find --email <email>',
|
||||
company: 'company find --domain <domain>',
|
||||
combined: 'combined find --email <email>',
|
||||
reveal: 'reveal find --ip <ip>',
|
||||
'name-to-domain': 'name-to-domain find --name <company_name>',
|
||||
prospector: 'prospector search --domain <domain> [--role <role>] [--seniority <level>] [--title <title>]',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+146
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.DEMIO_API_KEY
|
||||
const API_SECRET = process.env.DEMIO_API_SECRET
|
||||
const BASE_URL = 'https://my.demio.com/api/v1'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'DEMIO_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!API_SECRET) {
|
||||
console.error(JSON.stringify({ error: 'DEMIO_API_SECRET environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Api-Key': API_KEY,
|
||||
'Api-Secret': API_SECRET,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
|
||||
switch (cmd) {
|
||||
case 'ping':
|
||||
result = await api('GET', '/ping')
|
||||
break
|
||||
|
||||
case 'events':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const type = args.type
|
||||
let qs = ''
|
||||
if (type) qs = `?type=${type}`
|
||||
result = await api('GET', `/events${qs}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/event/${id}`)
|
||||
break
|
||||
}
|
||||
case 'date': {
|
||||
const eventId = args['event-id']
|
||||
const dateId = args['date-id']
|
||||
if (!eventId) { result = { error: '--event-id required' }; break }
|
||||
if (!dateId) { result = { error: '--date-id required' }; break }
|
||||
result = await api('GET', `/event/${eventId}/date/${dateId}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown events subcommand. Use: list, get, date' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'register':
|
||||
switch (sub) {
|
||||
case 'create': {
|
||||
const id = args.id || args['event-id']
|
||||
const name = args.name
|
||||
const email = args.email
|
||||
if (!id) { result = { error: '--id (event id) required' }; break }
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const payload = { id, name, email }
|
||||
if (args['date-id']) payload.date_id = args['date-id']
|
||||
if (args['ref-url']) payload.ref_url = args['ref-url']
|
||||
result = await api('POST', '/event/register', payload)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown register subcommand. Use: create' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'participants':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const dateId = args['date-id']
|
||||
if (!dateId) { result = { error: '--date-id required' }; break }
|
||||
result = await api('GET', `/date/${dateId}/participants`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown participants subcommand. Use: list' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
ping: 'ping',
|
||||
events: 'events [list --type <upcoming|past|all> | get --id <id> | date --event-id <id> --date-id <id>]',
|
||||
register: 'register [create --id <event_id> --name <name> --email <email> --date-id <date_id>]',
|
||||
participants: 'participants [list --date-id <id>]',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+183
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_TOKEN = process.env.G2_API_TOKEN
|
||||
const BASE_URL = 'https://data.g2.com/api/v1'
|
||||
|
||||
if (!API_TOKEN) {
|
||||
console.error(JSON.stringify({ error: 'G2_API_TOKEN environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Token token=${API_TOKEN}`,
|
||||
'Content-Type': 'application/vnd.api+json',
|
||||
'Accept': 'application/vnd.api+json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const page = args.page ? Number(args.page) : 1
|
||||
const perPage = args['per-page'] ? Number(args['per-page']) : 25
|
||||
const productId = args['product-id'] || args.product
|
||||
|
||||
switch (cmd) {
|
||||
case 'reviews':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
let qs = `?page[size]=${perPage}&page[number]=${page}`
|
||||
if (productId) qs += `&filter[product_id]=${productId}`
|
||||
if (args.state) qs += `&filter[state]=${args.state}`
|
||||
result = await api('GET', `/survey-responses${qs}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/survey-responses/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown reviews subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'products':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
let qs = `?page[size]=${perPage}&page[number]=${page}`
|
||||
if (args.name) qs += `&filter[name]=${encodeURIComponent(args.name)}`
|
||||
result = await api('GET', `/products${qs}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/products/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown products subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'reports':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
let qs = `?page[size]=${perPage}&page[number]=${page}`
|
||||
result = await api('GET', `/reports${qs}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/reports/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown reports subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'competitors':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
if (!productId) { result = { error: '--product-id required' }; break }
|
||||
let qs = `?page[size]=${perPage}&page[number]=${page}&filter[product_id]=${productId}`
|
||||
result = await api('GET', `/competitor-comparisons${qs}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown competitors subcommand. Use: list' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'categories':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
let qs = `?page[size]=${perPage}&page[number]=${page}`
|
||||
if (args.name) qs += `&filter[name]=${encodeURIComponent(args.name)}`
|
||||
result = await api('GET', `/categories${qs}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/categories/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown categories subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'tracking':
|
||||
switch (sub) {
|
||||
case 'visitors': {
|
||||
let qs = `?page[size]=${perPage}&page[number]=${page}`
|
||||
if (args.start) qs += `&filter[start_date]=${args.start}`
|
||||
if (args.end) qs += `&filter[end_date]=${args.end}`
|
||||
result = await api('GET', `/tracking-events${qs}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown tracking subcommand. Use: visitors' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
reviews: 'reviews [list --product-id <id> | get --id <id>]',
|
||||
products: 'products [list --name <name> | get --id <id>]',
|
||||
reports: 'reports [list | get --id <id>]',
|
||||
competitors: 'competitors [list --product-id <id>]',
|
||||
categories: 'categories [list --name <name> | get --id <id>]',
|
||||
tracking: 'tracking [visitors --start <date> --end <date>]',
|
||||
options: '--page <n> --per-page <n>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+163
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const CLIENT_ID = process.env.HOTJAR_CLIENT_ID
|
||||
const CLIENT_SECRET = process.env.HOTJAR_CLIENT_SECRET
|
||||
const BASE_URL = 'https://api.hotjar.io/v1'
|
||||
|
||||
if (!CLIENT_ID || !CLIENT_SECRET) {
|
||||
console.error(JSON.stringify({ error: 'HOTJAR_CLIENT_ID and HOTJAR_CLIENT_SECRET environment variables required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let cachedToken = null
|
||||
|
||||
async function getToken() {
|
||||
if (cachedToken) return cachedToken
|
||||
const res = await fetch(`${BASE_URL}/oauth/token`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: `grant_type=client_credentials&client_id=${encodeURIComponent(CLIENT_ID)}&client_secret=${encodeURIComponent(CLIENT_SECRET)}`,
|
||||
})
|
||||
const data = await res.json()
|
||||
if (!data.access_token) {
|
||||
throw new Error(data.error_description || data.error || 'Failed to obtain access token')
|
||||
}
|
||||
cachedToken = data.access_token
|
||||
return cachedToken
|
||||
}
|
||||
|
||||
async function api(method, path) {
|
||||
const token = await getToken()
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const siteId = args['site-id']
|
||||
const limit = args.limit || '100'
|
||||
const cursor = args.cursor
|
||||
|
||||
switch (cmd) {
|
||||
case 'sites':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', '/sites')
|
||||
break
|
||||
default:
|
||||
result = { error: 'Unknown sites subcommand. Use: list' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'surveys':
|
||||
if (!siteId) { result = { error: '--site-id required' }; break }
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/sites/${siteId}/surveys`)
|
||||
break
|
||||
case 'responses': {
|
||||
const surveyId = args['survey-id']
|
||||
if (!surveyId) { result = { error: '--survey-id required' }; break }
|
||||
const params = new URLSearchParams({ limit })
|
||||
if (cursor) params.set('cursor', cursor)
|
||||
result = await api('GET', `/sites/${siteId}/surveys/${surveyId}/responses?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown surveys subcommand. Use: list, responses' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'heatmaps':
|
||||
if (!siteId) { result = { error: '--site-id required' }; break }
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/sites/${siteId}/heatmaps`)
|
||||
break
|
||||
default:
|
||||
result = { error: 'Unknown heatmaps subcommand. Use: list' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'recordings':
|
||||
if (!siteId) { result = { error: '--site-id required' }; break }
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams({ limit })
|
||||
if (cursor) params.set('cursor', cursor)
|
||||
if (args['date-from']) params.set('date_from', args['date-from'])
|
||||
if (args['date-to']) params.set('date_to', args['date-to'])
|
||||
result = await api('GET', `/sites/${siteId}/recordings?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown recordings subcommand. Use: list' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'forms':
|
||||
if (!siteId) { result = { error: '--site-id required' }; break }
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/sites/${siteId}/forms`)
|
||||
break
|
||||
default:
|
||||
result = { error: 'Unknown forms subcommand. Use: list' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
sites: 'sites list',
|
||||
surveys: 'surveys list --site-id <id> | surveys responses --site-id <id> --survey-id <id> [--limit <n>] [--cursor <cursor>]',
|
||||
heatmaps: 'heatmaps list --site-id <id>',
|
||||
recordings: 'recordings list --site-id <id> [--limit <n>] [--cursor <cursor>] [--date-from <date>] [--date-to <date>]',
|
||||
forms: 'forms list --site-id <id>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+396
@@ -0,0 +1,396 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.INTERCOM_API_KEY
|
||||
const BASE_URL = 'https://api.intercom.io'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'INTERCOM_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Intercom-Version': '2.11',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const perPage = args['per-page'] ? Number(args['per-page']) : undefined
|
||||
|
||||
switch (cmd) {
|
||||
case 'contacts':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (perPage) params.set('per_page', String(perPage))
|
||||
if (args['starting-after']) params.set('starting_after', args['starting-after'])
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/contacts${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/contacts/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const body = {
|
||||
role: args.role || 'user',
|
||||
email,
|
||||
}
|
||||
if (args.name) body.name = args.name
|
||||
if (args.phone) body.phone = args.phone
|
||||
result = await api('POST', '/contacts', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const body = {}
|
||||
if (args.name) body.name = args.name
|
||||
if (args.email) body.email = args.email
|
||||
if (args.phone) body.phone = args.phone
|
||||
if (args.role) body.role = args.role
|
||||
result = await api('PUT', `/contacts/${id}`, body)
|
||||
break
|
||||
}
|
||||
case 'search': {
|
||||
const field = args.field
|
||||
const operator = args.operator || '='
|
||||
const value = args.value
|
||||
if (!field || !value) { result = { error: '--field and --value required' }; break }
|
||||
const body = {
|
||||
query: { field, operator, value },
|
||||
}
|
||||
if (perPage) body.pagination = { per_page: perPage }
|
||||
result = await api('POST', '/contacts/search', body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/contacts/${id}`)
|
||||
break
|
||||
}
|
||||
case 'tag': {
|
||||
const id = args.id
|
||||
const tagId = args['tag-id']
|
||||
if (!id || !tagId) { result = { error: '--id (contact ID) and --tag-id required' }; break }
|
||||
result = await api('POST', `/contacts/${id}/tags`, { id: tagId })
|
||||
break
|
||||
}
|
||||
case 'untag': {
|
||||
const id = args.id
|
||||
const tagId = args['tag-id']
|
||||
if (!id || !tagId) { result = { error: '--id (contact ID) and --tag-id required' }; break }
|
||||
result = await api('DELETE', `/contacts/${id}/tags/${tagId}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown contacts subcommand. Use: list, get, create, update, search, delete, tag, untag' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'conversations':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (perPage) params.set('per_page', String(perPage))
|
||||
if (args['starting-after']) params.set('starting_after', args['starting-after'])
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/conversations${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/conversations/${id}`)
|
||||
break
|
||||
}
|
||||
case 'search': {
|
||||
const field = args.field
|
||||
const operator = args.operator || '='
|
||||
const value = args.value
|
||||
if (!field || value === undefined) { result = { error: '--field and --value required' }; break }
|
||||
const body = {
|
||||
query: { field, operator, value },
|
||||
}
|
||||
if (perPage) body.pagination = { per_page: perPage }
|
||||
result = await api('POST', '/conversations/search', body)
|
||||
break
|
||||
}
|
||||
case 'reply': {
|
||||
const id = args.id
|
||||
const body = args.body
|
||||
const adminId = args['admin-id']
|
||||
if (!id || !body || !adminId) { result = { error: '--id, --body, and --admin-id required' }; break }
|
||||
result = await api('POST', `/conversations/${id}/reply`, {
|
||||
message_type: 'comment',
|
||||
type: 'admin',
|
||||
admin_id: adminId,
|
||||
body,
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'close': {
|
||||
const id = args.id
|
||||
const adminId = args['admin-id']
|
||||
if (!id || !adminId) { result = { error: '--id and --admin-id required' }; break }
|
||||
result = await api('POST', `/conversations/${id}/parts`, {
|
||||
message_type: 'close',
|
||||
type: 'admin',
|
||||
admin_id: adminId,
|
||||
body: args.body || '',
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown conversations subcommand. Use: list, get, search, reply, close' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'messages':
|
||||
switch (sub) {
|
||||
case 'create': {
|
||||
const messageType = args.type || 'inapp'
|
||||
const body = args.body
|
||||
const adminId = args['admin-id']
|
||||
const to = args.to
|
||||
if (!body || !adminId || !to) { result = { error: '--body, --admin-id, and --to (user ID) required' }; break }
|
||||
result = await api('POST', '/messages', {
|
||||
message_type: messageType,
|
||||
body,
|
||||
from: { type: 'admin', id: adminId },
|
||||
to: { type: 'user', id: to },
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown messages subcommand. Use: create --body <text> --admin-id <id> --to <user_id> [--type inapp|email]' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'companies':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (perPage) params.set('per_page', String(perPage))
|
||||
if (args.page) params.set('page', args.page)
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/companies${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/companies/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const companyId = args['company-id']
|
||||
const name = args.name
|
||||
if (!companyId) { result = { error: '--company-id required' }; break }
|
||||
const body = { company_id: companyId }
|
||||
if (name) body.name = name
|
||||
if (args.plan) body.plan = args.plan
|
||||
if (args.industry) body.industry = args.industry
|
||||
result = await api('POST', '/companies', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const body = {}
|
||||
if (args.name) body.name = args.name
|
||||
if (args.plan) body.plan = args.plan
|
||||
if (args.industry) body.industry = args.industry
|
||||
result = await api('PUT', `/companies/${id}`, body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown companies subcommand. Use: list, get, create, update' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'tags':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', '/tags')
|
||||
break
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
result = await api('POST', '/tags', { name })
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/tags/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown tags subcommand. Use: list, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'articles':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (perPage) params.set('per_page', String(perPage))
|
||||
if (args.page) params.set('page', args.page)
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/articles${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/articles/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const title = args.title
|
||||
const authorId = args['author-id']
|
||||
if (!title || !authorId) { result = { error: '--title and --author-id required' }; break }
|
||||
const body = {
|
||||
title,
|
||||
author_id: Number(authorId),
|
||||
state: args.state || 'draft',
|
||||
}
|
||||
if (args.body) body.body = args.body
|
||||
result = await api('POST', '/articles', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const body = {}
|
||||
if (args.title) body.title = args.title
|
||||
if (args.body) body.body = args.body
|
||||
if (args.state) body.state = args.state
|
||||
result = await api('PUT', `/articles/${id}`, body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/articles/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown articles subcommand. Use: list, get, create, update, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'admins':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', '/admins')
|
||||
break
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/admins/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown admins subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'events':
|
||||
switch (sub) {
|
||||
case 'create': {
|
||||
const eventName = args.name
|
||||
const userId = args['user-id']
|
||||
if (!eventName || !userId) { result = { error: '--name and --user-id required' }; break }
|
||||
const body = {
|
||||
event_name: eventName,
|
||||
user_id: userId,
|
||||
created_at: args['created-at'] ? Number(args['created-at']) : Math.floor(Date.now() / 1000),
|
||||
}
|
||||
if (args.metadata) {
|
||||
try { body.metadata = JSON.parse(args.metadata) } catch { body.metadata = {} }
|
||||
}
|
||||
result = await api('POST', '/events', body)
|
||||
break
|
||||
}
|
||||
case 'list': {
|
||||
const userId = args['user-id']
|
||||
if (!userId) { result = { error: '--user-id required' }; break }
|
||||
const params = new URLSearchParams({ type: 'user', user_id: userId })
|
||||
if (perPage) params.set('per_page', String(perPage))
|
||||
result = await api('GET', `/events?${params}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown events subcommand. Use: create, list' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
contacts: 'contacts [list | get --id <id> | create --email <email> | update --id <id> | search --field <f> --value <v> | delete --id <id> | tag --id <id> --tag-id <id> | untag --id <id> --tag-id <id>]',
|
||||
conversations: 'conversations [list | get --id <id> | search --field <f> --value <v> | reply --id <id> --body <text> --admin-id <id> | close --id <id> --admin-id <id>]',
|
||||
messages: 'messages [create --body <text> --admin-id <id> --to <user_id>]',
|
||||
companies: 'companies [list | get --id <id> | create --company-id <id> --name <name> | update --id <id>]',
|
||||
tags: 'tags [list | create --name <name> | delete --id <id>]',
|
||||
articles: 'articles [list | get --id <id> | create --title <title> --author-id <id> | update --id <id> | delete --id <id>]',
|
||||
admins: 'admins [list | get --id <id>]',
|
||||
events: 'events [create --name <name> --user-id <id> | list --user-id <id>]',
|
||||
options: '--per-page <n> --starting-after <cursor> --page <n>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+344
@@ -0,0 +1,344 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.KLAVIYO_API_KEY
|
||||
const BASE_URL = 'https://a.klaviyo.com/api'
|
||||
const REVISION = '2024-10-15'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'KLAVIYO_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Klaviyo-API-Key ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'revision': REVISION,
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
|
||||
switch (cmd) {
|
||||
case 'profiles':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.filter) params.set('filter', args.filter)
|
||||
if (args.sort) params.set('sort', args.sort)
|
||||
if (args['page-size']) params.set('page[size]', args['page-size'])
|
||||
if (args['page-cursor']) params.set('page[cursor]', args['page-cursor'])
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/profiles/${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/profiles/${id}/`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const attributes = { email }
|
||||
if (args['first-name']) attributes.first_name = args['first-name']
|
||||
if (args['last-name']) attributes.last_name = args['last-name']
|
||||
if (args.phone) attributes.phone_number = args.phone
|
||||
result = await api('POST', '/profiles/', {
|
||||
data: { type: 'profile', attributes }
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const attributes = {}
|
||||
if (args.email) attributes.email = args.email
|
||||
if (args['first-name']) attributes.first_name = args['first-name']
|
||||
if (args['last-name']) attributes.last_name = args['last-name']
|
||||
if (args.phone) attributes.phone_number = args.phone
|
||||
result = await api('PATCH', `/profiles/${id}/`, {
|
||||
data: { type: 'profile', id, attributes }
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown profiles subcommand. Use: list, get, create, update' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'lists':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args['page-size']) params.set('page[size]', args['page-size'])
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/lists/${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/lists/${id}/`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
result = await api('POST', '/lists/', {
|
||||
data: { type: 'list', attributes: { name } }
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/lists/${id}/`)
|
||||
break
|
||||
}
|
||||
case 'add-profiles': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (list ID)' }; break }
|
||||
const profileIds = args.profiles?.split(',')
|
||||
if (!profileIds) { result = { error: '--profiles required (comma-separated profile IDs)' }; break }
|
||||
result = await api('POST', `/lists/${id}/relationships/profiles/`, {
|
||||
data: profileIds.map(pid => ({ type: 'profile', id: pid }))
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'remove-profiles': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (list ID)' }; break }
|
||||
const profileIds = args.profiles?.split(',')
|
||||
if (!profileIds) { result = { error: '--profiles required (comma-separated profile IDs)' }; break }
|
||||
result = await api('DELETE', `/lists/${id}/relationships/profiles/`, {
|
||||
data: profileIds.map(pid => ({ type: 'profile', id: pid }))
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown lists subcommand. Use: list, get, create, delete, add-profiles, remove-profiles' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'events':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.filter) params.set('filter', args.filter)
|
||||
if (args['page-size']) params.set('page[size]', args['page-size'])
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/events/${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/events/${id}/`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const metric = args.metric
|
||||
const email = args.email
|
||||
if (!metric) { result = { error: '--metric required (metric name)' }; break }
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const properties = {}
|
||||
if (args.value) properties.value = Number(args.value)
|
||||
if (args.property) {
|
||||
const pairs = args.property.split(',')
|
||||
for (const pair of pairs) {
|
||||
const [k, v] = pair.split(':')
|
||||
if (k && v) properties[k] = v
|
||||
}
|
||||
}
|
||||
result = await api('POST', '/events/', {
|
||||
data: {
|
||||
type: 'event',
|
||||
attributes: {
|
||||
metric: { data: { type: 'metric', attributes: { name: metric } } },
|
||||
profile: { data: { type: 'profile', attributes: { email } } },
|
||||
properties,
|
||||
time: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown events subcommand. Use: list, get, create' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'campaigns':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.filter) params.set('filter', args.filter)
|
||||
if (args['page-size']) params.set('page[size]', args['page-size'])
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/campaigns/${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/campaigns/${id}/`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown campaigns subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'flows':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.filter) params.set('filter', args.filter)
|
||||
if (args['page-size']) params.set('page[size]', args['page-size'])
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/flows/${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/flows/${id}/`)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const attributes = {}
|
||||
if (args.status) attributes.status = args.status
|
||||
result = await api('PATCH', `/flows/${id}/`, {
|
||||
data: { type: 'flow', id, attributes }
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown flows subcommand. Use: list, get, update' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'metrics':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args['page-size']) params.set('page[size]', args['page-size'])
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/metrics/${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/metrics/${id}/`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown metrics subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'segments':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args['page-size']) params.set('page[size]', args['page-size'])
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/segments/${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/segments/${id}/`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown segments subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'templates':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.filter) params.set('filter', args.filter)
|
||||
if (args['page-size']) params.set('page[size]', args['page-size'])
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/templates/${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/templates/${id}/`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown templates subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
profiles: 'profiles [list | get --id <id> | create --email <email> | update --id <id>]',
|
||||
lists: 'lists [list | get --id <id> | create --name <name> | delete --id <id> | add-profiles --id <list-id> --profiles <id1,id2> | remove-profiles --id <list-id> --profiles <id1,id2>]',
|
||||
events: 'events [list | get --id <id> | create --metric <name> --email <email>]',
|
||||
campaigns: 'campaigns [list | get --id <id>]',
|
||||
flows: 'flows [list | get --id <id> | update --id <id> --status <status>]',
|
||||
metrics: 'metrics [list | get --id <id>]',
|
||||
segments: 'segments [list | get --id <id>]',
|
||||
templates: 'templates [list | get --id <id>]',
|
||||
options: '--filter <filter> --page-size <n> --page-cursor <cursor>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+288
@@ -0,0 +1,288 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_TOKEN = process.env.LIVESTORM_API_TOKEN
|
||||
const BASE_URL = 'https://api.livestorm.co/v1'
|
||||
|
||||
if (!API_TOKEN) {
|
||||
console.error(JSON.stringify({ error: 'LIVESTORM_API_TOKEN environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': API_TOKEN,
|
||||
'Content-Type': 'application/vnd.api+json',
|
||||
'Accept': 'application/vnd.api+json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const page = args.page ? Number(args.page) : 1
|
||||
const perPage = args['per-page'] ? Number(args['per-page']) : 25
|
||||
|
||||
switch (cmd) {
|
||||
case 'ping':
|
||||
result = await api('GET', '/ping')
|
||||
break
|
||||
|
||||
case 'events':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
let qs = `?page[number]=${page}&page[size]=${perPage}`
|
||||
if (args.title) qs += `&filter[title]=${encodeURIComponent(args.title)}`
|
||||
result = await api('GET', `/events${qs}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/events/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const title = args.title
|
||||
if (!title) { result = { error: '--title required' }; break }
|
||||
const payload = {
|
||||
data: {
|
||||
type: 'events',
|
||||
attributes: {
|
||||
title,
|
||||
slug: args.slug || title.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
|
||||
},
|
||||
},
|
||||
}
|
||||
if (args.description) payload.data.attributes.description = args.description
|
||||
if (args['estimated-duration']) payload.data.attributes.estimated_duration = Number(args['estimated-duration'])
|
||||
result = await api('POST', '/events', payload)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const attrs = {}
|
||||
if (args.title) attrs.title = args.title
|
||||
if (args.description) attrs.description = args.description
|
||||
if (args.slug) attrs.slug = args.slug
|
||||
result = await api('PATCH', `/events/${id}`, {
|
||||
data: { type: 'events', id, attributes: attrs },
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/events/${id}`)
|
||||
break
|
||||
}
|
||||
case 'people': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
let qs = `?page[number]=${page}&page[size]=${perPage}`
|
||||
result = await api('GET', `/events/${id}/people${qs}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown events subcommand. Use: list, get, create, update, delete, people' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'sessions':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
let qs = `?page[number]=${page}&page[size]=${perPage}`
|
||||
result = await api('GET', `/sessions${qs}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/sessions/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const eventId = args['event-id']
|
||||
if (!eventId) { result = { error: '--event-id required' }; break }
|
||||
const payload = {
|
||||
data: {
|
||||
type: 'sessions',
|
||||
attributes: {},
|
||||
},
|
||||
}
|
||||
if (args['estimated-started-at']) payload.data.attributes.estimated_started_at = args['estimated-started-at']
|
||||
if (args.timezone) payload.data.attributes.timezone = args.timezone
|
||||
result = await api('POST', `/events/${eventId}/sessions`, payload)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/sessions/${id}`)
|
||||
break
|
||||
}
|
||||
case 'people': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
let qs = `?page[number]=${page}&page[size]=${perPage}`
|
||||
result = await api('GET', `/sessions/${id}/people${qs}`)
|
||||
break
|
||||
}
|
||||
case 'register': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id (session id) required' }; break }
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const fields = { email }
|
||||
if (args['first-name']) fields.first_name = args['first-name']
|
||||
if (args['last-name']) fields.last_name = args['last-name']
|
||||
result = await api('POST', `/sessions/${id}/people`, {
|
||||
data: {
|
||||
type: 'people',
|
||||
attributes: { fields },
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'unregister': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id (session id) required' }; break }
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
result = await api('DELETE', `/sessions/${id}/people?filter[email]=${encodeURIComponent(email)}`)
|
||||
break
|
||||
}
|
||||
case 'chat': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
let qs = `?page[number]=${page}&page[size]=${perPage}`
|
||||
result = await api('GET', `/sessions/${id}/chat-messages${qs}`)
|
||||
break
|
||||
}
|
||||
case 'questions': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
let qs = `?page[number]=${page}&page[size]=${perPage}`
|
||||
result = await api('GET', `/sessions/${id}/questions${qs}`)
|
||||
break
|
||||
}
|
||||
case 'recordings': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/sessions/${id}/recordings`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown sessions subcommand. Use: list, get, create, delete, people, register, unregister, chat, questions, recordings' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'people':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
let qs = `?page[number]=${page}&page[size]=${perPage}`
|
||||
if (args.email) qs += `&filter[email]=${encodeURIComponent(args.email)}`
|
||||
result = await api('GET', `/people${qs}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/people/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown people subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'webhooks':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
result = await api('GET', '/webhooks')
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const url = args.url
|
||||
if (!url) { result = { error: '--url required' }; break }
|
||||
const eventName = args.event || 'attendance'
|
||||
result = await api('POST', '/webhooks', {
|
||||
data: {
|
||||
type: 'webhooks',
|
||||
attributes: {
|
||||
target_url: url,
|
||||
event_name: eventName,
|
||||
},
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/webhooks/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown webhooks subcommand. Use: list, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'organization':
|
||||
result = await api('GET', '/organization')
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
ping: 'ping',
|
||||
events: 'events [list | get --id <id> | create --title <t> | update --id <id> --title <t> | delete --id <id> | people --id <id>]',
|
||||
sessions: 'sessions [list | get --id <id> | create --event-id <id> | delete --id <id> | people --id <id> | register --id <id> --email <e> | unregister --id <id> --email <e> | chat --id <id> | questions --id <id> | recordings --id <id>]',
|
||||
people: 'people [list --email <e> | get --id <id>]',
|
||||
webhooks: 'webhooks [list | create --url <url> --event <name> | delete --id <id>]',
|
||||
organization: 'organization',
|
||||
options: '--page <n> --per-page <n>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+236
@@ -0,0 +1,236 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const REST_API_KEY = process.env.ONESIGNAL_REST_API_KEY
|
||||
const APP_ID = process.env.ONESIGNAL_APP_ID
|
||||
const BASE_URL = 'https://api.onesignal.com'
|
||||
|
||||
if (!REST_API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'ONESIGNAL_REST_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!APP_ID) {
|
||||
console.error(JSON.stringify({ error: 'ONESIGNAL_APP_ID environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Basic ${REST_API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const limit = args.limit ? Number(args.limit) : 50
|
||||
const offset = args.offset ? Number(args.offset) : 0
|
||||
|
||||
switch (cmd) {
|
||||
case 'notifications':
|
||||
switch (sub) {
|
||||
case 'send': {
|
||||
const message = args.message
|
||||
if (!message) { result = { error: '--message required' }; break }
|
||||
const payload = {
|
||||
app_id: APP_ID,
|
||||
contents: { en: message },
|
||||
}
|
||||
if (args.heading) payload.headings = { en: args.heading }
|
||||
if (args.url) payload.url = args.url
|
||||
if (args.data) {
|
||||
try { payload.data = JSON.parse(args.data) } catch { payload.data = { value: args.data } }
|
||||
}
|
||||
if (args.segment) {
|
||||
payload.included_segments = args.segment.split(',')
|
||||
} else if (args.emails) {
|
||||
payload.include_email_tokens = args.emails.split(',')
|
||||
} else if (args['player-ids']) {
|
||||
payload.include_player_ids = args['player-ids'].split(',')
|
||||
} else if (args.aliases) {
|
||||
try {
|
||||
payload.include_aliases = JSON.parse(args.aliases)
|
||||
} catch {
|
||||
payload.include_aliases = { external_id: args.aliases.split(',') }
|
||||
}
|
||||
payload.target_channel = args.channel || 'push'
|
||||
} else {
|
||||
payload.included_segments = ['Subscribed Users']
|
||||
}
|
||||
if (args['send-after']) payload.send_after = args['send-after']
|
||||
if (args.ttl) payload.ttl = Number(args.ttl)
|
||||
result = await api('POST', '/api/v1/notifications', payload)
|
||||
break
|
||||
}
|
||||
case 'list': {
|
||||
result = await api('GET', `/api/v1/notifications?app_id=${APP_ID}&limit=${limit}&offset=${offset}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/api/v1/notifications/${id}?app_id=${APP_ID}`)
|
||||
break
|
||||
}
|
||||
case 'cancel': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/api/v1/notifications/${id}?app_id=${APP_ID}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown notifications subcommand. Use: send, list, get, cancel' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'segments':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
result = await api('GET', `/api/v1/apps/${APP_ID}/segments?offset=${offset}&limit=${limit}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
const filters = args.filters ? JSON.parse(args.filters) : [{ field: 'session_count', relation: '>', value: '0' }]
|
||||
result = await api('POST', `/api/v1/apps/${APP_ID}/segments`, { name, filters })
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/api/v1/apps/${APP_ID}/segments/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown segments subcommand. Use: list, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'users':
|
||||
switch (sub) {
|
||||
case 'get': {
|
||||
const aliasLabel = args['alias-label'] || 'external_id'
|
||||
const aliasId = args['alias-id']
|
||||
if (!aliasId) { result = { error: '--alias-id required' }; break }
|
||||
result = await api('GET', `/api/v1/apps/${APP_ID}/users/by/${aliasLabel}/${aliasId}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const payload = {}
|
||||
if (args['external-id']) {
|
||||
payload.identity = { external_id: args['external-id'] }
|
||||
}
|
||||
if (args.email) {
|
||||
payload.subscriptions = [{ type: 'Email', token: args.email }]
|
||||
}
|
||||
if (args.tags) {
|
||||
try { payload.tags = JSON.parse(args.tags) } catch { result = { error: 'Invalid --tags JSON' }; break }
|
||||
}
|
||||
result = await api('POST', `/api/v1/apps/${APP_ID}/users`, payload)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const aliasLabel = args['alias-label'] || 'external_id'
|
||||
const aliasId = args['alias-id']
|
||||
if (!aliasId) { result = { error: '--alias-id required' }; break }
|
||||
result = await api('DELETE', `/api/v1/apps/${APP_ID}/users/by/${aliasLabel}/${aliasId}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown users subcommand. Use: get, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'templates':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
result = await api('GET', `/api/v1/templates?app_id=${APP_ID}&limit=${limit}&offset=${offset}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/api/v1/templates/${id}?app_id=${APP_ID}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
const payload = { app_id: APP_ID, name }
|
||||
if (args.message) payload.contents = { en: args.message }
|
||||
if (args.heading) payload.headings = { en: args.heading }
|
||||
result = await api('POST', '/api/v1/templates', payload)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown templates subcommand. Use: list, get, create' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'app':
|
||||
switch (sub) {
|
||||
case 'get': {
|
||||
result = await api('GET', `/api/v1/apps/${APP_ID}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown app subcommand. Use: get' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
notifications: 'notifications [send --message <msg> --segment <s> | list | get --id <id> | cancel --id <id>]',
|
||||
segments: 'segments [list | create --name <n> --filters <json> | delete --id <id>]',
|
||||
users: 'users [get --alias-id <id> | create --external-id <id> --email <e> | delete --alias-id <id>]',
|
||||
templates: 'templates [list | get --id <id> | create --name <n> --message <msg>]',
|
||||
app: 'app [get]',
|
||||
options: '--limit <n> --offset <n>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+230
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.OPTIMIZELY_API_KEY
|
||||
const BASE_URL = 'https://api.optimizely.com/v2'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'OPTIMIZELY_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const projectId = args['project-id']
|
||||
const page = args.page ? Number(args.page) : 1
|
||||
const perPage = args['per-page'] ? Number(args['per-page']) : 25
|
||||
|
||||
switch (cmd) {
|
||||
case 'projects':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/projects?page=${page}&per_page=${perPage}`)
|
||||
break
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/projects/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
const body = { name }
|
||||
if (args.platform) body.platform = args.platform
|
||||
result = await api('POST', '/projects', body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown projects subcommand. Use: list, get, create' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'experiments':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
if (!projectId) { result = { error: '--project-id required' }; break }
|
||||
const params = new URLSearchParams({ project_id: projectId, page: String(page), per_page: String(perPage) })
|
||||
if (args.status) params.set('status', args.status)
|
||||
result = await api('GET', `/experiments?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/experiments/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
if (!projectId) { result = { error: '--project-id required' }; break }
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
const body = {
|
||||
project_id: Number(projectId),
|
||||
name,
|
||||
type: args.type || 'a/b',
|
||||
status: 'not_started',
|
||||
}
|
||||
if (args['traffic-allocation']) body.traffic_allocation = Number(args['traffic-allocation'])
|
||||
result = await api('POST', '/experiments', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const body = {}
|
||||
if (args.name) body.name = args.name
|
||||
if (args.status) body.status = args.status
|
||||
if (args['traffic-allocation']) body.traffic_allocation = Number(args['traffic-allocation'])
|
||||
result = await api('PATCH', `/experiments/${id}`, body)
|
||||
break
|
||||
}
|
||||
case 'results': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const params = new URLSearchParams()
|
||||
if (args['start-time']) params.set('start_time', args['start-time'])
|
||||
if (args['end-time']) params.set('end_time', args['end-time'])
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/experiments/${id}/results${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'archive': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/experiments/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown experiments subcommand. Use: list, get, create, update, results, archive' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'campaigns':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
if (!projectId) { result = { error: '--project-id required' }; break }
|
||||
result = await api('GET', `/campaigns?project_id=${projectId}&page=${page}&per_page=${perPage}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/campaigns/${id}`)
|
||||
break
|
||||
}
|
||||
case 'results': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/campaigns/${id}/results`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown campaigns subcommand. Use: list, get, results' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'audiences':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
if (!projectId) { result = { error: '--project-id required' }; break }
|
||||
result = await api('GET', `/audiences?project_id=${projectId}&page=${page}&per_page=${perPage}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/audiences/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown audiences subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'events':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
if (!projectId) { result = { error: '--project-id required' }; break }
|
||||
result = await api('GET', `/events?project_id=${projectId}&page=${page}&per_page=${perPage}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown events subcommand. Use: list' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'pages':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
if (!projectId) { result = { error: '--project-id required' }; break }
|
||||
result = await api('GET', `/pages?project_id=${projectId}&page=${page}&per_page=${perPage}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown pages subcommand. Use: list' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
projects: 'projects [list | get --id <id> | create --name <name>]',
|
||||
experiments: 'experiments [list --project-id <id> | get --id <id> | create --project-id <id> --name <name> | update --id <id> --status <status> | results --id <id> | archive --id <id>]',
|
||||
campaigns: 'campaigns [list --project-id <id> | get --id <id> | results --id <id>]',
|
||||
audiences: 'audiences [list --project-id <id> | get --id <id>]',
|
||||
events: 'events list --project-id <id>',
|
||||
pages: 'pages list --project-id <id>',
|
||||
options: '--page <n> --per-page <n> --status <not_started|running|paused|archived>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+376
@@ -0,0 +1,376 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.PADDLE_API_KEY
|
||||
const BASE_URL = process.env.PADDLE_SANDBOX === 'true'
|
||||
? 'https://sandbox-api.paddle.com'
|
||||
: 'https://api.paddle.com'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'PADDLE_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
function buildQuery() {
|
||||
const params = new URLSearchParams()
|
||||
if (args.status) params.set('status', args.status)
|
||||
if (args.after) params.set('after', args.after)
|
||||
if (args['per-page']) params.set('per_page', args['per-page'])
|
||||
if (args['order-by']) params.set('order_by', args['order-by'])
|
||||
return params.toString() ? `?${params.toString()}` : ''
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
|
||||
switch (cmd) {
|
||||
case 'products':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/products${buildQuery()}`)
|
||||
break
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/products/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
const taxCategory = args['tax-category']
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
if (!taxCategory) { result = { error: '--tax-category required (e.g. standard, digital-goods, saas)' }; break }
|
||||
const body = { name, tax_category: taxCategory }
|
||||
if (args.description) body.description = args.description
|
||||
if (args['image-url']) body.image_url = args['image-url']
|
||||
result = await api('POST', '/products', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const body = {}
|
||||
if (args.name) body.name = args.name
|
||||
if (args.description) body.description = args.description
|
||||
if (args.status) body.status = args.status
|
||||
if (args['tax-category']) body.tax_category = args['tax-category']
|
||||
result = await api('PATCH', `/products/${id}`, body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown products subcommand. Use: list, get, create, update' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'prices':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/prices${buildQuery()}`)
|
||||
break
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/prices/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const productId = args['product-id']
|
||||
const amount = args.amount
|
||||
const currency = args.currency || 'USD'
|
||||
if (!productId) { result = { error: '--product-id required' }; break }
|
||||
if (!amount) { result = { error: '--amount required (in lowest denomination, e.g. cents)' }; break }
|
||||
const body = {
|
||||
product_id: productId,
|
||||
description: args.description || 'Price',
|
||||
unit_price: { amount, currency_code: currency },
|
||||
}
|
||||
if (args.interval && args.frequency) {
|
||||
body.billing_cycle = {
|
||||
interval: args.interval,
|
||||
frequency: Number(args.frequency),
|
||||
}
|
||||
}
|
||||
result = await api('POST', '/prices', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const body = {}
|
||||
if (args.description) body.description = args.description
|
||||
if (args.amount && args.currency) {
|
||||
body.unit_price = { amount: args.amount, currency_code: args.currency }
|
||||
}
|
||||
if (args.status) body.status = args.status
|
||||
result = await api('PATCH', `/prices/${id}`, body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown prices subcommand. Use: list, get, create, update' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'customers':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/customers${buildQuery()}`)
|
||||
break
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/customers/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const body = { email }
|
||||
if (args.name) body.name = args.name
|
||||
result = await api('POST', '/customers', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const body = {}
|
||||
if (args.name) body.name = args.name
|
||||
if (args.email) body.email = args.email
|
||||
if (args.status) body.status = args.status
|
||||
result = await api('PATCH', `/customers/${id}`, body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown customers subcommand. Use: list, get, create, update' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'subscriptions':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/subscriptions${buildQuery()}`)
|
||||
break
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/subscriptions/${id}`)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const body = {}
|
||||
if (args['proration-billing-mode']) body.proration_billing_mode = args['proration-billing-mode']
|
||||
if (args['scheduled-change']) body.scheduled_change = JSON.parse(args['scheduled-change'])
|
||||
result = await api('PATCH', `/subscriptions/${id}`, body)
|
||||
break
|
||||
}
|
||||
case 'cancel': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const body = { effective_from: args['effective-from'] || 'next_billing_period' }
|
||||
result = await api('POST', `/subscriptions/${id}/cancel`, body)
|
||||
break
|
||||
}
|
||||
case 'pause': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const body = {}
|
||||
if (args['resume-at']) body.resume_at = args['resume-at']
|
||||
result = await api('POST', `/subscriptions/${id}/pause`, body)
|
||||
break
|
||||
}
|
||||
case 'resume': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const body = { effective_from: args['effective-from'] || 'immediately' }
|
||||
result = await api('POST', `/subscriptions/${id}/resume`, body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown subscriptions subcommand. Use: list, get, update, cancel, pause, resume' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'transactions':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/transactions${buildQuery()}`)
|
||||
break
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/transactions/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const items = args.items
|
||||
if (!items) { result = { error: '--items required (JSON array of {price_id, quantity})' }; break }
|
||||
const body = { items: JSON.parse(items) }
|
||||
if (args['customer-id']) body.customer_id = args['customer-id']
|
||||
result = await api('POST', '/transactions', body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown transactions subcommand. Use: list, get, create' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'discounts':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/discounts${buildQuery()}`)
|
||||
break
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/discounts/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const amount = args.amount
|
||||
const type = args.type
|
||||
if (!amount) { result = { error: '--amount required' }; break }
|
||||
if (!type) { result = { error: '--type required (flat, flat_per_seat, percentage)' }; break }
|
||||
const body = {
|
||||
amount,
|
||||
type,
|
||||
description: args.description || 'Discount',
|
||||
}
|
||||
if (args.code) body.code = args.code
|
||||
if (args['max-uses']) body.maximum_recurring_intervals = Number(args['max-uses'])
|
||||
if (args['currency-code']) body.currency_code = args['currency-code']
|
||||
result = await api('POST', '/discounts', body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown discounts subcommand. Use: list, get, create' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'adjustments':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/adjustments${buildQuery()}`)
|
||||
break
|
||||
case 'create': {
|
||||
const transactionId = args['transaction-id']
|
||||
const action = args.action
|
||||
const items = args.items
|
||||
const reason = args.reason
|
||||
if (!transactionId) { result = { error: '--transaction-id required' }; break }
|
||||
if (!action) { result = { error: '--action required (refund, credit, chargeback)' }; break }
|
||||
if (!reason) { result = { error: '--reason required' }; break }
|
||||
if (!items) { result = { error: '--items required (JSON array of {item_id, type, amount})' }; break }
|
||||
result = await api('POST', '/adjustments', {
|
||||
transaction_id: transactionId,
|
||||
action,
|
||||
reason,
|
||||
items: JSON.parse(items),
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown adjustments subcommand. Use: list, create' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'events':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/events${buildQuery()}`)
|
||||
break
|
||||
case 'types':
|
||||
result = await api('GET', '/event-types')
|
||||
break
|
||||
default:
|
||||
result = { error: 'Unknown events subcommand. Use: list, types' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'notifications':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/notifications${buildQuery()}`)
|
||||
break
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/notifications/${id}`)
|
||||
break
|
||||
}
|
||||
case 'replay': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('POST', `/notifications/${id}/replay`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown notifications subcommand. Use: list, get, replay' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
products: 'products [list | get --id <id> | create --name <n> --tax-category <cat> | update --id <id>]',
|
||||
prices: 'prices [list | get --id <id> | create --product-id <id> --amount <amt> [--currency USD] [--interval month --frequency 1] | update --id <id>]',
|
||||
customers: 'customers [list | get --id <id> | create --email <email> [--name <name>] | update --id <id>]',
|
||||
subscriptions: 'subscriptions [list | get --id <id> | update --id <id> | cancel --id <id> [--effective-from next_billing_period] | pause --id <id> | resume --id <id>]',
|
||||
transactions: 'transactions [list | get --id <id> | create --items <json>]',
|
||||
discounts: 'discounts [list | get --id <id> | create --amount <amt> --type <type> [--code <code>]]',
|
||||
adjustments: 'adjustments [list | create --transaction-id <id> --action <action> --reason <reason> --items <json>]',
|
||||
events: 'events [list | types]',
|
||||
notifications: 'notifications [list | get --id <id> | replay --id <id>]',
|
||||
env: 'Set PADDLE_SANDBOX=true for sandbox environment',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+379
@@ -0,0 +1,379 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const PUBLIC_KEY = process.env.PARTNERSTACK_PUBLIC_KEY
|
||||
const SECRET_KEY = process.env.PARTNERSTACK_SECRET_KEY
|
||||
const BASE_URL = 'https://api.partnerstack.com/api/v2'
|
||||
|
||||
if (!PUBLIC_KEY || !SECRET_KEY) {
|
||||
console.error(JSON.stringify({ error: 'PARTNERSTACK_PUBLIC_KEY and PARTNERSTACK_SECRET_KEY environment variables required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const AUTH = 'Basic ' + Buffer.from(`${PUBLIC_KEY}:${SECRET_KEY}`).toString('base64')
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': AUTH,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
const limit = args.limit ? Number(args.limit) : 10
|
||||
|
||||
function buildQuery() {
|
||||
const params = new URLSearchParams()
|
||||
if (args.limit) params.set('limit', args.limit)
|
||||
if (args.after) params.set('starting_after', args.after)
|
||||
if (args.before) params.set('ending_before', args.before)
|
||||
if (args['order-by']) params.set('order_by', args['order-by'])
|
||||
return params.toString() ? `?${params.toString()}` : ''
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
|
||||
switch (cmd) {
|
||||
case 'partnerships':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/partnerships${buildQuery()}`)
|
||||
break
|
||||
case 'get': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (partnership key)' }; break }
|
||||
result = await api('GET', `/partnerships/${key}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const email = args.email
|
||||
const group = args.group
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
if (!group) { result = { error: '--group required (group key)' }; break }
|
||||
const body = { email, group_key: group }
|
||||
if (args.name) body.name = args.name
|
||||
if (args['first-name']) body.first_name = args['first-name']
|
||||
if (args['last-name']) body.last_name = args['last-name']
|
||||
result = await api('POST', '/partnerships', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (partnership key)' }; break }
|
||||
const body = {}
|
||||
if (args.name) body.name = args.name
|
||||
if (args.group) body.group_key = args.group
|
||||
result = await api('PATCH', `/partnerships/${key}`, body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown partnerships subcommand. Use: list, get, create, update' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'customers':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/customers${buildQuery()}`)
|
||||
break
|
||||
case 'get': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (customer key)' }; break }
|
||||
result = await api('GET', `/customers/${key}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const email = args.email
|
||||
const partnerKey = args['partner-key']
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
if (!partnerKey) { result = { error: '--partner-key required' }; break }
|
||||
const body = { email, partner_key: partnerKey }
|
||||
if (args.name) body.name = args.name
|
||||
if (args['customer-key']) body.customer_key = args['customer-key']
|
||||
result = await api('POST', '/customers', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (customer key)' }; break }
|
||||
const body = {}
|
||||
if (args.name) body.name = args.name
|
||||
if (args.email) body.email = args.email
|
||||
if (args['partner-key']) body.partner_key = args['partner-key']
|
||||
result = await api('PATCH', `/customers/${key}`, body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (customer key)' }; break }
|
||||
result = await api('DELETE', `/customers/${key}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown customers subcommand. Use: list, get, create, update, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'transactions':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/transactions${buildQuery()}`)
|
||||
break
|
||||
case 'get': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (transaction key)' }; break }
|
||||
result = await api('GET', `/transactions/${key}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const customerKey = args['customer-key']
|
||||
const amount = args.amount
|
||||
if (!customerKey) { result = { error: '--customer-key required' }; break }
|
||||
if (!amount) { result = { error: '--amount required (in cents)' }; break }
|
||||
const body = {
|
||||
customer_key: customerKey,
|
||||
amount: Number(amount),
|
||||
}
|
||||
if (args.currency) body.currency = args.currency
|
||||
if (args.category) body.category = args.category
|
||||
if (args['product-key']) body.product_key = args['product-key']
|
||||
result = await api('POST', '/transactions', body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (transaction key)' }; break }
|
||||
result = await api('DELETE', `/transactions/${key}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown transactions subcommand. Use: list, get, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'deals':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/deals${buildQuery()}`)
|
||||
break
|
||||
case 'get': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (deal key)' }; break }
|
||||
result = await api('GET', `/deals/${key}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const partnerKey = args['partner-key']
|
||||
const name = args.name
|
||||
if (!partnerKey) { result = { error: '--partner-key required' }; break }
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
const body = { partner_key: partnerKey, name }
|
||||
if (args.amount) body.amount = Number(args.amount)
|
||||
if (args.currency) body.currency = args.currency
|
||||
if (args.stage) body.stage = args.stage
|
||||
result = await api('POST', '/deals', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (deal key)' }; break }
|
||||
const body = {}
|
||||
if (args.name) body.name = args.name
|
||||
if (args.amount) body.amount = Number(args.amount)
|
||||
if (args.stage) body.stage = args.stage
|
||||
if (args.status) body.status = args.status
|
||||
result = await api('PATCH', `/deals/${key}`, body)
|
||||
break
|
||||
}
|
||||
case 'archive': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (deal key)' }; break }
|
||||
result = await api('DELETE', `/deals/${key}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown deals subcommand. Use: list, get, create, update, archive' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'actions':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/actions${buildQuery()}`)
|
||||
break
|
||||
case 'create': {
|
||||
const customerKey = args['customer-key']
|
||||
const actionKey = args['action-key']
|
||||
if (!customerKey) { result = { error: '--customer-key required' }; break }
|
||||
if (!actionKey) { result = { error: '--action-key required' }; break }
|
||||
const body = {
|
||||
customer_key: customerKey,
|
||||
key: actionKey,
|
||||
}
|
||||
if (args.value) body.value = Number(args.value)
|
||||
result = await api('POST', '/actions', body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown actions subcommand. Use: list, create' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'rewards':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/rewards${buildQuery()}`)
|
||||
break
|
||||
case 'create': {
|
||||
const partnerKey = args['partner-key']
|
||||
const amount = args.amount
|
||||
if (!partnerKey) { result = { error: '--partner-key required' }; break }
|
||||
if (!amount) { result = { error: '--amount required (in cents)' }; break }
|
||||
const body = {
|
||||
partner_key: partnerKey,
|
||||
amount: Number(amount),
|
||||
}
|
||||
if (args.description) body.description = args.description
|
||||
if (args.currency) body.currency = args.currency
|
||||
result = await api('POST', '/rewards', body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown rewards subcommand. Use: list, create' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'leads':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/leads${buildQuery()}`)
|
||||
break
|
||||
case 'get': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (lead key)' }; break }
|
||||
result = await api('GET', `/leads/${key}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const partnerKey = args['partner-key']
|
||||
const email = args.email
|
||||
if (!partnerKey) { result = { error: '--partner-key required' }; break }
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const body = { partner_key: partnerKey, email }
|
||||
if (args.name) body.name = args.name
|
||||
if (args.company) body.company = args.company
|
||||
result = await api('POST', '/leads', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (lead key)' }; break }
|
||||
const body = {}
|
||||
if (args.email) body.email = args.email
|
||||
if (args.name) body.name = args.name
|
||||
if (args.status) body.status = args.status
|
||||
result = await api('PATCH', `/leads/${key}`, body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown leads subcommand. Use: list, get, create, update' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'groups':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/groups${buildQuery()}`)
|
||||
break
|
||||
default:
|
||||
result = { error: 'Unknown groups subcommand. Use: list' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'webhooks':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/webhooks${buildQuery()}`)
|
||||
break
|
||||
case 'get': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (webhook key)' }; break }
|
||||
result = await api('GET', `/webhooks/${key}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const target = args.target
|
||||
if (!target) { result = { error: '--target required (webhook URL)' }; break }
|
||||
const body = { target }
|
||||
if (args.events) body.events = args.events.split(',')
|
||||
result = await api('POST', '/webhooks', body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (webhook key)' }; break }
|
||||
result = await api('DELETE', `/webhooks/${key}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown webhooks subcommand. Use: list, get, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
partnerships: 'partnerships [list | get --key <key> | create --email <email> --group <group-key> | update --key <key>]',
|
||||
customers: 'customers [list | get --key <key> | create --email <email> --partner-key <key> | update --key <key> | delete --key <key>]',
|
||||
transactions: 'transactions [list | get --key <key> | create --customer-key <key> --amount <cents> | delete --key <key>]',
|
||||
deals: 'deals [list | get --key <key> | create --partner-key <key> --name <name> | update --key <key> | archive --key <key>]',
|
||||
actions: 'actions [list | create --customer-key <key> --action-key <key> [--value <n>]]',
|
||||
rewards: 'rewards [list | create --partner-key <key> --amount <cents>]',
|
||||
leads: 'leads [list | get --key <key> | create --partner-key <key> --email <email> | update --key <key>]',
|
||||
groups: 'groups [list]',
|
||||
webhooks: 'webhooks [list | get --key <key> | create --target <url> [--events <evt1,evt2>] | delete --key <key>]',
|
||||
options: '--limit <n> --after <cursor> --before <cursor> --order-by <field>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+246
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.PLAUSIBLE_API_KEY
|
||||
const BASE_URL = process.env.PLAUSIBLE_BASE_URL || 'https://plausible.io'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'PLAUSIBLE_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const siteId = args['site-id']
|
||||
const dateRange = args['date-range'] || '30d'
|
||||
const limit = args.limit ? Number(args.limit) : 100
|
||||
|
||||
switch (cmd) {
|
||||
case 'stats':
|
||||
if (!siteId) { result = { error: '--site-id required (your domain, e.g. example.com)' }; break }
|
||||
switch (sub) {
|
||||
case 'aggregate': {
|
||||
const metrics = args.metrics?.split(',') || ['visitors', 'pageviews', 'bounce_rate', 'visit_duration']
|
||||
result = await api('POST', '/api/v2/query', {
|
||||
site_id: siteId,
|
||||
metrics,
|
||||
date_range: dateRange,
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'timeseries': {
|
||||
const metrics = args.metrics?.split(',') || ['visitors', 'pageviews']
|
||||
const period = args.period || 'time:day'
|
||||
result = await api('POST', '/api/v2/query', {
|
||||
site_id: siteId,
|
||||
metrics,
|
||||
date_range: dateRange,
|
||||
dimensions: [period],
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'pages': {
|
||||
const metrics = args.metrics?.split(',') || ['visitors', 'pageviews']
|
||||
result = await api('POST', '/api/v2/query', {
|
||||
site_id: siteId,
|
||||
metrics,
|
||||
date_range: dateRange,
|
||||
dimensions: ['event:page'],
|
||||
pagination: { limit },
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'sources': {
|
||||
const metrics = args.metrics?.split(',') || ['visitors', 'bounce_rate']
|
||||
result = await api('POST', '/api/v2/query', {
|
||||
site_id: siteId,
|
||||
metrics,
|
||||
date_range: dateRange,
|
||||
dimensions: ['visit:source'],
|
||||
pagination: { limit },
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'countries': {
|
||||
const metrics = args.metrics?.split(',') || ['visitors', 'percentage']
|
||||
result = await api('POST', '/api/v2/query', {
|
||||
site_id: siteId,
|
||||
metrics,
|
||||
date_range: dateRange,
|
||||
dimensions: ['visit:country'],
|
||||
pagination: { limit },
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'devices': {
|
||||
const metrics = args.metrics?.split(',') || ['visitors', 'percentage']
|
||||
result = await api('POST', '/api/v2/query', {
|
||||
site_id: siteId,
|
||||
metrics,
|
||||
date_range: dateRange,
|
||||
dimensions: ['visit:device'],
|
||||
pagination: { limit },
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'utm': {
|
||||
const param = args.param || 'utm_source'
|
||||
const metrics = args.metrics?.split(',') || ['visitors', 'bounce_rate']
|
||||
result = await api('POST', '/api/v2/query', {
|
||||
site_id: siteId,
|
||||
metrics,
|
||||
date_range: dateRange,
|
||||
dimensions: [`visit:${param}`],
|
||||
pagination: { limit },
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'query': {
|
||||
const metrics = args.metrics?.split(',')
|
||||
if (!metrics) { result = { error: '--metrics required (comma-separated)' }; break }
|
||||
const body = { site_id: siteId, metrics, date_range: dateRange }
|
||||
if (args.dimensions) body.dimensions = args.dimensions.split(',')
|
||||
if (args.filters) {
|
||||
try { body.filters = JSON.parse(args.filters) } catch { result = { error: '--filters must be valid JSON' }; break }
|
||||
}
|
||||
body.pagination = { limit }
|
||||
result = await api('POST', '/api/v2/query', body)
|
||||
break
|
||||
}
|
||||
case 'realtime':
|
||||
result = await api('GET', `/api/v1/stats/realtime/visitors?site_id=${encodeURIComponent(siteId)}`)
|
||||
break
|
||||
default:
|
||||
result = { error: 'Unknown stats subcommand. Use: aggregate, timeseries, pages, sources, countries, devices, utm, query, realtime' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'sites':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', '/api/v1/sites')
|
||||
break
|
||||
case 'get': {
|
||||
if (!siteId) { result = { error: '--site-id required' }; break }
|
||||
result = await api('GET', `/api/v1/sites/${encodeURIComponent(siteId)}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const domain = args.domain
|
||||
if (!domain) { result = { error: '--domain required' }; break }
|
||||
const body = { domain }
|
||||
if (args.timezone) body.timezone = args.timezone
|
||||
result = await api('POST', '/api/v1/sites', body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
if (!siteId) { result = { error: '--site-id required' }; break }
|
||||
result = await api('DELETE', `/api/v1/sites/${encodeURIComponent(siteId)}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown sites subcommand. Use: list, get, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'goals':
|
||||
if (!siteId) { result = { error: '--site-id required' }; break }
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/api/v1/sites/goals?site_id=${encodeURIComponent(siteId)}`)
|
||||
break
|
||||
case 'create': {
|
||||
const goalType = args['goal-type']
|
||||
if (!goalType) { result = { error: '--goal-type required (event or page)' }; break }
|
||||
const body = { site_id: siteId, goal_type: goalType }
|
||||
if (goalType === 'event') {
|
||||
if (!args['event-name']) { result = { error: '--event-name required for event goals' }; break }
|
||||
body.event_name = args['event-name']
|
||||
} else if (goalType === 'page') {
|
||||
if (!args['page-path']) { result = { error: '--page-path required for page goals' }; break }
|
||||
body.page_path = args['page-path']
|
||||
}
|
||||
result = await api('PUT', '/api/v1/sites/goals', body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const goalId = args['goal-id']
|
||||
if (!goalId) { result = { error: '--goal-id required' }; break }
|
||||
result = await api('DELETE', `/api/v1/sites/goals/${goalId}`, { site_id: siteId })
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown goals subcommand. Use: list, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
stats: {
|
||||
aggregate: 'stats aggregate --site-id <domain> [--date-range <30d>] [--metrics <m1,m2>]',
|
||||
timeseries: 'stats timeseries --site-id <domain> [--date-range <30d>] [--period <time:day>]',
|
||||
pages: 'stats pages --site-id <domain> [--date-range <30d>] [--limit <n>]',
|
||||
sources: 'stats sources --site-id <domain> [--date-range <30d>]',
|
||||
countries: 'stats countries --site-id <domain> [--date-range <30d>]',
|
||||
devices: 'stats devices --site-id <domain> [--date-range <30d>]',
|
||||
utm: 'stats utm --site-id <domain> [--param <utm_source>] [--date-range <30d>]',
|
||||
query: 'stats query --site-id <domain> --metrics <m1,m2> [--dimensions <d1,d2>] [--filters <json>]',
|
||||
realtime: 'stats realtime --site-id <domain>',
|
||||
},
|
||||
sites: 'sites [list | get --site-id <domain> | create --domain <domain> | delete --site-id <domain>]',
|
||||
goals: 'goals [list | create --goal-type <event|page> --event-name <name> | delete --goal-id <id>] --site-id <domain>',
|
||||
options: '--date-range <day|7d|30d|month|6mo|12mo|year> --limit <n>',
|
||||
env: 'PLAUSIBLE_BASE_URL for self-hosted instances (default: https://plausible.io)',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+366
@@ -0,0 +1,366 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.POSTMARK_API_KEY
|
||||
const BASE_URL = 'https://api.postmarkapp.com'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'POSTMARK_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body, useAccountToken) {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
if (useAccountToken) {
|
||||
headers['X-Postmark-Account-Token'] = API_KEY
|
||||
} else {
|
||||
headers['X-Postmark-Server-Token'] = API_KEY
|
||||
}
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
|
||||
switch (cmd) {
|
||||
case 'email':
|
||||
switch (sub) {
|
||||
case 'send': {
|
||||
const from = args.from
|
||||
const to = args.to
|
||||
const subject = args.subject
|
||||
if (!from) { result = { error: '--from required' }; break }
|
||||
if (!to) { result = { error: '--to required' }; break }
|
||||
if (!subject) { result = { error: '--subject required' }; break }
|
||||
const body = {
|
||||
From: from,
|
||||
To: to,
|
||||
Subject: subject,
|
||||
}
|
||||
if (args.html) body.HtmlBody = args.html
|
||||
if (args.text) body.TextBody = args.text
|
||||
if (!args.html && !args.text) body.TextBody = ''
|
||||
if (args.tag) body.Tag = args.tag
|
||||
if (args.stream) body.MessageStream = args.stream
|
||||
if (args['track-opens']) body.TrackOpens = true
|
||||
if (args['track-links']) body.TrackLinks = args['track-links']
|
||||
if (args.cc) body.Cc = args.cc
|
||||
if (args.bcc) body.Bcc = args.bcc
|
||||
if (args['reply-to']) body.ReplyTo = args['reply-to']
|
||||
result = await api('POST', '/email', body)
|
||||
break
|
||||
}
|
||||
case 'send-template': {
|
||||
const from = args.from
|
||||
const to = args.to
|
||||
const template = args.template
|
||||
if (!from) { result = { error: '--from required' }; break }
|
||||
if (!to) { result = { error: '--to required' }; break }
|
||||
if (!template) { result = { error: '--template required (template ID or alias)' }; break }
|
||||
const body = {
|
||||
From: from,
|
||||
To: to,
|
||||
TemplateModel: {},
|
||||
}
|
||||
const templateNum = Number(template)
|
||||
if (!isNaN(templateNum)) {
|
||||
body.TemplateId = templateNum
|
||||
} else {
|
||||
body.TemplateAlias = template
|
||||
}
|
||||
if (args.model) {
|
||||
const pairs = args.model.split(',')
|
||||
for (const pair of pairs) {
|
||||
const [k, v] = pair.split(':')
|
||||
if (k && v) body.TemplateModel[k] = v
|
||||
}
|
||||
}
|
||||
if (args.stream) body.MessageStream = args.stream
|
||||
if (args.tag) body.Tag = args.tag
|
||||
result = await api('POST', '/email/withTemplate', body)
|
||||
break
|
||||
}
|
||||
case 'send-batch': {
|
||||
const from = args.from
|
||||
const to = args.to
|
||||
const subject = args.subject
|
||||
if (!from || !to || !subject) {
|
||||
result = { error: '--from, --to (comma-separated), and --subject required' }; break
|
||||
}
|
||||
const recipients = to.split(',')
|
||||
const messages = recipients.map(recipient => ({
|
||||
From: from,
|
||||
To: recipient.trim(),
|
||||
Subject: subject,
|
||||
TextBody: args.text || '',
|
||||
HtmlBody: args.html || undefined,
|
||||
MessageStream: args.stream || undefined,
|
||||
Tag: args.tag || undefined,
|
||||
}))
|
||||
result = await api('POST', '/email/batch', messages)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown email subcommand. Use: send, send-template, send-batch' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'templates':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('Count', args.count || '100')
|
||||
params.set('Offset', args.offset || '0')
|
||||
if (args.type) params.set('TemplateType', args.type)
|
||||
result = await api('GET', `/templates?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (template ID or alias)' }; break }
|
||||
result = await api('GET', `/templates/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
const body = {
|
||||
Name: name,
|
||||
Subject: args.subject || '',
|
||||
}
|
||||
if (args.html) body.HtmlBody = args.html
|
||||
if (args.text) body.TextBody = args.text
|
||||
if (args.alias) body.Alias = args.alias
|
||||
if (args.type) body.TemplateType = args.type
|
||||
result = await api('POST', '/templates', body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (template ID or alias)' }; break }
|
||||
result = await api('DELETE', `/templates/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown templates subcommand. Use: list, get, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'bounces':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('count', args.count || '50')
|
||||
params.set('offset', args.offset || '0')
|
||||
if (args.type) params.set('type', args.type)
|
||||
if (args.inactive) params.set('inactive', args.inactive)
|
||||
if (args.email) params.set('emailFilter', args.email)
|
||||
result = await api('GET', `/bounces?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/bounces/${id}`)
|
||||
break
|
||||
}
|
||||
case 'stats':
|
||||
result = await api('GET', '/deliverystats')
|
||||
break
|
||||
case 'activate': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('PUT', `/bounces/${id}/activate`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown bounces subcommand. Use: list, get, stats, activate' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'messages':
|
||||
switch (sub) {
|
||||
case 'outbound': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('count', args.count || '50')
|
||||
params.set('offset', args.offset || '0')
|
||||
if (args.recipient) params.set('recipient', args.recipient)
|
||||
if (args.tag) params.set('tag', args.tag)
|
||||
if (args.status) params.set('status', args.status)
|
||||
result = await api('GET', `/messages/outbound?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'inbound': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('count', args.count || '50')
|
||||
params.set('offset', args.offset || '0')
|
||||
if (args.recipient) params.set('recipient', args.recipient)
|
||||
if (args.status) params.set('status', args.status)
|
||||
result = await api('GET', `/messages/inbound?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (message ID)' }; break }
|
||||
result = await api('GET', `/messages/outbound/${id}/details`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown messages subcommand. Use: outbound, inbound, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'stats':
|
||||
switch (sub) {
|
||||
case 'overview': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.tag) params.set('tag', args.tag)
|
||||
if (args.from) params.set('fromdate', args.from)
|
||||
if (args.to) params.set('todate', args.to)
|
||||
result = await api('GET', `/stats/outbound?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'sends': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.tag) params.set('tag', args.tag)
|
||||
if (args.from) params.set('fromdate', args.from)
|
||||
if (args.to) params.set('todate', args.to)
|
||||
result = await api('GET', `/stats/outbound/sends?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'bounces': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.tag) params.set('tag', args.tag)
|
||||
if (args.from) params.set('fromdate', args.from)
|
||||
if (args.to) params.set('todate', args.to)
|
||||
result = await api('GET', `/stats/outbound/bounces?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'opens': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.tag) params.set('tag', args.tag)
|
||||
if (args.from) params.set('fromdate', args.from)
|
||||
if (args.to) params.set('todate', args.to)
|
||||
result = await api('GET', `/stats/outbound/opens?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'clicks': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.tag) params.set('tag', args.tag)
|
||||
if (args.from) params.set('fromdate', args.from)
|
||||
if (args.to) params.set('todate', args.to)
|
||||
result = await api('GET', `/stats/outbound/clicks?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'spam': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.tag) params.set('tag', args.tag)
|
||||
if (args.from) params.set('fromdate', args.from)
|
||||
if (args.to) params.set('todate', args.to)
|
||||
result = await api('GET', `/stats/outbound/spam?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown stats subcommand. Use: overview, sends, bounces, opens, clicks, spam' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'server':
|
||||
switch (sub) {
|
||||
case 'get':
|
||||
result = await api('GET', '/server')
|
||||
break
|
||||
default:
|
||||
result = { error: 'Unknown server subcommand. Use: get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'suppressions':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (args.stream) params.set('MessageStream', args.stream)
|
||||
result = await api('GET', `/message-streams/${args.stream || 'outbound'}/suppressions/dump`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const stream = args.stream || 'outbound'
|
||||
result = await api('POST', `/message-streams/${stream}/suppressions`, {
|
||||
Suppressions: email.split(',').map(e => ({ EmailAddress: e.trim() }))
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const email = args.email
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
const stream = args.stream || 'outbound'
|
||||
result = await api('POST', `/message-streams/${stream}/suppressions/delete`, {
|
||||
Suppressions: email.split(',').map(e => ({ EmailAddress: e.trim() }))
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown suppressions subcommand. Use: list, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
email: 'email [send --from <from> --to <to> --subject <subj> | send-template --from <from> --to <to> --template <id> | send-batch --from <from> --to <to1,to2> --subject <subj>]',
|
||||
templates: 'templates [list | get --id <id> | create --name <name> | delete --id <id>]',
|
||||
bounces: 'bounces [list | get --id <id> | stats | activate --id <id>]',
|
||||
messages: 'messages [outbound | inbound | get --id <id>]',
|
||||
stats: 'stats [overview | sends | bounces | opens | clicks | spam]',
|
||||
server: 'server [get]',
|
||||
suppressions: 'suppressions [list | create --email <email> | delete --email <email>]',
|
||||
options: '--tag <tag> --from <date> --to <date> --stream <stream-id>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+220
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.SAVVYCAL_API_KEY
|
||||
const BASE_URL = 'https://api.savvycal.com/v1'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'SAVVYCAL_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const limit = args.limit ? Number(args.limit) : 20
|
||||
|
||||
switch (cmd) {
|
||||
case 'me':
|
||||
result = await api('GET', '/me')
|
||||
break
|
||||
|
||||
case 'links':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
if (args.after) params.set('after', args.after)
|
||||
if (args.before) params.set('before', args.before)
|
||||
result = await api('GET', `/scheduling-links?${params}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/scheduling-links/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
const body = { name }
|
||||
if (args.slug) body.slug = args.slug
|
||||
if (args.duration) body.duration_minutes = Number(args.duration)
|
||||
result = await api('POST', '/scheduling-links', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const body = {}
|
||||
if (args.name) body.name = args.name
|
||||
if (args.slug) body.slug = args.slug
|
||||
if (args.duration) body.duration_minutes = Number(args.duration)
|
||||
result = await api('PATCH', `/scheduling-links/${id}`, body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/scheduling-links/${id}`)
|
||||
break
|
||||
}
|
||||
case 'duplicate': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('POST', `/scheduling-links/${id}/duplicate`)
|
||||
break
|
||||
}
|
||||
case 'toggle': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('POST', `/scheduling-links/${id}/toggle`)
|
||||
break
|
||||
}
|
||||
case 'slots': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const params = new URLSearchParams()
|
||||
if (args['start-time']) params.set('start_time', args['start-time'])
|
||||
if (args['end-time']) params.set('end_time', args['end-time'])
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/scheduling-links/${id}/slots${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown links subcommand. Use: list, get, create, update, delete, duplicate, toggle, slots' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'events':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
if (args.after) params.set('after', args.after)
|
||||
if (args.before) params.set('before', args.before)
|
||||
result = await api('GET', `/events?${params}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/events/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const linkId = args['link-id']
|
||||
const startAt = args['start-at']
|
||||
const name = args.name
|
||||
const email = args.email
|
||||
if (!linkId || !startAt || !name || !email) {
|
||||
result = { error: '--link-id, --start-at, --name, and --email required' }
|
||||
break
|
||||
}
|
||||
result = await api('POST', '/events', {
|
||||
scheduling_link_id: linkId,
|
||||
start_at: startAt,
|
||||
name,
|
||||
email,
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'cancel': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('POST', `/events/${id}/cancel`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown events subcommand. Use: list, get, create, cancel' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'webhooks':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
params.set('limit', String(limit))
|
||||
if (args.after) params.set('after', args.after)
|
||||
if (args.before) params.set('before', args.before)
|
||||
result = await api('GET', `/webhooks?${params}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const url = args.url
|
||||
const events = args.events?.split(',')
|
||||
if (!url || !events) { result = { error: '--url and --events (comma-separated) required' }; break }
|
||||
result = await api('POST', '/webhooks', { url, events })
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/webhooks/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown webhooks subcommand. Use: list, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
me: 'me',
|
||||
links: 'links [list | get --id <id> | create --name <name> | update --id <id> | delete --id <id> | duplicate --id <id> | toggle --id <id> | slots --id <id>]',
|
||||
events: 'events [list | get --id <id> | create --link-id <id> --start-at <iso> --name <name> --email <email> | cancel --id <id>]',
|
||||
webhooks: 'webhooks [list | create --url <url> --events <e1,e2> | delete --id <id>]',
|
||||
options: '--limit <n> --after <cursor> --before <cursor>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+266
@@ -0,0 +1,266 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.TRUSTPILOT_API_KEY
|
||||
const API_SECRET = process.env.TRUSTPILOT_API_SECRET
|
||||
const BUSINESS_UNIT_ID = process.env.TRUSTPILOT_BUSINESS_UNIT_ID
|
||||
const BASE_URL = 'https://api.trustpilot.com/v1'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'TRUSTPILOT_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let accessToken = null
|
||||
|
||||
async function getAccessToken() {
|
||||
if (accessToken) return accessToken
|
||||
if (!API_SECRET) return null
|
||||
const res = await fetch(`${BASE_URL}/oauth/oauth-business-users-for-applications/accesstoken`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Basic ' + Buffer.from(`${API_KEY}:${API_SECRET}`).toString('base64'),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: 'grant_type=client_credentials',
|
||||
})
|
||||
const data = await res.json()
|
||||
if (data.access_token) {
|
||||
accessToken = data.access_token
|
||||
return accessToken
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function api(method, path, body, auth = 'apikey') {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
if (auth === 'bearer') {
|
||||
const token = await getAccessToken()
|
||||
if (!token) {
|
||||
return { error: 'TRUSTPILOT_API_SECRET required for private API endpoints' }
|
||||
}
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
} else {
|
||||
headers['apikey'] = API_KEY
|
||||
}
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const businessUnitId = args['business-unit'] || BUSINESS_UNIT_ID
|
||||
const limit = args.limit ? Number(args.limit) : 20
|
||||
|
||||
switch (cmd) {
|
||||
case 'business':
|
||||
switch (sub) {
|
||||
case 'search': {
|
||||
const query = args.query
|
||||
if (!query) { result = { error: '--query required' }; break }
|
||||
result = await api('GET', `/business-units/search?query=${encodeURIComponent(query)}&limit=${limit}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
if (!businessUnitId) { result = { error: '--business-unit or TRUSTPILOT_BUSINESS_UNIT_ID required' }; break }
|
||||
result = await api('GET', `/business-units/${businessUnitId}`)
|
||||
break
|
||||
}
|
||||
case 'profile': {
|
||||
if (!businessUnitId) { result = { error: '--business-unit or TRUSTPILOT_BUSINESS_UNIT_ID required' }; break }
|
||||
result = await api('GET', `/business-units/${businessUnitId}/profileinfo`)
|
||||
break
|
||||
}
|
||||
case 'categories': {
|
||||
if (!businessUnitId) { result = { error: '--business-unit or TRUSTPILOT_BUSINESS_UNIT_ID required' }; break }
|
||||
result = await api('GET', `/business-units/${businessUnitId}/categories`)
|
||||
break
|
||||
}
|
||||
case 'web-links': {
|
||||
if (!businessUnitId) { result = { error: '--business-unit or TRUSTPILOT_BUSINESS_UNIT_ID required' }; break }
|
||||
const locale = args.locale || 'en-US'
|
||||
result = await api('GET', `/business-units/${businessUnitId}/web-links?locale=${locale}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown business subcommand. Use: search, get, profile, categories, web-links' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'reviews':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
if (!businessUnitId) { result = { error: '--business-unit or TRUSTPILOT_BUSINESS_UNIT_ID required' }; break }
|
||||
const stars = args.stars ? `&stars=${args.stars}` : ''
|
||||
const lang = args.language ? `&language=${args.language}` : ''
|
||||
const orderBy = args['order-by'] || 'createdat.desc'
|
||||
result = await api('GET', `/business-units/${businessUnitId}/reviews?perPage=${limit}&orderBy=${orderBy}${stars}${lang}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const reviewId = args.id
|
||||
if (!reviewId) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/reviews/${reviewId}`)
|
||||
break
|
||||
}
|
||||
case 'private': {
|
||||
if (!businessUnitId) { result = { error: '--business-unit or TRUSTPILOT_BUSINESS_UNIT_ID required' }; break }
|
||||
const stars = args.stars ? `&stars=${args.stars}` : ''
|
||||
result = await api('GET', `/private/business-units/${businessUnitId}/reviews?perPage=${limit}${stars}`, null, 'bearer')
|
||||
break
|
||||
}
|
||||
case 'latest':
|
||||
result = await api('GET', `/reviews/latest?count=${limit}`)
|
||||
break
|
||||
case 'reply': {
|
||||
const reviewId = args.id
|
||||
const message = args.message
|
||||
if (!reviewId) { result = { error: '--id required' }; break }
|
||||
if (!message) { result = { error: '--message required' }; break }
|
||||
result = await api('POST', `/private/reviews/${reviewId}/reply`, { message }, 'bearer')
|
||||
break
|
||||
}
|
||||
case 'delete-reply': {
|
||||
const reviewId = args.id
|
||||
if (!reviewId) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/private/reviews/${reviewId}/reply`, null, 'bearer')
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown reviews subcommand. Use: list, get, private, latest, reply, delete-reply' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'invitations':
|
||||
switch (sub) {
|
||||
case 'create': {
|
||||
if (!businessUnitId) { result = { error: '--business-unit or TRUSTPILOT_BUSINESS_UNIT_ID required' }; break }
|
||||
const email = args.email
|
||||
const name = args.name
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
const templateId = args.template
|
||||
const redirectUri = args['redirect-uri'] || 'https://trustpilot.com'
|
||||
const payload = {
|
||||
consumerEmail: email,
|
||||
consumerName: name,
|
||||
referenceNumber: args.reference || '',
|
||||
senderEmail: args['sender-email'] || undefined,
|
||||
replyTo: args['reply-to'] || undefined,
|
||||
templateId: templateId || undefined,
|
||||
redirectUri,
|
||||
}
|
||||
result = await api('POST', `/private/business-units/${businessUnitId}/email-invitations`, payload, 'bearer')
|
||||
break
|
||||
}
|
||||
case 'link': {
|
||||
if (!businessUnitId) { result = { error: '--business-unit or TRUSTPILOT_BUSINESS_UNIT_ID required' }; break }
|
||||
const email = args.email
|
||||
const name = args.name
|
||||
if (!email) { result = { error: '--email required' }; break }
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
result = await api('POST', `/private/business-units/${businessUnitId}/invitation-links`, {
|
||||
email,
|
||||
name,
|
||||
referenceId: args.reference || '',
|
||||
redirectUri: args['redirect-uri'] || 'https://trustpilot.com',
|
||||
}, 'bearer')
|
||||
break
|
||||
}
|
||||
case 'templates': {
|
||||
if (!businessUnitId) { result = { error: '--business-unit or TRUSTPILOT_BUSINESS_UNIT_ID required' }; break }
|
||||
result = await api('GET', `/private/business-units/${businessUnitId}/templates`, null, 'bearer')
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown invitations subcommand. Use: create, link, templates' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'tags':
|
||||
switch (sub) {
|
||||
case 'get': {
|
||||
const reviewId = args.id
|
||||
if (!reviewId) { result = { error: '--id required' }; break }
|
||||
result = await api('GET', `/private/reviews/${reviewId}/tags`, null, 'bearer')
|
||||
break
|
||||
}
|
||||
case 'add': {
|
||||
const reviewId = args.id
|
||||
const group = args.group
|
||||
const value = args.value
|
||||
if (!reviewId) { result = { error: '--id required' }; break }
|
||||
if (!group || !value) { result = { error: '--group and --value required' }; break }
|
||||
result = await api('PUT', `/private/reviews/${reviewId}/tags`, {
|
||||
tags: [{ group, value }],
|
||||
}, 'bearer')
|
||||
break
|
||||
}
|
||||
case 'remove': {
|
||||
const reviewId = args.id
|
||||
const group = args.group
|
||||
const value = args.value
|
||||
if (!reviewId) { result = { error: '--id required' }; break }
|
||||
if (!group || !value) { result = { error: '--group and --value required' }; break }
|
||||
result = await api('DELETE', `/private/reviews/${reviewId}/tags?group=${encodeURIComponent(group)}&value=${encodeURIComponent(value)}`, null, 'bearer')
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown tags subcommand. Use: get, add, remove' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
business: 'business [search --query <q> | get | profile | categories | web-links]',
|
||||
reviews: 'reviews [list | get --id <id> | private | latest | reply --id <id> --message <msg> | delete-reply --id <id>]',
|
||||
invitations: 'invitations [create --email <e> --name <n> | link --email <e> --name <n> | templates]',
|
||||
tags: 'tags [get --id <id> | add --id <id> --group <g> --value <v> | remove --id <id> --group <g> --value <v>]',
|
||||
options: '--business-unit <id> --limit <n> --stars <1-5> --language <code>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+266
@@ -0,0 +1,266 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.TYPEFORM_API_KEY
|
||||
const BASE_URL = 'https://api.typeform.com'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'TYPEFORM_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
const pageSize = args['page-size'] ? Number(args['page-size']) : undefined
|
||||
|
||||
switch (cmd) {
|
||||
case 'forms':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (pageSize) params.set('page_size', String(pageSize))
|
||||
if (args.page) params.set('page', args.page)
|
||||
if (args['workspace-id']) params.set('workspace_id', args['workspace-id'])
|
||||
if (args.search) params.set('search', args.search)
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/forms${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (form ID)' }; break }
|
||||
result = await api('GET', `/forms/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const title = args.title
|
||||
if (!title) { result = { error: '--title required' }; break }
|
||||
const body = { title }
|
||||
if (args['workspace-id']) {
|
||||
body.workspace = { href: `${BASE_URL}/workspaces/${args['workspace-id']}` }
|
||||
}
|
||||
result = await api('POST', '/forms', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (form ID)' }; break }
|
||||
const body = {}
|
||||
if (args.title) body.title = args.title
|
||||
result = await api('PUT', `/forms/${id}`, body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (form ID)' }; break }
|
||||
result = await api('DELETE', `/forms/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown forms subcommand. Use: list, get, create, update, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'responses':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (form ID)' }; break }
|
||||
const params = new URLSearchParams()
|
||||
if (pageSize) params.set('page_size', String(pageSize))
|
||||
if (args.since) params.set('since', args.since)
|
||||
if (args.until) params.set('until', args.until)
|
||||
if (args.after) params.set('after', args.after)
|
||||
if (args.before) params.set('before', args.before)
|
||||
if (args['response-type']) params.set('response_type', args['response-type'])
|
||||
if (args.query) params.set('query', args.query)
|
||||
if (args.fields) params.set('fields', args.fields)
|
||||
if (args.sort) params.set('sort', args.sort)
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/forms/${id}/responses${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (form ID)' }; break }
|
||||
const responseIds = args['response-ids']
|
||||
if (!responseIds) { result = { error: '--response-ids required (comma-separated)' }; break }
|
||||
result = await api('DELETE', `/forms/${id}/responses?included_response_ids=${responseIds}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown responses subcommand. Use: list, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'webhooks':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (form ID)' }; break }
|
||||
result = await api('GET', `/forms/${id}/webhooks`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
const tag = args.tag
|
||||
if (!id || !tag) { result = { error: '--id (form ID) and --tag required' }; break }
|
||||
result = await api('GET', `/forms/${id}/webhooks/${tag}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const id = args.id
|
||||
const tag = args.tag
|
||||
const url = args.url
|
||||
if (!id || !tag || !url) { result = { error: '--id (form ID), --tag, and --url required' }; break }
|
||||
const body = { url, enabled: args.enabled !== 'false' }
|
||||
result = await api('PUT', `/forms/${id}/webhooks/${tag}`, body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
const tag = args.tag
|
||||
if (!id || !tag) { result = { error: '--id (form ID) and --tag required' }; break }
|
||||
result = await api('DELETE', `/forms/${id}/webhooks/${tag}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown webhooks subcommand. Use: list, get, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'themes':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (pageSize) params.set('page_size', String(pageSize))
|
||||
if (args.page) params.set('page', args.page)
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/themes${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (theme ID)' }; break }
|
||||
result = await api('GET', `/themes/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
const body = { name }
|
||||
if (args.font) body.font = args.font
|
||||
result = await api('POST', '/themes', body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (theme ID)' }; break }
|
||||
result = await api('DELETE', `/themes/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown themes subcommand. Use: list, get, create, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'images':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', '/images')
|
||||
break
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (image ID)' }; break }
|
||||
result = await api('GET', `/images/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown images subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'workspaces':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams()
|
||||
if (pageSize) params.set('page_size', String(pageSize))
|
||||
if (args.page) params.set('page', args.page)
|
||||
if (args.search) params.set('search', args.search)
|
||||
const qs = params.toString()
|
||||
result = await api('GET', `/workspaces${qs ? '?' + qs : ''}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (workspace ID)' }; break }
|
||||
result = await api('GET', `/workspaces/${id}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown workspaces subcommand. Use: list, get' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
forms: 'forms [list | get --id <id> | create --title <title> | update --id <id> --title <title> | delete --id <id>]',
|
||||
responses: 'responses [list --id <form_id> | delete --id <form_id> --response-ids <id1,id2>]',
|
||||
webhooks: 'webhooks [list --id <form_id> | get --id <form_id> --tag <tag> | create --id <form_id> --tag <tag> --url <url> | delete --id <form_id> --tag <tag>]',
|
||||
themes: 'themes [list | get --id <id> | create --name <name> | delete --id <id>]',
|
||||
images: 'images [list | get --id <id>]',
|
||||
workspaces: 'workspaces [list | get --id <id>]',
|
||||
options: '--page-size <n> --page <n> --since <iso> --until <iso> --query <text>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+249
@@ -0,0 +1,249 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const API_KEY = process.env.WISTIA_API_KEY
|
||||
const BASE_URL = 'https://api.wistia.com/v1'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'WISTIA_API_KEY environment variable required' }))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return { status: res.status, body: text }
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const result = { _: [] }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2)
|
||||
const next = args[i + 1]
|
||||
if (next && !next.startsWith('--')) {
|
||||
result[key] = next
|
||||
i++
|
||||
} else {
|
||||
result[key] = true
|
||||
}
|
||||
} else {
|
||||
result._.push(arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const [cmd, sub, ...rest] = args._
|
||||
const page = args.page ? Number(args.page) : 1
|
||||
const perPage = args['per-page'] ? Number(args['per-page']) : 25
|
||||
|
||||
async function main() {
|
||||
let result
|
||||
|
||||
switch (cmd) {
|
||||
case 'projects':
|
||||
switch (sub) {
|
||||
case 'list':
|
||||
result = await api('GET', `/projects.json?page=${page}&per_page=${perPage}`)
|
||||
break
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (project hashed ID)' }; break }
|
||||
result = await api('GET', `/projects/${id}.json`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const name = args.name
|
||||
if (!name) { result = { error: '--name required' }; break }
|
||||
const body = { name }
|
||||
if (args.public) body.public = true
|
||||
result = await api('POST', '/projects.json', body)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (project hashed ID)' }; break }
|
||||
const body = {}
|
||||
if (args.name) body.name = args.name
|
||||
result = await api('PUT', `/projects/${id}.json`, body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (project hashed ID)' }; break }
|
||||
result = await api('DELETE', `/projects/${id}.json`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown projects subcommand. Use: list, get, create, update, delete' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'medias':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const params = new URLSearchParams({ page: String(page), per_page: String(perPage) })
|
||||
if (args.project) params.set('project_id', args.project)
|
||||
if (args.name) params.set('name', args.name)
|
||||
if (args.type) params.set('type', args.type)
|
||||
result = await api('GET', `/medias.json?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'get': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (media hashed ID)' }; break }
|
||||
result = await api('GET', `/medias/${id}.json`)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (media hashed ID)' }; break }
|
||||
const body = {}
|
||||
if (args.name) body.name = args.name
|
||||
if (args.description) body.description = args.description
|
||||
result = await api('PUT', `/medias/${id}.json`, body)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (media hashed ID)' }; break }
|
||||
result = await api('DELETE', `/medias/${id}.json`)
|
||||
break
|
||||
}
|
||||
case 'copy': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (media hashed ID)' }; break }
|
||||
const body = {}
|
||||
if (args['target-project']) body.project_id = args['target-project']
|
||||
result = await api('POST', `/medias/${id}/copy.json`, body)
|
||||
break
|
||||
}
|
||||
case 'stats': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (media hashed ID)' }; break }
|
||||
result = await api('GET', `/medias/${id}/stats.json`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown medias subcommand. Use: list, get, update, delete, copy, stats' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'stats':
|
||||
switch (sub) {
|
||||
case 'account':
|
||||
result = await api('GET', '/stats/account.json')
|
||||
break
|
||||
case 'project': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (project hashed ID)' }; break }
|
||||
result = await api('GET', `/stats/projects/${id}.json`)
|
||||
break
|
||||
}
|
||||
case 'media': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (media hashed ID)' }; break }
|
||||
result = await api('GET', `/stats/medias/${id}.json`)
|
||||
break
|
||||
}
|
||||
case 'media-by-date': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (media hashed ID)' }; break }
|
||||
const params = new URLSearchParams()
|
||||
if (args.start) params.set('start_date', args.start)
|
||||
if (args.end) params.set('end_date', args.end)
|
||||
const qs = params.toString() ? `?${params.toString()}` : ''
|
||||
result = await api('GET', `/stats/medias/${id}/by_date.json${qs}`)
|
||||
break
|
||||
}
|
||||
case 'engagement': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (media hashed ID)' }; break }
|
||||
result = await api('GET', `/stats/medias/${id}/engagement.json`)
|
||||
break
|
||||
}
|
||||
case 'visitors': {
|
||||
const params = new URLSearchParams({ page: String(page), per_page: String(perPage) })
|
||||
if (args.search) params.set('search', args.search)
|
||||
result = await api('GET', `/stats/visitors.json?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'visitor': {
|
||||
const key = args.key
|
||||
if (!key) { result = { error: '--key required (visitor key)' }; break }
|
||||
result = await api('GET', `/stats/visitors/${key}.json`)
|
||||
break
|
||||
}
|
||||
case 'events': {
|
||||
const params = new URLSearchParams({ page: String(page), per_page: String(perPage) })
|
||||
if (args['media-id']) params.set('media_id', args['media-id'])
|
||||
result = await api('GET', `/stats/events.json?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown stats subcommand. Use: account, project, media, media-by-date, engagement, visitors, visitor, events' }
|
||||
}
|
||||
break
|
||||
|
||||
case 'account':
|
||||
result = await api('GET', '/account.json')
|
||||
break
|
||||
|
||||
case 'captions':
|
||||
switch (sub) {
|
||||
case 'list': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required (media hashed ID)' }; break }
|
||||
result = await api('GET', `/medias/${id}/captions.json`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const id = args.id
|
||||
const language = args.language
|
||||
if (!id) { result = { error: '--id required (media hashed ID)' }; break }
|
||||
if (!language) { result = { error: '--language required (e.g. eng)' }; break }
|
||||
const body = { language }
|
||||
if (args['srt-file']) body.caption_file = args['srt-file']
|
||||
result = await api('POST', `/medias/${id}/captions.json`, body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = { error: 'Unknown captions subcommand. Use: list, create' }
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
result = {
|
||||
error: 'Unknown command',
|
||||
usage: {
|
||||
projects: 'projects [list | get --id <id> | create --name <name> | update --id <id> --name <name> | delete --id <id>]',
|
||||
medias: 'medias [list [--project <id>] | get --id <id> | update --id <id> --name <name> | delete --id <id> | copy --id <id> [--target-project <id>] | stats --id <id>]',
|
||||
stats: 'stats [account | project --id <id> | media --id <id> | media-by-date --id <id> [--start <date> --end <date>] | engagement --id <id> | visitors | visitor --key <key> | events [--media-id <id>]]',
|
||||
account: 'account',
|
||||
captions: 'captions [list --id <media-id> | create --id <media-id> --language <lang>]',
|
||||
options: '--page <n> --per-page <n>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(JSON.stringify({ error: err.message }))
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user