@adkit.so/cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,841 @@
1
+ #!/usr/bin/env node
2
+ import { resolveConfig, readConfig } from './config.js';
3
+ import { AdkitClient } from './client.js';
4
+ import { formatOutput } from './output.js';
5
+ import { login, logout } from './commands/auth.js';
6
+ import { listProjects, useProject, currentProject } from './commands/projects.js';
7
+ import { listAccounts, connectAccount, disconnectAccount, listPages, listPixels, listCampaigns, createCampaign, updateCampaign, deleteCampaign, listAdSets, createAdSet, updateAdSet, deleteAdSet, listAds, createAd, updateAd, deleteAd, createCreative, updateCreative, deleteCreative, uploadMedia, listMedia, deleteMedia, searchInterests } from './commands/meta.js';
8
+ import { status } from './commands/status.js';
9
+ import { listDrafts, getDraft, publishDraft, deleteDraft } from './commands/drafts.js';
10
+ import { parseArgs, unwrapList } from './cli-utils.js';
11
+ import { CliError } from './errors.js';
12
+ const DEFAULT_BASE_URL = 'https://app.adkit.so/api/v1';
13
+ const USAGE = `
14
+ Usage: adkit <command> [options]
15
+
16
+ Commands:
17
+ status Show project context and connected accounts
18
+ setup [area...] Set up AdKit (opens browser)
19
+ manage <platform> <entity> <action> Manage ad platforms (meta, google, etc.)
20
+ manage drafts <action> Manage drafts (list, get, publish, delete)
21
+ projects <action> Manage projects (list, use, current)
22
+
23
+ Advanced:
24
+ logout Remove stored API key
25
+
26
+ Global flags:
27
+ --json Force JSON output (default when not a TTY)
28
+ --project <id> Use a specific project (overrides default)
29
+ --fields <list> Comma-separated fields to show (for list commands)
30
+ --help Show help (--help full = complete field reference, but more verbose)
31
+
32
+ Run \`adkit <command> --help\` for details.
33
+ `.trim();
34
+ // ---------------------------------------------------------------------------
35
+ // Shared flag descriptions (DRY building blocks for help text)
36
+ // ---------------------------------------------------------------------------
37
+ const FLAG = {
38
+ account: ' --account <id> Ad account ID (auto-resolved if only one)',
39
+ publish: ' --publish Publish immediately (default: draft)',
40
+ overrides: ' --platform-overrides <json> Raw Meta API fields merged onto payload',
41
+ data: ' --data <json> Full JSON body — bypasses named flags',
42
+ status: ' --status <s> active, paused, archived',
43
+ budgetDaily: ' --budget-daily <n> Daily budget in account currency',
44
+ cta: ' --cta <type> sign_up, learn_more, shop_now, download, get_offer, contact_us, subscribe, get_quote, book_now, apply_now',
45
+ };
46
+ // ---------------------------------------------------------------------------
47
+ // Entity-specific help text
48
+ // ---------------------------------------------------------------------------
49
+ // --- Campaigns ---
50
+ const CAMPAIGN_HELP = `adkit manage meta campaigns — Meta ad campaigns
51
+
52
+ list List campaigns
53
+ create Create a campaign
54
+ update <id> Update a campaign
55
+ delete <id> Delete a campaign
56
+
57
+ Flags (create/update):
58
+ --name <name> Campaign name (required for create)
59
+ --objective <type> traffic, awareness, engagement, leads, sales, app_promotion
60
+ ${FLAG.status}
61
+ ${FLAG.budgetDaily}
62
+
63
+ Examples:
64
+ adkit manage meta campaigns create --name "Spring Sale" --objective sales --budget-daily 50 --publish
65
+ adkit manage meta campaigns update cmp_abc --status paused
66
+ adkit manage meta campaigns delete cmp_abc
67
+
68
+ Run --help full for all fields and advanced options.`;
69
+ const CAMPAIGN_HELP_FULL = `adkit manage meta campaigns — Meta ad campaigns
70
+
71
+ list List campaigns
72
+ create Create a campaign
73
+ update <id> Update a campaign
74
+ delete <id> Delete a campaign
75
+
76
+ Flags (create/update):
77
+ --name <name> Campaign name (required for create)
78
+ --objective <type> traffic, awareness, engagement, leads, sales, app_promotion
79
+ ${FLAG.status}
80
+ ${FLAG.budgetDaily}
81
+
82
+ Advanced flags:
83
+ --bid-strategy <s> lowest_cost (default), cost_cap, bid_cap, min_roas
84
+ --abo Adset budget optimization (disables campaign-level budget)
85
+
86
+ --data JSON fields:
87
+ budget.lifetime (number) Lifetime budget in account currency
88
+ spendCap (number) Total spend cap
89
+ specialAdCategories (string[]) credit, housing, employment, social_issues
90
+ startDate (string) ISO date, e.g. "2026-04-01T00:00:00+0000"
91
+ endDate (string|null) ISO date or null for no end
92
+
93
+ Examples:
94
+ adkit manage meta campaigns list --account act_123456789
95
+ adkit manage meta campaigns create --name "Spring Sale" --objective sales --budget-daily 50 --publish
96
+ adkit manage meta campaigns update cmp_abc --status paused
97
+ adkit manage meta campaigns update cmp_abc --budget-daily 100
98
+ adkit manage meta campaigns delete cmp_abc
99
+ adkit manage meta campaigns create --data '{
100
+ "name":"Housing Leads","objective":"leads",
101
+ "budget":{"lifetime":500},"specialAdCategories":["housing"],
102
+ "startDate":"2026-04-01T00:00:00+0000","endDate":"2026-04-30T23:59:59+0000"
103
+ }' --publish`;
104
+ // --- Ad Sets ---
105
+ const ADSET_HELP = `adkit manage meta adsets — Meta ad sets
106
+
107
+ list List ad sets
108
+ create Create an ad set
109
+ update <id> Update an ad set
110
+ delete <id> Delete an ad set
111
+
112
+ Flags (create/update):
113
+ --campaign <id> Parent campaign ID (required for create)
114
+ --name <name> Ad set name (required for create)
115
+ ${FLAG.status}
116
+ ${FLAG.budgetDaily}
117
+ --optimization <t> link_clicks, impressions, reach, conversions, landing_page_views, lead_generation
118
+ --event-type <t> purchase, lead, complete_registration, add_to_cart, subscribe, start_trial, contact, content_view, other
119
+ --pixel <id> Pixel ID (falls back to account default)
120
+ --countries <codes> Comma-separated ISO codes, e.g. US,CA
121
+ --interest <id> Interest ID (repeatable). Find IDs: adkit manage meta interests search <query>
122
+
123
+ Rules:
124
+ - objective=sales → event-type: purchase, add_to_cart, complete_registration, subscribe, start_trial, content_view
125
+ - objective=leads → event-type: lead, contact, complete_registration, subscribe, other
126
+
127
+ Examples:
128
+ adkit manage meta adsets create --campaign cmp_abc --name "US Broad" \\
129
+ --optimization conversions --event-type purchase --countries US --budget-daily 20 --publish
130
+ adkit manage meta adsets create --campaign cmp_abc --name "US SaaS" \\
131
+ --optimization conversions --event-type purchase --countries US \\
132
+ --interest "6003344765839" --interest "6003127206524" --budget-daily 20 --publish
133
+ adkit manage meta adsets update as_xyz --budget-daily 50
134
+ adkit manage meta adsets delete as_xyz
135
+
136
+ Run --help full for all fields and advanced options.`;
137
+ const ADSET_HELP_FULL = `adkit manage meta adsets — Meta ad sets
138
+
139
+ list List ad sets
140
+ create Create an ad set
141
+ update <id> Update an ad set
142
+ delete <id> Delete an ad set
143
+
144
+ Flags (create/update):
145
+ --campaign <id> Parent campaign ID (required for create)
146
+ --name <name> Ad set name (required for create)
147
+ ${FLAG.status}
148
+ ${FLAG.budgetDaily}
149
+ --optimization <t> link_clicks, impressions, reach, conversions, landing_page_views, lead_generation
150
+ --event-type <t> purchase, lead, complete_registration, add_to_cart, subscribe, start_trial, contact, content_view, other
151
+ --pixel <id> Pixel ID (falls back to account default)
152
+ --countries <codes> Comma-separated ISO codes, e.g. US,CA
153
+ --interest <id> Interest ID (repeatable). Find IDs: adkit manage meta interests search <query>
154
+
155
+ Rules:
156
+ - objective=sales → event-type: purchase, add_to_cart, complete_registration, subscribe, start_trial, content_view
157
+ - objective=leads → event-type: lead, contact, complete_registration, subscribe, other
158
+
159
+ Advanced flags:
160
+ --targeting <json> Complex targeting JSON, merged with simple flags (JSON keys take precedence)
161
+ --genders <list> male, female
162
+
163
+ --data JSON fields:
164
+ budget.lifetime (number) Lifetime budget
165
+ bidAmount (number) Bid amount in account currency
166
+ destinationType (string) website, app, messenger, instagram, whatsapp
167
+ startTime (string) ISO date
168
+ endTime (string) ISO date
169
+ isDynamicCreative (boolean) Enable dynamic creative
170
+ promotedObject.pixelId (string) Meta Pixel ID
171
+ promotedObject.customEventType (string) Custom conversion event
172
+ promotedObject.pageId (string) Facebook Page ID
173
+ targeting.age (object) {"min":25,"max":55}
174
+ targeting.publisherPlatforms (string[]) facebook, instagram, audience_network, messenger
175
+ targeting.devicePlatforms (string[]) mobile, desktop
176
+ targeting.interests (string[]) Interest IDs
177
+ targeting.behaviors (string[]) Behavior IDs
178
+ targeting.customAudiences (string[]) Custom audience IDs
179
+ targeting.excludedCustomAudiences (string[]) Excluded audience IDs
180
+ targeting.expand (object) Advantage+ audience controls
181
+
182
+ Note: Meta defaults to Advantage+ audience (AI-driven targeting).
183
+ With Advantage+, age.min cannot exceed 25 and age.max cannot be below 65.
184
+ To use exact age ranges, disable Advantage+ for age:
185
+ --data '{"targeting":{"age":{"min":25,"max":55},"expand":{"age":false}}}'
186
+
187
+ Examples:
188
+ adkit manage meta adsets create --campaign cmp_abc --name "US Broad" \\
189
+ --optimization conversions --event-type purchase --countries US --budget-daily 20 --publish
190
+ adkit manage meta adsets create --campaign cmp_abc --name "US SaaS" \\
191
+ --optimization conversions --event-type purchase --countries US \\
192
+ --interest "6003344765839" --interest "6003127206524" --budget-daily 20 --publish
193
+ adkit manage meta adsets create --data '{
194
+ "campaignId":"cmp_abc","name":"US SaaS 25-55",
195
+ "budget":{"daily":30},"optimization":"conversions",
196
+ "promotedObject":{"pixelId":"px_123","customEventType":"PURCHASE"},
197
+ "targeting":{"countries":["US"],"age":{"min":25,"max":55},
198
+ "expand":{"age":false},"interests":["6003279598823"]}
199
+ }' --publish
200
+ adkit manage meta adsets update as_xyz --budget-daily 50
201
+ adkit manage meta adsets delete as_xyz`;
202
+ // --- Ads ---
203
+ const ADS_HELP = `adkit manage meta ads — Meta ads
204
+
205
+ list List ads
206
+ create Create an ad
207
+ update <id> Update an ad
208
+ delete <id> Delete an ad
209
+
210
+ Flags (create — new creative from media):
211
+ --adset <id> Ad set ID (required)
212
+ --media <path|url> Media file or URL (repeatable). Handles upload + processing automatically
213
+ --primary-text <t> Ad body text (repeatable)
214
+ --headline <text> Headline text (repeatable)
215
+ --description <t> Description text (repeatable, optional)
216
+ ${FLAG.cta}
217
+ --name <name> Ad name
218
+ --url <url> Landing page URL
219
+ --page <id> Facebook Page ID (auto-resolved from account defaults)
220
+
221
+ Flags (create — existing creative):
222
+ --adset <id> Ad set ID (required)
223
+ --creative <id> Existing creative ID
224
+ --name <name> Ad name
225
+
226
+ Flags (list):
227
+ --adset <id> Filter by ad set ID
228
+
229
+ Examples:
230
+ adkit manage meta ads create --adset as_789 \\
231
+ --media ./hero.mp4 --primary-text "Try it free" --headline "Get started" \\
232
+ --cta sign_up --url https://example.com --publish
233
+ adkit manage meta ads create --adset as_789 --creative cr_abc --name "Hero v1" --publish
234
+ adkit manage meta ads list --adset as_xyz
235
+ adkit manage meta ads update ad_123 --data '{"status":"paused"}'
236
+ adkit manage meta ads delete ad_123
237
+
238
+ Run --help full for all fields and advanced options.`;
239
+ const ADS_HELP_FULL = `adkit manage meta ads — Meta ads
240
+
241
+ list List ads
242
+ create Create an ad
243
+ update <id> Update an ad
244
+ delete <id> Delete an ad
245
+
246
+ Flags (create — new creative from media):
247
+ --adset <id> Ad set ID (required)
248
+ --media <path|url> Media file or URL (repeatable). Handles upload + processing automatically
249
+ --primary-text <t> Ad body text (repeatable)
250
+ --headline <text> Headline text (repeatable)
251
+ --description <t> Description text (repeatable, optional)
252
+ ${FLAG.cta}
253
+ --name <name> Ad name
254
+ --url <url> Landing page URL
255
+ --page <id> Facebook Page ID (auto-resolved from account defaults)
256
+
257
+ Flags (create — existing creative):
258
+ --adset <id> Ad set ID (required)
259
+ --creative <id> Existing creative ID
260
+ --name <name> Ad name
261
+
262
+ Flags (list):
263
+ --adset <id> Filter by ad set ID
264
+
265
+ --data JSON fields:
266
+ adsetId (required) Parent ad set ID
267
+ name (required) Ad name
268
+ status active, paused, archived, deleted
269
+ creative { creativeId: "cr_..." } — reference existing creative
270
+ platformOverrides Raw Meta API fields merged onto the payload
271
+
272
+ Examples:
273
+ adkit manage meta ads create --adset as_789 \\
274
+ --media ./hero.mp4 --primary-text "Try it free" --headline "Get started" \\
275
+ --cta sign_up --url https://example.com --publish
276
+ adkit manage meta ads create --adset as_789 --creative cr_abc --name "Hero v1" --publish
277
+ adkit manage meta ads create --data '{"adsetId":"as_xyz","creative":{"creativeId":"cr_abc"},"name":"Hero v1"}' --publish
278
+ adkit manage meta ads list --adset as_xyz
279
+ adkit manage meta ads update ad_123 --data '{"status":"paused"}'
280
+ adkit manage meta ads delete ad_123`;
281
+ // --- Creatives ---
282
+ const CREATIVE_HELP = `adkit manage meta creatives — Meta ad creatives
283
+
284
+ Prefer using "ads create --media" which handles creative creation automatically.
285
+
286
+ create Create an ad creative
287
+ update <id> Update an ad creative
288
+ delete <id> Delete an ad creative
289
+
290
+ Flags (create/update):
291
+ --page-id <id> Facebook Page ID (required for create)
292
+ --headline <text> Ad headline (required for create)
293
+ --primary-text <t> Ad body text (required for create, repeatable)
294
+ --link-url <url> Destination URL (required for create)
295
+ ${FLAG.cta}
296
+ --name <name> Creative name
297
+ --image-hash <hash> Image hash from media upload
298
+ --image-url <url> Image URL
299
+ --video-id <id> Video ID from media upload
300
+
301
+ Examples:
302
+ adkit manage meta creatives create --page-id pg_123 --headline "Get Started" \\
303
+ --primary-text "Try it free" --link-url https://example.com \\
304
+ --cta sign_up --image-hash abc123 --publish
305
+ adkit manage meta creatives update cr_abc --primary-text "Start free trial"
306
+ adkit manage meta creatives delete cr_abc
307
+
308
+ Run --help full for all fields and advanced options.`;
309
+ const CREATIVE_HELP_FULL = `adkit manage meta creatives — Meta ad creatives
310
+
311
+ Prefer using "ads create --media" which handles creative creation automatically.
312
+
313
+ create Create an ad creative
314
+ update <id> Update an ad creative
315
+ delete <id> Delete an ad creative
316
+
317
+ Flags (create/update):
318
+ --page-id <id> Facebook Page ID (required for create)
319
+ --headline <text> Ad headline (required for create)
320
+ --primary-text <t> Ad body text (required for create, repeatable)
321
+ --link-url <url> Destination URL (required for create)
322
+ ${FLAG.cta}
323
+ --name <name> Creative name
324
+ --link-description <t> Link description (repeatable, optional)
325
+ --image-hash <hash> Image hash from media upload
326
+ --image-url <url> Image URL
327
+ --video-id <id> Video ID from media upload
328
+
329
+ --data JSON fields:
330
+ thumbnailUrl (string) Video thumbnail URL
331
+ pixelId (string) Pixel ID for conversion tracking
332
+ variants Multi-variant creative:
333
+ images (string[]), videos (string[]), texts (string[]),
334
+ headlines (string[]), descriptions (string[]),
335
+ ctas (string[]), linkUrls (string[])
336
+
337
+ Examples:
338
+ adkit manage meta creatives create --page-id pg_123 --headline "Get Started" \\
339
+ --primary-text "Try it free" --link-url https://example.com \\
340
+ --cta sign_up --image-hash abc123 --publish
341
+ adkit manage meta creatives create --data '{
342
+ "pageId":"pg_123","name":"Multi-variant test",
343
+ "headline":"Get Started","primaryText":"Try it free",
344
+ "linkUrl":"https://example.com","cta":"sign_up",
345
+ "imageHash":"abc123","pixelId":"px_456"
346
+ }' --publish
347
+ adkit manage meta creatives update cr_abc --primary-text "Start free trial"
348
+ adkit manage meta creatives delete cr_abc`;
349
+ // ---------------------------------------------------------------------------
350
+ // Composed HELP and HELP_FULL
351
+ // ---------------------------------------------------------------------------
352
+ const HELP = {
353
+ 'meta accounts': `adkit manage meta accounts — Meta ad accounts
354
+
355
+ list List connected ad accounts
356
+ connect <id> Connect an ad account
357
+ disconnect <id> Disconnect an ad account
358
+ <id> pages List Facebook Pages for an account
359
+ <id> pixels List Meta Pixels for an account
360
+
361
+ Examples:
362
+ adkit manage meta accounts list
363
+ adkit manage meta accounts connect act_123456789
364
+ adkit manage meta accounts act_123456789 pages
365
+ adkit manage meta accounts disconnect act_123456789`.trim(),
366
+ 'meta campaigns': CAMPAIGN_HELP,
367
+ 'meta adsets': ADSET_HELP,
368
+ 'meta ads': ADS_HELP,
369
+ 'meta creatives': CREATIVE_HELP,
370
+ 'meta media': `adkit manage meta media — Upload media to Meta
371
+
372
+ upload Upload an image or video
373
+
374
+ Flags:
375
+ --file <path> Local file to upload
376
+ --url <url> Image URL to upload
377
+
378
+ Examples:
379
+ adkit manage meta media upload --file ./hero-video.mp4
380
+ adkit manage meta media upload --url https://example.com/hero.png`.trim(),
381
+ 'meta interests': `adkit manage meta interests — Search targeting interests
382
+
383
+ search <query> Search for interests (multiple queries supported)
384
+
385
+ Output: Returns id and name for each interest. Use the id with --interest flag.
386
+
387
+ Examples:
388
+ adkit manage meta interests search "yoga"
389
+ adkit manage meta interests search "saas" "marketing" "digital ads"`.trim(),
390
+ manage: `adkit manage — Ad platform management
391
+
392
+ Platforms:
393
+ meta Meta (Facebook/Instagram) ads
394
+ drafts Manage drafts across platforms
395
+
396
+ Run adkit manage <platform> --help for details.`.trim(),
397
+ meta: `adkit manage meta — Meta Ads management
398
+
399
+ Entity groups:
400
+ accounts List connected ad accounts
401
+ campaigns Manage campaigns
402
+ adsets Manage ad sets
403
+ ads Manage ads
404
+ creatives Manage ad creatives (prefer using ads --media directly)
405
+ interests Search targeting interests
406
+
407
+ General flags (all create/update commands):
408
+ ${FLAG.account}
409
+ ${FLAG.publish}
410
+ ${FLAG.data}
411
+
412
+ Mutations are draft-first by default. Use --publish to publish immediately.
413
+ Run adkit manage meta <group> --help for details.`.trim(),
414
+ drafts: `adkit manage drafts — Manage drafts
415
+
416
+ list List all drafts
417
+ get <id> Get a draft by ID
418
+ publish <id> Publish a draft
419
+ delete <id> Delete a draft
420
+
421
+ Flags (list):
422
+ --platform <name> Filter by platform
423
+ --limit <n> Max results
424
+ --offset <n> Pagination offset
425
+
426
+ Examples:
427
+ adkit manage drafts list --platform meta --limit 10
428
+ adkit manage drafts publish dft_abc123
429
+ adkit manage drafts delete dft_abc123`.trim(),
430
+ status: `adkit status — Show project context and connected accounts
431
+
432
+ Displays project name, connected platforms, and ad accounts with defaults.
433
+ AI agents should call this first to understand the context.
434
+
435
+ Examples:
436
+ adkit status
437
+ adkit status --json`.trim(),
438
+ projects: `adkit projects — Manage projects
439
+
440
+ list List all configured projects
441
+ use <id> Switch active project
442
+ current Show active project
443
+
444
+ Examples:
445
+ adkit projects list
446
+ adkit projects use proj_abc123`.trim(),
447
+ };
448
+ const HELP_FULL = {
449
+ 'meta campaigns': CAMPAIGN_HELP_FULL,
450
+ 'meta adsets': ADSET_HELP_FULL,
451
+ 'meta ads': ADS_HELP_FULL,
452
+ 'meta creatives': CREATIVE_HELP_FULL,
453
+ };
454
+ function getBaseUrl() {
455
+ return process.env.ADKIT_BASE_URL || DEFAULT_BASE_URL;
456
+ }
457
+ function wantsJson(flags) {
458
+ return flags.json === true || !(process.stdout.isTTY ?? false);
459
+ }
460
+ function requireClient(flags) {
461
+ const projectFlag = typeof flags.project === 'string' ? flags.project : undefined;
462
+ const { apiKey } = resolveConfig({ project: projectFlag });
463
+ if (!apiKey)
464
+ throw new CliError('NOT_AUTHENTICATED', 'No API key found', 'Run: adkit setup');
465
+ return new AdkitClient({ apiKey, baseUrl: getBaseUrl() });
466
+ }
467
+ function printList(data, flags, emptyHint = 'No results.') {
468
+ const rows = unwrapList(data);
469
+ const fields = typeof flags.fields === 'string' ? flags.fields.split(',') : undefined;
470
+ const output = formatOutput(rows, { fields, json: wantsJson(flags) });
471
+ if (output)
472
+ console.log(output);
473
+ else
474
+ console.log(emptyHint);
475
+ }
476
+ function hasArrayValues(v) {
477
+ return v !== null && typeof v === 'object' && !Array.isArray(v) && Object.values(v).some(Array.isArray);
478
+ }
479
+ function printResult(data, flags, emptyHint) {
480
+ if (data === undefined) {
481
+ console.log('Done.');
482
+ return;
483
+ }
484
+ if (wantsJson(flags))
485
+ console.log(JSON.stringify(data));
486
+ else if (Array.isArray(data) || hasArrayValues(data))
487
+ printList(data, flags, emptyHint);
488
+ else
489
+ console.log(JSON.stringify(data, null, 2));
490
+ }
491
+ function showHelp(key, full = false) {
492
+ const text = full ? HELP_FULL[key] || HELP[key] : HELP[key];
493
+ if (text) {
494
+ console.log(text);
495
+ return true;
496
+ }
497
+ return false;
498
+ }
499
+ async function main() {
500
+ const { args, flags } = parseArgs(process.argv.slice(2), {
501
+ multi: ['media', 'primary-text', 'headline', 'description', 'link-description', 'interest'],
502
+ });
503
+ if (args.length === 0) {
504
+ console.log(USAGE);
505
+ process.exit(0);
506
+ }
507
+ // Top-level --help with no command
508
+ if (flags.help && args.length === 0) {
509
+ console.log(USAGE);
510
+ process.exit(0);
511
+ }
512
+ try {
513
+ // --- status ---
514
+ if (args[0] === 'status') {
515
+ const client = requireClient(flags);
516
+ await status(client, wantsJson(flags));
517
+ return;
518
+ }
519
+ // --- manage commands (platform management) ---
520
+ if (args[0] === 'manage') {
521
+ const manageTarget = args[1];
522
+ if (flags.help && !manageTarget) {
523
+ showHelp('manage', flags.help === 'full');
524
+ process.exit(0);
525
+ }
526
+ if (!manageTarget) {
527
+ showHelp('manage', flags.help === 'full');
528
+ process.exit(0);
529
+ }
530
+ // --- manage drafts ---
531
+ if (manageTarget === 'drafts') {
532
+ const action = args[2];
533
+ const restArgs = args.slice(3);
534
+ if (flags.help) {
535
+ showHelp('drafts', flags.help === 'full');
536
+ process.exit(0);
537
+ }
538
+ const client = requireClient(flags);
539
+ let data;
540
+ let emptyHint;
541
+ switch (action) {
542
+ case 'list':
543
+ case undefined:
544
+ data = await listDrafts(client, restArgs, flags);
545
+ emptyHint = 'No drafts found. Drafts are created automatically when you create or update ads without `--publish`.';
546
+ break;
547
+ case 'get':
548
+ data = await getDraft(client, restArgs, flags);
549
+ break;
550
+ case 'publish':
551
+ data = await publishDraft(client, restArgs, flags);
552
+ break;
553
+ case 'delete':
554
+ data = await deleteDraft(client, restArgs, flags);
555
+ break;
556
+ default:
557
+ throw new CliError('UNKNOWN_COMMAND', `Unknown action: manage drafts ${action}`, 'Available: list, get, publish, delete');
558
+ }
559
+ printResult(data, flags, emptyHint);
560
+ return;
561
+ }
562
+ // --- manage <platform> (meta, google, etc.) ---
563
+ const platform = manageTarget;
564
+ if (platform !== 'meta')
565
+ throw new CliError('UNKNOWN_COMMAND', `Unknown platform: ${platform}`, 'Available: meta');
566
+ const entity = args[2];
567
+ const action = args[3];
568
+ const restArgs = args.slice(4);
569
+ // Help routing
570
+ if (flags.help) {
571
+ const helpKey = entity ? `meta ${entity}` : 'meta';
572
+ if (showHelp(helpKey, flags.help === 'full'))
573
+ process.exit(0);
574
+ console.log(USAGE);
575
+ process.exit(0);
576
+ }
577
+ if (!entity) {
578
+ showHelp('meta', flags.help === 'full');
579
+ process.exit(0);
580
+ }
581
+ const client = requireClient(flags);
582
+ let data;
583
+ let emptyHint;
584
+ switch (entity) {
585
+ case 'accounts': {
586
+ // Special case: meta accounts <id> pages/pixels
587
+ if (action && action !== 'list' && action !== 'connect' && action !== 'disconnect') {
588
+ const accountId = action;
589
+ const sub = args[4];
590
+ if (sub === 'pages') {
591
+ data = await listPages(client, [accountId], flags);
592
+ emptyHint = 'No Facebook Pages found for this account.';
593
+ }
594
+ else if (sub === 'pixels') {
595
+ data = await listPixels(client, [accountId], flags);
596
+ emptyHint = 'No Meta Pixels found for this account.';
597
+ }
598
+ else
599
+ throw new CliError('UNKNOWN_COMMAND', `Unknown subcommand: meta accounts ${accountId} ${sub ?? ''}`, 'Expected: pages or pixels');
600
+ break;
601
+ }
602
+ switch (action) {
603
+ case 'list':
604
+ case undefined:
605
+ data = await listAccounts(client, restArgs, flags);
606
+ emptyHint = 'No Meta ad accounts connected. Run `adkit setup manage` or connect via https://app.adkit.so/settings/integrations';
607
+ break;
608
+ case 'connect':
609
+ data = await connectAccount(client, restArgs, flags);
610
+ break;
611
+ case 'disconnect':
612
+ data = await disconnectAccount(client, restArgs, flags);
613
+ break;
614
+ default:
615
+ throw new CliError('UNKNOWN_COMMAND', `Unknown action: meta accounts ${action}`, 'Run: adkit manage meta accounts --help');
616
+ }
617
+ break;
618
+ }
619
+ case 'campaigns': {
620
+ if (!action) {
621
+ showHelp('meta campaigns', flags.help === 'full');
622
+ process.exit(0);
623
+ }
624
+ switch (action) {
625
+ case 'list':
626
+ data = await listCampaigns(client, restArgs, flags);
627
+ emptyHint = 'No campaigns found. Create one with `adkit manage meta campaigns create --name "My Campaign" --objective sales --publish`.';
628
+ break;
629
+ case 'create':
630
+ data = await createCampaign(client, restArgs, flags);
631
+ break;
632
+ case 'update':
633
+ data = await updateCampaign(client, restArgs, flags);
634
+ break;
635
+ case 'delete':
636
+ data = await deleteCampaign(client, restArgs, flags);
637
+ break;
638
+ default:
639
+ throw new CliError('UNKNOWN_COMMAND', `Unknown action: meta campaigns ${action}`, 'Run: adkit manage meta campaigns --help');
640
+ }
641
+ break;
642
+ }
643
+ case 'adsets': {
644
+ if (!action) {
645
+ showHelp('meta adsets', flags.help === 'full');
646
+ process.exit(0);
647
+ }
648
+ switch (action) {
649
+ case 'list':
650
+ data = await listAdSets(client, restArgs, flags);
651
+ emptyHint = 'No ad sets found. Create one with `adkit manage meta adsets create --campaign cmp_123 --name "My AdSet" --publish`.';
652
+ break;
653
+ case 'create':
654
+ data = await createAdSet(client, restArgs, flags);
655
+ break;
656
+ case 'update':
657
+ data = await updateAdSet(client, restArgs, flags);
658
+ break;
659
+ case 'delete':
660
+ data = await deleteAdSet(client, restArgs, flags);
661
+ break;
662
+ default:
663
+ throw new CliError('UNKNOWN_COMMAND', `Unknown action: meta adsets ${action}`, 'Run: adkit manage meta adsets --help');
664
+ }
665
+ break;
666
+ }
667
+ case 'ads': {
668
+ if (!action) {
669
+ showHelp('meta ads', flags.help === 'full');
670
+ process.exit(0);
671
+ }
672
+ switch (action) {
673
+ case 'list':
674
+ data = await listAds(client, restArgs, flags);
675
+ emptyHint = 'No ads found. Create one with `adkit manage meta ads create --data \'{"adsetId":"as_123","creativeId":"cr_123","name":"My Ad"}\'`.';
676
+ break;
677
+ case 'create':
678
+ data = await createAd(client, restArgs, flags);
679
+ break;
680
+ case 'update':
681
+ data = await updateAd(client, restArgs, flags);
682
+ break;
683
+ case 'delete':
684
+ data = await deleteAd(client, restArgs, flags);
685
+ break;
686
+ default:
687
+ throw new CliError('UNKNOWN_COMMAND', `Unknown action: meta ads ${action}`, 'Run: adkit manage meta ads --help');
688
+ }
689
+ break;
690
+ }
691
+ case 'creatives': {
692
+ switch (action) {
693
+ case 'create':
694
+ data = await createCreative(client, restArgs, flags);
695
+ break;
696
+ case 'update':
697
+ data = await updateCreative(client, restArgs, flags);
698
+ break;
699
+ case 'delete':
700
+ data = await deleteCreative(client, restArgs, flags);
701
+ break;
702
+ default:
703
+ throw new CliError('UNKNOWN_COMMAND', `Unknown action: meta creatives ${action ?? 'list'}`, 'Available: create, update, delete');
704
+ }
705
+ break;
706
+ }
707
+ case 'media': {
708
+ switch (action) {
709
+ case 'upload':
710
+ data = await uploadMedia(client, restArgs, flags);
711
+ break;
712
+ case 'list':
713
+ data = await listMedia(client, restArgs, flags);
714
+ emptyHint = 'No media found for this account.';
715
+ break;
716
+ case 'delete':
717
+ data = await deleteMedia(client, restArgs, flags);
718
+ break;
719
+ default:
720
+ throw new CliError('UNKNOWN_COMMAND', `Unknown action: meta media ${action ?? ''}`, 'Available: upload, list, delete');
721
+ }
722
+ break;
723
+ }
724
+ case 'interests': {
725
+ switch (action) {
726
+ case 'search':
727
+ data = await searchInterests(client, restArgs, flags);
728
+ emptyHint = 'No interests found for that query.';
729
+ break;
730
+ default:
731
+ throw new CliError('UNKNOWN_COMMAND', `Unknown action: meta interests ${action ?? ''}`, 'Available: search');
732
+ }
733
+ break;
734
+ }
735
+ default:
736
+ throw new CliError('UNKNOWN_COMMAND', `Unknown entity: meta ${entity}`, 'Available: accounts, campaigns, adsets, ads, creatives, media, interests');
737
+ }
738
+ printResult(data, flags, emptyHint);
739
+ return;
740
+ }
741
+ // --- projects commands ---
742
+ if (args[0] === 'projects') {
743
+ const action = args[1];
744
+ if (flags.help) {
745
+ showHelp('projects', flags.help === 'full');
746
+ process.exit(0);
747
+ }
748
+ switch (action) {
749
+ case 'list':
750
+ case undefined: {
751
+ const config = readConfig() ?? {};
752
+ const projects = listProjects(config);
753
+ if (projects.length === 0)
754
+ console.log('No projects configured. Run `adkit setup` to set up your first project.');
755
+ else {
756
+ for (const p of projects) {
757
+ const marker = p.current ? '* ' : ' ';
758
+ console.log(`${marker}${p.name} (${p.id})`);
759
+ }
760
+ }
761
+ break;
762
+ }
763
+ case 'current': {
764
+ const config = readConfig() ?? {};
765
+ const project = currentProject(config);
766
+ if (!project)
767
+ console.log('No project selected. Run `adkit projects use <id>`.');
768
+ else
769
+ console.log(`Active project: ${project.name} (${project.id})`);
770
+ break;
771
+ }
772
+ case 'use': {
773
+ const projectId = args[2];
774
+ if (!projectId)
775
+ throw new CliError('MISSING_ARGUMENT', 'Missing required argument: `<project-id>`', 'Run: adkit projects use <project-id>');
776
+ useProject(projectId);
777
+ console.log(`Switched to project: ${projectId}`);
778
+ break;
779
+ }
780
+ default:
781
+ throw new CliError('UNKNOWN_COMMAND', `Unknown action: projects ${action}`, 'Available: list, use, current');
782
+ }
783
+ return;
784
+ }
785
+ // --- logout ---
786
+ if (args[0] === 'logout') {
787
+ logout();
788
+ return;
789
+ }
790
+ // --- setup (auth + onboarding) ---
791
+ if (args[0] === 'setup') {
792
+ const scope = args.slice(1); // e.g. ['manage'], ['manage', 'studio'], or []
793
+ await login({ baseUrl: getBaseUrl(), scope });
794
+ return;
795
+ }
796
+ // --- fallback ---
797
+ throw new CliError('UNKNOWN_COMMAND', `Unknown command: ${args.join(' ')}`, 'Run: adkit --help');
798
+ }
799
+ catch (err) {
800
+ const jsonErrors = wantsJson(flags);
801
+ if (err instanceof CliError) {
802
+ if (!jsonErrors) {
803
+ console.error(`Error: ${err.message}`);
804
+ if (err.suggestion)
805
+ console.error(`Suggestion: ${err.suggestion}`);
806
+ }
807
+ else {
808
+ console.error(JSON.stringify({
809
+ error: true,
810
+ code: err.code,
811
+ message: err.message,
812
+ ...(err.suggestion && { suggestion: err.suggestion }),
813
+ }));
814
+ }
815
+ }
816
+ else {
817
+ const message = err instanceof Error ? err.message : String(err);
818
+ if (!jsonErrors)
819
+ console.error(`Error: ${message}`);
820
+ else {
821
+ console.error(JSON.stringify({
822
+ error: true,
823
+ code: 'UNKNOWN',
824
+ message,
825
+ }));
826
+ }
827
+ }
828
+ const EXIT_CODES = {
829
+ MISSING_ARGUMENT: 2,
830
+ MISSING_FLAG: 2,
831
+ INVALID_VALUE: 2,
832
+ UNKNOWN_COMMAND: 2,
833
+ UNKNOWN_FLAG: 2,
834
+ NOT_FOUND: 3,
835
+ INVALID_API_KEY: 4,
836
+ NOT_AUTHENTICATED: 4,
837
+ };
838
+ process.exit(err instanceof CliError ? (EXIT_CODES[err.code] ?? 1) : 1);
839
+ }
840
+ }
841
+ void main();