@adkit.so/cli 1.0.0 → 1.0.2

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.
@@ -1,534 +0,0 @@
1
- import { validateFlags } from '../cli-utils.js';
2
- import { getDefaultAccount } from '../config.js';
3
- import { CliError } from '../errors.js';
4
- import { IMAGE_HASH_RE, VIDEO_ID_RE } from '@adkit/shared/manage/meta/media-utils';
5
- /** Parse a JSON string and validate it's a plain object. Returns unknown — callers cast to the target type. */
6
- function parseJsonObject(raw, label) {
7
- let parsed;
8
- try {
9
- parsed = JSON.parse(raw);
10
- }
11
- catch {
12
- throw new CliError('INVALID_VALUE', `Invalid JSON in \`--${label}\``, `Check JSON syntax in --${label}`);
13
- }
14
- if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
15
- throw new CliError('INVALID_VALUE', `Expected a JSON object in \`--${label}\``, `Check JSON syntax in --${label}`);
16
- return parsed;
17
- }
18
- function dateStamp() {
19
- return new Date().toISOString().slice(0, 10);
20
- }
21
- function generateCampaignName() {
22
- return `campaign ${dateStamp()}`;
23
- }
24
- function generateAdSetName() {
25
- return `adset ${dateStamp()}`;
26
- }
27
- function generateAdName() {
28
- return `ad ${dateStamp()}`;
29
- }
30
- function generateCreativeName() {
31
- return `creative ${dateStamp()}`;
32
- }
33
- function requireArg(args, index, label, hint) {
34
- const val = args[index];
35
- if (!val)
36
- throw new CliError('MISSING_ARGUMENT', `Missing required argument: \`<${label}>\``, hint);
37
- return val;
38
- }
39
- function requireFlag(flags, key, hint) {
40
- const val = flags[key];
41
- if (typeof val !== 'string')
42
- throw new CliError('MISSING_FLAG', `Missing required flag: \`--${key}\``, hint);
43
- return val;
44
- }
45
- function parseDataFlag(flags, hint) {
46
- const raw = requireFlag(flags, 'data', hint);
47
- try {
48
- return JSON.parse(raw);
49
- }
50
- catch {
51
- const truncated = raw.length > 80 ? raw.slice(0, 80) + '...' : raw;
52
- throw new CliError('INVALID_VALUE', `Invalid JSON in \`--data\` flag: ${truncated}`, 'Check JSON syntax in --data');
53
- }
54
- }
55
- function mergeAccountId(body, flags) {
56
- if (typeof body !== 'object' || body === null || Array.isArray(body))
57
- return body;
58
- const obj = body;
59
- if (!obj.accountId) {
60
- if (typeof flags.account === 'string')
61
- obj.accountId = flags.account;
62
- else {
63
- const defaultAccount = getDefaultAccount('meta');
64
- if (defaultAccount)
65
- obj.accountId = defaultAccount;
66
- }
67
- }
68
- return obj;
69
- }
70
- function flagStr(flags, key) {
71
- const val = flags[key];
72
- if (typeof val === 'string')
73
- return val;
74
- if (Array.isArray(val))
75
- return val[0];
76
- return undefined;
77
- }
78
- function flagArr(flags, key) {
79
- const val = flags[key];
80
- if (Array.isArray(val))
81
- return val;
82
- if (typeof val === 'string')
83
- return [val];
84
- return [];
85
- }
86
- function queryString(params) {
87
- const entries = Object.entries(params).filter((e) => e[1] !== undefined);
88
- if (entries.length === 0)
89
- return '';
90
- return '?' + entries.map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&');
91
- }
92
- /** Type guard for MediaUploadResult — checks discriminated union tag and payload fields. */
93
- function isMediaUploadResult(value) {
94
- if (typeof value !== 'object' || value === null)
95
- return false;
96
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowing after null check
97
- const obj = value;
98
- return (obj.type === 'image' && typeof obj.imageHash === 'string') || (obj.type === 'video' && typeof obj.videoId === 'string');
99
- }
100
- // --- Accounts ---
101
- export async function listAccounts(client, _args, _flags) {
102
- return client.get('/manage/meta/accounts');
103
- }
104
- export async function connectAccount(client, args, _flags) {
105
- const id = requireArg(args, 0, 'account-id', 'Run: adkit manage meta accounts connect <account-id>');
106
- return client.post('/manage/meta/accounts/connect', { id });
107
- }
108
- export async function disconnectAccount(client, args, _flags) {
109
- const id = requireArg(args, 0, 'account-id', 'Run: adkit manage meta accounts disconnect <account-id>');
110
- return client.delete(`/manage/meta/accounts/${id}`);
111
- }
112
- export async function listPages(client, args, _flags) {
113
- const id = requireArg(args, 0, 'account-id', 'Run: adkit manage meta accounts <account-id> pages');
114
- return client.get(`/manage/meta/accounts/${id}/pages`);
115
- }
116
- export async function listPixels(client, args, _flags) {
117
- const id = requireArg(args, 0, 'account-id', 'Run: adkit manage meta accounts <account-id> pixels');
118
- return client.get(`/manage/meta/accounts/${id}/pixels`);
119
- }
120
- // --- Campaigns ---
121
- function buildCampaignPayload(flags) {
122
- const payload = {};
123
- if (typeof flags.name === 'string')
124
- payload.name = flags.name;
125
- // Server validates enum values — CLI forwards raw input
126
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- server validates enum, CLI forwards raw string
127
- if (typeof flags.objective === 'string')
128
- payload.objective = flags.objective;
129
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- server validates enum, CLI forwards raw string
130
- if (typeof flags.status === 'string')
131
- payload.status = flags.status;
132
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- server validates enum, CLI forwards raw string
133
- if (typeof flags['bid-strategy'] === 'string')
134
- payload.bidStrategy = flags['bid-strategy'];
135
- // CBO is ON by default (matches Meta UI). Use --abo for adset-level budget.
136
- if (flags.abo === true)
137
- payload.advantageCampaignBudget = false;
138
- else
139
- payload.advantageCampaignBudget = true;
140
- // Budget
141
- if (typeof flags['budget-daily'] === 'string')
142
- payload.budget = { daily: parseFloat(flags['budget-daily']) };
143
- if (typeof flags['budget-total'] === 'string')
144
- payload.budget = { ...payload.budget, lifetime: parseFloat(flags['budget-total']) };
145
- // JSON flags
146
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- raw JSON from user flag, server validates shape
147
- if (typeof flags['platform-overrides'] === 'string')
148
- payload.platformOverrides = parseJsonObject(flags['platform-overrides'], 'platform-overrides');
149
- return payload;
150
- }
151
- export async function listCampaigns(client, _args, flags) {
152
- validateFlags(flags, [], 'manage meta campaigns list');
153
- const accountId = typeof flags.account === 'string' ? flags.account : undefined;
154
- const qs = queryString({ accountId });
155
- return client.get(`/manage/meta/campaigns${qs}`);
156
- }
157
- const CAMPAIGN_FLAGS = ['name', 'objective', 'status', 'budget-daily', 'budget-total', 'abo', 'bid-strategy'];
158
- const ADSET_FLAGS = ['campaign', 'name', 'status', 'optimization', 'budget-daily', 'budget-total', 'countries', 'genders', 'targeting', 'pixel', 'event-type', 'interest'];
159
- const AD_FLAGS = ['creative', 'adset', 'name', 'status', 'media', 'primary-text', 'headline', 'description', 'cta', 'url', 'page'];
160
- const CREATIVE_FLAGS = ['page-id', 'headline', 'primary-text', 'link-description', 'link-url', 'cta', 'name', 'image-hash', 'image-url', 'video-id', 'force'];
161
- const MEDIA_FLAGS = ['file', 'url', 'account'];
162
- export async function createCampaign(client, _args, flags) {
163
- if (typeof flags.data === 'string') {
164
- const body = parseDataFlag(flags, 'Check JSON syntax in --data');
165
- mergeAccountId(body, flags);
166
- const publish = flags.publish === true ? '?publish=true' : '';
167
- return client.post(`/manage/meta/campaigns${publish}`, body);
168
- }
169
- validateFlags(flags, CAMPAIGN_FLAGS, 'manage meta campaigns create');
170
- const payload = buildCampaignPayload(flags);
171
- if (!payload.name)
172
- payload.name = generateCampaignName();
173
- const body = { campaigns: [payload] };
174
- mergeAccountId(body, flags);
175
- const publish = flags.publish === true ? '?publish=true' : '';
176
- return client.post(`/manage/meta/campaigns${publish}`, body);
177
- }
178
- export async function updateCampaign(client, args, flags) {
179
- const id = requireArg(args, 0, 'campaign-id', `Run: adkit manage meta campaigns update <campaign-id> --status paused`);
180
- if (typeof flags.data === 'string') {
181
- const body = parseDataFlag(flags, 'Check JSON syntax in --data');
182
- mergeAccountId(body, flags);
183
- return client.patch(`/manage/meta/campaigns/${id}`, body);
184
- }
185
- validateFlags(flags, CAMPAIGN_FLAGS, 'manage meta campaigns update');
186
- const payload = buildCampaignPayload(flags);
187
- mergeAccountId(payload, flags);
188
- return client.patch(`/manage/meta/campaigns/${id}`, payload);
189
- }
190
- export async function deleteCampaign(client, args, _flags) {
191
- const id = requireArg(args, 0, 'campaign-id', 'Run: adkit manage meta campaigns delete <campaign-id>');
192
- return client.delete(`/manage/meta/campaigns/${id}`);
193
- }
194
- // --- AdSets ---
195
- function buildAdSetPayload(flags) {
196
- const payload = {};
197
- if (typeof flags.campaign === 'string')
198
- payload.campaignId = flags.campaign;
199
- if (typeof flags.name === 'string')
200
- payload.name = flags.name;
201
- // Server validates enum values — CLI forwards raw input
202
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- server validates enum, CLI forwards raw string
203
- if (typeof flags.status === 'string')
204
- payload.status = flags.status;
205
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- server validates enum, CLI forwards raw string
206
- if (typeof flags.optimization === 'string')
207
- payload.optimization = flags.optimization;
208
- // Budget
209
- if (typeof flags['budget-daily'] === 'string')
210
- payload.budget = { daily: parseFloat(flags['budget-daily']) };
211
- if (typeof flags['budget-total'] === 'string')
212
- payload.budget = { ...payload.budget, lifetime: parseFloat(flags['budget-total']) };
213
- // Targeting (simple flags)
214
- const targeting = {};
215
- if (typeof flags.countries === 'string')
216
- targeting.countries = flags.countries.split(',');
217
- // --interest (repeatable) → targeting.interests
218
- if (Array.isArray(flags.interest))
219
- targeting.interests = flags.interest;
220
- else if (typeof flags.interest === 'string')
221
- targeting.interests = [flags.interest];
222
- // Deep-merge --targeting JSON (takes precedence for nested keys)
223
- if (typeof flags.targeting === 'string')
224
- Object.assign(targeting, parseJsonObject(flags.targeting, 'targeting'));
225
- if (Object.keys(targeting).length)
226
- payload.targeting = targeting;
227
- // Conversion tracking convenience flags
228
- if (typeof flags.pixel === 'string')
229
- payload.pixelId = flags.pixel;
230
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- server validates enum, CLI forwards raw string
231
- if (typeof flags['event-type'] === 'string')
232
- payload.eventType = flags['event-type'];
233
- // JSON flags
234
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- raw JSON from user flag, server validates shape
235
- if (typeof flags['platform-overrides'] === 'string')
236
- payload.platformOverrides = parseJsonObject(flags['platform-overrides'], 'platform-overrides');
237
- return payload;
238
- }
239
- export async function listAdSets(client, _args, flags) {
240
- validateFlags(flags, ['campaign'], 'manage meta adsets list');
241
- const accountId = typeof flags.account === 'string' ? flags.account : undefined;
242
- const campaignId = typeof flags.campaign === 'string' ? flags.campaign : undefined;
243
- const qs = queryString({ accountId, campaignId });
244
- return client.get(`/manage/meta/adsets${qs}`);
245
- }
246
- export async function createAdSet(client, _args, flags) {
247
- if (typeof flags.data === 'string') {
248
- const body = parseDataFlag(flags, 'Check JSON syntax in --data');
249
- mergeAccountId(body, flags);
250
- const publish = flags.publish === true ? '?publish=true' : '';
251
- return client.post(`/manage/meta/adsets${publish}`, body);
252
- }
253
- validateFlags(flags, ADSET_FLAGS, 'manage meta adsets create');
254
- const payload = buildAdSetPayload(flags);
255
- if (!payload.campaignId)
256
- throw new CliError('MISSING_FLAG', 'Missing required flag: `--campaign`', 'Run: adkit manage meta adsets create --campaign cmp_abc --name "US 25-44" --budget-daily 20 --optimization link_clicks');
257
- if (!payload.name)
258
- payload.name = generateAdSetName();
259
- const body = { adsets: [payload] };
260
- mergeAccountId(body, flags);
261
- const publish = flags.publish === true ? '?publish=true' : '';
262
- return client.post(`/manage/meta/adsets${publish}`, body);
263
- }
264
- export async function updateAdSet(client, args, flags) {
265
- const id = requireArg(args, 0, 'adset-id', `Run: adkit manage meta adsets update <adset-id> --budget-daily 50`);
266
- if (typeof flags.data === 'string') {
267
- const body = parseDataFlag(flags, 'Check JSON syntax in --data');
268
- mergeAccountId(body, flags);
269
- return client.patch(`/manage/meta/adsets/${id}`, body);
270
- }
271
- validateFlags(flags, ADSET_FLAGS, 'manage meta adsets update');
272
- const payload = buildAdSetPayload(flags);
273
- mergeAccountId(payload, flags);
274
- return client.patch(`/manage/meta/adsets/${id}`, payload);
275
- }
276
- export async function deleteAdSet(client, args, _flags) {
277
- const id = requireArg(args, 0, 'adset-id', 'Run: adkit manage meta adsets delete <adset-id>');
278
- return client.delete(`/manage/meta/adsets/${id}`);
279
- }
280
- // --- Ads ---
281
- export async function listAds(client, _args, flags) {
282
- validateFlags(flags, ['adset'], 'manage meta ads list');
283
- const accountId = typeof flags.account === 'string' ? flags.account : undefined;
284
- const adsetId = typeof flags.adset === 'string' ? flags.adset : undefined;
285
- const qs = queryString({ accountId, adsetId });
286
- return client.get(`/manage/meta/ads${qs}`);
287
- }
288
- export async function createAd(client, _args, flags) {
289
- // Low-level flow: --data bypasses the rich flow
290
- if (typeof flags.data === 'string') {
291
- const body = parseDataFlag(flags, 'Check JSON syntax in --data');
292
- mergeAccountId(body, flags);
293
- const publish = flags.publish === true ? '?publish=true' : '';
294
- return client.post(`/manage/meta/ads${publish}`, body);
295
- }
296
- validateFlags(flags, AD_FLAGS, 'manage meta ads create');
297
- const creativeId = typeof flags.creative === 'string' ? flags.creative : undefined;
298
- const mediaFlags = ['media', 'primary-text', 'headline', 'description', 'cta'];
299
- const hasMediaFlags = mediaFlags.some((f) => flags[f] !== undefined);
300
- if (creativeId && hasMediaFlags)
301
- throw new CliError('INVALID_VALUE', '`--creative` cannot be combined with --media, --primary-text, --headline, --description, or --cta', 'Use --creative to reference an existing creative, OR use --media/--primary-text/etc. to create a new one');
302
- // Existing creative flow: --creative <id> → uses ads[] low-level API
303
- if (creativeId) {
304
- const adsetId = requireFlag(flags, 'adset', 'Run: adkit manage meta ads create --creative cr_abc --adset as_xyz --publish');
305
- const ad = {
306
- adsetId,
307
- name: typeof flags.name === 'string' ? flags.name : generateAdName(),
308
- creativeId,
309
- };
310
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- server validates enum, CLI forwards raw string
311
- if (typeof flags.status === 'string')
312
- ad.status = flags.status;
313
- const body = { ads: [ad] };
314
- mergeAccountId(body, flags);
315
- const publish = flags.publish === true ? '?publish=true' : '';
316
- return client.post(`/manage/meta/ads${publish}`, body);
317
- }
318
- // New creative flow: --media, --primary-text, --headline, --cta, --adset
319
- const mediaInputs = flagArr(flags, 'media');
320
- const primaryTexts = flagArr(flags, 'primary-text');
321
- const headlines = flagArr(flags, 'headline');
322
- const descriptions = flagArr(flags, 'description');
323
- if (mediaInputs.length === 0)
324
- throw new CliError('MISSING_FLAG', 'Missing required flag: `--media` (or use `--creative` to reference an existing creative)', 'Run: adkit manage meta ads create --media ./hero.mp4 --primary-text "Hello" --adset as_xyz --publish');
325
- const adsetId = typeof flags.adset === 'string' ? flags.adset : undefined;
326
- if (!adsetId)
327
- throw new CliError('MISSING_FLAG', 'Missing required flag: `--adset`', 'Run: adkit manage meta ads create --media ./hero.mp4 --adset as_789 --publish');
328
- // Auto-detect: media ID (image hash or video ID) vs file path vs URL
329
- function isMediaId(input) {
330
- return IMAGE_HASH_RE.test(input) || VIDEO_ID_RE.test(input);
331
- }
332
- const mediaIds = [];
333
- for (const input of mediaInputs) {
334
- if (isMediaId(input)) {
335
- // Already a resolved media ID — pass through directly
336
- mediaIds.push(input);
337
- }
338
- else {
339
- // File path or URL — upload first, extract returned ID
340
- let uploadBody;
341
- if (input.startsWith('http://') || input.startsWith('https://'))
342
- uploadBody = { imageUrl: input };
343
- else {
344
- const fs = await import('node:fs');
345
- const path = await import('node:path');
346
- const VIDEO_EXTENSIONS = new Set(['.mp4', '.mov', '.avi', '.webm', '.mkv']);
347
- const resolved = path.resolve(input);
348
- const ext = path.extname(resolved).toLowerCase();
349
- const filename = path.basename(resolved);
350
- const base64 = fs.readFileSync(resolved).toString('base64');
351
- if (VIDEO_EXTENSIONS.has(ext))
352
- uploadBody = { videoBase64: base64, filename };
353
- else
354
- uploadBody = { imageBase64: base64, filename };
355
- }
356
- const result = await client.post('/manage/meta/media', uploadBody);
357
- if (!isMediaUploadResult(result))
358
- throw new CliError('SERVER_ERROR', 'Media upload did not return an image hash or video ID');
359
- const id = result.type === 'image' ? result.imageHash : result.videoId;
360
- mediaIds.push(id);
361
- }
362
- }
363
- const ad = { adsetId, mediaIds };
364
- if (primaryTexts.length)
365
- ad.primaryTexts = primaryTexts;
366
- if (headlines.length)
367
- ad.headlines = headlines;
368
- if (descriptions.length)
369
- ad.descriptions = descriptions;
370
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- server validates enum, CLI forwards raw string
371
- if (typeof flags.cta === 'string')
372
- ad.cta = flags.cta;
373
- if (typeof flags.name === 'string')
374
- ad.name = flags.name;
375
- if (typeof flags.url === 'string')
376
- ad.url = flags.url;
377
- if (typeof flags.page === 'string')
378
- ad.pageId = flags.page;
379
- const body = { ads: [ad] };
380
- mergeAccountId(body, flags);
381
- const publish = flags.publish === true ? '?publish=true' : '';
382
- return client.post(`/manage/meta/ads${publish}`, body);
383
- }
384
- export async function updateAd(client, args, flags) {
385
- const id = requireArg(args, 0, 'ad-id', `Run: adkit manage meta ads update <ad-id> --data '{"status":"paused"}'`);
386
- const body = parseDataFlag(flags, 'Check JSON syntax in --data');
387
- mergeAccountId(body, flags);
388
- return client.patch(`/manage/meta/ads/${id}`, body);
389
- }
390
- export async function deleteAd(client, args, _flags) {
391
- const id = requireArg(args, 0, 'ad-id', 'Run: adkit manage meta ads delete <ad-id>');
392
- return client.delete(`/manage/meta/ads/${id}`);
393
- }
394
- // --- Creatives ---
395
- function buildCreativePayload(flags) {
396
- const payload = {};
397
- const pageId = flagStr(flags, 'page-id');
398
- const linkUrl = flagStr(flags, 'link-url');
399
- const cta = flagStr(flags, 'cta');
400
- const name = flagStr(flags, 'name');
401
- const imageHash = flagStr(flags, 'image-hash');
402
- const imageUrl = flagStr(flags, 'image-url');
403
- const videoId = flagStr(flags, 'video-id');
404
- if (pageId)
405
- payload.pageId = pageId;
406
- if (linkUrl)
407
- payload.linkUrl = linkUrl;
408
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- server validates enum, CLI forwards raw string
409
- if (cta)
410
- payload.cta = cta;
411
- if (name)
412
- payload.name = name;
413
- if (imageHash)
414
- payload.imageHash = imageHash;
415
- if (imageUrl)
416
- payload.imageUrl = imageUrl;
417
- if (videoId)
418
- payload.videoId = videoId;
419
- // Array fields (repeatable flags)
420
- const headlines = flagArr(flags, 'headline');
421
- if (headlines.length)
422
- payload.headlines = headlines;
423
- const primaryTexts = flagArr(flags, 'primary-text');
424
- if (primaryTexts.length)
425
- payload.primaryTexts = primaryTexts;
426
- const linkDescriptions = flagArr(flags, 'link-description');
427
- if (linkDescriptions.length)
428
- payload.linkDescriptions = linkDescriptions;
429
- // JSON flags
430
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- raw JSON from user flag, server validates shape
431
- if (typeof flags['platform-overrides'] === 'string')
432
- payload.platformOverrides = parseJsonObject(flags['platform-overrides'], 'platform-overrides');
433
- return payload;
434
- }
435
- export async function createCreative(client, _args, flags) {
436
- if (typeof flags.data === 'string') {
437
- const body = parseDataFlag(flags, 'Check JSON syntax in --data');
438
- mergeAccountId(body, flags);
439
- const publish = flags.publish === true ? '?publish=true' : '';
440
- return client.post(`/manage/meta/creatives${publish}`, body);
441
- }
442
- validateFlags(flags, CREATIVE_FLAGS, 'manage meta creatives create');
443
- const payload = buildCreativePayload(flags);
444
- if (!payload.pageId)
445
- throw new CliError('MISSING_FLAG', 'Missing required flag: `--page-id`', 'Run: adkit manage meta creatives create --page-id pg_123 --headline "Get Started" --primary-text "Try it free" --link-url https://example.com');
446
- if (!payload.headlines?.length)
447
- throw new CliError('MISSING_FLAG', 'Missing required flag: `--headline`', 'Run: adkit manage meta creatives create --page-id pg_123 --headline "Get Started" --primary-text "Try it free" --link-url https://example.com');
448
- if (!payload.primaryTexts?.length)
449
- throw new CliError('MISSING_FLAG', 'Missing required flag: `--primary-text`', 'Run: adkit manage meta creatives create --page-id pg_123 --headline "Get Started" --primary-text "Try it free" --link-url https://example.com');
450
- if (!payload.linkUrl)
451
- throw new CliError('MISSING_FLAG', 'Missing required flag: `--link-url`', 'Run: adkit manage meta creatives create --page-id pg_123 --headline "Get Started" --primary-text "Try it free" --link-url https://example.com');
452
- // Multi-asset detection: standalone creatives with multiple text variants
453
- // use Dynamic Creative format, which Meta doesn't support for all objectives.
454
- const isMultiAsset = (payload.headlines?.length ?? 0) > 1 || (payload.primaryTexts?.length ?? 0) > 1 || (payload.linkDescriptions?.length ?? 0) > 1;
455
- if (isMultiAsset && flags.force !== true)
456
- throw new CliError('INVALID_VALUE', "Standalone creatives with multiple assets use Dynamic Creative format, which Meta doesn't support for sales/app_promotion. Use `ads create` instead (auto-detects the right format). Add --force to proceed anyway.");
457
- if (!payload.name)
458
- payload.name = generateCreativeName();
459
- mergeAccountId(payload, flags);
460
- const publish = flags.publish === true ? '?publish=true' : '';
461
- return client.post(`/manage/meta/creatives${publish}`, payload);
462
- }
463
- export async function updateCreative(client, args, flags) {
464
- const id = requireArg(args, 0, 'creative-id', `Run: adkit manage meta creatives update <creative-id> --primary-text "Start free trial"`);
465
- if (typeof flags.data === 'string') {
466
- const body = parseDataFlag(flags, 'Check JSON syntax in --data');
467
- mergeAccountId(body, flags);
468
- return client.patch(`/manage/meta/creatives/${id}`, body);
469
- }
470
- validateFlags(flags, CREATIVE_FLAGS, 'manage meta creatives update');
471
- const payload = buildCreativePayload(flags);
472
- mergeAccountId(payload, flags);
473
- return client.patch(`/manage/meta/creatives/${id}`, payload);
474
- }
475
- export async function deleteCreative(client, args, _flags) {
476
- const id = requireArg(args, 0, 'creative-id', 'Run: adkit manage meta creatives delete <creative-id>');
477
- return client.delete(`/manage/meta/creatives/${id}`);
478
- }
479
- // --- Interests ---
480
- export async function searchInterests(client, args, flags) {
481
- if (!args.length)
482
- throw new CliError('MISSING_ARGUMENT', 'Missing query', 'Run: adkit manage meta interests search <query> [query2] [query3]');
483
- const accountId = typeof flags.account === 'string' ? flags.account : undefined;
484
- const allResults = [];
485
- for (const query of args) {
486
- const qs = queryString({ accountId, q: query });
487
- const res = await client.get(`/manage/meta/interests/search${qs}`);
488
- const obj = res && typeof res === 'object' && 'results' in res ? res : null;
489
- const arr = obj && Array.isArray(obj.results) ? obj.results : [];
490
- for (const item of arr)
491
- allResults.push(item);
492
- }
493
- return { results: allResults };
494
- }
495
- // --- Media ---
496
- export async function uploadMedia(client, _args, flags) {
497
- validateFlags(flags, MEDIA_FLAGS, 'manage meta media upload');
498
- const filePath = typeof flags.file === 'string' ? flags.file : undefined;
499
- const url = typeof flags.url === 'string' ? flags.url : undefined;
500
- if (!filePath && !url)
501
- throw new CliError('MISSING_FLAG', 'Missing required flag: `--file` or `--url`', 'Run: adkit manage meta media upload --file ./video.mp4');
502
- if (filePath) {
503
- const fs = await import('node:fs');
504
- const path = await import('node:path');
505
- const resolved = path.resolve(filePath);
506
- const ext = path.extname(resolved).toLowerCase();
507
- const isVideo = ['.mp4', '.mov', '.avi', '.webm', '.mkv'].includes(ext);
508
- const base64 = fs.readFileSync(resolved).toString('base64');
509
- const filename = path.basename(resolved);
510
- if (isVideo)
511
- return client.post('/manage/meta/media', { videoBase64: base64, filename });
512
- else
513
- return client.post('/manage/meta/media', { imageBase64: base64, filename });
514
- }
515
- // URL-based upload — detect video vs image from URL extension
516
- if (!url)
517
- throw new CliError('MISSING_FLAG', 'Missing required flag: `--file` or `--url`');
518
- const VIDEO_EXTENSIONS = new Set(['.mp4', '.mov', '.avi', '.mkv', '.webm']);
519
- const urlExt = new URL(url).pathname.match(/\.[^.]+$/)?.[0]?.toLowerCase() ?? '';
520
- if (VIDEO_EXTENSIONS.has(urlExt))
521
- return client.post('/manage/meta/media', { videoUrl: url });
522
- return client.post('/manage/meta/media', { imageUrl: url });
523
- }
524
- export async function listMedia(client, _args, flags) {
525
- const accountId = typeof flags.account === 'string' ? flags.account : undefined;
526
- const qs = queryString({ accountId });
527
- return client.get(`/manage/meta/media${qs}`);
528
- }
529
- export async function deleteMedia(client, args, flags) {
530
- const id = requireArg(args, 0, 'media-id', 'Run: adkit manage meta media delete <media-id>');
531
- const accountId = typeof flags.account === 'string' ? flags.account : undefined;
532
- const qs = queryString({ accountId });
533
- return client.delete(`/manage/meta/media/${id}${qs}`);
534
- }
@@ -1,25 +0,0 @@
1
- interface ProjectConfig {
2
- apiKey?: string;
3
- selectedProject?: string;
4
- projects?: Record<string, {
5
- name: string;
6
- apiKey: string;
7
- }>;
8
- }
9
- export interface ProjectEntry {
10
- id: string;
11
- name: string;
12
- apiKey: string;
13
- current?: boolean;
14
- }
15
- /** Returns a formatted list of all configured projects. */
16
- export declare function listProjects(config: ProjectConfig): ProjectEntry[];
17
- /** Writes selectedProject to config, making projectId the active project. */
18
- export declare function useProject(projectId: string): void;
19
- /** Returns info about the current default project, or null if none is set. */
20
- export declare function currentProject(config: ProjectConfig): {
21
- id: string;
22
- name: string;
23
- apiKey: string;
24
- } | null;
25
- export {};
@@ -1,30 +0,0 @@
1
- import { readConfig, writeConfig } from '../config.js';
2
- /** Returns a formatted list of all configured projects. */
3
- export function listProjects(config) {
4
- if (!config.projects)
5
- return [];
6
- return Object.entries(config.projects).map(([id, value]) => {
7
- const entry = { id, name: value.name, apiKey: value.apiKey };
8
- if (config.selectedProject === id) {
9
- entry.current = true;
10
- }
11
- return entry;
12
- });
13
- }
14
- /** Writes selectedProject to config, making projectId the active project. */
15
- export function useProject(projectId) {
16
- const config = readConfig() ?? {};
17
- if (!config.projects?.[projectId]) {
18
- throw new Error(`Project not found: ${projectId}`);
19
- }
20
- writeConfig({ selectedProject: projectId });
21
- }
22
- /** Returns info about the current default project, or null if none is set. */
23
- export function currentProject(config) {
24
- if (!config.selectedProject)
25
- return null;
26
- const entry = config.projects?.[config.selectedProject];
27
- if (!entry)
28
- return null;
29
- return { id: config.selectedProject, name: entry.name, apiKey: entry.apiKey };
30
- }
@@ -1,2 +0,0 @@
1
- import type { AdkitClient } from '../client.js';
2
- export declare function status(client: AdkitClient, json: boolean): Promise<void>;
@@ -1,36 +0,0 @@
1
- export async function status(client, json) {
2
- const data = (await client.get('/manage/status'));
3
- if (json) {
4
- console.log(JSON.stringify(data));
5
- return;
6
- }
7
- const { project, platforms } = data;
8
- const lines = [];
9
- lines.push(`Project: ${project.name || 'unnamed'} (${project.id})`);
10
- lines.push('');
11
- // Meta
12
- if (platforms.meta.connected) {
13
- lines.push('Meta: connected');
14
- for (const a of platforms.meta.accounts) {
15
- lines.push(` ${a.id} — ${a.name} (${a.currency}, ${a.status})`);
16
- if (a.defaults.pageId)
17
- lines.push(` Page: ${a.defaults.pageId}`);
18
- if (a.defaults.pixelId)
19
- lines.push(` Pixel: ${a.defaults.pixelId}`);
20
- }
21
- }
22
- else {
23
- lines.push('Meta: not connected');
24
- }
25
- // Google
26
- if (platforms.google.connected) {
27
- lines.push('Google: connected');
28
- for (const a of platforms.google.accounts) {
29
- lines.push(` ${a.id} — ${a.name} (${a.currency})`);
30
- }
31
- }
32
- else {
33
- lines.push('Google: not connected');
34
- }
35
- console.log(lines.join('\n'));
36
- }
package/dist/config.d.ts DELETED
@@ -1,24 +0,0 @@
1
- export declare const CONFIG_DIR: string;
2
- export declare const CONFIG_PATH: string;
3
- interface ConfigFile {
4
- apiKey?: string;
5
- selectedProject?: string;
6
- projects?: Record<string, {
7
- name: string;
8
- apiKey: string;
9
- defaultMetaAccount?: string;
10
- defaultGoogleAccount?: string;
11
- }>;
12
- }
13
- interface ResolvedConfig {
14
- apiKey: string | null;
15
- selectedProject?: string;
16
- }
17
- interface ResolveFlags {
18
- project?: string;
19
- }
20
- export declare function readConfig(): ConfigFile | null;
21
- export declare function writeConfig(updates: Partial<ConfigFile>): void;
22
- export declare function resolveConfig(flags?: ResolveFlags): ResolvedConfig;
23
- export declare function getDefaultAccount(platform: 'meta' | 'google'): string | null;
24
- export {};