@adkit.so/cli 1.0.1 → 1.0.3

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 CHANGED
@@ -96,7 +96,14 @@ var AdkitClient = class {
96
96
  response = await fetch(url, init);
97
97
  } catch (err) {
98
98
  if (err instanceof DOMException && err.name === "AbortError") throw new CliError("NETWORK_ERROR", "Request timed out", "Retry the command");
99
- throw new CliError("NETWORK_ERROR", "Network connection error \u2014 check your internet connection and try again. If using a custom base URL, verify `ADKIT_BASE_URL` is correct.", "Check internet connection");
99
+ const code = err instanceof Error && err.cause != null && typeof err.cause === "object" && "code" in err.cause && typeof err.cause.code === "string" ? err.cause.code : void 0;
100
+ const isCustomUrl = !!process.env.ADKIT_BASE_URL;
101
+ const target = isCustomUrl ? `API at ${this.baseUrl}` : `AdKit API (${this.baseUrl})`;
102
+ if (code === "ECONNREFUSED") throw new CliError("NETWORK_ERROR", `Connection refused \u2014 ${target} is not running`, isCustomUrl ? "Start the server or check ADKIT_BASE_URL" : "The API may be down \u2014 try again in a moment");
103
+ if (code === "ENOTFOUND") throw new CliError("NETWORK_ERROR", `DNS lookup failed for ${this.baseUrl}`, "Verify your network connection");
104
+ if (code === "ETIMEDOUT" || code === "UND_ERR_CONNECT_TIMEOUT") throw new CliError("NETWORK_ERROR", `Connection to ${target} timed out`, "Verify your network connection");
105
+ const detail = code ?? (err instanceof Error ? err.message : String(err));
106
+ throw new CliError("NETWORK_ERROR", `Cannot reach ${target} (${detail})`, "Verify your network connection");
100
107
  }
101
108
  if (response.status === 204) return void 0;
102
109
  if (!response.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adkit.so/cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "AdKit CLI — manage campaigns and accounts from the terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,7 +10,8 @@
10
10
  "dist"
11
11
  ],
12
12
  "scripts": {
13
- "build": "esbuild src/cli.ts --bundle --format=esm --platform=node --target=node18 --outfile=dist/cli.js --banner:js='#!/usr/bin/env node'"
13
+ "build": "esbuild src/cli.ts --bundle --format=esm --platform=node --target=node18 --outfile=dist/cli.js --banner:js='#!/usr/bin/env node'",
14
+ "release": "npm version patch && npm run build && npm publish --access public"
14
15
  },
15
16
  "devDependencies": {
16
17
  "@types/node": "^20.10.0",
@@ -1,11 +0,0 @@
1
- export type Flags = Record<string, string | string[] | true>;
2
- interface ParseOptions {
3
- multi?: string[];
4
- }
5
- export declare function parseArgs(argv: string[], options?: ParseOptions): {
6
- args: string[];
7
- flags: Record<string, string | true | string[]>;
8
- };
9
- export declare function validateFlags(flags: Flags, allowed: string[], command: string): void;
10
- export declare function unwrapList(data: unknown): Record<string, unknown>[];
11
- export {};
package/dist/cli-utils.js DELETED
@@ -1,58 +0,0 @@
1
- import { CliError } from './errors.js';
2
- export function parseArgs(argv, options) {
3
- const args = [];
4
- const flags = {};
5
- const multiSet = new Set(options?.multi);
6
- for (let i = 0; i < argv.length; i++) {
7
- const arg = argv[i];
8
- if (!arg.startsWith('--')) {
9
- args.push(arg);
10
- continue;
11
- }
12
- const eqIdx = arg.indexOf('=');
13
- let key;
14
- let value;
15
- if (eqIdx !== -1) {
16
- key = arg.slice(2, eqIdx);
17
- value = arg.slice(eqIdx + 1);
18
- }
19
- else {
20
- key = arg.slice(2);
21
- const next = argv[i + 1];
22
- if (next && !next.startsWith('--')) {
23
- value = next;
24
- i++;
25
- }
26
- else
27
- value = true;
28
- }
29
- if (multiSet.has(key) && typeof value === 'string') {
30
- const existing = flags[key];
31
- if (Array.isArray(existing))
32
- existing.push(value);
33
- else
34
- flags[key] = [value];
35
- }
36
- else
37
- flags[key] = value;
38
- }
39
- return { args, flags };
40
- }
41
- const GLOBAL_FLAGS = ['account', 'json', 'fields', 'publish', 'data', 'platform-overrides', 'force', 'project'];
42
- export function validateFlags(flags, allowed, command) {
43
- const allAllowed = new Set([...GLOBAL_FLAGS, ...allowed]);
44
- const unknown = Object.keys(flags).filter((k) => !allAllowed.has(k));
45
- if (unknown.length)
46
- throw new CliError('UNKNOWN_FLAG', `Unknown flag${unknown.length > 1 ? 's' : ''}: ${unknown.map((f) => `--${f}`).join(', ')}`, `Run: adkit ${command} --help`);
47
- }
48
- export function unwrapList(data) {
49
- if (Array.isArray(data))
50
- return data;
51
- if (data && typeof data === 'object') {
52
- const values = Object.values(data);
53
- const arr = values.find((v) => Array.isArray(v));
54
- if (arr)
55
- return arr;
56
- }
57
- return [];
58
- }
package/dist/cli.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
package/dist/client.d.ts DELETED
@@ -1,13 +0,0 @@
1
- export declare class AdkitClient {
2
- private apiKey;
3
- private baseUrl;
4
- constructor({ apiKey, baseUrl }: {
5
- apiKey: string;
6
- baseUrl: string;
7
- });
8
- private request;
9
- get(path: string): Promise<unknown>;
10
- post(path: string, body?: unknown): Promise<unknown>;
11
- patch(path: string, body?: unknown): Promise<unknown>;
12
- delete(path: string): Promise<unknown>;
13
- }
package/dist/client.js DELETED
@@ -1,84 +0,0 @@
1
- import { CliError } from './errors.js';
2
- function isErrorResponse(value) {
3
- return value != null && typeof value === 'object';
4
- }
5
- export class AdkitClient {
6
- apiKey;
7
- baseUrl;
8
- constructor({ apiKey, baseUrl }) {
9
- const isLocalhost = baseUrl.startsWith('http://localhost') || baseUrl.startsWith('http://127.0.0.1');
10
- if (baseUrl.startsWith('http://') && !isLocalhost)
11
- throw new Error('HTTPS is required — insecure HTTP base URLs are not allowed');
12
- this.apiKey = apiKey;
13
- this.baseUrl = baseUrl;
14
- }
15
- async request(method, path, body) {
16
- const url = `${this.baseUrl}${path}`;
17
- const headers = {
18
- Authorization: `Bearer ${this.apiKey}`,
19
- 'Content-Type': 'application/json',
20
- };
21
- const init = { method, headers };
22
- if (body !== undefined)
23
- init.body = JSON.stringify(body);
24
- let response;
25
- try {
26
- response = await fetch(url, init);
27
- }
28
- catch (err) {
29
- if (err instanceof DOMException && err.name === 'AbortError')
30
- throw new CliError('NETWORK_ERROR', 'Request timed out', 'Retry the command');
31
- throw new CliError('NETWORK_ERROR', 'Network connection error — check your internet connection and try again. If using a custom base URL, verify `ADKIT_BASE_URL` is correct.', 'Check internet connection');
32
- }
33
- if (response.status === 204)
34
- return undefined;
35
- if (!response.ok) {
36
- let serverMessage = '';
37
- let dataMessage = '';
38
- let retryable;
39
- try {
40
- const json = await response.json();
41
- if (isErrorResponse(json)) {
42
- serverMessage = json.statusMessage ?? json.message ?? '';
43
- dataMessage = json.data?.message ?? '';
44
- retryable = json.data?.retryable;
45
- }
46
- }
47
- catch {
48
- // no parseable body
49
- }
50
- if (response.status === 401)
51
- throw new CliError('INVALID_API_KEY', `Invalid or expired API key${serverMessage ? ` — ${serverMessage}` : ''}`, 'Run: adkit setup');
52
- if (response.status === 429) {
53
- const retryAfter = response.headers.get('Retry-After') ?? '60';
54
- throw new CliError('RATE_LIMITED', serverMessage || 'Rate limit exceeded', `Wait ${retryAfter} seconds before retrying`);
55
- }
56
- if (response.status === 404) {
57
- const fallback = `Not found: ${method} ${path} — the resource may have been deleted or the ID is wrong`;
58
- throw new CliError('NOT_FOUND', serverMessage || fallback, 'Check the resource ID');
59
- }
60
- if (response.status >= 500) {
61
- // H3/Nuxt sanitizes statusMessage on 5xx — prefer data.message (always set by normalizeError)
62
- const errorMsg = dataMessage || serverMessage || `Server error (HTTP ${String(response.status)})`;
63
- let suggestion;
64
- if (retryable === true)
65
- suggestion = 'Retry in a few seconds';
66
- throw new CliError('SERVER_ERROR', errorMsg, suggestion);
67
- }
68
- throw new CliError('SERVER_ERROR', serverMessage || `Request failed (HTTP ${String(response.status)})`);
69
- }
70
- return response.json();
71
- }
72
- async get(path) {
73
- return this.request('GET', path);
74
- }
75
- async post(path, body) {
76
- return this.request('POST', path, body);
77
- }
78
- async patch(path, body) {
79
- return this.request('PATCH', path, body);
80
- }
81
- async delete(path) {
82
- return this.request('DELETE', path);
83
- }
84
- }
@@ -1,7 +0,0 @@
1
- interface LoginOptions {
2
- baseUrl: string;
3
- scope?: string[];
4
- }
5
- export declare function login({ baseUrl, scope }: LoginOptions): Promise<void>;
6
- export declare function logout(): void;
7
- export {};
@@ -1,184 +0,0 @@
1
- import open from 'open';
2
- import { unlinkSync, existsSync } from 'node:fs';
3
- import { hostname } from 'node:os';
4
- import { writeConfig, readConfig, CONFIG_PATH } from '../config.js';
5
- import { AdkitClient } from '../client.js';
6
- const MAX_POLL_ATTEMPTS = 450; // 450 × 2s = 15 min — setup flow can take a while
7
- const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
8
- function createSpinner() {
9
- let frame = 0;
10
- let timer = null;
11
- let currentText = '';
12
- return {
13
- start(text) {
14
- currentText = text;
15
- frame = 0;
16
- timer = setInterval(() => {
17
- process.stderr.write(`\r${SPINNER_FRAMES[frame++ % SPINNER_FRAMES.length]} ${currentText}`);
18
- }, 80);
19
- },
20
- update(text) {
21
- currentText = text;
22
- },
23
- stop(finalText) {
24
- if (timer)
25
- clearInterval(timer);
26
- process.stderr.write('\r\x1b[K'); // clear line
27
- if (finalText)
28
- console.log(finalText);
29
- },
30
- };
31
- }
32
- function printNextSteps(scope) {
33
- const lines = ['Getting started:'];
34
- if (scope?.includes('manage')) {
35
- lines.push(' adkit meta accounts list List your connected ad accounts');
36
- lines.push(' adkit meta campaigns list List campaigns');
37
- lines.push(' adkit meta campaigns create Create a campaign');
38
- lines.push(' adkit drafts list View pending drafts');
39
- }
40
- else
41
- lines.push(' adkit setup manage Connect Meta Ads');
42
- lines.push(' adkit --help See all commands');
43
- lines.push('');
44
- console.log(lines.join('\n'));
45
- }
46
- async function detectDefaultAccounts(baseUrl, apiKey) {
47
- try {
48
- const client = new AdkitClient({ apiKey, baseUrl });
49
- const data = (await client.get('/manage/status'));
50
- const config = readConfig();
51
- if (!config?.selectedProject || !config.projects)
52
- return;
53
- const project = config.projects[config.selectedProject];
54
- if (!project)
55
- return;
56
- let changed = false;
57
- const metaAccounts = data.platforms?.meta?.accounts;
58
- if (metaAccounts?.length === 1) {
59
- project.defaultMetaAccount = metaAccounts[0].id;
60
- changed = true;
61
- console.log(`Default Meta account: ${metaAccounts[0].name} (${metaAccounts[0].id})`);
62
- }
63
- const googleAccounts = data.platforms?.google?.accounts;
64
- if (googleAccounts?.length === 1) {
65
- project.defaultGoogleAccount = googleAccounts[0].id;
66
- changed = true;
67
- console.log(`Default Google account: ${googleAccounts[0].name} (${googleAccounts[0].id})`);
68
- }
69
- if (changed)
70
- writeConfig({ projects: config.projects });
71
- }
72
- catch {
73
- // Non-critical — skip silently if account detection fails
74
- }
75
- }
76
- export async function login({ baseUrl, scope }) {
77
- const spinner = createSpinner();
78
- // Step 1: Create CLI session
79
- spinner.start('Connecting to AdKit...');
80
- let createResponse;
81
- try {
82
- createResponse = await fetch(`${baseUrl}/auth/cli-session`, {
83
- method: 'POST',
84
- headers: { 'Content-Type': 'application/json' },
85
- body: JSON.stringify({ src: 'cli', hostname: hostname(), scope: scope && scope.length > 0 ? scope : undefined }),
86
- });
87
- }
88
- catch {
89
- spinner.stop();
90
- throw new Error('Network connection failed — check your internet connection');
91
- }
92
- if (!createResponse.ok) {
93
- spinner.stop();
94
- let msg = 'Failed to create session';
95
- try {
96
- const errData = (await createResponse.json());
97
- if (errData.message)
98
- msg = errData.message;
99
- }
100
- catch {
101
- // no parseable body
102
- }
103
- throw new Error(msg);
104
- }
105
- const createData = (await createResponse.json());
106
- const sessionId = createData.session.sessionId;
107
- const rawUrl = createData.session.url;
108
- // Step 2: Open browser
109
- const origin = new URL(baseUrl).origin;
110
- const sessionUrl = rawUrl.startsWith('http') ? rawUrl : `${origin}${rawUrl}`;
111
- await open(sessionUrl);
112
- spinner.stop(`✓ Browser opened — complete setup at ${sessionUrl}`);
113
- // Step 3: Poll until complete
114
- spinner.start('Waiting for setup to complete...');
115
- let projects;
116
- let legacyApiKey;
117
- for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt++) {
118
- let pollResponse;
119
- try {
120
- pollResponse = await fetch(`${baseUrl}/auth/cli-session/${sessionId}`, {
121
- method: 'GET',
122
- });
123
- }
124
- catch {
125
- spinner.stop();
126
- throw new Error('Network connection failed during polling');
127
- }
128
- if (!pollResponse.ok) {
129
- spinner.stop();
130
- if (pollResponse.status === 404)
131
- throw new Error('Session not found or expired');
132
- throw new Error(`Poll failed with status ${pollResponse.status}`);
133
- }
134
- let pollData;
135
- try {
136
- pollData = (await pollResponse.json());
137
- }
138
- catch {
139
- continue;
140
- }
141
- const status = pollData.session?.status;
142
- if (status === 'complete') {
143
- projects = pollData.session.projects;
144
- legacyApiKey = pollData.session.apiKey;
145
- break;
146
- }
147
- if (status === 'expired') {
148
- spinner.stop();
149
- throw new Error('Session expired — timed out waiting for authentication');
150
- }
151
- await new Promise((r) => setTimeout(r, 2000));
152
- }
153
- spinner.stop();
154
- if (!projects && !legacyApiKey)
155
- throw new Error('Login timed out — max poll attempts exceeded');
156
- // Step 4: Write config
157
- if (projects && projects.length > 0) {
158
- const projectsMap = {};
159
- for (const p of projects)
160
- projectsMap[p.id] = { name: p.name, apiKey: p.apiKey };
161
- writeConfig({
162
- projects: projectsMap,
163
- selectedProject: projects[0].id,
164
- apiKey: projects[0].apiKey,
165
- });
166
- console.log('\n✅ AdKit is configured!\n');
167
- const names = projects.map((p) => ` • ${p.name}`).join('\n');
168
- console.log(`Projects:\n${names}\n`);
169
- // Auto-detect default ad accounts
170
- if (scope?.includes('manage'))
171
- await detectDefaultAccounts(baseUrl, projects[0].apiKey);
172
- printNextSteps(scope);
173
- }
174
- else {
175
- writeConfig({ apiKey: legacyApiKey });
176
- console.log('\n✅ AdKit is configured!\n');
177
- printNextSteps(scope);
178
- }
179
- }
180
- export function logout() {
181
- if (existsSync(CONFIG_PATH))
182
- unlinkSync(CONFIG_PATH);
183
- console.log('Logged out — API key removed.');
184
- }
@@ -1,6 +0,0 @@
1
- import type { AdkitClient } from '../client.js';
2
- import { type Flags } from '../cli-utils.js';
3
- export declare function listDrafts(client: AdkitClient, _args: string[], flags: Flags): Promise<unknown>;
4
- export declare function getDraft(client: AdkitClient, args: string[], _flags: Flags): Promise<unknown>;
5
- export declare function publishDraft(client: AdkitClient, args: string[], _flags: Flags): Promise<unknown>;
6
- export declare function deleteDraft(client: AdkitClient, args: string[], _flags: Flags): Promise<unknown>;
@@ -1,36 +0,0 @@
1
- import { validateFlags } from '../cli-utils.js';
2
- import { CliError } from '../errors.js';
3
- function requireArg(args, index, label, hint) {
4
- const val = args[index];
5
- if (!val) {
6
- throw new CliError('MISSING_ARGUMENT', `Missing required argument: \`<${label}>\``, hint);
7
- }
8
- return val;
9
- }
10
- function queryString(params) {
11
- const entries = Object.entries(params).filter((e) => e[1] !== undefined);
12
- if (entries.length === 0)
13
- return '';
14
- return '?' + entries.map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&');
15
- }
16
- const DRAFT_LIST_FLAGS = ['platform', 'limit', 'offset'];
17
- export async function listDrafts(client, _args, flags) {
18
- validateFlags(flags, DRAFT_LIST_FLAGS, 'manage drafts list');
19
- const platform = typeof flags.platform === 'string' ? flags.platform : undefined;
20
- const limit = typeof flags.limit === 'string' ? flags.limit : undefined;
21
- const offset = typeof flags.offset === 'string' ? flags.offset : undefined;
22
- const qs = queryString({ platform, limit, offset });
23
- return client.get(`/manage/drafts${qs}`);
24
- }
25
- export async function getDraft(client, args, _flags) {
26
- const id = requireArg(args, 0, 'draft-id', 'Run: adkit drafts get <draft-id>');
27
- return client.get(`/manage/drafts/${id}`);
28
- }
29
- export async function publishDraft(client, args, _flags) {
30
- const id = requireArg(args, 0, 'draft-id', 'Run: adkit drafts publish <draft-id>');
31
- return client.post(`/manage/drafts/${id}/publish`);
32
- }
33
- export async function deleteDraft(client, args, _flags) {
34
- const id = requireArg(args, 0, 'draft-id', 'Run: adkit drafts delete <draft-id>');
35
- return client.delete(`/manage/drafts/${id}`);
36
- }
@@ -1,28 +0,0 @@
1
- import type { AdkitClient } from '../client.js';
2
- import { type Flags } from '../cli-utils.js';
3
- export declare function listAccounts(client: AdkitClient, _args: string[], _flags: Flags): Promise<unknown>;
4
- export declare function connectAccount(client: AdkitClient, args: string[], _flags: Flags): Promise<unknown>;
5
- export declare function disconnectAccount(client: AdkitClient, args: string[], _flags: Flags): Promise<unknown>;
6
- export declare function listPages(client: AdkitClient, args: string[], _flags: Flags): Promise<unknown>;
7
- export declare function listPixels(client: AdkitClient, args: string[], _flags: Flags): Promise<unknown>;
8
- export declare function listCampaigns(client: AdkitClient, _args: string[], flags: Flags): Promise<unknown>;
9
- export declare function createCampaign(client: AdkitClient, _args: string[], flags: Flags): Promise<unknown>;
10
- export declare function updateCampaign(client: AdkitClient, args: string[], flags: Flags): Promise<unknown>;
11
- export declare function deleteCampaign(client: AdkitClient, args: string[], _flags: Flags): Promise<unknown>;
12
- export declare function listAdSets(client: AdkitClient, _args: string[], flags: Flags): Promise<unknown>;
13
- export declare function createAdSet(client: AdkitClient, _args: string[], flags: Flags): Promise<unknown>;
14
- export declare function updateAdSet(client: AdkitClient, args: string[], flags: Flags): Promise<unknown>;
15
- export declare function deleteAdSet(client: AdkitClient, args: string[], _flags: Flags): Promise<unknown>;
16
- export declare function listAds(client: AdkitClient, _args: string[], flags: Flags): Promise<unknown>;
17
- export declare function createAd(client: AdkitClient, _args: string[], flags: Flags): Promise<unknown>;
18
- export declare function updateAd(client: AdkitClient, args: string[], flags: Flags): Promise<unknown>;
19
- export declare function deleteAd(client: AdkitClient, args: string[], _flags: Flags): Promise<unknown>;
20
- export declare function createCreative(client: AdkitClient, _args: string[], flags: Flags): Promise<unknown>;
21
- export declare function updateCreative(client: AdkitClient, args: string[], flags: Flags): Promise<unknown>;
22
- export declare function deleteCreative(client: AdkitClient, args: string[], _flags: Flags): Promise<unknown>;
23
- export declare function searchInterests(client: AdkitClient, args: string[], flags: Flags): Promise<{
24
- results: unknown[];
25
- }>;
26
- export declare function uploadMedia(client: AdkitClient, _args: string[], flags: Flags): Promise<unknown>;
27
- export declare function listMedia(client: AdkitClient, _args: string[], flags: Flags): Promise<unknown>;
28
- export declare function deleteMedia(client: AdkitClient, args: string[], flags: Flags): Promise<unknown>;
@@ -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 {};
package/dist/config.js DELETED
@@ -1,70 +0,0 @@
1
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { homedir } from 'node:os';
4
- export const CONFIG_DIR = join(homedir(), '.config', 'adkit');
5
- export const CONFIG_PATH = join(CONFIG_DIR, 'config.json');
6
- function parseConfigFile(raw) {
7
- const parsed = JSON.parse(raw);
8
- if (!parsed || typeof parsed !== 'object')
9
- return {};
10
- return parsed;
11
- }
12
- export function readConfig() {
13
- if (!existsSync(CONFIG_PATH))
14
- return null;
15
- try {
16
- return parseConfigFile(readFileSync(CONFIG_PATH, 'utf-8'));
17
- }
18
- catch {
19
- return null;
20
- }
21
- }
22
- export function writeConfig(updates) {
23
- mkdirSync(CONFIG_DIR, { recursive: true });
24
- let existing = {};
25
- if (existsSync(CONFIG_PATH)) {
26
- try {
27
- existing = parseConfigFile(readFileSync(CONFIG_PATH, 'utf-8'));
28
- }
29
- catch {
30
- // invalid existing config — start fresh
31
- }
32
- }
33
- const merged = { ...existing, ...updates };
34
- writeFileSync(CONFIG_PATH, JSON.stringify(merged, null, 2), { mode: 0o600 });
35
- }
36
- export function resolveConfig(flags = {}) {
37
- // Priority 1: env
38
- const envKey = process.env.ADKIT_API_KEY;
39
- if (envKey)
40
- return { apiKey: envKey };
41
- // Priority 2: config file
42
- const config = readConfig();
43
- if (!config)
44
- return { apiKey: null };
45
- // If --project flag, look up in projects map
46
- if (flags.project) {
47
- const projectKey = config.projects?.[flags.project]?.apiKey ?? null;
48
- return { apiKey: projectKey, selectedProject: config.selectedProject };
49
- }
50
- // If selectedProject is set and exists in projects map, use that key
51
- if (config.selectedProject && config.projects?.[config.selectedProject]) {
52
- return {
53
- apiKey: config.projects[config.selectedProject]?.apiKey ?? null,
54
- selectedProject: config.selectedProject,
55
- };
56
- }
57
- return {
58
- apiKey: config.apiKey ?? null,
59
- selectedProject: config.selectedProject,
60
- };
61
- }
62
- export function getDefaultAccount(platform) {
63
- const config = readConfig();
64
- if (!config?.selectedProject || !config.projects)
65
- return null;
66
- const project = config.projects[config.selectedProject];
67
- if (!project)
68
- return null;
69
- return (platform === 'meta' ? project.defaultMetaAccount : project.defaultGoogleAccount) ?? null;
70
- }
package/dist/errors.d.ts DELETED
@@ -1,6 +0,0 @@
1
- export type ErrorCode = 'NOT_AUTHENTICATED' | 'INVALID_API_KEY' | 'MISSING_ARGUMENT' | 'MISSING_FLAG' | 'INVALID_VALUE' | 'NOT_FOUND' | 'RATE_LIMITED' | 'SERVER_ERROR' | 'NETWORK_ERROR' | 'UNKNOWN_COMMAND' | 'UNKNOWN_FLAG';
2
- export declare class CliError extends Error {
3
- code: ErrorCode;
4
- suggestion?: string;
5
- constructor(code: ErrorCode, message: string, suggestion?: string);
6
- }
package/dist/errors.js DELETED
@@ -1,9 +0,0 @@
1
- export class CliError extends Error {
2
- code;
3
- suggestion;
4
- constructor(code, message, suggestion) {
5
- super(message);
6
- this.code = code;
7
- this.suggestion = suggestion;
8
- }
9
- }
package/dist/output.d.ts DELETED
@@ -1,6 +0,0 @@
1
- interface FormatOptions {
2
- fields?: string[];
3
- json?: boolean;
4
- }
5
- export declare function formatOutput(data: Record<string, unknown>[], options: FormatOptions): string;
6
- export {};
package/dist/output.js DELETED
@@ -1,48 +0,0 @@
1
- function filterFields(data, fields) {
2
- return data.map((row) => {
3
- const filtered = {};
4
- for (const f of fields) {
5
- if (f in row)
6
- filtered[f] = row[f];
7
- }
8
- return filtered;
9
- });
10
- }
11
- function toStr(value) {
12
- if (value == null)
13
- return '';
14
- if (typeof value === 'string')
15
- return value;
16
- if (typeof value === 'number' || typeof value === 'boolean')
17
- return `${value}`;
18
- return JSON.stringify(value);
19
- }
20
- function formatTable(data) {
21
- if (data.length === 0)
22
- return '';
23
- const keys = Object.keys(data[0]);
24
- const widths = {};
25
- for (const key of keys) {
26
- widths[key] = key.length;
27
- for (const row of data) {
28
- const val = toStr(row[key]);
29
- if (val.length > widths[key])
30
- widths[key] = val.length;
31
- }
32
- }
33
- const header = keys.map((k) => k.toUpperCase().padEnd(widths[k])).join(' ');
34
- const separator = keys.map((k) => '-'.repeat(widths[k])).join(' ');
35
- const rows = data.map((row) => keys.map((k) => toStr(row[k]).padEnd(widths[k])).join(' '));
36
- return [header, separator, ...rows].join('\n');
37
- }
38
- export function formatOutput(data, options) {
39
- let rows = data;
40
- if (options.fields) {
41
- rows = filterFields(rows, options.fields);
42
- }
43
- const json = options.json ?? !(process.stdout.isTTY ?? false);
44
- if (json) {
45
- return JSON.stringify(rows);
46
- }
47
- return formatTable(rows);
48
- }