adport 0.5.2 → 0.6.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/index.js +35 -5
- package/dist/index.js.map +1 -1
- package/dist/linkedin-CLVSH2JO.js +40 -0
- package/dist/linkedin-CLVSH2JO.js.map +1 -0
- package/dist/pinterest-EHR7Q4UM.js +66 -0
- package/dist/pinterest-EHR7Q4UM.js.map +1 -0
- package/dist/snapchat-MCEUPY2Y.js +69 -0
- package/dist/snapchat-MCEUPY2Y.js.map +1 -0
- package/dist/spotify-WXJ6Y2ZG.js +68 -0
- package/dist/spotify-WXJ6Y2ZG.js.map +1 -0
- package/dist/x-PLGN5QEG.js +24 -0
- package/dist/x-PLGN5QEG.js.map +1 -0
- package/package.json +14 -9
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "adport",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.6.0",
|
|
10
10
|
description: "adport CLI \u2014 the open control plane for paid media",
|
|
11
11
|
license: "Apache-2.0",
|
|
12
12
|
type: "module",
|
|
@@ -28,6 +28,11 @@ var package_default = {
|
|
|
28
28
|
"@adport/provider-meta": "workspace:*",
|
|
29
29
|
"@adport/provider-microsoft": "workspace:*",
|
|
30
30
|
"@adport/provider-reddit": "workspace:*",
|
|
31
|
+
"@adport/provider-snapchat": "workspace:*",
|
|
32
|
+
"@adport/provider-spotify": "workspace:*",
|
|
33
|
+
"@adport/provider-pinterest": "workspace:*",
|
|
34
|
+
"@adport/provider-linkedin": "workspace:*",
|
|
35
|
+
"@adport/provider-x": "workspace:*",
|
|
31
36
|
"@adport/provider-tiktok": "workspace:*",
|
|
32
37
|
commander: "^13.0.0",
|
|
33
38
|
yaml: "^2.6.0",
|
|
@@ -217,16 +222,41 @@ function buildProgram(io = defaultIO) {
|
|
|
217
222
|
await connectReddit({ openBrowser: opts.browser, io });
|
|
218
223
|
return;
|
|
219
224
|
}
|
|
225
|
+
if (provider === "snapchat") {
|
|
226
|
+
const { connectSnapchat } = await import("./snapchat-MCEUPY2Y.js");
|
|
227
|
+
await connectSnapchat({ openBrowser: opts.browser, io });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (provider === "spotify") {
|
|
231
|
+
const { connectSpotify } = await import("./spotify-WXJ6Y2ZG.js");
|
|
232
|
+
await connectSpotify({ openBrowser: opts.browser, io });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (provider === "pinterest") {
|
|
236
|
+
const { connectPinterest } = await import("./pinterest-EHR7Q4UM.js");
|
|
237
|
+
await connectPinterest({ openBrowser: opts.browser, io });
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (provider === "linkedin") {
|
|
241
|
+
const { connectLinkedIn } = await import("./linkedin-CLVSH2JO.js");
|
|
242
|
+
await connectLinkedIn({ openBrowser: opts.browser, io });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
220
245
|
if (provider === "mock") {
|
|
221
246
|
io.out("The mock provider is explicit demo mode, not a connection. Try: adport --demo accounts");
|
|
222
247
|
return;
|
|
223
248
|
}
|
|
224
|
-
|
|
249
|
+
if (provider === "x") {
|
|
250
|
+
const { connectX } = await import("./x-PLGN5QEG.js");
|
|
251
|
+
await connectX({ openBrowser: opts.browser, io });
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
io.err(`Provider "${provider}" is not supported. Available: google, meta, tiktok, apple, microsoft, reddit, snapchat, spotify, pinterest, linkedin, x, mock.`);
|
|
225
255
|
process.exitCode = 1;
|
|
226
256
|
});
|
|
227
257
|
program.command("disconnect <provider>").description("Remove a provider connection from this machine").action(async (provider) => {
|
|
228
|
-
if (!["google", "meta", "tiktok", "apple", "microsoft", "reddit"].includes(provider)) {
|
|
229
|
-
io.err(`Provider "${provider}" is not supported. Available: google, meta, tiktok, apple, microsoft, reddit.`);
|
|
258
|
+
if (!["google", "meta", "tiktok", "apple", "microsoft", "reddit", "snapchat", "spotify", "pinterest", "linkedin", "x"].includes(provider)) {
|
|
259
|
+
io.err(`Provider "${provider}" is not supported. Available: google, meta, tiktok, apple, microsoft, reddit, snapchat, spotify, pinterest, linkedin, x.`);
|
|
230
260
|
process.exitCode = 1;
|
|
231
261
|
return;
|
|
232
262
|
}
|
|
@@ -279,7 +309,7 @@ function buildProgram(io = defaultIO) {
|
|
|
279
309
|
for (const record of records) {
|
|
280
310
|
io.out(`${record.provider}: credentials stored (source: ${record.source}, updated ${record.updatedAt})`);
|
|
281
311
|
}
|
|
282
|
-
for (const id of ["google", "meta", "tiktok", "apple", "microsoft", "reddit"]) {
|
|
312
|
+
for (const id of ["google", "meta", "tiktok", "apple", "microsoft", "reddit", "snapchat", "spotify", "pinterest", "linkedin", "x"]) {
|
|
283
313
|
const provider = rt.ctx.providers.list().find((p) => p.id === id);
|
|
284
314
|
if (!provider) {
|
|
285
315
|
io.out(`${id}: not connected (run \`adport connect ${id}\`)`);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/program.ts","../package.json","../src/index.ts"],"sourcesContent":["import { Command } from 'commander';\nimport packageJson from '../package.json';\nimport {\n AdportError,\n AuditLog,\n CredentialStore,\n type AdportRuntime,\n type AnyToolDefinition,\n} from '@adport/core';\n\nexport interface ProgramIO {\n out: (line: string) => void;\n err: (line: string) => void;\n}\n\nconst defaultIO: ProgramIO = {\n out: (line) => console.log(line),\n err: (line) => console.error(line),\n};\n\nfunction json(value: unknown): string {\n return JSON.stringify(value, null, 2);\n}\n\nfunction table(rows: Array<Record<string, unknown>>): string {\n if (rows.length === 0) return '(empty)';\n const keys = Object.keys(rows[0]!);\n const widths = keys.map((k) => Math.max(k.length, ...rows.map((r) => String(r[k] ?? '').length)));\n const line = (values: string[]) => values.map((v, i) => v.padEnd(widths[i]!)).join(' ');\n return [line(keys), line(widths.map((w) => '-'.repeat(w))), ...rows.map((r) => line(keys.map((k) => String(r[k] ?? ''))))].join('\\n');\n}\n\nasync function runtime(includeMock?: boolean): Promise<AdportRuntime> {\n const { assembleRuntime } = await import('@adport/mcp');\n return assembleRuntime({ includeMock });\n}\n\nexport function buildProgram(io: ProgramIO = defaultIO): Command {\n const program = new Command('adport');\n program\n .description('The open control plane for paid media (MCP + CLI)')\n .option('--demo', 'Use synthetic mock accounts and tools (never real provider data)')\n .version(packageJson.version)\n .configureOutput({ writeOut: (s) => io.out(s.trimEnd()), writeErr: (s) => io.err(s.trimEnd()) });\n const commandRuntime = () => runtime(program.opts<{ demo?: boolean }>().demo === true ? true : undefined);\n\n const tools = program.command('tools').description('Inspect and invoke the shared tool registry');\n\n tools\n .command('list')\n .description('List available tools')\n .option('--json', 'JSON output')\n .action(async (opts: { json?: boolean }) => {\n const { registry } = await commandRuntime();\n const defs = registry.list();\n if (opts.json) {\n io.out(\n json(\n defs.map((t: AnyToolDefinition) => ({\n name: t.name,\n namespace: t.namespace,\n readOnly: t.annotations.readOnly ?? false,\n destructive: t.annotations.destructive ?? false,\n })),\n ),\n );\n return;\n }\n io.out(\n table(\n defs.map((t) => ({\n name: t.name,\n namespace: t.namespace,\n mode: t.annotations.readOnly ? 'read' : t.annotations.destructive ? 'DESTRUCTIVE' : 'write',\n })),\n ),\n );\n });\n\n tools\n .command('run <name>')\n .description('Invoke a tool with JSON input (the same tools the MCP server exposes)')\n .option('--input <json>', 'JSON object with the tool arguments', '{}')\n .action(async (name: string, opts: { input: string }) => {\n const rt = await commandRuntime();\n const result = await rt.registry.call(name, JSON.parse(opts.input), rt.ctx);\n io.out(json(result));\n });\n\n program\n .command('accounts')\n .description('List connected ad accounts across providers')\n .option('--provider <id>', 'Limit to one provider')\n .option('--json', 'JSON output')\n .action(async (opts: { provider?: string; json?: boolean }) => {\n const rt = await commandRuntime();\n const result = (await rt.registry.call('accounts_list', { provider: opts.provider }, rt.ctx)) as {\n accounts: Array<Record<string, unknown>>;\n };\n io.out(opts.json ? json(result.accounts) : table(result.accounts));\n });\n\n program\n .command('report')\n .description('Normalized cross-platform performance report')\n .option('--provider <id>', 'Limit to one provider')\n .option('--level <level>', 'account | campaign | ad_group | ad', 'campaign')\n .option('--metrics <list>', 'Comma-separated metrics', 'spend,impressions,clicks,conversions')\n .option('--range <range>', 'Preset (last_7_days, ...) or START..END', 'last_7_days')\n .option('--limit <n>', 'Max rows', '100')\n .option('--json', 'JSON output')\n .action(async (opts: { provider?: string; level: string; metrics: string; range: string; limit: string; json?: boolean }) => {\n const rt = await commandRuntime();\n const dateRange = opts.range.includes('..')\n ? { start: opts.range.split('..')[0]!, end: opts.range.split('..')[1]! }\n : opts.range;\n const result = (await rt.registry.call(\n 'report',\n {\n provider: opts.provider,\n level: opts.level,\n metrics: opts.metrics.split(',').map((m) => m.trim()),\n date_range: dateRange,\n limit: Number(opts.limit),\n },\n rt.ctx,\n )) as { rows: Array<{ provider: string; accountId: string; entity: { name: string; status?: string }; metrics: Record<string, number> }>; truncated: boolean };\n if (opts.json) {\n io.out(json(result));\n return;\n }\n io.out(\n table(\n result.rows.map((r) => ({\n provider: r.provider,\n account: r.accountId,\n entity: r.entity.name,\n status: r.entity.status ?? '',\n ...r.metrics,\n })),\n ),\n );\n if (result.truncated) io.err('(truncated — raise --limit for more rows)');\n });\n\n program\n .command('policy')\n .description('Show the active write policy and where it was loaded from')\n .action(async () => {\n const rt = await commandRuntime();\n io.out(json({ source: rt.policySource, policy: rt.ctx.engine.policy }));\n });\n\n const audit = program.command('audit').description('Inspect or extend the write-audit trail');\n\n audit\n .command('show', { isDefault: true })\n .description('Show recent write-audit entries')\n .option('--limit <n>', 'Max entries', '20')\n .action(async (opts: { limit: string }) => {\n const entries = await new AuditLog().read(Number(opts.limit));\n if (entries.length === 0) {\n io.out('No audit entries yet.');\n return;\n }\n io.out(entries.map((e) => JSON.stringify(e)).join('\\n'));\n });\n\n audit\n .command('export')\n .description('Export write-audit entries to stdout for archival or compliance ingestion')\n .option('--limit <n>', 'Max entries', '10000')\n .option('--format <format>', 'jsonl | json', 'jsonl')\n .action(async (opts: { limit: string; format: string }) => {\n const limit = Number(opts.limit);\n if (!Number.isInteger(limit) || limit < 1) {\n throw new AdportError('INVALID_INPUT', '--limit must be a positive integer');\n }\n if (opts.format !== 'jsonl' && opts.format !== 'json') {\n throw new AdportError('INVALID_INPUT', '--format must be jsonl or json');\n }\n const entries = await new AuditLog().read(limit);\n io.out(opts.format === 'json' ? json(entries) : entries.map((e) => JSON.stringify(e)).join('\\n'));\n });\n\n audit\n .command('run')\n .description('Run the audit rule packs over connected accounts and persist findings')\n .option('--provider <id>', 'Limit to one provider')\n .option('--range <range>', 'Preset or START..END', 'last_30_days')\n .action(async (opts: { provider?: string; range: string }) => {\n const rt = await commandRuntime();\n const dateRange = opts.range.includes('..')\n ? { start: opts.range.split('..')[0]!, end: opts.range.split('..')[1]! }\n : opts.range;\n const result = (await rt.registry.call(\n 'audit_run',\n { provider: opts.provider, date_range: dateRange },\n rt.ctx,\n )) as { findings: Array<{ id: string; severity: string; title: string }>; counts: Record<string, number>; evaluatedAccounts: number };\n io.out(`Evaluated ${result.evaluatedAccounts} account(s): ${result.counts.critical} critical, ${result.counts.warn} warn, ${result.counts.info} info`);\n for (const finding of result.findings) {\n io.out(` [${finding.severity.toUpperCase()}] ${finding.id}\\n ${finding.title}`);\n }\n if (result.findings.length > 0) io.out('Next: adport recommendations · adport recommendations apply <id>');\n });\n\n audit\n .command('note <text>')\n .description('Record an external/manual change in the audit trail (e.g. a change made in the platform UI)')\n .option('--provider <id>', 'Provider the change concerns', 'external')\n .option('--account <id>', 'Account id the change concerns', '-')\n .action(async (text: string, opts: { provider: string; account: string }) => {\n await new AuditLog().append({\n event: 'note',\n provider: opts.provider,\n tool: 'audit_note',\n accountId: opts.account,\n summary: text,\n });\n io.out('Noted.');\n });\n\n program\n .command('connect <provider>')\n .description('Connect an ad account (guided wizard)')\n .option('--no-browser', 'Print auth URLs instead of opening a browser')\n .action(async (provider: string, opts: { browser: boolean }) => {\n if (provider === 'google') {\n const { connectGoogle } = await import('./connect/google.js');\n await connectGoogle({ openBrowser: opts.browser, io });\n return;\n }\n if (provider === 'meta') {\n const { connectMeta } = await import('./connect/meta.js');\n await connectMeta({ io });\n return;\n }\n if (provider === 'tiktok') {\n const { connectTikTok } = await import('./connect/tiktok.js');\n await connectTikTok({ io });\n return;\n }\n if (provider === 'apple') {\n const { connectApple } = await import('./connect/apple.js');\n await connectApple({ io });\n return;\n }\n if (provider === 'microsoft') {\n const { connectMicrosoft } = await import('./connect/microsoft.js');\n await connectMicrosoft({ openBrowser: opts.browser, io });\n return;\n }\n if (provider === 'reddit') {\n const { connectReddit } = await import('./connect/reddit.js');\n await connectReddit({ openBrowser: opts.browser, io });\n return;\n }\n if (provider === 'mock') {\n io.out('The mock provider is explicit demo mode, not a connection. Try: adport --demo accounts');\n return;\n }\n io.err(`Provider \"${provider}\" is not supported. Available: google, meta, tiktok, apple, microsoft, reddit, mock.`);\n process.exitCode = 1;\n });\n\n program\n .command('disconnect <provider>')\n .description('Remove a provider connection from this machine')\n .action(async (provider: string) => {\n if (!['google', 'meta', 'tiktok', 'apple', 'microsoft', 'reddit'].includes(provider)) {\n io.err(`Provider \"${provider}\" is not supported. Available: google, meta, tiktok, apple, microsoft, reddit.`);\n process.exitCode = 1;\n return;\n }\n const removed = await new CredentialStore().delete(provider);\n if (!removed) {\n io.out(`No local ${provider} credentials were stored.`);\n return;\n }\n io.out(`Removed local ${provider} credentials.`);\n io.out('Provider-side access was not revoked. Revoke the token or key at the provider if needed.');\n });\n\n const recommendations = program.command('recommendations').description('Review and act on audit findings');\n\n recommendations\n .command('list', { isDefault: true })\n .description('List findings (default: open), most severe first')\n .option('--status <status>', 'open | dismissed | applied', 'open')\n .option('--json', 'JSON output')\n .action(async (opts: { status: string; json?: boolean }) => {\n const rt = await commandRuntime();\n const result = (await rt.registry.call('recommendations_list', { status: opts.status }, rt.ctx)) as {\n findings: Array<{ id: string; severity: string; title: string; recommendation: string; proposedAction?: unknown }>;\n };\n if (opts.json) {\n io.out(json(result.findings));\n return;\n }\n if (result.findings.length === 0) {\n io.out(`No ${opts.status} findings. Run: adport audit run`);\n return;\n }\n for (const f of result.findings) {\n io.out(`[${f.severity.toUpperCase()}] ${f.id}`);\n io.out(` ${f.title}`);\n io.out(` → ${f.recommendation}${f.proposedAction ? ' (has ready-to-apply action)' : ''}`);\n }\n });\n\n recommendations\n .command('apply <findingId>')\n .description('Run a finding\\'s proposed action through the two-step validate→apply flow')\n .option('--pending <id>', 'pending_operation_id from the validation step')\n .action(async (findingId: string, opts: { pending?: string }) => {\n const rt = await commandRuntime();\n const result = await rt.registry.call(\n 'recommendation_apply',\n { finding_id: findingId, pending_operation_id: opts.pending },\n rt.ctx,\n );\n io.out(json(result));\n });\n\n recommendations\n .command('dismiss <findingId>')\n .description('Dismiss a finding (never reopened by future runs)')\n .action(async (findingId: string) => {\n const rt = await commandRuntime();\n const result = await rt.registry.call('recommendation_dismiss', { finding_id: findingId }, rt.ctx);\n io.out(json(result));\n });\n\n program\n .command('doctor')\n .description('Check credential and connection health for every provider')\n .action(async () => {\n const store = new CredentialStore();\n const records = await store.list();\n const rt = await commandRuntime();\n io.out(`policy: ${rt.policySource}`);\n io.out(`providers loaded: ${rt.ctx.providers.list().map((p) => p.id).join(', ') || '(none)'}`);\n for (const record of records) {\n io.out(`${record.provider}: credentials stored (source: ${record.source}, updated ${record.updatedAt})`);\n }\n for (const id of ['google', 'meta', 'tiktok', 'apple', 'microsoft', 'reddit'] as const) {\n const provider = rt.ctx.providers.list().find((p) => p.id === id);\n if (!provider) {\n io.out(`${id}: not connected (run \\`adport connect ${id}\\`)`);\n continue;\n }\n try {\n const accounts = await provider.listAccounts();\n io.out(`${id}: OK — ${accounts.length} accessible account(s)`);\n } catch (err) {\n io.err(`${id}: FAILED — ${err instanceof Error ? err.message : String(err)}`);\n process.exitCode = 1;\n }\n }\n });\n\n program\n .command('mcp')\n .description('Start the adport MCP server on stdio')\n .action(async () => {\n const { runStdioServer } = await import('@adport/mcp');\n await runStdioServer(await commandRuntime());\n // Keep the process alive for the stdio transport.\n await new Promise(() => {});\n });\n\n return program;\n}\n\nexport async function main(argv: string[]): Promise<void> {\n const program = buildProgram();\n try {\n await program.parseAsync(argv);\n } catch (err) {\n if (err instanceof AdportError) {\n console.error(JSON.stringify(err.toJSON(), null, 2));\n process.exitCode = 1;\n return;\n }\n throw err;\n }\n}\n","{\n \"name\": \"adport\",\n \"version\": \"0.5.2\",\n \"description\": \"adport CLI — the open control plane for paid media\",\n \"license\": \"Apache-2.0\",\n \"type\": \"module\",\n \"bin\": {\n \"adport\": \"./dist/index.js\"\n },\n \"files\": [\"dist\"],\n \"scripts\": {\n \"build\": \"tsup src/index.ts --format esm --sourcemap --clean\",\n \"test\": \"vitest run\",\n \"typecheck\": \"tsc --noEmit\",\n \"clean\": \"rm -rf dist\"\n },\n \"dependencies\": {\n \"@adport/core\": \"workspace:*\",\n \"@adport/mcp\": \"workspace:*\",\n \"@adport/provider-apple\": \"workspace:*\",\n \"@adport/provider-google\": \"workspace:*\",\n \"@adport/provider-meta\": \"workspace:*\",\n \"@adport/provider-microsoft\": \"workspace:*\",\n \"@adport/provider-reddit\": \"workspace:*\",\n \"@adport/provider-tiktok\": \"workspace:*\",\n \"commander\": \"^13.0.0\",\n \"yaml\": \"^2.6.0\",\n \"zod\": \"^4.4.3\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^26.1.2\",\n \"tsup\": \"^8.3.0\",\n \"typescript\": \"^5.8.0\",\n \"vitest\": \"^4.1.10\"\n }\n}\n","#!/usr/bin/env node\nimport { main } from './program.js';\n\nawait main(process.argv);\n"],"mappings":";;;AAAA,SAAS,eAAe;;;ACAxB;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,aAAe;AAAA,EACf,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,KAAO;AAAA,IACL,QAAU;AAAA,EACZ;AAAA,EACA,OAAS,CAAC,MAAM;AAAA,EAChB,SAAW;AAAA,IACT,OAAS;AAAA,IACT,MAAQ;AAAA,IACR,WAAa;AAAA,IACb,OAAS;AAAA,EACX;AAAA,EACA,cAAgB;AAAA,IACd,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf,0BAA0B;AAAA,IAC1B,2BAA2B;AAAA,IAC3B,yBAAyB;AAAA,IACzB,8BAA8B;AAAA,IAC9B,2BAA2B;AAAA,IAC3B,2BAA2B;AAAA,IAC3B,WAAa;AAAA,IACb,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,iBAAmB;AAAA,IACjB,eAAe;AAAA,IACf,MAAQ;AAAA,IACR,YAAc;AAAA,IACd,QAAU;AAAA,EACZ;AACF;;;ADjCA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAOP,IAAM,YAAuB;AAAA,EAC3B,KAAK,CAAC,SAAS,QAAQ,IAAI,IAAI;AAAA,EAC/B,KAAK,CAAC,SAAS,QAAQ,MAAM,IAAI;AACnC;AAEA,SAAS,KAAK,OAAwB;AACpC,SAAO,KAAK,UAAU,OAAO,MAAM,CAAC;AACtC;AAEA,SAAS,MAAM,MAA8C;AAC3D,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,OAAO,OAAO,KAAK,KAAK,CAAC,CAAE;AACjC,QAAM,SAAS,KAAK,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,QAAQ,GAAG,KAAK,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,MAAM,CAAC,CAAC;AAChG,QAAM,OAAO,CAAC,WAAqB,OAAO,IAAI,CAAC,GAAG,MAAM,EAAE,OAAO,OAAO,CAAC,CAAE,CAAC,EAAE,KAAK,IAAI;AACvF,SAAO,CAAC,KAAK,IAAI,GAAG,KAAK,OAAO,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,IAAI,CAAC,MAAM,KAAK,KAAK,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI;AACtI;AAEA,eAAe,QAAQ,aAA+C;AACpE,QAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,aAAa;AACtD,SAAO,gBAAgB,EAAE,YAAY,CAAC;AACxC;AAEO,SAAS,aAAa,KAAgB,WAAoB;AAC/D,QAAM,UAAU,IAAI,QAAQ,QAAQ;AACpC,UACG,YAAY,mDAAmD,EAC/D,OAAO,UAAU,kEAAkE,EACnF,QAAQ,gBAAY,OAAO,EAC3B,gBAAgB,EAAE,UAAU,CAAC,MAAM,GAAG,IAAI,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC;AACjG,QAAM,iBAAiB,MAAM,QAAQ,QAAQ,KAAyB,EAAE,SAAS,OAAO,OAAO,MAAS;AAExG,QAAM,QAAQ,QAAQ,QAAQ,OAAO,EAAE,YAAY,6CAA6C;AAEhG,QACG,QAAQ,MAAM,EACd,YAAY,sBAAsB,EAClC,OAAO,UAAU,aAAa,EAC9B,OAAO,OAAO,SAA6B;AAC1C,UAAM,EAAE,SAAS,IAAI,MAAM,eAAe;AAC1C,UAAM,OAAO,SAAS,KAAK;AAC3B,QAAI,KAAK,MAAM;AACb,SAAG;AAAA,QACD;AAAA,UACE,KAAK,IAAI,CAAC,OAA0B;AAAA,YAClC,MAAM,EAAE;AAAA,YACR,WAAW,EAAE;AAAA,YACb,UAAU,EAAE,YAAY,YAAY;AAAA,YACpC,aAAa,EAAE,YAAY,eAAe;AAAA,UAC5C,EAAE;AAAA,QACJ;AAAA,MACF;AACA;AAAA,IACF;AACA,OAAG;AAAA,MACD;AAAA,QACE,KAAK,IAAI,CAAC,OAAO;AAAA,UACf,MAAM,EAAE;AAAA,UACR,WAAW,EAAE;AAAA,UACb,MAAM,EAAE,YAAY,WAAW,SAAS,EAAE,YAAY,cAAc,gBAAgB;AAAA,QACtF,EAAE;AAAA,MACJ;AAAA,IACF;AAAA,EACF,CAAC;AAEH,QACG,QAAQ,YAAY,EACpB,YAAY,uEAAuE,EACnF,OAAO,kBAAkB,uCAAuC,IAAI,EACpE,OAAO,OAAO,MAAc,SAA4B;AACvD,UAAM,KAAK,MAAM,eAAe;AAChC,UAAM,SAAS,MAAM,GAAG,SAAS,KAAK,MAAM,KAAK,MAAM,KAAK,KAAK,GAAG,GAAG,GAAG;AAC1E,OAAG,IAAI,KAAK,MAAM,CAAC;AAAA,EACrB,CAAC;AAEH,UACG,QAAQ,UAAU,EAClB,YAAY,6CAA6C,EACzD,OAAO,mBAAmB,uBAAuB,EACjD,OAAO,UAAU,aAAa,EAC9B,OAAO,OAAO,SAAgD;AAC7D,UAAM,KAAK,MAAM,eAAe;AAChC,UAAM,SAAU,MAAM,GAAG,SAAS,KAAK,iBAAiB,EAAE,UAAU,KAAK,SAAS,GAAG,GAAG,GAAG;AAG3F,OAAG,IAAI,KAAK,OAAO,KAAK,OAAO,QAAQ,IAAI,MAAM,OAAO,QAAQ,CAAC;AAAA,EACnE,CAAC;AAEH,UACG,QAAQ,QAAQ,EAChB,YAAY,8CAA8C,EAC1D,OAAO,mBAAmB,uBAAuB,EACjD,OAAO,mBAAmB,sCAAsC,UAAU,EAC1E,OAAO,oBAAoB,2BAA2B,sCAAsC,EAC5F,OAAO,mBAAmB,2CAA2C,aAAa,EAClF,OAAO,eAAe,YAAY,KAAK,EACvC,OAAO,UAAU,aAAa,EAC9B,OAAO,OAAO,SAA8G;AAC3H,UAAM,KAAK,MAAM,eAAe;AAChC,UAAM,YAAY,KAAK,MAAM,SAAS,IAAI,IACtC,EAAE,OAAO,KAAK,MAAM,MAAM,IAAI,EAAE,CAAC,GAAI,KAAK,KAAK,MAAM,MAAM,IAAI,EAAE,CAAC,EAAG,IACrE,KAAK;AACT,UAAM,SAAU,MAAM,GAAG,SAAS;AAAA,MAChC;AAAA,MACA;AAAA,QACE,UAAU,KAAK;AAAA,QACf,OAAO,KAAK;AAAA,QACZ,SAAS,KAAK,QAAQ,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAAA,QACpD,YAAY;AAAA,QACZ,OAAO,OAAO,KAAK,KAAK;AAAA,MAC1B;AAAA,MACA,GAAG;AAAA,IACL;AACA,QAAI,KAAK,MAAM;AACb,SAAG,IAAI,KAAK,MAAM,CAAC;AACnB;AAAA,IACF;AACA,OAAG;AAAA,MACD;AAAA,QACE,OAAO,KAAK,IAAI,CAAC,OAAO;AAAA,UACtB,UAAU,EAAE;AAAA,UACZ,SAAS,EAAE;AAAA,UACX,QAAQ,EAAE,OAAO;AAAA,UACjB,QAAQ,EAAE,OAAO,UAAU;AAAA,UAC3B,GAAG,EAAE;AAAA,QACP,EAAE;AAAA,MACJ;AAAA,IACF;AACA,QAAI,OAAO,UAAW,IAAG,IAAI,gDAA2C;AAAA,EAC1E,CAAC;AAEH,UACG,QAAQ,QAAQ,EAChB,YAAY,2DAA2D,EACvE,OAAO,YAAY;AAClB,UAAM,KAAK,MAAM,eAAe;AAChC,OAAG,IAAI,KAAK,EAAE,QAAQ,GAAG,cAAc,QAAQ,GAAG,IAAI,OAAO,OAAO,CAAC,CAAC;AAAA,EACxE,CAAC;AAEH,QAAM,QAAQ,QAAQ,QAAQ,OAAO,EAAE,YAAY,yCAAyC;AAE5F,QACG,QAAQ,QAAQ,EAAE,WAAW,KAAK,CAAC,EACnC,YAAY,iCAAiC,EAC7C,OAAO,eAAe,eAAe,IAAI,EACzC,OAAO,OAAO,SAA4B;AACzC,UAAM,UAAU,MAAM,IAAI,SAAS,EAAE,KAAK,OAAO,KAAK,KAAK,CAAC;AAC5D,QAAI,QAAQ,WAAW,GAAG;AACxB,SAAG,IAAI,uBAAuB;AAC9B;AAAA,IACF;AACA,OAAG,IAAI,QAAQ,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,EACzD,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,2EAA2E,EACvF,OAAO,eAAe,eAAe,OAAO,EAC5C,OAAO,qBAAqB,gBAAgB,OAAO,EACnD,OAAO,OAAO,SAA4C;AACzD,UAAM,QAAQ,OAAO,KAAK,KAAK;AAC/B,QAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,GAAG;AACzC,YAAM,IAAI,YAAY,iBAAiB,oCAAoC;AAAA,IAC7E;AACA,QAAI,KAAK,WAAW,WAAW,KAAK,WAAW,QAAQ;AACrD,YAAM,IAAI,YAAY,iBAAiB,gCAAgC;AAAA,IACzE;AACA,UAAM,UAAU,MAAM,IAAI,SAAS,EAAE,KAAK,KAAK;AAC/C,OAAG,IAAI,KAAK,WAAW,SAAS,KAAK,OAAO,IAAI,QAAQ,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,EAClG,CAAC;AAEH,QACG,QAAQ,KAAK,EACb,YAAY,uEAAuE,EACnF,OAAO,mBAAmB,uBAAuB,EACjD,OAAO,mBAAmB,wBAAwB,cAAc,EAChE,OAAO,OAAO,SAA+C;AAC5D,UAAM,KAAK,MAAM,eAAe;AAChC,UAAM,YAAY,KAAK,MAAM,SAAS,IAAI,IACtC,EAAE,OAAO,KAAK,MAAM,MAAM,IAAI,EAAE,CAAC,GAAI,KAAK,KAAK,MAAM,MAAM,IAAI,EAAE,CAAC,EAAG,IACrE,KAAK;AACT,UAAM,SAAU,MAAM,GAAG,SAAS;AAAA,MAChC;AAAA,MACA,EAAE,UAAU,KAAK,UAAU,YAAY,UAAU;AAAA,MACjD,GAAG;AAAA,IACL;AACA,OAAG,IAAI,aAAa,OAAO,iBAAiB,gBAAgB,OAAO,OAAO,QAAQ,cAAc,OAAO,OAAO,IAAI,UAAU,OAAO,OAAO,IAAI,OAAO;AACrJ,eAAW,WAAW,OAAO,UAAU;AACrC,SAAG,IAAI,MAAM,QAAQ,SAAS,YAAY,CAAC,KAAK,QAAQ,EAAE;AAAA,MAAS,QAAQ,KAAK,EAAE;AAAA,IACpF;AACA,QAAI,OAAO,SAAS,SAAS,EAAG,IAAG,IAAI,uEAAoE;AAAA,EAC7G,CAAC;AAEH,QACG,QAAQ,aAAa,EACrB,YAAY,6FAA6F,EACzG,OAAO,mBAAmB,gCAAgC,UAAU,EACpE,OAAO,kBAAkB,kCAAkC,GAAG,EAC9D,OAAO,OAAO,MAAc,SAAgD;AAC3E,UAAM,IAAI,SAAS,EAAE,OAAO;AAAA,MAC1B,OAAO;AAAA,MACP,UAAU,KAAK;AAAA,MACf,MAAM;AAAA,MACN,WAAW,KAAK;AAAA,MAChB,SAAS;AAAA,IACX,CAAC;AACD,OAAG,IAAI,QAAQ;AAAA,EACjB,CAAC;AAEH,UACG,QAAQ,oBAAoB,EAC5B,YAAY,uCAAuC,EACnD,OAAO,gBAAgB,8CAA8C,EACrE,OAAO,OAAO,UAAkB,SAA+B;AAC9D,QAAI,aAAa,UAAU;AACzB,YAAM,EAAE,cAAc,IAAI,MAAM,OAAO,sBAAqB;AAC5D,YAAM,cAAc,EAAE,aAAa,KAAK,SAAS,GAAG,CAAC;AACrD;AAAA,IACF;AACA,QAAI,aAAa,QAAQ;AACvB,YAAM,EAAE,YAAY,IAAI,MAAM,OAAO,oBAAmB;AACxD,YAAM,YAAY,EAAE,GAAG,CAAC;AACxB;AAAA,IACF;AACA,QAAI,aAAa,UAAU;AACzB,YAAM,EAAE,cAAc,IAAI,MAAM,OAAO,sBAAqB;AAC5D,YAAM,cAAc,EAAE,GAAG,CAAC;AAC1B;AAAA,IACF;AACA,QAAI,aAAa,SAAS;AACxB,YAAM,EAAE,aAAa,IAAI,MAAM,OAAO,qBAAoB;AAC1D,YAAM,aAAa,EAAE,GAAG,CAAC;AACzB;AAAA,IACF;AACA,QAAI,aAAa,aAAa;AAC5B,YAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,yBAAwB;AAClE,YAAM,iBAAiB,EAAE,aAAa,KAAK,SAAS,GAAG,CAAC;AACxD;AAAA,IACF;AACA,QAAI,aAAa,UAAU;AACzB,YAAM,EAAE,cAAc,IAAI,MAAM,OAAO,sBAAqB;AAC5D,YAAM,cAAc,EAAE,aAAa,KAAK,SAAS,GAAG,CAAC;AACrD;AAAA,IACF;AACA,QAAI,aAAa,QAAQ;AACvB,SAAG,IAAI,wFAAwF;AAC/F;AAAA,IACF;AACA,OAAG,IAAI,aAAa,QAAQ,sFAAsF;AAClH,YAAQ,WAAW;AAAA,EACrB,CAAC;AAEH,UACG,QAAQ,uBAAuB,EAC/B,YAAY,gDAAgD,EAC5D,OAAO,OAAO,aAAqB;AAClC,QAAI,CAAC,CAAC,UAAU,QAAQ,UAAU,SAAS,aAAa,QAAQ,EAAE,SAAS,QAAQ,GAAG;AACpF,SAAG,IAAI,aAAa,QAAQ,gFAAgF;AAC5G,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,UAAM,UAAU,MAAM,IAAI,gBAAgB,EAAE,OAAO,QAAQ;AAC3D,QAAI,CAAC,SAAS;AACZ,SAAG,IAAI,YAAY,QAAQ,2BAA2B;AACtD;AAAA,IACF;AACA,OAAG,IAAI,iBAAiB,QAAQ,eAAe;AAC/C,OAAG,IAAI,0FAA0F;AAAA,EACnG,CAAC;AAEH,QAAM,kBAAkB,QAAQ,QAAQ,iBAAiB,EAAE,YAAY,kCAAkC;AAEzG,kBACG,QAAQ,QAAQ,EAAE,WAAW,KAAK,CAAC,EACnC,YAAY,kDAAkD,EAC9D,OAAO,qBAAqB,8BAA8B,MAAM,EAChE,OAAO,UAAU,aAAa,EAC9B,OAAO,OAAO,SAA6C;AAC1D,UAAM,KAAK,MAAM,eAAe;AAChC,UAAM,SAAU,MAAM,GAAG,SAAS,KAAK,wBAAwB,EAAE,QAAQ,KAAK,OAAO,GAAG,GAAG,GAAG;AAG9F,QAAI,KAAK,MAAM;AACb,SAAG,IAAI,KAAK,OAAO,QAAQ,CAAC;AAC5B;AAAA,IACF;AACA,QAAI,OAAO,SAAS,WAAW,GAAG;AAChC,SAAG,IAAI,MAAM,KAAK,MAAM,kCAAkC;AAC1D;AAAA,IACF;AACA,eAAW,KAAK,OAAO,UAAU;AAC/B,SAAG,IAAI,IAAI,EAAE,SAAS,YAAY,CAAC,KAAK,EAAE,EAAE,EAAE;AAC9C,SAAG,IAAI,KAAK,EAAE,KAAK,EAAE;AACrB,SAAG,IAAI,YAAO,EAAE,cAAc,GAAG,EAAE,iBAAiB,kCAAkC,EAAE,EAAE;AAAA,IAC5F;AAAA,EACF,CAAC;AAEH,kBACG,QAAQ,mBAAmB,EAC3B,YAAY,+EAA2E,EACvF,OAAO,kBAAkB,+CAA+C,EACxE,OAAO,OAAO,WAAmB,SAA+B;AAC/D,UAAM,KAAK,MAAM,eAAe;AAChC,UAAM,SAAS,MAAM,GAAG,SAAS;AAAA,MAC/B;AAAA,MACA,EAAE,YAAY,WAAW,sBAAsB,KAAK,QAAQ;AAAA,MAC5D,GAAG;AAAA,IACL;AACA,OAAG,IAAI,KAAK,MAAM,CAAC;AAAA,EACrB,CAAC;AAEH,kBACG,QAAQ,qBAAqB,EAC7B,YAAY,mDAAmD,EAC/D,OAAO,OAAO,cAAsB;AACnC,UAAM,KAAK,MAAM,eAAe;AAChC,UAAM,SAAS,MAAM,GAAG,SAAS,KAAK,0BAA0B,EAAE,YAAY,UAAU,GAAG,GAAG,GAAG;AACjG,OAAG,IAAI,KAAK,MAAM,CAAC;AAAA,EACrB,CAAC;AAEH,UACG,QAAQ,QAAQ,EAChB,YAAY,2DAA2D,EACvE,OAAO,YAAY;AAClB,UAAM,QAAQ,IAAI,gBAAgB;AAClC,UAAM,UAAU,MAAM,MAAM,KAAK;AACjC,UAAM,KAAK,MAAM,eAAe;AAChC,OAAG,IAAI,WAAW,GAAG,YAAY,EAAE;AACnC,OAAG,IAAI,qBAAqB,GAAG,IAAI,UAAU,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,IAAI,KAAK,QAAQ,EAAE;AAC7F,eAAW,UAAU,SAAS;AAC5B,SAAG,IAAI,GAAG,OAAO,QAAQ,iCAAiC,OAAO,MAAM,aAAa,OAAO,SAAS,GAAG;AAAA,IACzG;AACA,eAAW,MAAM,CAAC,UAAU,QAAQ,UAAU,SAAS,aAAa,QAAQ,GAAY;AACtF,YAAM,WAAW,GAAG,IAAI,UAAU,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAChE,UAAI,CAAC,UAAU;AACb,WAAG,IAAI,GAAG,EAAE,yCAAyC,EAAE,KAAK;AAC5D;AAAA,MACF;AACA,UAAI;AACF,cAAM,WAAW,MAAM,SAAS,aAAa;AAC7C,WAAG,IAAI,GAAG,EAAE,eAAU,SAAS,MAAM,wBAAwB;AAAA,MAC/D,SAAS,KAAK;AACZ,WAAG,IAAI,GAAG,EAAE,mBAAc,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAC5E,gBAAQ,WAAW;AAAA,MACrB;AAAA,IACF;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,KAAK,EACb,YAAY,sCAAsC,EAClD,OAAO,YAAY;AAClB,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,aAAa;AACrD,UAAM,eAAe,MAAM,eAAe,CAAC;AAE3C,UAAM,IAAI,QAAQ,MAAM;AAAA,IAAC,CAAC;AAAA,EAC5B,CAAC;AAEH,SAAO;AACT;AAEA,eAAsB,KAAK,MAA+B;AACxD,QAAM,UAAU,aAAa;AAC7B,MAAI;AACF,UAAM,QAAQ,WAAW,IAAI;AAAA,EAC/B,SAAS,KAAK;AACZ,QAAI,eAAe,aAAa;AAC9B,cAAQ,MAAM,KAAK,UAAU,IAAI,OAAO,GAAG,MAAM,CAAC,CAAC;AACnD,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACF;;;AEhYA,MAAM,KAAK,QAAQ,IAAI;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/program.ts","../package.json","../src/index.ts"],"sourcesContent":["import { Command } from 'commander';\nimport packageJson from '../package.json';\nimport {\n AdportError,\n AuditLog,\n CredentialStore,\n type AdportRuntime,\n type AnyToolDefinition,\n} from '@adport/core';\n\nexport interface ProgramIO {\n out: (line: string) => void;\n err: (line: string) => void;\n}\n\nconst defaultIO: ProgramIO = {\n out: (line) => console.log(line),\n err: (line) => console.error(line),\n};\n\nfunction json(value: unknown): string {\n return JSON.stringify(value, null, 2);\n}\n\nfunction table(rows: Array<Record<string, unknown>>): string {\n if (rows.length === 0) return '(empty)';\n const keys = Object.keys(rows[0]!);\n const widths = keys.map((k) => Math.max(k.length, ...rows.map((r) => String(r[k] ?? '').length)));\n const line = (values: string[]) => values.map((v, i) => v.padEnd(widths[i]!)).join(' ');\n return [line(keys), line(widths.map((w) => '-'.repeat(w))), ...rows.map((r) => line(keys.map((k) => String(r[k] ?? ''))))].join('\\n');\n}\n\nasync function runtime(includeMock?: boolean): Promise<AdportRuntime> {\n const { assembleRuntime } = await import('@adport/mcp');\n return assembleRuntime({ includeMock });\n}\n\nexport function buildProgram(io: ProgramIO = defaultIO): Command {\n const program = new Command('adport');\n program\n .description('The open control plane for paid media (MCP + CLI)')\n .option('--demo', 'Use synthetic mock accounts and tools (never real provider data)')\n .version(packageJson.version)\n .configureOutput({ writeOut: (s) => io.out(s.trimEnd()), writeErr: (s) => io.err(s.trimEnd()) });\n const commandRuntime = () => runtime(program.opts<{ demo?: boolean }>().demo === true ? true : undefined);\n\n const tools = program.command('tools').description('Inspect and invoke the shared tool registry');\n\n tools\n .command('list')\n .description('List available tools')\n .option('--json', 'JSON output')\n .action(async (opts: { json?: boolean }) => {\n const { registry } = await commandRuntime();\n const defs = registry.list();\n if (opts.json) {\n io.out(\n json(\n defs.map((t: AnyToolDefinition) => ({\n name: t.name,\n namespace: t.namespace,\n readOnly: t.annotations.readOnly ?? false,\n destructive: t.annotations.destructive ?? false,\n })),\n ),\n );\n return;\n }\n io.out(\n table(\n defs.map((t) => ({\n name: t.name,\n namespace: t.namespace,\n mode: t.annotations.readOnly ? 'read' : t.annotations.destructive ? 'DESTRUCTIVE' : 'write',\n })),\n ),\n );\n });\n\n tools\n .command('run <name>')\n .description('Invoke a tool with JSON input (the same tools the MCP server exposes)')\n .option('--input <json>', 'JSON object with the tool arguments', '{}')\n .action(async (name: string, opts: { input: string }) => {\n const rt = await commandRuntime();\n const result = await rt.registry.call(name, JSON.parse(opts.input), rt.ctx);\n io.out(json(result));\n });\n\n program\n .command('accounts')\n .description('List connected ad accounts across providers')\n .option('--provider <id>', 'Limit to one provider')\n .option('--json', 'JSON output')\n .action(async (opts: { provider?: string; json?: boolean }) => {\n const rt = await commandRuntime();\n const result = (await rt.registry.call('accounts_list', { provider: opts.provider }, rt.ctx)) as {\n accounts: Array<Record<string, unknown>>;\n };\n io.out(opts.json ? json(result.accounts) : table(result.accounts));\n });\n\n program\n .command('report')\n .description('Normalized cross-platform performance report')\n .option('--provider <id>', 'Limit to one provider')\n .option('--level <level>', 'account | campaign | ad_group | ad', 'campaign')\n .option('--metrics <list>', 'Comma-separated metrics', 'spend,impressions,clicks,conversions')\n .option('--range <range>', 'Preset (last_7_days, ...) or START..END', 'last_7_days')\n .option('--limit <n>', 'Max rows', '100')\n .option('--json', 'JSON output')\n .action(async (opts: { provider?: string; level: string; metrics: string; range: string; limit: string; json?: boolean }) => {\n const rt = await commandRuntime();\n const dateRange = opts.range.includes('..')\n ? { start: opts.range.split('..')[0]!, end: opts.range.split('..')[1]! }\n : opts.range;\n const result = (await rt.registry.call(\n 'report',\n {\n provider: opts.provider,\n level: opts.level,\n metrics: opts.metrics.split(',').map((m) => m.trim()),\n date_range: dateRange,\n limit: Number(opts.limit),\n },\n rt.ctx,\n )) as { rows: Array<{ provider: string; accountId: string; entity: { name: string; status?: string }; metrics: Record<string, number> }>; truncated: boolean };\n if (opts.json) {\n io.out(json(result));\n return;\n }\n io.out(\n table(\n result.rows.map((r) => ({\n provider: r.provider,\n account: r.accountId,\n entity: r.entity.name,\n status: r.entity.status ?? '',\n ...r.metrics,\n })),\n ),\n );\n if (result.truncated) io.err('(truncated — raise --limit for more rows)');\n });\n\n program\n .command('policy')\n .description('Show the active write policy and where it was loaded from')\n .action(async () => {\n const rt = await commandRuntime();\n io.out(json({ source: rt.policySource, policy: rt.ctx.engine.policy }));\n });\n\n const audit = program.command('audit').description('Inspect or extend the write-audit trail');\n\n audit\n .command('show', { isDefault: true })\n .description('Show recent write-audit entries')\n .option('--limit <n>', 'Max entries', '20')\n .action(async (opts: { limit: string }) => {\n const entries = await new AuditLog().read(Number(opts.limit));\n if (entries.length === 0) {\n io.out('No audit entries yet.');\n return;\n }\n io.out(entries.map((e) => JSON.stringify(e)).join('\\n'));\n });\n\n audit\n .command('export')\n .description('Export write-audit entries to stdout for archival or compliance ingestion')\n .option('--limit <n>', 'Max entries', '10000')\n .option('--format <format>', 'jsonl | json', 'jsonl')\n .action(async (opts: { limit: string; format: string }) => {\n const limit = Number(opts.limit);\n if (!Number.isInteger(limit) || limit < 1) {\n throw new AdportError('INVALID_INPUT', '--limit must be a positive integer');\n }\n if (opts.format !== 'jsonl' && opts.format !== 'json') {\n throw new AdportError('INVALID_INPUT', '--format must be jsonl or json');\n }\n const entries = await new AuditLog().read(limit);\n io.out(opts.format === 'json' ? json(entries) : entries.map((e) => JSON.stringify(e)).join('\\n'));\n });\n\n audit\n .command('run')\n .description('Run the audit rule packs over connected accounts and persist findings')\n .option('--provider <id>', 'Limit to one provider')\n .option('--range <range>', 'Preset or START..END', 'last_30_days')\n .action(async (opts: { provider?: string; range: string }) => {\n const rt = await commandRuntime();\n const dateRange = opts.range.includes('..')\n ? { start: opts.range.split('..')[0]!, end: opts.range.split('..')[1]! }\n : opts.range;\n const result = (await rt.registry.call(\n 'audit_run',\n { provider: opts.provider, date_range: dateRange },\n rt.ctx,\n )) as { findings: Array<{ id: string; severity: string; title: string }>; counts: Record<string, number>; evaluatedAccounts: number };\n io.out(`Evaluated ${result.evaluatedAccounts} account(s): ${result.counts.critical} critical, ${result.counts.warn} warn, ${result.counts.info} info`);\n for (const finding of result.findings) {\n io.out(` [${finding.severity.toUpperCase()}] ${finding.id}\\n ${finding.title}`);\n }\n if (result.findings.length > 0) io.out('Next: adport recommendations · adport recommendations apply <id>');\n });\n\n audit\n .command('note <text>')\n .description('Record an external/manual change in the audit trail (e.g. a change made in the platform UI)')\n .option('--provider <id>', 'Provider the change concerns', 'external')\n .option('--account <id>', 'Account id the change concerns', '-')\n .action(async (text: string, opts: { provider: string; account: string }) => {\n await new AuditLog().append({\n event: 'note',\n provider: opts.provider,\n tool: 'audit_note',\n accountId: opts.account,\n summary: text,\n });\n io.out('Noted.');\n });\n\n program\n .command('connect <provider>')\n .description('Connect an ad account (guided wizard)')\n .option('--no-browser', 'Print auth URLs instead of opening a browser')\n .action(async (provider: string, opts: { browser: boolean }) => {\n if (provider === 'google') {\n const { connectGoogle } = await import('./connect/google.js');\n await connectGoogle({ openBrowser: opts.browser, io });\n return;\n }\n if (provider === 'meta') {\n const { connectMeta } = await import('./connect/meta.js');\n await connectMeta({ io });\n return;\n }\n if (provider === 'tiktok') {\n const { connectTikTok } = await import('./connect/tiktok.js');\n await connectTikTok({ io });\n return;\n }\n if (provider === 'apple') {\n const { connectApple } = await import('./connect/apple.js');\n await connectApple({ io });\n return;\n }\n if (provider === 'microsoft') {\n const { connectMicrosoft } = await import('./connect/microsoft.js');\n await connectMicrosoft({ openBrowser: opts.browser, io });\n return;\n }\n if (provider === 'reddit') {\n const { connectReddit } = await import('./connect/reddit.js');\n await connectReddit({ openBrowser: opts.browser, io });\n return;\n }\n if (provider === 'snapchat') {\n const { connectSnapchat } = await import('./connect/snapchat.js');\n await connectSnapchat({ openBrowser: opts.browser, io });\n return;\n }\n if (provider === 'spotify') {\n const { connectSpotify } = await import('./connect/spotify.js');\n await connectSpotify({ openBrowser: opts.browser, io });\n return;\n }\n if (provider === 'pinterest') {\n const { connectPinterest } = await import('./connect/pinterest.js');\n await connectPinterest({ openBrowser: opts.browser, io });\n return;\n }\n if (provider === 'linkedin') {\n const { connectLinkedIn } = await import('./connect/linkedin.js');\n await connectLinkedIn({ openBrowser: opts.browser, io });\n return;\n }\n if (provider === 'mock') {\n io.out('The mock provider is explicit demo mode, not a connection. Try: adport --demo accounts');\n return;\n }\n if (provider === 'x') {\n const { connectX } = await import('./connect/x.js');\n await connectX({ openBrowser: opts.browser, io });\n return;\n }\n io.err(`Provider \"${provider}\" is not supported. Available: google, meta, tiktok, apple, microsoft, reddit, snapchat, spotify, pinterest, linkedin, x, mock.`);\n process.exitCode = 1;\n });\n\n program\n .command('disconnect <provider>')\n .description('Remove a provider connection from this machine')\n .action(async (provider: string) => {\n if (!['google', 'meta', 'tiktok', 'apple', 'microsoft', 'reddit', 'snapchat', 'spotify', 'pinterest', 'linkedin', 'x'].includes(provider)) {\n io.err(`Provider \"${provider}\" is not supported. Available: google, meta, tiktok, apple, microsoft, reddit, snapchat, spotify, pinterest, linkedin, x.`);\n process.exitCode = 1;\n return;\n }\n const removed = await new CredentialStore().delete(provider);\n if (!removed) {\n io.out(`No local ${provider} credentials were stored.`);\n return;\n }\n io.out(`Removed local ${provider} credentials.`);\n io.out('Provider-side access was not revoked. Revoke the token or key at the provider if needed.');\n });\n\n const recommendations = program.command('recommendations').description('Review and act on audit findings');\n\n recommendations\n .command('list', { isDefault: true })\n .description('List findings (default: open), most severe first')\n .option('--status <status>', 'open | dismissed | applied', 'open')\n .option('--json', 'JSON output')\n .action(async (opts: { status: string; json?: boolean }) => {\n const rt = await commandRuntime();\n const result = (await rt.registry.call('recommendations_list', { status: opts.status }, rt.ctx)) as {\n findings: Array<{ id: string; severity: string; title: string; recommendation: string; proposedAction?: unknown }>;\n };\n if (opts.json) {\n io.out(json(result.findings));\n return;\n }\n if (result.findings.length === 0) {\n io.out(`No ${opts.status} findings. Run: adport audit run`);\n return;\n }\n for (const f of result.findings) {\n io.out(`[${f.severity.toUpperCase()}] ${f.id}`);\n io.out(` ${f.title}`);\n io.out(` → ${f.recommendation}${f.proposedAction ? ' (has ready-to-apply action)' : ''}`);\n }\n });\n\n recommendations\n .command('apply <findingId>')\n .description('Run a finding\\'s proposed action through the two-step validate→apply flow')\n .option('--pending <id>', 'pending_operation_id from the validation step')\n .action(async (findingId: string, opts: { pending?: string }) => {\n const rt = await commandRuntime();\n const result = await rt.registry.call(\n 'recommendation_apply',\n { finding_id: findingId, pending_operation_id: opts.pending },\n rt.ctx,\n );\n io.out(json(result));\n });\n\n recommendations\n .command('dismiss <findingId>')\n .description('Dismiss a finding (never reopened by future runs)')\n .action(async (findingId: string) => {\n const rt = await commandRuntime();\n const result = await rt.registry.call('recommendation_dismiss', { finding_id: findingId }, rt.ctx);\n io.out(json(result));\n });\n\n program\n .command('doctor')\n .description('Check credential and connection health for every provider')\n .action(async () => {\n const store = new CredentialStore();\n const records = await store.list();\n const rt = await commandRuntime();\n io.out(`policy: ${rt.policySource}`);\n io.out(`providers loaded: ${rt.ctx.providers.list().map((p) => p.id).join(', ') || '(none)'}`);\n for (const record of records) {\n io.out(`${record.provider}: credentials stored (source: ${record.source}, updated ${record.updatedAt})`);\n }\n for (const id of ['google', 'meta', 'tiktok', 'apple', 'microsoft', 'reddit', 'snapchat', 'spotify', 'pinterest', 'linkedin', 'x'] as const) {\n const provider = rt.ctx.providers.list().find((p) => p.id === id);\n if (!provider) {\n io.out(`${id}: not connected (run \\`adport connect ${id}\\`)`);\n continue;\n }\n try {\n const accounts = await provider.listAccounts();\n io.out(`${id}: OK — ${accounts.length} accessible account(s)`);\n } catch (err) {\n io.err(`${id}: FAILED — ${err instanceof Error ? err.message : String(err)}`);\n process.exitCode = 1;\n }\n }\n });\n\n program\n .command('mcp')\n .description('Start the adport MCP server on stdio')\n .action(async () => {\n const { runStdioServer } = await import('@adport/mcp');\n await runStdioServer(await commandRuntime());\n // Keep the process alive for the stdio transport.\n await new Promise(() => {});\n });\n\n return program;\n}\n\nexport async function main(argv: string[]): Promise<void> {\n const program = buildProgram();\n try {\n await program.parseAsync(argv);\n } catch (err) {\n if (err instanceof AdportError) {\n console.error(JSON.stringify(err.toJSON(), null, 2));\n process.exitCode = 1;\n return;\n }\n throw err;\n }\n}\n","{\n \"name\": \"adport\",\n \"version\": \"0.6.0\",\n \"description\": \"adport CLI — the open control plane for paid media\",\n \"license\": \"Apache-2.0\",\n \"type\": \"module\",\n \"bin\": {\n \"adport\": \"./dist/index.js\"\n },\n \"files\": [\"dist\"],\n \"scripts\": {\n \"build\": \"tsup src/index.ts --format esm --sourcemap --clean\",\n \"test\": \"vitest run\",\n \"typecheck\": \"tsc --noEmit\",\n \"clean\": \"rm -rf dist\"\n },\n \"dependencies\": {\n \"@adport/core\": \"workspace:*\",\n \"@adport/mcp\": \"workspace:*\",\n \"@adport/provider-apple\": \"workspace:*\",\n \"@adport/provider-google\": \"workspace:*\",\n \"@adport/provider-meta\": \"workspace:*\",\n \"@adport/provider-microsoft\": \"workspace:*\",\n \"@adport/provider-reddit\": \"workspace:*\",\n \"@adport/provider-snapchat\": \"workspace:*\",\n \"@adport/provider-spotify\": \"workspace:*\",\n \"@adport/provider-pinterest\": \"workspace:*\",\n \"@adport/provider-linkedin\": \"workspace:*\",\n \"@adport/provider-x\": \"workspace:*\",\n \"@adport/provider-tiktok\": \"workspace:*\",\n \"commander\": \"^13.0.0\",\n \"yaml\": \"^2.6.0\",\n \"zod\": \"^4.4.3\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^26.1.2\",\n \"tsup\": \"^8.3.0\",\n \"typescript\": \"^5.8.0\",\n \"vitest\": \"^4.1.10\"\n }\n}\n","#!/usr/bin/env node\nimport { main } from './program.js';\n\nawait main(process.argv);\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
printLocalConnectionIntro,
|
|
3
|
+
printLocalConnectionSaved
|
|
4
|
+
} from "./chunk-OPRADP2V.js";
|
|
5
|
+
|
|
6
|
+
// src/connect/linkedin.ts
|
|
7
|
+
import { AdportError, CredentialStore } from "@adport/core";
|
|
8
|
+
import { LinkedInAdsClient, LinkedInAdsProvider, linkedInTokensData } from "@adport/provider-linkedin";
|
|
9
|
+
async function connectLinkedIn(input) {
|
|
10
|
+
printLocalConnectionIntro(input.io, "LinkedIn Ads");
|
|
11
|
+
input.io.out("Use your own approved Advertising API app at https://www.linkedin.com/developers/apps. Obtain an authorized token with rw_ads and r_ads_reporting through its token generator or your own HTTPS OAuth callback.");
|
|
12
|
+
input.io.out("This command imports the token from your environment; it does not open a shared Cloud OAuth flow or request secrets through echoed prompts.");
|
|
13
|
+
const accessToken = process.env.LINKEDIN_ACCESS_TOKEN;
|
|
14
|
+
const refreshToken = process.env.LINKEDIN_REFRESH_TOKEN;
|
|
15
|
+
const clientId = process.env.LINKEDIN_CLIENT_ID, clientSecret = process.env.LINKEDIN_CLIENT_SECRET;
|
|
16
|
+
if (!accessToken && !(refreshToken && clientId && clientSecret)) throw new AdportError("INVALID_INPUT", "Set LINKEDIN_ACCESS_TOKEN securely in your environment. Approved partners may instead provide LINKEDIN_REFRESH_TOKEN, LINKEDIN_CLIENT_ID and LINKEDIN_CLIENT_SECRET together.");
|
|
17
|
+
if (refreshToken && (!clientId || !clientSecret)) throw new AdportError("INVALID_INPUT", "LINKEDIN_REFRESH_TOKEN requires both LINKEDIN_CLIENT_ID and LINKEDIN_CLIENT_SECRET. Omit refresh credentials for access-token-only onboarding.");
|
|
18
|
+
const expiry = (name) => {
|
|
19
|
+
const raw = process.env[name];
|
|
20
|
+
if (!raw) return void 0;
|
|
21
|
+
const value = Number(raw);
|
|
22
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new AdportError("INVALID_INPUT", `${name} must contain an expiration timestamp in Unix milliseconds.`);
|
|
23
|
+
return value;
|
|
24
|
+
};
|
|
25
|
+
let tokens = { accessToken, refreshToken, expiresAt: expiry("LINKEDIN_EXPIRES_AT"), refreshExpiresAt: expiry("LINKEDIN_REFRESH_EXPIRES_AT") };
|
|
26
|
+
const credentials = { ...tokens, ...refreshToken ? { clientId, clientSecret } : {}, onTokens: async (updated) => {
|
|
27
|
+
tokens = updated;
|
|
28
|
+
} };
|
|
29
|
+
const accounts = await new LinkedInAdsProvider(new LinkedInAdsClient(credentials)).listAccounts();
|
|
30
|
+
if (!tokens.accessToken) throw new AdportError("PROVIDER_ERROR", "LinkedIn verification completed without a usable access token. Credentials were not replaced.");
|
|
31
|
+
await new CredentialStore().set({ provider: "linkedin", source: "byo", data: { ...linkedInTokensData(tokens), ...refreshToken ? { client_id: clientId, client_secret: clientSecret } : {} } });
|
|
32
|
+
input.io.out(`Connected: ${accounts.length} accessible LinkedIn ad account(s).`);
|
|
33
|
+
for (const account of accounts) input.io.out(` ${account.id} ${account.name} ${account.currency ?? ""}`);
|
|
34
|
+
if (!refreshToken) input.io.out("This access-token-only connection cannot refresh automatically. Reauthorize your own app and re-import when the token expires.");
|
|
35
|
+
printLocalConnectionSaved(input.io);
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
connectLinkedIn
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=linkedin-CLVSH2JO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/connect/linkedin.ts"],"sourcesContent":["import { AdportError, CredentialStore } from '@adport/core';\nimport { LinkedInAdsClient, LinkedInAdsProvider, linkedInTokensData, type LinkedInCredentials, type LinkedInTokens } from '@adport/provider-linkedin';\nimport type { ProgramIO } from '../program.js';\nimport { printLocalConnectionIntro, printLocalConnectionSaved } from './local.js';\n\nexport async function connectLinkedIn(input: { openBrowser: boolean; io: ProgramIO }): Promise<void> {\n printLocalConnectionIntro(input.io, 'LinkedIn Ads');\n input.io.out('Use your own approved Advertising API app at https://www.linkedin.com/developers/apps. Obtain an authorized token with rw_ads and r_ads_reporting through its token generator or your own HTTPS OAuth callback.');\n input.io.out('This command imports the token from your environment; it does not open a shared Cloud OAuth flow or request secrets through echoed prompts.');\n const accessToken = process.env.LINKEDIN_ACCESS_TOKEN;\n const refreshToken = process.env.LINKEDIN_REFRESH_TOKEN;\n const clientId = process.env.LINKEDIN_CLIENT_ID, clientSecret = process.env.LINKEDIN_CLIENT_SECRET;\n if (!accessToken && !(refreshToken && clientId && clientSecret)) throw new AdportError('INVALID_INPUT', 'Set LINKEDIN_ACCESS_TOKEN securely in your environment. Approved partners may instead provide LINKEDIN_REFRESH_TOKEN, LINKEDIN_CLIENT_ID and LINKEDIN_CLIENT_SECRET together.');\n if (refreshToken && (!clientId || !clientSecret)) throw new AdportError('INVALID_INPUT', 'LINKEDIN_REFRESH_TOKEN requires both LINKEDIN_CLIENT_ID and LINKEDIN_CLIENT_SECRET. Omit refresh credentials for access-token-only onboarding.');\n const expiry = (name: string) => {\n const raw = process.env[name];\n if (!raw) return undefined;\n const value = Number(raw);\n if (!Number.isSafeInteger(value) || value < 0) throw new AdportError('INVALID_INPUT', `${name} must contain an expiration timestamp in Unix milliseconds.`);\n return value;\n };\n let tokens: Partial<LinkedInTokens> = { accessToken, refreshToken, expiresAt: expiry('LINKEDIN_EXPIRES_AT'), refreshExpiresAt: expiry('LINKEDIN_REFRESH_EXPIRES_AT') };\n const credentials: LinkedInCredentials = { ...tokens, ...(refreshToken ? { clientId, clientSecret } : {}), onTokens: async updated => { tokens = updated; } };\n const accounts = await new LinkedInAdsProvider(new LinkedInAdsClient(credentials)).listAccounts();\n if (!tokens.accessToken) throw new AdportError('PROVIDER_ERROR', 'LinkedIn verification completed without a usable access token. Credentials were not replaced.');\n await new CredentialStore().set({ provider: 'linkedin', source: 'byo', data: { ...linkedInTokensData(tokens as LinkedInTokens), ...(refreshToken ? { client_id: clientId!, client_secret: clientSecret! } : {}) } });\n input.io.out(`Connected: ${accounts.length} accessible LinkedIn ad account(s).`);\n for (const account of accounts) input.io.out(` ${account.id} ${account.name} ${account.currency ?? ''}`);\n if (!refreshToken) input.io.out('This access-token-only connection cannot refresh automatically. Reauthorize your own app and re-import when the token expires.');\n printLocalConnectionSaved(input.io);\n}\n"],"mappings":";;;;;;AAAA,SAAS,aAAa,uBAAuB;AAC7C,SAAS,mBAAmB,qBAAqB,0BAAyE;AAI1H,eAAsB,gBAAgB,OAA+D;AACnG,4BAA0B,MAAM,IAAI,cAAc;AAClD,QAAM,GAAG,IAAI,iNAAiN;AAC9N,QAAM,GAAG,IAAI,6IAA6I;AAC1J,QAAM,cAAc,QAAQ,IAAI;AAChC,QAAM,eAAe,QAAQ,IAAI;AACjC,QAAM,WAAW,QAAQ,IAAI,oBAAoB,eAAe,QAAQ,IAAI;AAC5E,MAAI,CAAC,eAAe,EAAE,gBAAgB,YAAY,cAAe,OAAM,IAAI,YAAY,iBAAiB,+KAA+K;AACvR,MAAI,iBAAiB,CAAC,YAAY,CAAC,cAAe,OAAM,IAAI,YAAY,iBAAiB,gJAAgJ;AACzO,QAAM,SAAS,CAAC,SAAiB;AAC/B,UAAM,MAAM,QAAQ,IAAI,IAAI;AAC5B,QAAI,CAAC,IAAK,QAAO;AACjB,UAAM,QAAQ,OAAO,GAAG;AACxB,QAAI,CAAC,OAAO,cAAc,KAAK,KAAK,QAAQ,EAAG,OAAM,IAAI,YAAY,iBAAiB,GAAG,IAAI,6DAA6D;AAC1J,WAAO;AAAA,EACT;AACA,MAAI,SAAkC,EAAE,aAAa,cAAc,WAAW,OAAO,qBAAqB,GAAG,kBAAkB,OAAO,6BAA6B,EAAE;AACrK,QAAM,cAAmC,EAAE,GAAG,QAAQ,GAAI,eAAe,EAAE,UAAU,aAAa,IAAI,CAAC,GAAI,UAAU,OAAM,YAAW;AAAE,aAAS;AAAA,EAAS,EAAE;AAC5J,QAAM,WAAW,MAAM,IAAI,oBAAoB,IAAI,kBAAkB,WAAW,CAAC,EAAE,aAAa;AAChG,MAAI,CAAC,OAAO,YAAa,OAAM,IAAI,YAAY,kBAAkB,+FAA+F;AAChK,QAAM,IAAI,gBAAgB,EAAE,IAAI,EAAE,UAAU,YAAY,QAAQ,OAAO,MAAM,EAAE,GAAG,mBAAmB,MAAwB,GAAG,GAAI,eAAe,EAAE,WAAW,UAAW,eAAe,aAAc,IAAI,CAAC,EAAG,EAAE,CAAC;AACnN,QAAM,GAAG,IAAI,cAAc,SAAS,MAAM,qCAAqC;AAC/E,aAAW,WAAW,SAAU,OAAM,GAAG,IAAI,KAAK,QAAQ,EAAE,KAAK,QAAQ,IAAI,KAAK,QAAQ,YAAY,EAAE,EAAE;AAC1G,MAAI,CAAC,aAAc,OAAM,GAAG,IAAI,gIAAgI;AAChK,4BAA0B,MAAM,EAAE;AACpC;","names":[]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateOAuthState,
|
|
3
|
+
openInBrowser,
|
|
4
|
+
startLoopbackServer
|
|
5
|
+
} from "./chunk-PFXNOLYI.js";
|
|
6
|
+
import {
|
|
7
|
+
printLocalConnectionIntro,
|
|
8
|
+
printLocalConnectionSaved
|
|
9
|
+
} from "./chunk-OPRADP2V.js";
|
|
10
|
+
|
|
11
|
+
// src/connect/pinterest.ts
|
|
12
|
+
import readline from "readline/promises";
|
|
13
|
+
import { AdportError, CredentialStore } from "@adport/core";
|
|
14
|
+
import { PinterestAdsClient, PinterestAdsProvider, buildPinterestAuthUrl, exchangePinterestCode } from "@adport/provider-pinterest";
|
|
15
|
+
async function connectPinterest(input) {
|
|
16
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
17
|
+
const store = new CredentialStore();
|
|
18
|
+
try {
|
|
19
|
+
printLocalConnectionIntro(input.io, "Pinterest Ads");
|
|
20
|
+
input.io.out("Register your own app at developers.pinterest.com/apps and obtain Trial access for your own account or Standard access for external users.");
|
|
21
|
+
input.io.out("Register http://localhost:53686/callback as an exact redirect URI on your app. This local flow requests ads:read and ads:write only.");
|
|
22
|
+
const existing = (await store.get("pinterest"))?.data;
|
|
23
|
+
const clientId = (await rl.question("App ID (Enter to reuse stored/environment): ")).trim() || existing?.client_id || process.env.PINTEREST_CLIENT_ID || "";
|
|
24
|
+
const clientSecret = process.env.PINTEREST_CLIENT_SECRET || existing?.client_secret || "";
|
|
25
|
+
if (!clientId || !clientSecret) throw new AdportError("INVALID_INPUT", "Set PINTEREST_CLIENT_SECRET securely in your terminal environment and provide an app ID. Secrets are not requested through echoed terminal prompts.");
|
|
26
|
+
const redirectUri = (await rl.question("Exact local redirect URI [http://localhost:53686/callback]: ")).trim() || "http://localhost:53686/callback";
|
|
27
|
+
const redirect = new URL(redirectUri);
|
|
28
|
+
if (redirect.protocol !== "http:" || !["localhost", "127.0.0.1"].includes(redirect.hostname) || !redirect.port || redirect.username || redirect.password || redirect.search || redirect.hash) {
|
|
29
|
+
throw new AdportError("INVALID_INPUT", "Use an http://localhost:<port> or http://127.0.0.1:<port> callback registered on your Pinterest app.");
|
|
30
|
+
}
|
|
31
|
+
const state = generateOAuthState();
|
|
32
|
+
const loopback = await startLoopbackServer(redirect.hostname, state, Number(redirect.port), redirect.pathname);
|
|
33
|
+
let timeout;
|
|
34
|
+
let refreshToken;
|
|
35
|
+
try {
|
|
36
|
+
const authUrl = buildPinterestAuthUrl(clientId, redirectUri, state);
|
|
37
|
+
input.io.out(`Authorize your own Pinterest app:
|
|
38
|
+
${authUrl}`);
|
|
39
|
+
if (input.openBrowser) openInBrowser(authUrl);
|
|
40
|
+
const code = await Promise.race([
|
|
41
|
+
loopback.waitForCode,
|
|
42
|
+
new Promise((_resolve, reject) => {
|
|
43
|
+
timeout = setTimeout(() => reject(new Error("Pinterest authorization timed out after five minutes.")), 3e5);
|
|
44
|
+
})
|
|
45
|
+
]);
|
|
46
|
+
refreshToken = await exchangePinterestCode({ clientId, clientSecret, code, redirectUri });
|
|
47
|
+
} finally {
|
|
48
|
+
clearTimeout(timeout);
|
|
49
|
+
loopback.close();
|
|
50
|
+
}
|
|
51
|
+
const client = new PinterestAdsClient({ clientId, clientSecret, refreshToken, onRefreshToken: async (token) => {
|
|
52
|
+
refreshToken = token;
|
|
53
|
+
} });
|
|
54
|
+
const accounts = await new PinterestAdsProvider(client).listAccounts();
|
|
55
|
+
await store.set({ provider: "pinterest", source: "byo", data: { client_id: clientId, client_secret: clientSecret, refresh_token: refreshToken, redirect_uri: redirectUri } });
|
|
56
|
+
input.io.out(`Connected: ${accounts.length} accessible Pinterest ad account(s).`);
|
|
57
|
+
for (const account of accounts) input.io.out(` ${account.id} ${account.name} ${account.currency ?? ""}`);
|
|
58
|
+
printLocalConnectionSaved(input.io);
|
|
59
|
+
} finally {
|
|
60
|
+
rl.close();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
connectPinterest
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=pinterest-EHR7Q4UM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/connect/pinterest.ts"],"sourcesContent":["import readline from 'node:readline/promises';\nimport { AdportError, CredentialStore } from '@adport/core';\nimport { PinterestAdsClient, PinterestAdsProvider, buildPinterestAuthUrl, exchangePinterestCode } from '@adport/provider-pinterest';\nimport type { ProgramIO } from '../program.js';\nimport { printLocalConnectionIntro, printLocalConnectionSaved } from './local.js';\nimport { generateOAuthState, openInBrowser, startLoopbackServer } from './oauth.js';\n\nexport async function connectPinterest(input: { openBrowser: boolean; io: ProgramIO }): Promise<void> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const store = new CredentialStore();\n try {\n printLocalConnectionIntro(input.io, 'Pinterest Ads');\n input.io.out('Register your own app at developers.pinterest.com/apps and obtain Trial access for your own account or Standard access for external users.');\n input.io.out('Register http://localhost:53686/callback as an exact redirect URI on your app. This local flow requests ads:read and ads:write only.');\n const existing = (await store.get('pinterest'))?.data;\n const clientId = (await rl.question('App ID (Enter to reuse stored/environment): ')).trim() || existing?.client_id || process.env.PINTEREST_CLIENT_ID || '';\n const clientSecret = process.env.PINTEREST_CLIENT_SECRET || existing?.client_secret || '';\n if (!clientId || !clientSecret) throw new AdportError('INVALID_INPUT', 'Set PINTEREST_CLIENT_SECRET securely in your terminal environment and provide an app ID. Secrets are not requested through echoed terminal prompts.');\n const redirectUri = (await rl.question('Exact local redirect URI [http://localhost:53686/callback]: ')).trim() || 'http://localhost:53686/callback';\n const redirect = new URL(redirectUri);\n if (redirect.protocol !== 'http:' || !['localhost', '127.0.0.1'].includes(redirect.hostname) || !redirect.port || redirect.username || redirect.password || redirect.search || redirect.hash) {\n throw new AdportError('INVALID_INPUT', 'Use an http://localhost:<port> or http://127.0.0.1:<port> callback registered on your Pinterest app.');\n }\n const state = generateOAuthState();\n const loopback = await startLoopbackServer(redirect.hostname as 'localhost' | '127.0.0.1', state, Number(redirect.port), redirect.pathname);\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let refreshToken: string;\n try {\n const authUrl = buildPinterestAuthUrl(clientId, redirectUri, state);\n input.io.out(`Authorize your own Pinterest app:\\n ${authUrl}`);\n if (input.openBrowser) openInBrowser(authUrl);\n const code = await Promise.race([\n loopback.waitForCode,\n new Promise<never>((_resolve, reject) => { timeout = setTimeout(() => reject(new Error('Pinterest authorization timed out after five minutes.')), 300_000); }),\n ]);\n refreshToken = await exchangePinterestCode({ clientId, clientSecret, code, redirectUri });\n } finally {\n clearTimeout(timeout); loopback.close();\n }\n const client = new PinterestAdsClient({ clientId, clientSecret, refreshToken, onRefreshToken: async token => { refreshToken = token; } });\n const accounts = await new PinterestAdsProvider(client).listAccounts();\n await store.set({ provider: 'pinterest', source: 'byo', data: { client_id: clientId, client_secret: clientSecret, refresh_token: refreshToken, redirect_uri: redirectUri } });\n input.io.out(`Connected: ${accounts.length} accessible Pinterest ad account(s).`);\n for (const account of accounts) input.io.out(` ${account.id} ${account.name} ${account.currency ?? ''}`);\n printLocalConnectionSaved(input.io);\n } finally {\n rl.close();\n }\n}\n"],"mappings":";;;;;;;;;;;AAAA,OAAO,cAAc;AACrB,SAAS,aAAa,uBAAuB;AAC7C,SAAS,oBAAoB,sBAAsB,uBAAuB,6BAA6B;AAKvG,eAAsB,iBAAiB,OAA+D;AACpG,QAAM,KAAK,SAAS,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AACpF,QAAM,QAAQ,IAAI,gBAAgB;AAClC,MAAI;AACF,8BAA0B,MAAM,IAAI,eAAe;AACnD,UAAM,GAAG,IAAI,4IAA4I;AACzJ,UAAM,GAAG,IAAI,sIAAsI;AACnJ,UAAM,YAAY,MAAM,MAAM,IAAI,WAAW,IAAI;AACjD,UAAM,YAAY,MAAM,GAAG,SAAS,8CAA8C,GAAG,KAAK,KAAK,UAAU,aAAa,QAAQ,IAAI,uBAAuB;AACzJ,UAAM,eAAe,QAAQ,IAAI,2BAA2B,UAAU,iBAAiB;AACvF,QAAI,CAAC,YAAY,CAAC,aAAc,OAAM,IAAI,YAAY,iBAAiB,qJAAqJ;AAC5N,UAAM,eAAe,MAAM,GAAG,SAAS,8DAA8D,GAAG,KAAK,KAAK;AAClH,UAAM,WAAW,IAAI,IAAI,WAAW;AACpC,QAAI,SAAS,aAAa,WAAW,CAAC,CAAC,aAAa,WAAW,EAAE,SAAS,SAAS,QAAQ,KAAK,CAAC,SAAS,QAAQ,SAAS,YAAY,SAAS,YAAY,SAAS,UAAU,SAAS,MAAM;AAC5L,YAAM,IAAI,YAAY,iBAAiB,sGAAsG;AAAA,IAC/I;AACA,UAAM,QAAQ,mBAAmB;AACjC,UAAM,WAAW,MAAM,oBAAoB,SAAS,UAAuC,OAAO,OAAO,SAAS,IAAI,GAAG,SAAS,QAAQ;AAC1I,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,YAAM,UAAU,sBAAsB,UAAU,aAAa,KAAK;AAClE,YAAM,GAAG,IAAI;AAAA,IAAwC,OAAO,EAAE;AAC9D,UAAI,MAAM,YAAa,eAAc,OAAO;AAC5C,YAAM,OAAO,MAAM,QAAQ,KAAK;AAAA,QAC9B,SAAS;AAAA,QACT,IAAI,QAAe,CAAC,UAAU,WAAW;AAAE,oBAAU,WAAW,MAAM,OAAO,IAAI,MAAM,uDAAuD,CAAC,GAAG,GAAO;AAAA,QAAG,CAAC;AAAA,MAC/J,CAAC;AACD,qBAAe,MAAM,sBAAsB,EAAE,UAAU,cAAc,MAAM,YAAY,CAAC;AAAA,IAC1F,UAAE;AACA,mBAAa,OAAO;AAAG,eAAS,MAAM;AAAA,IACxC;AACA,UAAM,SAAS,IAAI,mBAAmB,EAAE,UAAU,cAAc,cAAc,gBAAgB,OAAM,UAAS;AAAE,qBAAe;AAAA,IAAO,EAAE,CAAC;AACxI,UAAM,WAAW,MAAM,IAAI,qBAAqB,MAAM,EAAE,aAAa;AACrE,UAAM,MAAM,IAAI,EAAE,UAAU,aAAa,QAAQ,OAAO,MAAM,EAAE,WAAW,UAAU,eAAe,cAAc,eAAe,cAAc,cAAc,YAAY,EAAE,CAAC;AAC5K,UAAM,GAAG,IAAI,cAAc,SAAS,MAAM,sCAAsC;AAChF,eAAW,WAAW,SAAU,OAAM,GAAG,IAAI,KAAK,QAAQ,EAAE,KAAK,QAAQ,IAAI,KAAK,QAAQ,YAAY,EAAE,EAAE;AAC1G,8BAA0B,MAAM,EAAE;AAAA,EACpC,UAAE;AACA,OAAG,MAAM;AAAA,EACX;AACF;","names":[]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateOAuthState,
|
|
3
|
+
openInBrowser,
|
|
4
|
+
startLoopbackServer
|
|
5
|
+
} from "./chunk-PFXNOLYI.js";
|
|
6
|
+
import {
|
|
7
|
+
printLocalConnectionIntro,
|
|
8
|
+
printLocalConnectionSaved
|
|
9
|
+
} from "./chunk-OPRADP2V.js";
|
|
10
|
+
|
|
11
|
+
// src/connect/snapchat.ts
|
|
12
|
+
import readline from "readline/promises";
|
|
13
|
+
import { AdportError, CredentialStore } from "@adport/core";
|
|
14
|
+
import { SnapchatAdsClient, SnapchatAdsProvider, buildSnapchatAuthUrl, exchangeSnapchatCode } from "@adport/provider-snapchat";
|
|
15
|
+
async function connectSnapchat(input) {
|
|
16
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
17
|
+
const store = new CredentialStore();
|
|
18
|
+
try {
|
|
19
|
+
printLocalConnectionIntro(input.io, "Snapchat Ads");
|
|
20
|
+
input.io.out("In Snap Business Manager \u2192 Business Details \u2192 OAuth Apps, create your own app.");
|
|
21
|
+
input.io.out("Register the exact local callback http://127.0.0.1:53684/callback (or supply your registered local callback below).");
|
|
22
|
+
const existing = (await store.get("snapchat"))?.data;
|
|
23
|
+
const clientId = (await rl.question("Client ID (Enter to reuse stored/environment): ")).trim() || process.env.SNAPCHAT_CLIENT_ID || existing?.client_id || "";
|
|
24
|
+
const clientSecret = process.env.SNAPCHAT_CLIENT_SECRET || (existing?.client_id === clientId ? existing.client_secret : "") || "";
|
|
25
|
+
if (!clientId || !clientSecret) {
|
|
26
|
+
throw new AdportError("INVALID_INPUT", "Set SNAPCHAT_CLIENT_SECRET securely in your terminal environment and provide a client ID. Secrets are not requested through echoed terminal prompts.");
|
|
27
|
+
}
|
|
28
|
+
const defaultRedirect = existing?.client_id === clientId && existing.redirect_uri || "http://127.0.0.1:53684/callback";
|
|
29
|
+
const redirectUri = (await rl.question(`Exact local redirect URI [${defaultRedirect}]: `)).trim() || defaultRedirect;
|
|
30
|
+
const redirect = new URL(redirectUri);
|
|
31
|
+
if (redirect.protocol !== "http:" || !["127.0.0.1", "localhost"].includes(redirect.hostname) || !redirect.port || redirect.username || redirect.password || redirect.search || redirect.hash) {
|
|
32
|
+
throw new AdportError("INVALID_INPUT", "Use an http://127.0.0.1:<port> or http://localhost:<port> loopback callback.");
|
|
33
|
+
}
|
|
34
|
+
const state = generateOAuthState();
|
|
35
|
+
const loopback = await startLoopbackServer(redirect.hostname, state, Number(redirect.port), redirect.pathname);
|
|
36
|
+
let timeout;
|
|
37
|
+
let refreshToken;
|
|
38
|
+
try {
|
|
39
|
+
const authUrl = buildSnapchatAuthUrl(clientId, redirectUri, state);
|
|
40
|
+
input.io.out(`Authorize your own app (read/write marketing access):
|
|
41
|
+
${authUrl}`);
|
|
42
|
+
if (input.openBrowser) openInBrowser(authUrl);
|
|
43
|
+
const code = await Promise.race([
|
|
44
|
+
loopback.waitForCode,
|
|
45
|
+
new Promise((_resolve, reject) => {
|
|
46
|
+
timeout = setTimeout(() => reject(new Error("Snapchat authorization timed out after five minutes.")), 3e5);
|
|
47
|
+
})
|
|
48
|
+
]);
|
|
49
|
+
refreshToken = await exchangeSnapchatCode({ clientId, clientSecret, code, redirectUri });
|
|
50
|
+
} finally {
|
|
51
|
+
clearTimeout(timeout);
|
|
52
|
+
loopback.close();
|
|
53
|
+
}
|
|
54
|
+
const client = new SnapchatAdsClient({ clientId, clientSecret, refreshToken, onRefreshToken: async (token) => {
|
|
55
|
+
refreshToken = token;
|
|
56
|
+
} });
|
|
57
|
+
const accounts = await new SnapchatAdsProvider(client).listAccounts();
|
|
58
|
+
await store.set({ provider: "snapchat", source: "byo", data: { client_id: clientId, client_secret: clientSecret, refresh_token: refreshToken, redirect_uri: redirectUri } });
|
|
59
|
+
input.io.out(`Connected: ${accounts.length} accessible Snapchat ad account(s).`);
|
|
60
|
+
for (const account of accounts) input.io.out(` ${account.id} ${account.name} ${account.currency}`);
|
|
61
|
+
printLocalConnectionSaved(input.io);
|
|
62
|
+
} finally {
|
|
63
|
+
rl.close();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
connectSnapchat
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=snapchat-MCEUPY2Y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/connect/snapchat.ts"],"sourcesContent":["import readline from 'node:readline/promises';\nimport { AdportError, CredentialStore } from '@adport/core';\nimport { SnapchatAdsClient, SnapchatAdsProvider, buildSnapchatAuthUrl, exchangeSnapchatCode } from '@adport/provider-snapchat';\nimport type { ProgramIO } from '../program.js';\nimport { printLocalConnectionIntro, printLocalConnectionSaved } from './local.js';\nimport { generateOAuthState, openInBrowser, startLoopbackServer } from './oauth.js';\n\nexport async function connectSnapchat(input: { openBrowser: boolean; io: ProgramIO }): Promise<void> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const store = new CredentialStore();\n try {\n printLocalConnectionIntro(input.io, 'Snapchat Ads');\n input.io.out('In Snap Business Manager → Business Details → OAuth Apps, create your own app.');\n input.io.out('Register the exact local callback http://127.0.0.1:53684/callback (or supply your registered local callback below).');\n const existing = (await store.get('snapchat'))?.data;\n const clientId = (await rl.question('Client ID (Enter to reuse stored/environment): ')).trim() || process.env.SNAPCHAT_CLIENT_ID || existing?.client_id || '';\n const clientSecret = process.env.SNAPCHAT_CLIENT_SECRET || (existing?.client_id === clientId ? existing.client_secret : '') || '';\n if (!clientId || !clientSecret) {\n throw new AdportError('INVALID_INPUT', 'Set SNAPCHAT_CLIENT_SECRET securely in your terminal environment and provide a client ID. Secrets are not requested through echoed terminal prompts.');\n }\n const defaultRedirect = (existing?.client_id === clientId && existing.redirect_uri) || 'http://127.0.0.1:53684/callback';\n const redirectUri = (await rl.question(`Exact local redirect URI [${defaultRedirect}]: `)).trim() || defaultRedirect;\n const redirect = new URL(redirectUri);\n if (redirect.protocol !== 'http:' || !['127.0.0.1', 'localhost'].includes(redirect.hostname) || !redirect.port || redirect.username || redirect.password || redirect.search || redirect.hash) {\n throw new AdportError('INVALID_INPUT', 'Use an http://127.0.0.1:<port> or http://localhost:<port> loopback callback.');\n }\n const state = generateOAuthState();\n const loopback = await startLoopbackServer(redirect.hostname as '127.0.0.1' | 'localhost', state, Number(redirect.port), redirect.pathname);\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let refreshToken: string;\n try {\n const authUrl = buildSnapchatAuthUrl(clientId, redirectUri, state);\n input.io.out(`Authorize your own app (read/write marketing access):\\n ${authUrl}`);\n if (input.openBrowser) openInBrowser(authUrl);\n const code = await Promise.race([\n loopback.waitForCode,\n new Promise<never>((_resolve, reject) => { timeout = setTimeout(() => reject(new Error('Snapchat authorization timed out after five minutes.')), 300_000); }),\n ]);\n refreshToken = await exchangeSnapchatCode({ clientId, clientSecret, code, redirectUri });\n } finally {\n clearTimeout(timeout);\n loopback.close();\n }\n const client = new SnapchatAdsClient({ clientId, clientSecret, refreshToken, onRefreshToken: async token => { refreshToken = token; } });\n const accounts = await new SnapchatAdsProvider(client).listAccounts();\n await store.set({ provider: 'snapchat', source: 'byo', data: { client_id: clientId, client_secret: clientSecret, refresh_token: refreshToken, redirect_uri: redirectUri } });\n input.io.out(`Connected: ${accounts.length} accessible Snapchat ad account(s).`);\n for (const account of accounts) input.io.out(` ${account.id} ${account.name} ${account.currency}`);\n printLocalConnectionSaved(input.io);\n } finally {\n rl.close();\n }\n}\n"],"mappings":";;;;;;;;;;;AAAA,OAAO,cAAc;AACrB,SAAS,aAAa,uBAAuB;AAC7C,SAAS,mBAAmB,qBAAqB,sBAAsB,4BAA4B;AAKnG,eAAsB,gBAAgB,OAA+D;AACnG,QAAM,KAAK,SAAS,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AACpF,QAAM,QAAQ,IAAI,gBAAgB;AAClC,MAAI;AACF,8BAA0B,MAAM,IAAI,cAAc;AAClD,UAAM,GAAG,IAAI,0FAAgF;AAC7F,UAAM,GAAG,IAAI,qHAAqH;AAClI,UAAM,YAAY,MAAM,MAAM,IAAI,UAAU,IAAI;AAChD,UAAM,YAAY,MAAM,GAAG,SAAS,iDAAiD,GAAG,KAAK,KAAK,QAAQ,IAAI,sBAAsB,UAAU,aAAa;AAC3J,UAAM,eAAe,QAAQ,IAAI,2BAA2B,UAAU,cAAc,WAAW,SAAS,gBAAgB,OAAO;AAC/H,QAAI,CAAC,YAAY,CAAC,cAAc;AAC9B,YAAM,IAAI,YAAY,iBAAiB,sJAAsJ;AAAA,IAC/L;AACA,UAAM,kBAAmB,UAAU,cAAc,YAAY,SAAS,gBAAiB;AACvF,UAAM,eAAe,MAAM,GAAG,SAAS,6BAA6B,eAAe,KAAK,GAAG,KAAK,KAAK;AACrG,UAAM,WAAW,IAAI,IAAI,WAAW;AACpC,QAAI,SAAS,aAAa,WAAW,CAAC,CAAC,aAAa,WAAW,EAAE,SAAS,SAAS,QAAQ,KAAK,CAAC,SAAS,QAAQ,SAAS,YAAY,SAAS,YAAY,SAAS,UAAU,SAAS,MAAM;AAC5L,YAAM,IAAI,YAAY,iBAAiB,8EAA8E;AAAA,IACvH;AACA,UAAM,QAAQ,mBAAmB;AACjC,UAAM,WAAW,MAAM,oBAAoB,SAAS,UAAuC,OAAO,OAAO,SAAS,IAAI,GAAG,SAAS,QAAQ;AAC1I,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,YAAM,UAAU,qBAAqB,UAAU,aAAa,KAAK;AACjE,YAAM,GAAG,IAAI;AAAA,IAA4D,OAAO,EAAE;AAClF,UAAI,MAAM,YAAa,eAAc,OAAO;AAC5C,YAAM,OAAO,MAAM,QAAQ,KAAK;AAAA,QAC9B,SAAS;AAAA,QACT,IAAI,QAAe,CAAC,UAAU,WAAW;AAAE,oBAAU,WAAW,MAAM,OAAO,IAAI,MAAM,sDAAsD,CAAC,GAAG,GAAO;AAAA,QAAG,CAAC;AAAA,MAC9J,CAAC;AACD,qBAAe,MAAM,qBAAqB,EAAE,UAAU,cAAc,MAAM,YAAY,CAAC;AAAA,IACzF,UAAE;AACA,mBAAa,OAAO;AACpB,eAAS,MAAM;AAAA,IACjB;AACA,UAAM,SAAS,IAAI,kBAAkB,EAAE,UAAU,cAAc,cAAc,gBAAgB,OAAM,UAAS;AAAE,qBAAe;AAAA,IAAO,EAAE,CAAC;AACvI,UAAM,WAAW,MAAM,IAAI,oBAAoB,MAAM,EAAE,aAAa;AACpE,UAAM,MAAM,IAAI,EAAE,UAAU,YAAY,QAAQ,OAAO,MAAM,EAAE,WAAW,UAAU,eAAe,cAAc,eAAe,cAAc,cAAc,YAAY,EAAE,CAAC;AAC3K,UAAM,GAAG,IAAI,cAAc,SAAS,MAAM,qCAAqC;AAC/E,eAAW,WAAW,SAAU,OAAM,GAAG,IAAI,KAAK,QAAQ,EAAE,KAAK,QAAQ,IAAI,KAAK,QAAQ,QAAQ,EAAE;AACpG,8BAA0B,MAAM,EAAE;AAAA,EACpC,UAAE;AACA,OAAG,MAAM;AAAA,EACX;AACF;","names":[]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateOAuthState,
|
|
3
|
+
openInBrowser,
|
|
4
|
+
startLoopbackServer
|
|
5
|
+
} from "./chunk-PFXNOLYI.js";
|
|
6
|
+
import {
|
|
7
|
+
printLocalConnectionIntro,
|
|
8
|
+
printLocalConnectionSaved
|
|
9
|
+
} from "./chunk-OPRADP2V.js";
|
|
10
|
+
|
|
11
|
+
// src/connect/spotify.ts
|
|
12
|
+
import readline from "readline/promises";
|
|
13
|
+
import { AdportError, CredentialStore } from "@adport/core";
|
|
14
|
+
import { SpotifyAdsClient, SpotifyAdsProvider, buildSpotifyAuthUrl, exchangeSpotifyCode } from "@adport/provider-spotify";
|
|
15
|
+
async function connectSpotify(input) {
|
|
16
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
17
|
+
const store = new CredentialStore();
|
|
18
|
+
try {
|
|
19
|
+
printLocalConnectionIntro(input.io, "Spotify Ads");
|
|
20
|
+
input.io.out("Create your own app at developer.spotify.com/dashboard, select Ads API, and accept the Ads API terms in Ads Manager for that client ID.");
|
|
21
|
+
input.io.out("Allowlisting can take one hour. Register http://127.0.0.1:53685/callback as the exact local redirect URI.");
|
|
22
|
+
const existing = (await store.get("spotify"))?.data;
|
|
23
|
+
const clientId = (await rl.question("Client ID (Enter to reuse stored/environment): ")).trim() || existing?.client_id || process.env.SPOTIFY_CLIENT_ID || "";
|
|
24
|
+
const clientSecret = process.env.SPOTIFY_CLIENT_SECRET || existing?.client_secret || "";
|
|
25
|
+
if (!clientId || !clientSecret) {
|
|
26
|
+
throw new AdportError("INVALID_INPUT", "Set SPOTIFY_CLIENT_SECRET securely in your terminal environment and provide a client ID. Secrets are not requested through echoed terminal prompts.");
|
|
27
|
+
}
|
|
28
|
+
const redirectUri = (await rl.question("Exact local redirect URI [http://127.0.0.1:53685/callback]: ")).trim() || "http://127.0.0.1:53685/callback";
|
|
29
|
+
const redirect = new URL(redirectUri);
|
|
30
|
+
if (redirect.protocol !== "http:" || redirect.hostname !== "127.0.0.1" || !redirect.port || redirect.username || redirect.password || redirect.search || redirect.hash) {
|
|
31
|
+
throw new AdportError("INVALID_INPUT", "Use an http://127.0.0.1:<port> loopback callback registered on your Spotify app.");
|
|
32
|
+
}
|
|
33
|
+
const state = generateOAuthState();
|
|
34
|
+
const loopback = await startLoopbackServer("127.0.0.1", state, Number(redirect.port), redirect.pathname);
|
|
35
|
+
let timeout;
|
|
36
|
+
let refreshToken;
|
|
37
|
+
try {
|
|
38
|
+
const authUrl = buildSpotifyAuthUrl(clientId, redirectUri, state);
|
|
39
|
+
input.io.out(`Authorize your own Spotify Ads app:
|
|
40
|
+
${authUrl}`);
|
|
41
|
+
if (input.openBrowser) openInBrowser(authUrl);
|
|
42
|
+
const code = await Promise.race([
|
|
43
|
+
loopback.waitForCode,
|
|
44
|
+
new Promise((_resolve, reject) => {
|
|
45
|
+
timeout = setTimeout(() => reject(new Error("Spotify authorization timed out after five minutes.")), 3e5);
|
|
46
|
+
})
|
|
47
|
+
]);
|
|
48
|
+
refreshToken = await exchangeSpotifyCode({ clientId, clientSecret, code, redirectUri });
|
|
49
|
+
} finally {
|
|
50
|
+
clearTimeout(timeout);
|
|
51
|
+
loopback.close();
|
|
52
|
+
}
|
|
53
|
+
const client = new SpotifyAdsClient({ clientId, clientSecret, refreshToken, onRefreshToken: async (token) => {
|
|
54
|
+
refreshToken = token;
|
|
55
|
+
} });
|
|
56
|
+
const accounts = await new SpotifyAdsProvider(client).listAccounts();
|
|
57
|
+
await store.set({ provider: "spotify", source: "byo", data: { client_id: clientId, client_secret: clientSecret, refresh_token: refreshToken, redirect_uri: redirectUri } });
|
|
58
|
+
input.io.out(`Connected: ${accounts.length} accessible Spotify ad account(s).`);
|
|
59
|
+
for (const account of accounts) input.io.out(` ${account.id} ${account.name} ${account.currency}`);
|
|
60
|
+
printLocalConnectionSaved(input.io);
|
|
61
|
+
} finally {
|
|
62
|
+
rl.close();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
connectSpotify
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=spotify-WXJ6Y2ZG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/connect/spotify.ts"],"sourcesContent":["import readline from 'node:readline/promises';\nimport { AdportError, CredentialStore } from '@adport/core';\nimport { SpotifyAdsClient, SpotifyAdsProvider, buildSpotifyAuthUrl, exchangeSpotifyCode } from '@adport/provider-spotify';\nimport type { ProgramIO } from '../program.js';\nimport { printLocalConnectionIntro, printLocalConnectionSaved } from './local.js';\nimport { generateOAuthState, openInBrowser, startLoopbackServer } from './oauth.js';\n\nexport async function connectSpotify(input: { openBrowser: boolean; io: ProgramIO }): Promise<void> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const store = new CredentialStore();\n try {\n printLocalConnectionIntro(input.io, 'Spotify Ads');\n input.io.out('Create your own app at developer.spotify.com/dashboard, select Ads API, and accept the Ads API terms in Ads Manager for that client ID.');\n input.io.out('Allowlisting can take one hour. Register http://127.0.0.1:53685/callback as the exact local redirect URI.');\n const existing = (await store.get('spotify'))?.data;\n const clientId = (await rl.question('Client ID (Enter to reuse stored/environment): ')).trim() || existing?.client_id || process.env.SPOTIFY_CLIENT_ID || '';\n const clientSecret = process.env.SPOTIFY_CLIENT_SECRET || existing?.client_secret || '';\n if (!clientId || !clientSecret) {\n throw new AdportError('INVALID_INPUT', 'Set SPOTIFY_CLIENT_SECRET securely in your terminal environment and provide a client ID. Secrets are not requested through echoed terminal prompts.');\n }\n const redirectUri = (await rl.question('Exact local redirect URI [http://127.0.0.1:53685/callback]: ')).trim() || 'http://127.0.0.1:53685/callback';\n const redirect = new URL(redirectUri);\n if (redirect.protocol !== 'http:' || redirect.hostname !== '127.0.0.1' || !redirect.port || redirect.username || redirect.password || redirect.search || redirect.hash) {\n throw new AdportError('INVALID_INPUT', 'Use an http://127.0.0.1:<port> loopback callback registered on your Spotify app.');\n }\n const state = generateOAuthState();\n const loopback = await startLoopbackServer('127.0.0.1', state, Number(redirect.port), redirect.pathname);\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let refreshToken: string;\n try {\n const authUrl = buildSpotifyAuthUrl(clientId, redirectUri, state);\n input.io.out(`Authorize your own Spotify Ads app:\\n ${authUrl}`);\n if (input.openBrowser) openInBrowser(authUrl);\n const code = await Promise.race([\n loopback.waitForCode,\n new Promise<never>((_resolve, reject) => { timeout = setTimeout(() => reject(new Error('Spotify authorization timed out after five minutes.')), 300_000); }),\n ]);\n refreshToken = await exchangeSpotifyCode({ clientId, clientSecret, code, redirectUri });\n } finally {\n clearTimeout(timeout);\n loopback.close();\n }\n const client = new SpotifyAdsClient({ clientId, clientSecret, refreshToken, onRefreshToken: async token => { refreshToken = token; } });\n const accounts = await new SpotifyAdsProvider(client).listAccounts();\n await store.set({ provider: 'spotify', source: 'byo', data: { client_id: clientId, client_secret: clientSecret, refresh_token: refreshToken, redirect_uri: redirectUri } });\n input.io.out(`Connected: ${accounts.length} accessible Spotify ad account(s).`);\n for (const account of accounts) input.io.out(` ${account.id} ${account.name} ${account.currency}`);\n printLocalConnectionSaved(input.io);\n } finally {\n rl.close();\n }\n}\n"],"mappings":";;;;;;;;;;;AAAA,OAAO,cAAc;AACrB,SAAS,aAAa,uBAAuB;AAC7C,SAAS,kBAAkB,oBAAoB,qBAAqB,2BAA2B;AAK/F,eAAsB,eAAe,OAA+D;AAClG,QAAM,KAAK,SAAS,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AACpF,QAAM,QAAQ,IAAI,gBAAgB;AAClC,MAAI;AACF,8BAA0B,MAAM,IAAI,aAAa;AACjD,UAAM,GAAG,IAAI,yIAAyI;AACtJ,UAAM,GAAG,IAAI,2GAA2G;AACxH,UAAM,YAAY,MAAM,MAAM,IAAI,SAAS,IAAI;AAC/C,UAAM,YAAY,MAAM,GAAG,SAAS,iDAAiD,GAAG,KAAK,KAAK,UAAU,aAAa,QAAQ,IAAI,qBAAqB;AAC1J,UAAM,eAAe,QAAQ,IAAI,yBAAyB,UAAU,iBAAiB;AACrF,QAAI,CAAC,YAAY,CAAC,cAAc;AAC9B,YAAM,IAAI,YAAY,iBAAiB,qJAAqJ;AAAA,IAC9L;AACA,UAAM,eAAe,MAAM,GAAG,SAAS,8DAA8D,GAAG,KAAK,KAAK;AAClH,UAAM,WAAW,IAAI,IAAI,WAAW;AACpC,QAAI,SAAS,aAAa,WAAW,SAAS,aAAa,eAAe,CAAC,SAAS,QAAQ,SAAS,YAAY,SAAS,YAAY,SAAS,UAAU,SAAS,MAAM;AACtK,YAAM,IAAI,YAAY,iBAAiB,kFAAkF;AAAA,IAC3H;AACA,UAAM,QAAQ,mBAAmB;AACjC,UAAM,WAAW,MAAM,oBAAoB,aAAa,OAAO,OAAO,SAAS,IAAI,GAAG,SAAS,QAAQ;AACvG,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,YAAM,UAAU,oBAAoB,UAAU,aAAa,KAAK;AAChE,YAAM,GAAG,IAAI;AAAA,IAA0C,OAAO,EAAE;AAChE,UAAI,MAAM,YAAa,eAAc,OAAO;AAC5C,YAAM,OAAO,MAAM,QAAQ,KAAK;AAAA,QAC9B,SAAS;AAAA,QACT,IAAI,QAAe,CAAC,UAAU,WAAW;AAAE,oBAAU,WAAW,MAAM,OAAO,IAAI,MAAM,qDAAqD,CAAC,GAAG,GAAO;AAAA,QAAG,CAAC;AAAA,MAC7J,CAAC;AACD,qBAAe,MAAM,oBAAoB,EAAE,UAAU,cAAc,MAAM,YAAY,CAAC;AAAA,IACxF,UAAE;AACA,mBAAa,OAAO;AACpB,eAAS,MAAM;AAAA,IACjB;AACA,UAAM,SAAS,IAAI,iBAAiB,EAAE,UAAU,cAAc,cAAc,gBAAgB,OAAM,UAAS;AAAE,qBAAe;AAAA,IAAO,EAAE,CAAC;AACtI,UAAM,WAAW,MAAM,IAAI,mBAAmB,MAAM,EAAE,aAAa;AACnE,UAAM,MAAM,IAAI,EAAE,UAAU,WAAW,QAAQ,OAAO,MAAM,EAAE,WAAW,UAAU,eAAe,cAAc,eAAe,cAAc,cAAc,YAAY,EAAE,CAAC;AAC1K,UAAM,GAAG,IAAI,cAAc,SAAS,MAAM,oCAAoC;AAC9E,eAAW,WAAW,SAAU,OAAM,GAAG,IAAI,KAAK,QAAQ,EAAE,KAAK,QAAQ,IAAI,KAAK,QAAQ,QAAQ,EAAE;AACpG,8BAA0B,MAAM,EAAE;AAAA,EACpC,UAAE;AACA,OAAG,MAAM;AAAA,EACX;AACF;","names":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
printLocalConnectionIntro,
|
|
3
|
+
printLocalConnectionSaved
|
|
4
|
+
} from "./chunk-OPRADP2V.js";
|
|
5
|
+
|
|
6
|
+
// src/connect/x.ts
|
|
7
|
+
import { AdportError, CredentialStore } from "@adport/core";
|
|
8
|
+
import { XAdsClient, XAdsProvider, xCredentialsFromData, xEnvironmentData } from "@adport/provider-x";
|
|
9
|
+
async function connectX(input) {
|
|
10
|
+
printLocalConnectionIntro(input.io, "X Ads");
|
|
11
|
+
input.io.out("Use your own Ads API-approved X developer app. General X API access is not Ads API approval. Regenerate user access tokens after approval.");
|
|
12
|
+
input.io.out("This local flow imports OAuth 1.0a credentials from your environment; no shared Cloud OAuth or echoed secret prompts are used.");
|
|
13
|
+
const data = xEnvironmentData(), credentials = xCredentialsFromData(data);
|
|
14
|
+
if (!credentials) throw new AdportError("INVALID_INPUT", "Set X_CONSUMER_KEY, X_CONSUMER_SECRET, X_ACCESS_TOKEN and X_ACCESS_TOKEN_SECRET securely in your environment. All four must belong to the same approved app/user grant.");
|
|
15
|
+
const accounts = await new XAdsProvider(new XAdsClient(credentials)).listAccounts();
|
|
16
|
+
await new CredentialStore().set({ provider: "x", source: "byo", data });
|
|
17
|
+
input.io.out(`Connected: ${accounts.length} accessible X ad account(s).`);
|
|
18
|
+
for (const account of accounts) input.io.out(` ${account.id} ${account.name}`);
|
|
19
|
+
printLocalConnectionSaved(input.io);
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
connectX
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=x-PLGN5QEG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/connect/x.ts"],"sourcesContent":["import { AdportError, CredentialStore } from '@adport/core';\nimport { XAdsClient, XAdsProvider, xCredentialsFromData, xEnvironmentData } from '@adport/provider-x';\nimport type { ProgramIO } from '../program.js';\nimport { printLocalConnectionIntro, printLocalConnectionSaved } from './local.js';\n\nexport async function connectX(input: { openBrowser: boolean; io: ProgramIO }): Promise<void> {\n printLocalConnectionIntro(input.io, 'X Ads');\n input.io.out('Use your own Ads API-approved X developer app. General X API access is not Ads API approval. Regenerate user access tokens after approval.');\n input.io.out('This local flow imports OAuth 1.0a credentials from your environment; no shared Cloud OAuth or echoed secret prompts are used.');\n const data = xEnvironmentData(), credentials = xCredentialsFromData(data);\n if (!credentials) throw new AdportError('INVALID_INPUT', 'Set X_CONSUMER_KEY, X_CONSUMER_SECRET, X_ACCESS_TOKEN and X_ACCESS_TOKEN_SECRET securely in your environment. All four must belong to the same approved app/user grant.');\n const accounts = await new XAdsProvider(new XAdsClient(credentials)).listAccounts();\n await new CredentialStore().set({ provider: 'x', source: 'byo', data });\n input.io.out(`Connected: ${accounts.length} accessible X ad account(s).`);\n for (const account of accounts) input.io.out(` ${account.id} ${account.name}`);\n printLocalConnectionSaved(input.io);\n}\n"],"mappings":";;;;;;AAAA,SAAS,aAAa,uBAAuB;AAC7C,SAAS,YAAY,cAAc,sBAAsB,wBAAwB;AAIjF,eAAsB,SAAS,OAA+D;AAC5F,4BAA0B,MAAM,IAAI,OAAO;AAC3C,QAAM,GAAG,IAAI,4IAA4I;AACzJ,QAAM,GAAG,IAAI,gIAAgI;AAC7I,QAAM,OAAO,iBAAiB,GAAG,cAAc,qBAAqB,IAAI;AACxE,MAAI,CAAC,YAAa,OAAM,IAAI,YAAY,iBAAiB,yKAAyK;AAClO,QAAM,WAAW,MAAM,IAAI,aAAa,IAAI,WAAW,WAAW,CAAC,EAAE,aAAa;AAClF,QAAM,IAAI,gBAAgB,EAAE,IAAI,EAAE,UAAU,KAAK,QAAQ,OAAO,KAAK,CAAC;AACtE,QAAM,GAAG,IAAI,cAAc,SAAS,MAAM,8BAA8B;AACxE,aAAW,WAAW,SAAU,OAAM,GAAG,IAAI,KAAK,QAAQ,EAAE,KAAK,QAAQ,IAAI,EAAE;AAC/E,4BAA0B,MAAM,EAAE;AACpC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adport",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "adport CLI — the open control plane for paid media",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -14,14 +14,19 @@
|
|
|
14
14
|
"commander": "^13.0.0",
|
|
15
15
|
"yaml": "^2.6.0",
|
|
16
16
|
"zod": "^4.4.3",
|
|
17
|
-
"@adport/
|
|
18
|
-
"@adport/
|
|
19
|
-
"@adport/
|
|
20
|
-
"@adport/provider-
|
|
21
|
-
"@adport/provider-
|
|
22
|
-
"@adport/provider-microsoft": "0.
|
|
23
|
-
"@adport/provider-reddit": "0.
|
|
24
|
-
"@adport/provider-
|
|
17
|
+
"@adport/core": "0.6.0",
|
|
18
|
+
"@adport/mcp": "0.6.0",
|
|
19
|
+
"@adport/provider-apple": "0.6.0",
|
|
20
|
+
"@adport/provider-meta": "0.6.0",
|
|
21
|
+
"@adport/provider-google": "0.6.0",
|
|
22
|
+
"@adport/provider-microsoft": "0.6.0",
|
|
23
|
+
"@adport/provider-reddit": "0.6.0",
|
|
24
|
+
"@adport/provider-snapchat": "0.6.0",
|
|
25
|
+
"@adport/provider-spotify": "0.6.0",
|
|
26
|
+
"@adport/provider-pinterest": "0.6.0",
|
|
27
|
+
"@adport/provider-linkedin": "0.6.0",
|
|
28
|
+
"@adport/provider-tiktok": "0.6.0",
|
|
29
|
+
"@adport/provider-x": "0.6.0"
|
|
25
30
|
},
|
|
26
31
|
"devDependencies": {
|
|
27
32
|
"@types/node": "^26.1.2",
|