adport 0.3.0 → 0.5.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/{apple-ZLY4ZTQ3.js → apple-YVGL356A.js} +3 -4
- package/dist/{apple-ZLY4ZTQ3.js.map → apple-YVGL356A.js.map} +1 -1
- package/dist/{chunk-ZBNODOHE.js → chunk-PFXNOLYI.js} +50 -4
- package/dist/chunk-PFXNOLYI.js.map +1 -0
- package/dist/{google-35WAUFUS.js → google-YRYPPXWU.js} +2 -2
- package/dist/index.js +14 -8
- package/dist/index.js.map +1 -1
- package/dist/{microsoft-GXB6A6JC.js → microsoft-IXYBD4GY.js} +2 -2
- package/dist/reddit-EVDTD2DI.js +89 -0
- package/dist/reddit-EVDTD2DI.js.map +1 -0
- package/package.json +9 -8
- package/dist/chunk-ZBNODOHE.js.map +0 -1
- /package/dist/{google-35WAUFUS.js.map → google-YRYPPXWU.js.map} +0 -0
- /package/dist/{microsoft-GXB6A6JC.js.map → microsoft-IXYBD4GY.js.map} +0 -0
|
@@ -44,14 +44,13 @@ async function connectApple({ io }) {
|
|
|
44
44
|
data: { client_id: clientId, team_id: teamId, key_id: keyId, private_key: privateKeyPem }
|
|
45
45
|
});
|
|
46
46
|
io.out("");
|
|
47
|
-
io.out(`\u2713 Connected. ${accounts.length}
|
|
47
|
+
io.out(`\u2713 Connected. ${accounts.length} ad account(s):`);
|
|
48
48
|
for (const account of accounts) {
|
|
49
49
|
io.out(` ${account.id} ${account.name} ${account.currency ?? ""} ${account.status ?? ""}`);
|
|
50
50
|
}
|
|
51
51
|
printLocalConnectionSaved(io);
|
|
52
52
|
io.out("");
|
|
53
|
-
io.out("
|
|
54
|
-
io.out("to the new Ads Platform API when its docs go live \u2014 your credentials carry over.");
|
|
53
|
+
io.out("Using Apple Ads Platform API v1. The API-user credentials stay local.");
|
|
55
54
|
io.out("");
|
|
56
55
|
io.out("Try: adport accounts \xB7 adport report --provider apple \xB7 adport mcp");
|
|
57
56
|
} finally {
|
|
@@ -64,4 +63,4 @@ function expandHome(p) {
|
|
|
64
63
|
export {
|
|
65
64
|
connectApple
|
|
66
65
|
};
|
|
67
|
-
//# sourceMappingURL=apple-
|
|
66
|
+
//# sourceMappingURL=apple-YVGL356A.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/connect/apple.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport readline from 'node:readline/promises';\nimport { CredentialStore } from '@adport/core';\nimport { AppleAdsClient, AppleAdsProvider } from '@adport/provider-apple';\nimport type { ProgramIO } from '../program.js';\nimport { printLocalConnectionIntro, printLocalConnectionSaved } from './local.js';\n\n/**\n * Apple Ads is fully self-serve for your own org: create an API user, generate\n * an EC key pair locally, upload the public key — no Apple approval involved.\n */\nexport async function connectApple({ io }: { io: ProgramIO }): Promise<void> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const store = new CredentialStore();\n try {\n printLocalConnectionIntro(io, 'Apple Ads');\n io.out('Connecting Apple Ads (Apple Search Ads). One-time setup (~10 min, no approval):');\n io.out(' 1. In ads.apple.com: Account Settings → API → create an API user (API role).');\n io.out(' 2. Generate an EC key pair locally:');\n io.out(' openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem');\n io.out(' openssl ec -in private-key.pem -pubout -out public-key.pem');\n io.out(' 3. Upload public-key.pem in Account Settings → API; Apple shows your');\n io.out(' clientId, teamId, and keyId (all needed below).');\n io.out(' Note: access tokens last 1h and are re-minted automatically; no rotation chores.');\n io.out(' Your private key stays on this machine; upload only the public key to Apple.');\n io.out('');\n\n const clientId = (await rl.question('clientId (SEARCHADS.xxxx): ')).trim();\n const teamId = (await rl.question(`teamId [${clientId}]: `)).trim() || clientId;\n const keyId = (await rl.question('keyId: ')).trim();\n const keyPath = (await rl.question('Path to private-key.pem: ')).trim();\n if (!clientId || !keyId || !keyPath) {\n io.err('Missing values — aborting.');\n process.exitCode = 1;\n return;\n }\n const privateKeyPem = await fs.readFile(expandHome(keyPath), 'utf8');\n\n io.out('Verifying access…');\n const provider = new AppleAdsProvider(new AppleAdsClient({ clientId, teamId, keyId, privateKeyPem }));\n const accounts = await provider.listAccounts();\n await store.set({\n provider: 'apple',\n source: 'byo',\n data: { client_id: clientId, team_id: teamId, key_id: keyId, private_key: privateKeyPem },\n });\n io.out('');\n io.out(`✓ Connected. ${accounts.length}
|
|
1
|
+
{"version":3,"sources":["../src/connect/apple.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport readline from 'node:readline/promises';\nimport { CredentialStore } from '@adport/core';\nimport { AppleAdsClient, AppleAdsProvider } from '@adport/provider-apple';\nimport type { ProgramIO } from '../program.js';\nimport { printLocalConnectionIntro, printLocalConnectionSaved } from './local.js';\n\n/**\n * Apple Ads is fully self-serve for your own org: create an API user, generate\n * an EC key pair locally, upload the public key — no Apple approval involved.\n */\nexport async function connectApple({ io }: { io: ProgramIO }): Promise<void> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const store = new CredentialStore();\n try {\n printLocalConnectionIntro(io, 'Apple Ads');\n io.out('Connecting Apple Ads (Apple Search Ads). One-time setup (~10 min, no approval):');\n io.out(' 1. In ads.apple.com: Account Settings → API → create an API user (API role).');\n io.out(' 2. Generate an EC key pair locally:');\n io.out(' openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem');\n io.out(' openssl ec -in private-key.pem -pubout -out public-key.pem');\n io.out(' 3. Upload public-key.pem in Account Settings → API; Apple shows your');\n io.out(' clientId, teamId, and keyId (all needed below).');\n io.out(' Note: access tokens last 1h and are re-minted automatically; no rotation chores.');\n io.out(' Your private key stays on this machine; upload only the public key to Apple.');\n io.out('');\n\n const clientId = (await rl.question('clientId (SEARCHADS.xxxx): ')).trim();\n const teamId = (await rl.question(`teamId [${clientId}]: `)).trim() || clientId;\n const keyId = (await rl.question('keyId: ')).trim();\n const keyPath = (await rl.question('Path to private-key.pem: ')).trim();\n if (!clientId || !keyId || !keyPath) {\n io.err('Missing values — aborting.');\n process.exitCode = 1;\n return;\n }\n const privateKeyPem = await fs.readFile(expandHome(keyPath), 'utf8');\n\n io.out('Verifying access…');\n const provider = new AppleAdsProvider(new AppleAdsClient({ clientId, teamId, keyId, privateKeyPem }));\n const accounts = await provider.listAccounts();\n await store.set({\n provider: 'apple',\n source: 'byo',\n data: { client_id: clientId, team_id: teamId, key_id: keyId, private_key: privateKeyPem },\n });\n io.out('');\n io.out(`✓ Connected. ${accounts.length} ad account(s):`);\n for (const account of accounts) {\n io.out(` ${account.id} ${account.name} ${account.currency ?? ''} ${account.status ?? ''}`);\n }\n printLocalConnectionSaved(io);\n io.out('');\n io.out('Using Apple Ads Platform API v1. The API-user credentials stay local.');\n io.out('');\n io.out('Try: adport accounts · adport report --provider apple · adport mcp');\n } finally {\n rl.close();\n }\n}\n\nfunction expandHome(p: string): string {\n return p.startsWith('~/') ? path.join(os.homedir(), p.slice(2)) : p;\n}\n"],"mappings":";;;;;;AAAA,SAAS,YAAY,UAAU;AAC/B,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,cAAc;AACrB,SAAS,uBAAuB;AAChC,SAAS,gBAAgB,wBAAwB;AAQjD,eAAsB,aAAa,EAAE,GAAG,GAAqC;AAC3E,QAAM,KAAK,SAAS,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AACpF,QAAM,QAAQ,IAAI,gBAAgB;AAClC,MAAI;AACF,8BAA0B,IAAI,WAAW;AACzC,OAAG,IAAI,iFAAiF;AACxF,OAAG,IAAI,0FAAgF;AACvF,OAAG,IAAI,uCAAuC;AAC9C,OAAG,IAAI,6EAA6E;AACpF,OAAG,IAAI,mEAAmE;AAC1E,OAAG,IAAI,6EAAwE;AAC/E,OAAG,IAAI,sDAAsD;AAC7D,OAAG,IAAI,oFAAoF;AAC3F,OAAG,IAAI,gFAAgF;AACvF,OAAG,IAAI,EAAE;AAET,UAAM,YAAY,MAAM,GAAG,SAAS,6BAA6B,GAAG,KAAK;AACzE,UAAM,UAAU,MAAM,GAAG,SAAS,WAAW,QAAQ,KAAK,GAAG,KAAK,KAAK;AACvE,UAAM,SAAS,MAAM,GAAG,SAAS,SAAS,GAAG,KAAK;AAClD,UAAM,WAAW,MAAM,GAAG,SAAS,2BAA2B,GAAG,KAAK;AACtE,QAAI,CAAC,YAAY,CAAC,SAAS,CAAC,SAAS;AACnC,SAAG,IAAI,iCAA4B;AACnC,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,UAAM,gBAAgB,MAAM,GAAG,SAAS,WAAW,OAAO,GAAG,MAAM;AAEnE,OAAG,IAAI,wBAAmB;AAC1B,UAAM,WAAW,IAAI,iBAAiB,IAAI,eAAe,EAAE,UAAU,QAAQ,OAAO,cAAc,CAAC,CAAC;AACpG,UAAM,WAAW,MAAM,SAAS,aAAa;AAC7C,UAAM,MAAM,IAAI;AAAA,MACd,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,MAAM,EAAE,WAAW,UAAU,SAAS,QAAQ,QAAQ,OAAO,aAAa,cAAc;AAAA,IAC1F,CAAC;AACD,OAAG,IAAI,EAAE;AACT,OAAG,IAAI,qBAAgB,SAAS,MAAM,iBAAiB;AACvD,eAAW,WAAW,UAAU;AAC9B,SAAG,IAAI,KAAK,QAAQ,EAAE,KAAK,QAAQ,IAAI,KAAK,QAAQ,YAAY,EAAE,KAAK,QAAQ,UAAU,EAAE,EAAE;AAAA,IAC/F;AACA,8BAA0B,EAAE;AAC5B,OAAG,IAAI,EAAE;AACT,OAAG,IAAI,uEAAuE;AAC9E,OAAG,IAAI,EAAE;AACT,OAAG,IAAI,mFAA6E;AAAA,EACtF,UAAE;AACA,OAAG,MAAM;AAAA,EACX;AACF;AAEA,SAAS,WAAW,GAAmB;AACrC,SAAO,EAAE,WAAW,IAAI,IAAI,KAAK,KAAK,GAAG,QAAQ,GAAG,EAAE,MAAM,CAAC,CAAC,IAAI;AACpE;","names":[]}
|
|
@@ -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) =>
|
|
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-
|
|
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 '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\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-
|
|
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-
|
|
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.
|
|
9
|
+
version: "0.5.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-
|
|
191
|
+
const { connectGoogle } = await import("./google-YRYPPXWU.js");
|
|
191
192
|
await connectGoogle({ openBrowser: opts.browser, io });
|
|
192
193
|
return;
|
|
193
194
|
}
|
|
@@ -202,25 +203,30 @@ function buildProgram(io = defaultIO) {
|
|
|
202
203
|
return;
|
|
203
204
|
}
|
|
204
205
|
if (provider === "apple") {
|
|
205
|
-
const { connectApple } = await import("./apple-
|
|
206
|
+
const { connectApple } = await import("./apple-YVGL356A.js");
|
|
206
207
|
await connectApple({ io });
|
|
207
208
|
return;
|
|
208
209
|
}
|
|
209
210
|
if (provider === "microsoft") {
|
|
210
|
-
const { connectMicrosoft } = await import("./microsoft-
|
|
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-EVDTD2DI.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.5.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-
|
|
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-
|
|
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.5.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-EVDTD2DI.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.5.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
|
+
"version": "0.5.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.
|
|
18
|
-
"@adport/mcp": "0.
|
|
19
|
-
"@adport/provider-
|
|
20
|
-
"@adport/provider-
|
|
21
|
-
"@adport/provider-
|
|
22
|
-
"@adport/provider-
|
|
23
|
-
"@adport/provider-
|
|
17
|
+
"@adport/core": "0.5.0",
|
|
18
|
+
"@adport/mcp": "0.5.0",
|
|
19
|
+
"@adport/provider-apple": "0.5.0",
|
|
20
|
+
"@adport/provider-meta": "0.5.0",
|
|
21
|
+
"@adport/provider-google": "0.5.0",
|
|
22
|
+
"@adport/provider-microsoft": "0.5.0",
|
|
23
|
+
"@adport/provider-reddit": "0.5.0",
|
|
24
|
+
"@adport/provider-tiktok": "0.5.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 '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n })[char]!);\n}\n"],"mappings":"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","names":[]}
|
|
File without changes
|
|
File without changes
|