adport 0.3.0 → 0.4.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.
@@ -51,7 +51,7 @@ function parseClientSecretJson(text) {
51
51
  }
52
52
  return { clientId: entry.client_id, clientSecret: entry.client_secret };
53
53
  }
54
- async function startLoopbackServer(redirectHostname = "127.0.0.1", expectedState) {
54
+ async function startLoopbackServer(redirectHostname = "127.0.0.1", expectedState, port = 0, callbackPath = "") {
55
55
  let resolveCode;
56
56
  let rejectCode;
57
57
  const waitForCode = new Promise((resolve, reject) => {
@@ -76,15 +76,59 @@ async function startLoopbackServer(redirectHostname = "127.0.0.1", expectedState
76
76
  if (code) resolveCode(code);
77
77
  else if (error) rejectCode(new Error(`OAuth authorization failed: ${error}`));
78
78
  });
79
- await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
79
+ await new Promise((resolve, reject) => {
80
+ server.once("error", reject);
81
+ server.listen(port, "127.0.0.1", () => {
82
+ server.off("error", reject);
83
+ resolve();
84
+ });
85
+ });
80
86
  const address = server.address();
81
87
  if (!address || typeof address === "string") throw new Error("Loopback server failed to bind");
82
88
  return {
83
- redirectUri: `http://${redirectHostname}:${address.port}`,
89
+ redirectUri: `http://${redirectHostname}:${address.port}${callbackPath}`,
84
90
  waitForCode,
85
91
  close: () => server.close()
86
92
  };
87
93
  }
94
+ var REDDIT_AUTHORIZE = "https://www.reddit.com/api/v1/authorize";
95
+ var REDDIT_TOKEN = "https://www.reddit.com/api/v1/access_token";
96
+ var REDDIT_ADS_SCOPES = "adsread,adsedit,adsdatadeletion";
97
+ function buildRedditAuthUrl(clientId, redirectUri, state) {
98
+ const params = new URLSearchParams({
99
+ client_id: clientId,
100
+ response_type: "code",
101
+ state,
102
+ redirect_uri: redirectUri,
103
+ duration: "permanent",
104
+ scope: REDDIT_ADS_SCOPES
105
+ });
106
+ return `${REDDIT_AUTHORIZE}?${params}`;
107
+ }
108
+ async function exchangeRedditCode(input, fetchImpl = fetch) {
109
+ const response = await fetchImpl(REDDIT_TOKEN, {
110
+ method: "POST",
111
+ headers: {
112
+ authorization: `Basic ${Buffer.from(`${input.clientId}:${input.clientSecret}`).toString("base64")}`,
113
+ "content-type": "application/x-www-form-urlencoded",
114
+ "user-agent": input.userAgent
115
+ },
116
+ body: new URLSearchParams({
117
+ grant_type: "authorization_code",
118
+ code: input.code.replace(/#_$/, ""),
119
+ redirect_uri: input.redirectUri
120
+ })
121
+ });
122
+ const data = await response.json();
123
+ if (!response.ok || !data.refresh_token) {
124
+ throw new AdportError(
125
+ "PROVIDER_ERROR",
126
+ `Reddit OAuth exchange failed${data.error ? ` (${data.error})` : ""}: ${data.message ?? "no refresh token returned"}`,
127
+ data
128
+ );
129
+ }
130
+ return { refreshToken: data.refresh_token };
131
+ }
88
132
  var MS_AUTHORIZE = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
89
133
  var MS_TOKEN = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
90
134
  var MS_ADS_SCOPE = "openid profile https://ads.microsoft.com/msads.manage offline_access";
@@ -151,10 +195,12 @@ export {
151
195
  exchangeCodeForTokens,
152
196
  parseClientSecretJson,
153
197
  startLoopbackServer,
198
+ buildRedditAuthUrl,
199
+ exchangeRedditCode,
154
200
  generatePkce,
155
201
  generateOAuthState,
156
202
  buildMicrosoftAuthUrl,
157
203
  exchangeMicrosoftCode,
158
204
  openInBrowser
159
205
  };
160
- //# sourceMappingURL=chunk-ZBNODOHE.js.map
206
+ //# sourceMappingURL=chunk-PFXNOLYI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/connect/oauth.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport http from 'node:http';\nimport { spawn } from 'node:child_process';\nimport { AdportError } from '@adport/core';\n\nexport const GOOGLE_ADS_SCOPE = 'https://www.googleapis.com/auth/adwords';\nconst AUTH_ENDPOINT = 'https://accounts.google.com/o/oauth2/v2/auth';\nconst TOKEN_ENDPOINT = 'https://oauth2.googleapis.com/token';\n\nexport function buildGoogleAuthUrl(\n clientId: string,\n redirectUri: string,\n codeChallenge: string,\n state: string,\n): string {\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n scope: GOOGLE_ADS_SCOPE,\n access_type: 'offline',\n prompt: 'consent',\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n state,\n });\n return `${AUTH_ENDPOINT}?${params}`;\n}\n\nexport async function exchangeCodeForTokens(\n input: { clientId: string; clientSecret: string; code: string; redirectUri: string; codeVerifier: string },\n fetchImpl: typeof fetch = fetch,\n): Promise<{ refreshToken: string; accessToken: string }> {\n const response = await fetchImpl(TOKEN_ENDPOINT, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n client_id: input.clientId,\n client_secret: input.clientSecret,\n code: input.code,\n redirect_uri: input.redirectUri,\n grant_type: 'authorization_code',\n code_verifier: input.codeVerifier,\n }),\n });\n const data = (await response.json()) as { refresh_token?: string; access_token?: string; error?: string };\n if (!response.ok || !data.refresh_token || !data.access_token) {\n throw new AdportError(\n 'PROVIDER_ERROR',\n `OAuth code exchange failed${data.error ? ` (${data.error})` : ''}. ` +\n 'If no refresh_token was returned, revoke the app at myaccount.google.com/permissions and retry.',\n data,\n );\n }\n return { refreshToken: data.refresh_token, accessToken: data.access_token };\n}\n\n/** Parse a downloaded OAuth client JSON (client_secret_*.json, \"installed\" or \"web\"). */\nexport function parseClientSecretJson(text: string): { clientId: string; clientSecret: string } {\n const parsed = JSON.parse(text) as {\n installed?: { client_id: string; client_secret: string };\n web?: { client_id: string; client_secret: string };\n };\n const entry = parsed.installed ?? parsed.web;\n if (!entry?.client_id || !entry?.client_secret) {\n throw new AdportError('INVALID_INPUT', 'Not a valid OAuth client JSON (expected \"installed\" or \"web\" key).');\n }\n return { clientId: entry.client_id, clientSecret: entry.client_secret };\n}\n\nexport interface LoopbackServer {\n redirectUri: string;\n /** Resolves with the authorization code once Google redirects back. */\n waitForCode: Promise<string>;\n close: () => void;\n}\n\n/** Loopback listener for the OAuth redirect — the desktop-app flow's return path. */\nexport async function startLoopbackServer(\n redirectHostname: '127.0.0.1' | 'localhost' = '127.0.0.1',\n expectedState?: string,\n port = 0,\n callbackPath = '',\n): Promise<LoopbackServer> {\n let resolveCode!: (code: string) => void;\n let rejectCode!: (err: Error) => void;\n const waitForCode = new Promise<string>((resolve, reject) => {\n resolveCode = resolve;\n rejectCode = reject;\n });\n\n const server = http.createServer((req, res) => {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1');\n const code = url.searchParams.get('code');\n const error = url.searchParams.get('error');\n const state = url.searchParams.get('state');\n if (expectedState && state !== expectedState) {\n res.writeHead(400, { 'content-type': 'text/html; charset=utf-8' });\n res.end('<h2>Authorization failed</h2><p>OAuth state validation failed. Return to the terminal and retry.</p>');\n rejectCode(new AdportError('PROVIDER_ERROR', 'OAuth state validation failed. Retry the connection.'));\n return;\n }\n res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });\n res.end(\n code\n ? '<h2>adport is connected.</h2><p>You can close this tab and return to the terminal.</p>'\n : `<h2>Authorization failed${error ? `: ${escapeHtml(error)}` : ''}</h2><p>Return to the terminal and retry.</p>`,\n );\n if (code) resolveCode(code);\n else if (error) rejectCode(new Error(`OAuth authorization failed: ${error}`));\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(port, '127.0.0.1', () => {\n server.off('error', reject);\n resolve();\n });\n });\n const address = server.address();\n if (!address || typeof address === 'string') throw new Error('Loopback server failed to bind');\n return {\n redirectUri: `http://${redirectHostname}:${address.port}${callbackPath}`,\n waitForCode,\n close: () => server.close(),\n };\n}\n\n// ---- Reddit Ads API (confidential web app + permanent refresh token) -------\n\nconst REDDIT_AUTHORIZE = 'https://www.reddit.com/api/v1/authorize';\nconst REDDIT_TOKEN = 'https://www.reddit.com/api/v1/access_token';\nexport const REDDIT_ADS_SCOPES = 'adsread,adsedit,adsdatadeletion';\n\nexport function buildRedditAuthUrl(clientId: string, redirectUri: string, state: string): string {\n const params = new URLSearchParams({\n client_id: clientId,\n response_type: 'code',\n state,\n redirect_uri: redirectUri,\n duration: 'permanent',\n scope: REDDIT_ADS_SCOPES,\n });\n return `${REDDIT_AUTHORIZE}?${params}`;\n}\n\nexport async function exchangeRedditCode(\n input: { clientId: string; clientSecret: string; code: string; redirectUri: string; userAgent: string },\n fetchImpl: typeof fetch = fetch,\n): Promise<{ refreshToken: string }> {\n const response = await fetchImpl(REDDIT_TOKEN, {\n method: 'POST',\n headers: {\n authorization: `Basic ${Buffer.from(`${input.clientId}:${input.clientSecret}`).toString('base64')}`,\n 'content-type': 'application/x-www-form-urlencoded',\n 'user-agent': input.userAgent,\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: input.code.replace(/#_$/, ''),\n redirect_uri: input.redirectUri,\n }),\n });\n const data = (await response.json()) as { refresh_token?: string; error?: string; message?: string };\n if (!response.ok || !data.refresh_token) {\n throw new AdportError(\n 'PROVIDER_ERROR',\n `Reddit OAuth exchange failed${data.error ? ` (${data.error})` : ''}: ${data.message ?? 'no refresh token returned'}`,\n data,\n );\n }\n return { refreshToken: data.refresh_token };\n}\n\n// ---- Microsoft identity platform (public client + PKCE) ---------------------\n\nconst MS_AUTHORIZE = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';\nconst MS_TOKEN = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';\nexport const MS_ADS_SCOPE = 'openid profile https://ads.microsoft.com/msads.manage offline_access';\n\nexport function generatePkce(): { verifier: string; challenge: string } {\n const verifier = randomBytes(32).toString('base64url');\n const challenge = createHash('sha256').update(verifier).digest('base64url');\n return { verifier, challenge };\n}\n\nexport function generateOAuthState(): string {\n return randomBytes(32).toString('base64url');\n}\n\nexport function buildMicrosoftAuthUrl(clientId: string, redirectUri: string, challenge: string, state: string): string {\n const params = new URLSearchParams({\n client_id: clientId,\n response_type: 'code',\n redirect_uri: redirectUri,\n scope: MS_ADS_SCOPE,\n code_challenge: challenge,\n code_challenge_method: 'S256',\n prompt: 'login',\n state,\n });\n return `${MS_AUTHORIZE}?${params}`;\n}\n\nexport async function exchangeMicrosoftCode(\n input: { clientId: string; code: string; redirectUri: string; codeVerifier: string },\n fetchImpl: typeof fetch = fetch,\n): Promise<{ refreshToken: string }> {\n const response = await fetchImpl(MS_TOKEN, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n client_id: input.clientId,\n scope: 'https://ads.microsoft.com/msads.manage offline_access',\n code: input.code,\n redirect_uri: input.redirectUri,\n grant_type: 'authorization_code',\n code_verifier: input.codeVerifier,\n }),\n });\n const data = (await response.json()) as { refresh_token?: string; error?: string; error_description?: string };\n if (!response.ok || !data.refresh_token) {\n throw new AdportError(\n 'PROVIDER_ERROR',\n `Microsoft OAuth exchange failed${data.error ? ` (${data.error})` : ''}: ${data.error_description ?? ''}`,\n data,\n );\n }\n return { refreshToken: data.refresh_token };\n}\n\nexport function openInBrowser(url: string): void {\n const [cmd, args] =\n process.platform === 'darwin'\n ? ['open', [url]]\n : process.platform === 'win32'\n ? ['cmd', ['/c', 'start', '', url]]\n : ['xdg-open', [url]];\n spawn(cmd as string, args as string[], { detached: true, stdio: 'ignore' }).unref();\n}\n\nfunction escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (char) => ({\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n })[char]!);\n}\n"],"mappings":"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","names":[]}
@@ -6,7 +6,7 @@ import {
6
6
  openInBrowser,
7
7
  parseClientSecretJson,
8
8
  startLoopbackServer
9
- } from "./chunk-ZBNODOHE.js";
9
+ } from "./chunk-PFXNOLYI.js";
10
10
  import {
11
11
  printLocalConnectionIntro,
12
12
  printLocalConnectionSaved
@@ -188,4 +188,4 @@ function expandHome(p) {
188
188
  export {
189
189
  connectGoogle
190
190
  };
191
- //# sourceMappingURL=google-35WAUFUS.js.map
191
+ //# sourceMappingURL=google-YRYPPXWU.js.map
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.3.0",
9
+ version: "0.4.0",
10
10
  description: "adport CLI \u2014 the open control plane for paid media",
11
11
  license: "Apache-2.0",
12
12
  type: "module",
@@ -27,6 +27,7 @@ var package_default = {
27
27
  "@adport/provider-google": "workspace:*",
28
28
  "@adport/provider-meta": "workspace:*",
29
29
  "@adport/provider-microsoft": "workspace:*",
30
+ "@adport/provider-reddit": "workspace:*",
30
31
  "@adport/provider-tiktok": "workspace:*",
31
32
  commander: "^13.0.0",
32
33
  yaml: "^2.6.0",
@@ -187,7 +188,7 @@ function buildProgram(io = defaultIO) {
187
188
  });
188
189
  program.command("connect <provider>").description("Connect an ad account (guided wizard)").option("--no-browser", "Print auth URLs instead of opening a browser").action(async (provider, opts) => {
189
190
  if (provider === "google") {
190
- const { connectGoogle } = await import("./google-35WAUFUS.js");
191
+ const { connectGoogle } = await import("./google-YRYPPXWU.js");
191
192
  await connectGoogle({ openBrowser: opts.browser, io });
192
193
  return;
193
194
  }
@@ -207,20 +208,25 @@ function buildProgram(io = defaultIO) {
207
208
  return;
208
209
  }
209
210
  if (provider === "microsoft") {
210
- const { connectMicrosoft } = await import("./microsoft-GXB6A6JC.js");
211
+ const { connectMicrosoft } = await import("./microsoft-IXYBD4GY.js");
211
212
  await connectMicrosoft({ openBrowser: opts.browser, io });
212
213
  return;
213
214
  }
215
+ if (provider === "reddit") {
216
+ const { connectReddit } = await import("./reddit-LC3F2OLD.js");
217
+ await connectReddit({ openBrowser: opts.browser, io });
218
+ return;
219
+ }
214
220
  if (provider === "mock") {
215
221
  io.out("The mock provider is explicit demo mode, not a connection. Try: adport --demo accounts");
216
222
  return;
217
223
  }
218
- io.err(`Provider "${provider}" is not supported. Available: google, meta, tiktok, apple, microsoft, mock.`);
224
+ io.err(`Provider "${provider}" is not supported. Available: google, meta, tiktok, apple, microsoft, reddit, mock.`);
219
225
  process.exitCode = 1;
220
226
  });
221
227
  program.command("disconnect <provider>").description("Remove a provider connection from this machine").action(async (provider) => {
222
- if (!["google", "meta", "tiktok", "apple", "microsoft"].includes(provider)) {
223
- io.err(`Provider "${provider}" is not supported. Available: google, meta, tiktok, apple, microsoft.`);
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.`);
224
230
  process.exitCode = 1;
225
231
  return;
226
232
  }
@@ -273,7 +279,7 @@ function buildProgram(io = defaultIO) {
273
279
  for (const record of records) {
274
280
  io.out(`${record.provider}: credentials stored (source: ${record.source}, updated ${record.updatedAt})`);
275
281
  }
276
- for (const id of ["google", "meta", "tiktok", "apple", "microsoft"]) {
282
+ for (const id of ["google", "meta", "tiktok", "apple", "microsoft", "reddit"]) {
277
283
  const provider = rt.ctx.providers.list().find((p) => p.id === id);
278
284
  if (!provider) {
279
285
  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 === '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, 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'].includes(provider)) {\n io.err(`Provider \"${provider}\" is not supported. Available: google, meta, tiktok, apple, microsoft.`);\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'] 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.3.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-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":[]}
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.4.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-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":[]}
@@ -5,7 +5,7 @@ import {
5
5
  generatePkce,
6
6
  openInBrowser,
7
7
  startLoopbackServer
8
- } from "./chunk-ZBNODOHE.js";
8
+ } from "./chunk-PFXNOLYI.js";
9
9
  import {
10
10
  printLocalConnectionIntro,
11
11
  printLocalConnectionSaved
@@ -96,4 +96,4 @@ async function connectMicrosoft({ openBrowser, io }) {
96
96
  export {
97
97
  connectMicrosoft
98
98
  };
99
- //# sourceMappingURL=microsoft-GXB6A6JC.js.map
99
+ //# sourceMappingURL=microsoft-IXYBD4GY.js.map
@@ -0,0 +1,89 @@
1
+ import {
2
+ buildRedditAuthUrl,
3
+ exchangeRedditCode,
4
+ generateOAuthState,
5
+ openInBrowser,
6
+ startLoopbackServer
7
+ } from "./chunk-PFXNOLYI.js";
8
+ import {
9
+ printLocalConnectionIntro,
10
+ printLocalConnectionSaved
11
+ } from "./chunk-OPRADP2V.js";
12
+
13
+ // src/connect/reddit.ts
14
+ import readline from "readline/promises";
15
+ import { CredentialStore } from "@adport/core";
16
+ import { RedditAdsClient, RedditAdsProvider } from "@adport/provider-reddit";
17
+ async function connectReddit(input) {
18
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
19
+ const store = new CredentialStore();
20
+ try {
21
+ printLocalConnectionIntro(input.io, "Reddit Ads");
22
+ input.io.out("Create a Reddit developer app at https://www.reddit.com/prefs/apps (type: web app).");
23
+ input.io.out("Reddit requires the redirect URI to match exactly. For local use, register");
24
+ input.io.out("http://localhost:53682 and keep this terminal running during authorization.");
25
+ input.io.out("");
26
+ const existing = await store.get("reddit");
27
+ const clientId = (await rl.question(`App/client ID${existing?.data.client_id ? " (Enter to reuse stored)" : ""}: `)).trim() || existing?.data.client_id || "";
28
+ const clientSecret = (await rl.question(`App secret${existing?.data.client_secret ? " (Enter to reuse stored)" : ""}: `)).trim() || existing?.data.client_secret || "";
29
+ const redirectUri = (await rl.question("Exact redirect URI [http://localhost:53682]: ")).trim() || "http://localhost:53682";
30
+ const userAgent = (await rl.question(
31
+ `Honest User-Agent${existing?.data.user_agent ? " (Enter to reuse stored)" : " (e.g. desktop:dev.adport.local:v0.4.0 (by /u/yourname))"}: `
32
+ )).trim() || existing?.data.user_agent || "";
33
+ if (!clientId || !clientSecret || !userAgent) {
34
+ input.io.err("Missing app id, app secret, or User-Agent \u2014 aborting.");
35
+ process.exitCode = 1;
36
+ return;
37
+ }
38
+ const redirect = validateLocalRedirect(redirectUri);
39
+ const state = generateOAuthState();
40
+ const loopback = await startLoopbackServer(
41
+ redirect.hostname,
42
+ state,
43
+ Number(redirect.port),
44
+ redirect.pathname === "/" ? "" : redirect.pathname
45
+ );
46
+ const authUrl = buildRedditAuthUrl(clientId, redirectUri, state);
47
+ input.io.out("Authorizing scopes: adsread, adsedit, adsdatadeletion (permanent refresh token).");
48
+ if (input.openBrowser) {
49
+ openInBrowser(authUrl);
50
+ input.io.out(`If the browser did not open, visit:
51
+ ${authUrl}`);
52
+ } else {
53
+ input.io.out(`Open this URL in a browser on this machine:
54
+ ${authUrl}`);
55
+ }
56
+ let refreshToken;
57
+ try {
58
+ const code = await loopback.waitForCode;
59
+ ({ refreshToken } = await exchangeRedditCode({ clientId, clientSecret, code, redirectUri, userAgent }));
60
+ } finally {
61
+ loopback.close();
62
+ }
63
+ const credentials = { clientId, clientSecret, refreshToken, userAgent };
64
+ input.io.out("Verifying business and ad-account access\u2026");
65
+ const accounts = await new RedditAdsProvider(new RedditAdsClient(credentials)).listAccounts();
66
+ await store.set({
67
+ provider: "reddit",
68
+ source: "byo",
69
+ data: { client_id: clientId, client_secret: clientSecret, refresh_token: refreshToken, user_agent: userAgent }
70
+ });
71
+ input.io.out(`\u2713 Connected. ${accounts.length} accessible Reddit ad account(s).`);
72
+ for (const account of accounts) input.io.out(` ${account.id} ${account.name}${account.currency ? ` ${account.currency}` : ""}`);
73
+ printLocalConnectionSaved(input.io);
74
+ input.io.out("Try: adport accounts --provider reddit \xB7 adport report --provider reddit \xB7 adport mcp");
75
+ } finally {
76
+ rl.close();
77
+ }
78
+ }
79
+ function validateLocalRedirect(value) {
80
+ const url = new URL(value);
81
+ if (url.protocol !== "http:" || !["localhost", "127.0.0.1"].includes(url.hostname) || !url.port || url.search || url.hash) {
82
+ throw new Error("Reddit local redirect URI must be http://localhost:<port> or http://127.0.0.1:<port>, optionally with a path.");
83
+ }
84
+ return url;
85
+ }
86
+ export {
87
+ connectReddit
88
+ };
89
+ //# sourceMappingURL=reddit-LC3F2OLD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/connect/reddit.ts"],"sourcesContent":["import readline from 'node:readline/promises';\nimport { CredentialStore } from '@adport/core';\nimport { RedditAdsClient, RedditAdsProvider, type RedditCredentials } from '@adport/provider-reddit';\nimport type { ProgramIO } from '../program.js';\nimport { printLocalConnectionIntro, printLocalConnectionSaved } from './local.js';\nimport {\n buildRedditAuthUrl,\n exchangeRedditCode,\n generateOAuthState,\n openInBrowser,\n startLoopbackServer,\n} from './oauth.js';\n\nexport async function connectReddit(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, 'Reddit Ads');\n input.io.out('Create a Reddit developer app at https://www.reddit.com/prefs/apps (type: web app).');\n input.io.out('Reddit requires the redirect URI to match exactly. For local use, register');\n input.io.out('http://localhost:53682 and keep this terminal running during authorization.');\n input.io.out('');\n\n const existing = await store.get('reddit');\n const clientId = (await rl.question(`App/client ID${existing?.data.client_id ? ' (Enter to reuse stored)' : ''}: `)).trim()\n || existing?.data.client_id || '';\n const clientSecret = (await rl.question(`App secret${existing?.data.client_secret ? ' (Enter to reuse stored)' : ''}: `)).trim()\n || existing?.data.client_secret || '';\n const redirectUri = (await rl.question('Exact redirect URI [http://localhost:53682]: ')).trim()\n || 'http://localhost:53682';\n const userAgent = (await rl.question(\n `Honest User-Agent${existing?.data.user_agent ? ' (Enter to reuse stored)' : ' (e.g. desktop:dev.adport.local:v0.4.0 (by /u/yourname))'}: `,\n )).trim() || existing?.data.user_agent || '';\n if (!clientId || !clientSecret || !userAgent) {\n input.io.err('Missing app id, app secret, or User-Agent — aborting.');\n process.exitCode = 1;\n return;\n }\n\n const redirect = validateLocalRedirect(redirectUri);\n const state = generateOAuthState();\n const loopback = await startLoopbackServer(\n redirect.hostname as '127.0.0.1' | 'localhost',\n state,\n Number(redirect.port),\n redirect.pathname === '/' ? '' : redirect.pathname,\n );\n const authUrl = buildRedditAuthUrl(clientId, redirectUri, state);\n input.io.out('Authorizing scopes: adsread, adsedit, adsdatadeletion (permanent refresh token).');\n if (input.openBrowser) {\n openInBrowser(authUrl);\n input.io.out(`If the browser did not open, visit:\\n ${authUrl}`);\n } else {\n input.io.out(`Open this URL in a browser on this machine:\\n ${authUrl}`);\n }\n\n let refreshToken: string;\n try {\n const code = await loopback.waitForCode;\n ({ refreshToken } = await exchangeRedditCode({ clientId, clientSecret, code, redirectUri, userAgent }));\n } finally {\n loopback.close();\n }\n const credentials: RedditCredentials = { clientId, clientSecret, refreshToken, userAgent };\n input.io.out('Verifying business and ad-account access…');\n const accounts = await new RedditAdsProvider(new RedditAdsClient(credentials)).listAccounts();\n await store.set({\n provider: 'reddit', source: 'byo',\n data: { client_id: clientId, client_secret: clientSecret, refresh_token: refreshToken, user_agent: userAgent },\n });\n input.io.out(`✓ Connected. ${accounts.length} accessible Reddit ad account(s).`);\n for (const account of accounts) input.io.out(` ${account.id} ${account.name}${account.currency ? ` ${account.currency}` : ''}`);\n printLocalConnectionSaved(input.io);\n input.io.out('Try: adport accounts --provider reddit · adport report --provider reddit · adport mcp');\n } finally {\n rl.close();\n }\n}\n\nfunction validateLocalRedirect(value: string): URL {\n const url = new URL(value);\n if (url.protocol !== 'http:' || !['localhost', '127.0.0.1'].includes(url.hostname) || !url.port || url.search || url.hash) {\n throw new Error('Reddit local redirect URI must be http://localhost:<port> or http://127.0.0.1:<port>, optionally with a path.');\n }\n return url;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adport",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "adport CLI — the open control plane for paid media",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -14,13 +14,14 @@
14
14
  "commander": "^13.0.0",
15
15
  "yaml": "^2.6.0",
16
16
  "zod": "^4.4.3",
17
- "@adport/core": "0.3.0",
18
- "@adport/mcp": "0.3.0",
19
- "@adport/provider-google": "0.3.0",
20
- "@adport/provider-apple": "0.3.0",
21
- "@adport/provider-meta": "0.3.0",
22
- "@adport/provider-tiktok": "0.3.0",
23
- "@adport/provider-microsoft": "0.3.0"
17
+ "@adport/core": "0.4.0",
18
+ "@adport/provider-apple": "0.4.0",
19
+ "@adport/mcp": "0.4.0",
20
+ "@adport/provider-google": "0.4.0",
21
+ "@adport/provider-meta": "0.4.0",
22
+ "@adport/provider-microsoft": "0.4.0",
23
+ "@adport/provider-reddit": "0.4.0",
24
+ "@adport/provider-tiktok": "0.4.0"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@types/node": "^26.1.2",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/connect/oauth.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport http from 'node:http';\nimport { spawn } from 'node:child_process';\nimport { AdportError } from '@adport/core';\n\nexport const GOOGLE_ADS_SCOPE = 'https://www.googleapis.com/auth/adwords';\nconst AUTH_ENDPOINT = 'https://accounts.google.com/o/oauth2/v2/auth';\nconst TOKEN_ENDPOINT = 'https://oauth2.googleapis.com/token';\n\nexport function buildGoogleAuthUrl(\n clientId: string,\n redirectUri: string,\n codeChallenge: string,\n state: string,\n): string {\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n scope: GOOGLE_ADS_SCOPE,\n access_type: 'offline',\n prompt: 'consent',\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n state,\n });\n return `${AUTH_ENDPOINT}?${params}`;\n}\n\nexport async function exchangeCodeForTokens(\n input: { clientId: string; clientSecret: string; code: string; redirectUri: string; codeVerifier: string },\n fetchImpl: typeof fetch = fetch,\n): Promise<{ refreshToken: string; accessToken: string }> {\n const response = await fetchImpl(TOKEN_ENDPOINT, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n client_id: input.clientId,\n client_secret: input.clientSecret,\n code: input.code,\n redirect_uri: input.redirectUri,\n grant_type: 'authorization_code',\n code_verifier: input.codeVerifier,\n }),\n });\n const data = (await response.json()) as { refresh_token?: string; access_token?: string; error?: string };\n if (!response.ok || !data.refresh_token || !data.access_token) {\n throw new AdportError(\n 'PROVIDER_ERROR',\n `OAuth code exchange failed${data.error ? ` (${data.error})` : ''}. ` +\n 'If no refresh_token was returned, revoke the app at myaccount.google.com/permissions and retry.',\n data,\n );\n }\n return { refreshToken: data.refresh_token, accessToken: data.access_token };\n}\n\n/** Parse a downloaded OAuth client JSON (client_secret_*.json, \"installed\" or \"web\"). */\nexport function parseClientSecretJson(text: string): { clientId: string; clientSecret: string } {\n const parsed = JSON.parse(text) as {\n installed?: { client_id: string; client_secret: string };\n web?: { client_id: string; client_secret: string };\n };\n const entry = parsed.installed ?? parsed.web;\n if (!entry?.client_id || !entry?.client_secret) {\n throw new AdportError('INVALID_INPUT', 'Not a valid OAuth client JSON (expected \"installed\" or \"web\" key).');\n }\n return { clientId: entry.client_id, clientSecret: entry.client_secret };\n}\n\nexport interface LoopbackServer {\n redirectUri: string;\n /** Resolves with the authorization code once Google redirects back. */\n waitForCode: Promise<string>;\n close: () => void;\n}\n\n/** Loopback listener for the OAuth redirect — the desktop-app flow's return path. */\nexport async function startLoopbackServer(\n redirectHostname: '127.0.0.1' | 'localhost' = '127.0.0.1',\n expectedState?: string,\n): Promise<LoopbackServer> {\n let resolveCode!: (code: string) => void;\n let rejectCode!: (err: Error) => void;\n const waitForCode = new Promise<string>((resolve, reject) => {\n resolveCode = resolve;\n rejectCode = reject;\n });\n\n const server = http.createServer((req, res) => {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1');\n const code = url.searchParams.get('code');\n const error = url.searchParams.get('error');\n const state = url.searchParams.get('state');\n if (expectedState && state !== expectedState) {\n res.writeHead(400, { 'content-type': 'text/html; charset=utf-8' });\n res.end('<h2>Authorization failed</h2><p>OAuth state validation failed. Return to the terminal and retry.</p>');\n rejectCode(new AdportError('PROVIDER_ERROR', 'OAuth state validation failed. Retry the connection.'));\n return;\n }\n res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });\n res.end(\n code\n ? '<h2>adport is connected.</h2><p>You can close this tab and return to the terminal.</p>'\n : `<h2>Authorization failed${error ? `: ${escapeHtml(error)}` : ''}</h2><p>Return to the terminal and retry.</p>`,\n );\n if (code) resolveCode(code);\n else if (error) rejectCode(new Error(`OAuth authorization failed: ${error}`));\n });\n\n await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));\n const address = server.address();\n if (!address || typeof address === 'string') throw new Error('Loopback server failed to bind');\n return {\n redirectUri: `http://${redirectHostname}:${address.port}`,\n waitForCode,\n close: () => server.close(),\n };\n}\n\n// ---- Microsoft identity platform (public client + PKCE) ---------------------\n\nconst MS_AUTHORIZE = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';\nconst MS_TOKEN = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';\nexport const MS_ADS_SCOPE = 'openid profile https://ads.microsoft.com/msads.manage offline_access';\n\nexport function generatePkce(): { verifier: string; challenge: string } {\n const verifier = randomBytes(32).toString('base64url');\n const challenge = createHash('sha256').update(verifier).digest('base64url');\n return { verifier, challenge };\n}\n\nexport function generateOAuthState(): string {\n return randomBytes(32).toString('base64url');\n}\n\nexport function buildMicrosoftAuthUrl(clientId: string, redirectUri: string, challenge: string, state: string): string {\n const params = new URLSearchParams({\n client_id: clientId,\n response_type: 'code',\n redirect_uri: redirectUri,\n scope: MS_ADS_SCOPE,\n code_challenge: challenge,\n code_challenge_method: 'S256',\n prompt: 'login',\n state,\n });\n return `${MS_AUTHORIZE}?${params}`;\n}\n\nexport async function exchangeMicrosoftCode(\n input: { clientId: string; code: string; redirectUri: string; codeVerifier: string },\n fetchImpl: typeof fetch = fetch,\n): Promise<{ refreshToken: string }> {\n const response = await fetchImpl(MS_TOKEN, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n client_id: input.clientId,\n scope: 'https://ads.microsoft.com/msads.manage offline_access',\n code: input.code,\n redirect_uri: input.redirectUri,\n grant_type: 'authorization_code',\n code_verifier: input.codeVerifier,\n }),\n });\n const data = (await response.json()) as { refresh_token?: string; error?: string; error_description?: string };\n if (!response.ok || !data.refresh_token) {\n throw new AdportError(\n 'PROVIDER_ERROR',\n `Microsoft OAuth exchange failed${data.error ? ` (${data.error})` : ''}: ${data.error_description ?? ''}`,\n data,\n );\n }\n return { refreshToken: data.refresh_token };\n}\n\nexport function openInBrowser(url: string): void {\n const [cmd, args] =\n process.platform === 'darwin'\n ? ['open', [url]]\n : process.platform === 'win32'\n ? ['cmd', ['/c', 'start', '', url]]\n : ['xdg-open', [url]];\n spawn(cmd as string, args as string[], { detached: true, stdio: 'ignore' }).unref();\n}\n\nfunction escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (char) => ({\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n })[char]!);\n}\n"],"mappings":"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","names":[]}