fix: correct API issues found by codex review across 10 CLIs

Fixes found by automated codex review of all 47 CLI tools:

- resend: webhook field name endpoint_url -> endpoint
- mailchimp: change from Bearer to Basic auth per API docs
- kit: fail fast when api_secret required but not set
- activecampaign: automation add-contact needs --contact-id not --email
- google-ads: budget updateMask must be snake_case (amount_micros)
- meta-ads: special_ad_categories default to ['NONE'] not empty array
- linkedin-ads: add required X-RestLi-Protocol-Version header
- onesignal: auth prefix should be Key, not Basic
- mixpanel: query dates must be YYYY-MM-DD, not relative strings
- wistia: change from Bearer to Basic auth per API docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Corey Haines 2026-02-17 11:57:42 -08:00
parent 8ce007c983
commit f123804827
10 changed files with 18 additions and 11 deletions

View file

@ -273,11 +273,11 @@ async function main() {
} }
case 'add-contact': { case 'add-contact': {
const automationId = args.id const automationId = args.id
const contactEmail = args.email const contactId = args['contact-id']
if (!automationId) { result = { error: '--id required (automation ID)' }; break } if (!automationId) { result = { error: '--id required (automation ID)' }; break }
if (!contactEmail) { result = { error: '--email required' }; break } if (!contactId) { result = { error: '--contact-id required (contact ID, not email)' }; break }
result = await api('POST', '/contactAutomations', { result = await api('POST', '/contactAutomations', {
contactAutomation: { contact: contactEmail, automation: automationId } contactAutomation: { contact: contactId, automation: automationId }
}) })
break break
} }

View file

@ -157,7 +157,7 @@ async function main() {
resourceName: `customers/${CUSTOMER_ID}/campaignBudgets/${args.id}`, resourceName: `customers/${CUSTOMER_ID}/campaignBudgets/${args.id}`,
amountMicros, amountMicros,
}, },
updateMask: 'amountMicros', updateMask: 'amount_micros',
}], }],
}) })
break break

View file

@ -14,6 +14,8 @@ async function api(method, path, body, useSecret = true) {
if (method === 'GET' || method === 'DELETE') { if (method === 'GET' || method === 'DELETE') {
if (useSecret && API_SECRET) { if (useSecret && API_SECRET) {
url.searchParams.set('api_secret', API_SECRET) url.searchParams.set('api_secret', API_SECRET)
} else if (useSecret && !API_SECRET) {
return { error: 'KIT_API_SECRET required for this endpoint' }
} else if (API_KEY) { } else if (API_KEY) {
url.searchParams.set('api_key', API_KEY) url.searchParams.set('api_key', API_KEY)
} }
@ -26,6 +28,8 @@ async function api(method, path, body, useSecret = true) {
const authBody = { ...body } const authBody = { ...body }
if (useSecret && API_SECRET) { if (useSecret && API_SECRET) {
authBody.api_secret = API_SECRET authBody.api_secret = API_SECRET
} else if (useSecret && !API_SECRET) {
return { error: 'KIT_API_SECRET required for this endpoint' }
} else if (API_KEY) { } else if (API_KEY) {
authBody.api_key = API_KEY authBody.api_key = API_KEY
} }

View file

@ -11,6 +11,7 @@ if (!TOKEN) {
async function api(method, path, body) { async function api(method, path, body) {
const headers = { const headers = {
'Authorization': `Bearer ${TOKEN}`, 'Authorization': `Bearer ${TOKEN}`,
'X-RestLi-Protocol-Version': '2.0.0',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
} }
if (args['dry-run']) { if (args['dry-run']) {

View file

@ -11,8 +11,9 @@ const dc = API_KEY.split('-').pop()
const BASE_URL = `https://${dc}.api.mailchimp.com/3.0` const BASE_URL = `https://${dc}.api.mailchimp.com/3.0`
async function api(method, path, body) { async function api(method, path, body) {
const auth = 'Basic ' + Buffer.from(`anystring:${API_KEY}`).toString('base64')
const headers = { const headers = {
'Authorization': `Bearer ${API_KEY}`, 'Authorization': auth,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
} }
if (args['dry-run']) { if (args['dry-run']) {

View file

@ -93,7 +93,7 @@ async function main() {
name: args.name, name: args.name,
objective: args.objective, objective: args.objective,
status: args.status || 'PAUSED', status: args.status || 'PAUSED',
special_ad_categories: [], special_ad_categories: ['NONE'],
} }
result = await api('POST', `/act_${accountId}/campaigns`, body) result = await api('POST', `/act_${accountId}/campaigns`, body)
break break

View file

@ -155,8 +155,8 @@ async function main() {
params: { params: {
events: [{ event: args.event || 'all' }], events: [{ event: args.event || 'all' }],
time_range: { time_range: {
from_date: args['from-date'] || '30daysAgo', from_date: args['from-date'] || new Date(Date.now() - 30 * 86400000).toISOString().slice(0, 10),
to_date: args['to-date'] || 'today', to_date: args['to-date'] || new Date().toISOString().slice(0, 10),
}, },
}, },
} }

View file

@ -16,7 +16,7 @@ if (!APP_ID) {
async function api(method, path, body) { async function api(method, path, body) {
const headers = { const headers = {
'Authorization': `Basic ${REST_API_KEY}`, 'Authorization': `Key ${REST_API_KEY}`,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json', 'Accept': 'application/json',
} }

View file

@ -203,7 +203,7 @@ async function main() {
break break
case 'create': { case 'create': {
const events = args.events?.split(',') || ['email.sent', 'email.delivered', 'email.bounced'] const events = args.events?.split(',') || ['email.sent', 'email.delivered', 'email.bounced']
result = await api('POST', '/webhooks', { endpoint_url: args.endpoint, events }) result = await api('POST', '/webhooks', { endpoint: args.endpoint, events })
break break
} }
case 'delete': case 'delete':

View file

@ -9,13 +9,14 @@ if (!API_KEY) {
} }
async function api(method, path, body) { async function api(method, path, body) {
const auth = 'Basic ' + Buffer.from(`${API_KEY}:`).toString('base64')
if (args['dry-run']) { if (args['dry-run']) {
return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { 'Authorization': '***', 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: body || undefined } return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { 'Authorization': '***', 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: body || undefined }
} }
const res = await fetch(`${BASE_URL}${path}`, { const res = await fetch(`${BASE_URL}${path}`, {
method, method,
headers: { headers: {
'Authorization': `Bearer ${API_KEY}`, 'Authorization': auth,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json', 'Accept': 'application/json',
}, },