@koda-sl/baker-cli 0.154.0 → 0.156.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/README.md +8 -1
- package/dist/{chunk-UFBKUMN2.js → chunk-JOPBGFXC.js} +2 -2
- package/dist/{chunk-FBUVCOH6.js → chunk-R4PG6VCS.js} +71 -44
- package/dist/chunk-R4PG6VCS.js.map +1 -0
- package/dist/cli.js +760 -102
- package/dist/cli.js.map +1 -1
- package/dist/{output-R534QTMO.js → output-KLK2GZXR.js} +2 -2
- package/dist/{shared-MUWU7BK5.js → shared-AXAXNKGE.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-FBUVCOH6.js.map +0 -1
- /package/dist/{chunk-UFBKUMN2.js.map → chunk-JOPBGFXC.js.map} +0 -0
- /package/dist/{output-R534QTMO.js.map → output-KLK2GZXR.js.map} +0 -0
- /package/dist/{shared-MUWU7BK5.js.map → shared-AXAXNKGE.js.map} +0 -0
package/README.md
CHANGED
|
@@ -2495,9 +2495,16 @@ baker actions claim <action-id> # live — returns action details + r
|
|
|
2495
2495
|
baker actions release <action-id>
|
|
2496
2496
|
|
|
2497
2497
|
baker actions create --name "Build hero" --priority high --tags landing,creative --description "..."
|
|
2498
|
-
# → returns { ok, data: { tempId }, hints: [...] } — check hints for tags/priority/dependencies/description reminders
|
|
2498
|
+
# → returns { ok, data: { tempId, draftCreateCount }, hints: [...] } — check hints for tags/priority/dependencies/description reminders
|
|
2499
2499
|
# If the name/description reads as recurring or future-dated (e.g. "weekly", "every Monday", "schedule"),
|
|
2500
2500
|
# create emits a hint to use `baker scheduled-actions create` instead of a Work Action.
|
|
2501
|
+
# If it instead names work a surface executes in-chat (a GTM container change, a site tag, an ad-platform
|
|
2502
|
+
# write, a page or form edit, or a value only the user has), create emits a do-it-now hint naming that
|
|
2503
|
+
# surface — doing the work is the default, an action is the exception. One advisory hint per call: the
|
|
2504
|
+
# scheduled redirect wins when both match.
|
|
2505
|
+
# `draftCreateCount` is how many creates this chat has staged (counted after this op, so it drops again
|
|
2506
|
+
# after `draft remove`). At 3 or more, create emits a fan-out hint: re-read the draft and merge actions
|
|
2507
|
+
# that touch the same system into one.
|
|
2501
2508
|
# If no --tags are passed, create emits a MISSING --tags hint — the action is invisible to the backlog's tag filter.
|
|
2502
2509
|
# If no --priority is passed, create emits a MISSING --priority hint — the action ranks as normal/medium in the do-first order.
|
|
2503
2510
|
baker actions update <action-id> --name "Better name" --tags google-ads,strategy # --tags REPLACES the set; '' clears
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handleConnectionError,
|
|
3
3
|
writeAdsJson
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-R4PG6VCS.js";
|
|
5
5
|
import {
|
|
6
6
|
ApiError
|
|
7
7
|
} from "./chunk-LVCBCJWF.js";
|
|
@@ -94,4 +94,4 @@ export {
|
|
|
94
94
|
csvOrJson,
|
|
95
95
|
resolveEffectiveStatus
|
|
96
96
|
};
|
|
97
|
-
//# sourceMappingURL=chunk-
|
|
97
|
+
//# sourceMappingURL=chunk-JOPBGFXC.js.map
|
|
@@ -1,10 +1,74 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ApiError,
|
|
2
3
|
apiGet
|
|
3
4
|
} from "./chunk-LVCBCJWF.js";
|
|
4
5
|
import {
|
|
5
6
|
getEnv
|
|
6
7
|
} from "./chunk-RK67WL4O.js";
|
|
7
8
|
|
|
9
|
+
// src/error-handler.ts
|
|
10
|
+
var REQUEST_CONNECTION_PLATFORM = {
|
|
11
|
+
google_ads: "google-ads",
|
|
12
|
+
ga4: "google-analytics",
|
|
13
|
+
gsc: "google-search-console",
|
|
14
|
+
linkedin_ads: "linkedin"
|
|
15
|
+
};
|
|
16
|
+
var PLATFORM_NAME = {
|
|
17
|
+
google_ads: "Google Ads",
|
|
18
|
+
ga4: "Google Analytics 4",
|
|
19
|
+
gsc: "Google Search Console",
|
|
20
|
+
x_ads: "X Ads",
|
|
21
|
+
meta_ads: "Meta Ads",
|
|
22
|
+
linkedin_ads: "LinkedIn Ads"
|
|
23
|
+
};
|
|
24
|
+
function connectionFixExplanation(platform) {
|
|
25
|
+
const name = PLATFORM_NAME[platform];
|
|
26
|
+
const slug = REQUEST_CONNECTION_PLATFORM[platform];
|
|
27
|
+
if (slug === void 0) {
|
|
28
|
+
return `${name} is not connected for this company, and it cannot be connected from the chat yet. Tell the user to connect it in the dashboard under Brain \u2192 Integrations \u2192 Tools \u2192 ${name}. Do not fail the whole job: say what stays unavailable and carry on with the rest.`;
|
|
29
|
+
}
|
|
30
|
+
return `${name} is not connected for this company (or no account has been picked). Do NOT send the user to Settings and do NOT end the turn here \u2014 call the \`request_connection\` tool with { platform: "${slug}", reason } and they connect and pick their accounts from inside the chat. If that tool is not available to you, tell them exactly where to do it: dashboard \u2192 Brain \u2192 Integrations \u2192 Tools \u2192 ${name}. Either way, carry on with the rest of the task and say what stays unavailable until it is connected.`;
|
|
31
|
+
}
|
|
32
|
+
function handleConnectionError(platform, originalMessage) {
|
|
33
|
+
const platformName = PLATFORM_NAME[platform];
|
|
34
|
+
const alternative = {
|
|
35
|
+
google_ads: "keywords-for-site or research advertisers",
|
|
36
|
+
ga4: "lighthouse for page performance",
|
|
37
|
+
gsc: "keywords-for-site or research keyword-gap",
|
|
38
|
+
x_ads: "research advertisers",
|
|
39
|
+
meta_ads: "research advertisers or research keyword-ideas",
|
|
40
|
+
linkedin_ads: "research advertisers (LinkedIn Ad Library) or research keyword-ideas"
|
|
41
|
+
}[platform];
|
|
42
|
+
const code = {
|
|
43
|
+
google_ads: "NO_GOOGLE_ADS_CONNECTION",
|
|
44
|
+
ga4: "NO_GA4_CONNECTION",
|
|
45
|
+
gsc: "NO_GSC_CONNECTION",
|
|
46
|
+
x_ads: "NO_X_ADS_CONNECTION",
|
|
47
|
+
meta_ads: "NO_META_ADS_CONNECTION",
|
|
48
|
+
linkedin_ads: "NO_LINKEDIN_ADS_CONNECTION"
|
|
49
|
+
}[platform];
|
|
50
|
+
const envelope = {
|
|
51
|
+
ok: false,
|
|
52
|
+
error: {
|
|
53
|
+
code,
|
|
54
|
+
message: originalMessage || `No ${platformName} connection found for this company.`,
|
|
55
|
+
fix: {
|
|
56
|
+
action: REQUEST_CONNECTION_PLATFORM[platform] === void 0 ? "ask_user" : "request_connection",
|
|
57
|
+
explanation: `${connectionFixExplanation(platform)} (Optional fallback for estimated external insights: 'baker research ${alternative}'.)`
|
|
58
|
+
},
|
|
59
|
+
retryable: false
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
writeJsonEnvelope(envelope);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
function isNotConnectedError(code, message) {
|
|
66
|
+
if (code !== "NOT_FOUND" && code !== "UNAUTHORIZED") {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return /no .* connection found|not connected|no .* (selected|picked)|is not in .* connected/i.test(message);
|
|
70
|
+
}
|
|
71
|
+
|
|
8
72
|
// src/commands/ads/cache.ts
|
|
9
73
|
import { createHash } from "crypto";
|
|
10
74
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
@@ -75,48 +139,6 @@ function getManagerIdForCustomer(customerId) {
|
|
|
75
139
|
return void 0;
|
|
76
140
|
}
|
|
77
141
|
|
|
78
|
-
// src/error-handler.ts
|
|
79
|
-
function handleConnectionError(platform, originalMessage) {
|
|
80
|
-
const platformName = {
|
|
81
|
-
google_ads: "Google Ads",
|
|
82
|
-
ga4: "Google Analytics 4",
|
|
83
|
-
gsc: "Google Search Console",
|
|
84
|
-
x_ads: "X Ads",
|
|
85
|
-
meta_ads: "Meta Ads",
|
|
86
|
-
linkedin_ads: "LinkedIn Ads"
|
|
87
|
-
}[platform];
|
|
88
|
-
const alternative = {
|
|
89
|
-
google_ads: "keywords-for-site or research advertisers",
|
|
90
|
-
ga4: "lighthouse for page performance",
|
|
91
|
-
gsc: "keywords-for-site or research keyword-gap",
|
|
92
|
-
x_ads: "research advertisers",
|
|
93
|
-
meta_ads: "research advertisers or research keyword-ideas",
|
|
94
|
-
linkedin_ads: "research advertisers (LinkedIn Ad Library) or research keyword-ideas"
|
|
95
|
-
}[platform];
|
|
96
|
-
const code = {
|
|
97
|
-
google_ads: "NO_GOOGLE_ADS_CONNECTION",
|
|
98
|
-
ga4: "NO_GA4_CONNECTION",
|
|
99
|
-
gsc: "NO_GSC_CONNECTION",
|
|
100
|
-
x_ads: "NO_X_ADS_CONNECTION",
|
|
101
|
-
meta_ads: "NO_META_ADS_CONNECTION",
|
|
102
|
-
linkedin_ads: "NO_LINKEDIN_ADS_CONNECTION"
|
|
103
|
-
}[platform];
|
|
104
|
-
const envelope = {
|
|
105
|
-
ok: false,
|
|
106
|
-
error: {
|
|
107
|
-
code,
|
|
108
|
-
message: originalMessage || `No ${platformName} connection found for this company.`,
|
|
109
|
-
fix: {
|
|
110
|
-
action: "authenticate",
|
|
111
|
-
explanation: `AGENT INSTRUCTION: You cannot resolve this error programmatically. The user has not connected their ${platformName} account. Step 1: Inform the user that they must connect ${platformName} in their Baker dashboard settings for direct data queries. Step 2: Do not fail the overall audit or playbook. Document the missing connection, skip this specific check, and proceed with the rest of your tasks. (Optional fallback: use 'baker research ${alternative}' for estimated external insights).`
|
|
112
|
-
},
|
|
113
|
-
retryable: false
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
writeJsonEnvelope(envelope);
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
142
|
// src/commands/ads/output.ts
|
|
121
143
|
function writeAdsJson(envelope) {
|
|
122
144
|
process.stdout.write(`${JSON.stringify(envelope, null, 2)}
|
|
@@ -236,7 +258,10 @@ ${list}`
|
|
|
236
258
|
}
|
|
237
259
|
});
|
|
238
260
|
process.exit(1);
|
|
239
|
-
} catch {
|
|
261
|
+
} catch (err) {
|
|
262
|
+
if (err instanceof ApiError && isNotConnectedError(err.code, err.message)) {
|
|
263
|
+
handleConnectionError("google_ads", err.message);
|
|
264
|
+
}
|
|
240
265
|
writeAdsJson({
|
|
241
266
|
ok: false,
|
|
242
267
|
error: {
|
|
@@ -254,11 +279,13 @@ export {
|
|
|
254
279
|
getQueryTtl,
|
|
255
280
|
buildQueryCacheKey,
|
|
256
281
|
getManagerIdForCustomer,
|
|
282
|
+
connectionFixExplanation,
|
|
257
283
|
handleConnectionError,
|
|
284
|
+
isNotConnectedError,
|
|
258
285
|
writeAdsJson,
|
|
259
286
|
writeJsonEnvelope,
|
|
260
287
|
toCsvRow,
|
|
261
288
|
writeAdsOutput,
|
|
262
289
|
resolveCustomerId
|
|
263
290
|
};
|
|
264
|
-
//# sourceMappingURL=chunk-
|
|
291
|
+
//# sourceMappingURL=chunk-R4PG6VCS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/error-handler.ts","../src/commands/ads/cache.ts","../src/commands/ads/output.ts"],"sourcesContent":["import { writeJsonEnvelope } from \"./commands/ads/output.ts\";\n\ntype ConnectionPlatform = \"google_ads\" | \"ga4\" | \"gsc\" | \"x_ads\" | \"meta_ads\" | \"linkedin_ads\";\n\n/**\n * Platforms the agent can get connected without the user leaving the chat, via\n * the blocking `request_connection` tool, and the id that tool expects. Meta and\n * X Ads are absent on purpose — they are still coming soon, so the only honest\n * answer there is the dashboard.\n */\nconst REQUEST_CONNECTION_PLATFORM: Partial<Record<ConnectionPlatform, string>> = {\n google_ads: \"google-ads\",\n ga4: \"google-analytics\",\n gsc: \"google-search-console\",\n linkedin_ads: \"linkedin\",\n};\n\nconst PLATFORM_NAME: Record<ConnectionPlatform, string> = {\n google_ads: \"Google Ads\",\n ga4: \"Google Analytics 4\",\n gsc: \"Google Search Console\",\n x_ads: \"X Ads\",\n meta_ads: \"Meta Ads\",\n linkedin_ads: \"LinkedIn Ads\",\n};\n\n/**\n * How to tell the agent to get a platform connected. Never \"send them to\n * Settings\" for a platform `request_connection` covers — that ends the turn on a\n * wall the agent could have opened itself. The fallback line matters too: a\n * Session booted before the tool shipped does not have it, and the agent must\n * still be able to say where the connect button is instead of stalling.\n */\nexport function connectionFixExplanation(platform: ConnectionPlatform): string {\n const name = PLATFORM_NAME[platform];\n const slug = REQUEST_CONNECTION_PLATFORM[platform];\n if (slug === undefined) {\n return (\n `${name} is not connected for this company, and it cannot be connected from the chat yet. Tell the user to ` +\n `connect it in the dashboard under Brain → Integrations → Tools → ${name}. Do not fail the whole job: say ` +\n `what stays unavailable and carry on with the rest.`\n );\n }\n return (\n `${name} is not connected for this company (or no account has been picked). Do NOT send the user to Settings ` +\n `and do NOT end the turn here — call the \\`request_connection\\` tool with { platform: \"${slug}\", reason } and ` +\n `they connect and pick their accounts from inside the chat. If that tool is not available to you, tell them ` +\n `exactly where to do it: dashboard → Brain → Integrations → Tools → ${name}. Either way, carry on with the ` +\n `rest of the task and say what stays unavailable until it is connected.`\n );\n}\n\n/**\n * Shared error handler for \"No Connection\" scenarios.\n * Provides a structured error for AI agents with a suggested fix and alternative commands.\n */\nexport function handleConnectionError(platform: ConnectionPlatform, originalMessage?: string): never {\n const platformName = PLATFORM_NAME[platform];\n\n const alternative = {\n google_ads: \"keywords-for-site or research advertisers\",\n ga4: \"lighthouse for page performance\",\n gsc: \"keywords-for-site or research keyword-gap\",\n x_ads: \"research advertisers\",\n meta_ads: \"research advertisers or research keyword-ideas\",\n linkedin_ads: \"research advertisers (LinkedIn Ad Library) or research keyword-ideas\",\n }[platform];\n\n const code = {\n google_ads: \"NO_GOOGLE_ADS_CONNECTION\",\n ga4: \"NO_GA4_CONNECTION\",\n gsc: \"NO_GSC_CONNECTION\",\n x_ads: \"NO_X_ADS_CONNECTION\",\n meta_ads: \"NO_META_ADS_CONNECTION\",\n linkedin_ads: \"NO_LINKEDIN_ADS_CONNECTION\",\n }[platform];\n\n const envelope = {\n ok: false,\n error: {\n code,\n message: originalMessage || `No ${platformName} connection found for this company.`,\n fix: {\n action: REQUEST_CONNECTION_PLATFORM[platform] === undefined ? \"ask_user\" : \"request_connection\",\n explanation: `${connectionFixExplanation(platform)} (Optional fallback for estimated external insights: 'baker research ${alternative}'.)`,\n },\n retryable: false,\n },\n };\n\n writeJsonEnvelope(envelope);\n process.exit(1);\n}\n\n/**\n * Does this API failure mean \"the platform isn't connected\"? The read commands\n * surface the backend's own 404/403 verbatim, so the connect fix has to be\n * recognised from the code the client mapped it to plus the message.\n */\nexport function isNotConnectedError(code: string, message: string): boolean {\n if (code !== \"NOT_FOUND\" && code !== \"UNAUTHORIZED\") {\n return false;\n }\n // The last alternative is the write-path 403 (\"customer X is not in this\n // company's connected Google Ads accounts\") — a different sentence for the\n // same remedy: connect, and pick the account.\n return /no .* connection found|not connected|no .* (selected|picked)|is not in .* connected/i.test(message);\n}\n","import { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { CacheEntry } from \"./types.ts\";\n\nconst CACHE_DIR = join(homedir(), \".baker\", \"cache\", \"ads\");\n\nfunction ensureDir(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n\nfunction hashKey(key: string): string {\n return createHash(\"sha256\").update(key).digest(\"hex\").slice(0, 16);\n}\n\nfunction cachePath(category: string, key: string): string {\n const dir = join(CACHE_DIR, category);\n ensureDir(dir);\n return join(dir, `${hashKey(key)}.json`);\n}\n\nexport function cacheGet<T>(category: string, key: string): CacheEntry<T> | null {\n const path = cachePath(category, key);\n if (!existsSync(path)) {\n return null;\n }\n try {\n const raw = readFileSync(path, \"utf-8\");\n const entry = JSON.parse(raw) as CacheEntry<T>;\n if (entry.expiresAt < Date.now()) {\n rmSync(path, { force: true });\n return null;\n }\n return entry;\n } catch {\n rmSync(path, { force: true });\n return null;\n }\n}\n\nexport function cacheSet<T>(\n category: string,\n key: string,\n data: T,\n ttlMs: number,\n fields?: Record<string, string>,\n): void {\n const path = cachePath(category, key);\n const entry: CacheEntry<T> = {\n expiresAt: Date.now() + ttlMs,\n data,\n fields,\n };\n writeFileSync(path, JSON.stringify(entry), \"utf-8\");\n}\n\nconst HOUR = 60 * 60 * 1000;\nconst MINUTE = 60 * 1000;\n\nexport function getQueryTtl(query: string): number {\n const upper = query.toUpperCase();\n if (upper.includes(\"TODAY\")) {\n return 15 * MINUTE;\n }\n if (upper.includes(\"LAST_7_DAYS\") || upper.includes(\"LAST_14_DAYS\")) {\n return 1 * HOUR;\n }\n // Historical data (BETWEEN, LAST_30_DAYS, LAST_90_DAYS, etc.)\n return 6 * HOUR;\n}\n\nexport function buildQueryCacheKey(customerId: string, query: string): string {\n const today = new Date().toISOString().slice(0, 10);\n return `${customerId}:${today}:${query.trim().replace(/\\s+/g, \" \")}`;\n}\n\ninterface CachedAccount {\n id: string;\n access_type: \"direct\" | \"managed\";\n manager_id?: string;\n}\n\nexport function getManagerIdForCustomer(customerId: string): string | undefined {\n const cached = cacheGet<CachedAccount[]>(\"accounts\", \"list\");\n if (!cached) return undefined;\n const account = cached.data.find((a) => a.id === customerId);\n if (account?.access_type === \"managed\") return account.manager_id;\n return undefined;\n}\n","import { ApiError, apiGet } from \"../../client.ts\";\nimport { getEnv } from \"../../env.ts\";\nimport { handleConnectionError, isNotConnectedError } from \"../../error-handler.ts\";\nimport type { QueryContext } from \"../../geo-context.ts\";\nimport { cacheGet, cacheSet } from \"./cache.ts\";\nimport type { AdsErrorEnvelope, AdsFileSummary, AdsSuccessEnvelope } from \"./types.ts\";\n\ntype AdsOutput =\n | AdsSuccessEnvelope<unknown>\n | AdsErrorEnvelope\n | AdsFileSummary\n | { ok: true; data: unknown; query_context?: QueryContext; cached?: true; fields?: Record<string, string> }\n | { ok: false; error: { code: string; message: string } };\n\nexport function writeAdsJson(envelope: AdsOutput): void {\n process.stdout.write(`${JSON.stringify(envelope, null, 2)}\\n`);\n}\n\nexport function writeJsonEnvelope(envelope: Record<string, unknown>): void {\n process.stdout.write(`${JSON.stringify(envelope, null, 2)}\\n`);\n}\n\nexport function toCsvRow(values: string[]): string {\n return values\n .map((v) => {\n if (v.includes(\",\") || v.includes('\"') || v.includes(\"\\n\")) {\n return `\"${v.replace(/\"/g, '\"\"')}\"`;\n }\n return v;\n })\n .join(\",\");\n}\n\nfunction flattenRow(row: Record<string, unknown>): Record<string, string> {\n const flat: Record<string, string> = {};\n for (const [key, val] of Object.entries(row)) {\n flat[key] = typeof val === \"object\" && val !== null ? JSON.stringify(val) : String(val ?? \"\");\n }\n return flat;\n}\n\nfunction writeAdsCsv(data: Array<Record<string, unknown>>, fields?: string[]): void {\n if (data.length === 0) return;\n const cols = fields ?? Object.keys(data[0] ?? {});\n process.stdout.write(`${toCsvRow(cols)}\\n`);\n for (const row of data) {\n const flat = flattenRow(row);\n process.stdout.write(`${toCsvRow(cols.map((f) => flat[f] ?? \"\"))}\\n`);\n }\n}\n\nfunction writeAdsJsonl(data: Array<Record<string, unknown>>): void {\n for (const row of data) {\n process.stdout.write(`${JSON.stringify(row)}\\n`);\n }\n}\n\nfunction writeAdsMd(data: Array<Record<string, unknown>>, fields?: string[]): void {\n if (data.length === 0) return;\n const cols = fields ?? Object.keys(data[0] ?? {});\n process.stdout.write(`| ${cols.join(\" | \")} |\\n`);\n process.stdout.write(`| ${cols.map(() => \"---\").join(\" | \")} |\\n`);\n for (const row of data) {\n const flat = flattenRow(row);\n process.stdout.write(`| ${cols.map((f) => flat[f] ?? \"\").join(\" | \")} |\\n`);\n }\n}\n\nexport function writeAdsOutput(data: Array<Record<string, unknown>>, format: string, fields?: string[]): void {\n switch (format) {\n case \"csv\":\n writeAdsCsv(data, fields);\n break;\n case \"jsonl\":\n writeAdsJsonl(data);\n break;\n case \"md\":\n writeAdsMd(data, fields);\n break;\n default:\n writeAdsJson({ ok: true, data });\n }\n}\n\ninterface AccountInfo {\n id: string;\n name: string;\n access_type: \"direct\" | \"managed\";\n level: number;\n manager_id?: string;\n}\n\nasync function fetchAccounts(useCache = true): Promise<AccountInfo[]> {\n if (useCache) {\n const cached = cacheGet<AccountInfo[]>(\"accounts\", \"list\");\n if (cached) return cached.data;\n }\n const params = !useCache ? { \"skip-cache\": \"true\" } : undefined;\n const data = await apiGet<AccountInfo[]>(\"/api/ads/google/accounts\", params);\n if (useCache) {\n cacheSet(\"accounts\", \"list\", data, 60 * 60 * 1000);\n }\n return data;\n}\n\nexport async function resolveCustomerId(args: Record<string, unknown>): Promise<string> {\n const fromArgs = args[\"customer-id\"] as string | undefined;\n const customerId = fromArgs || getEnv().BAKER_GOOGLE_ADS_CUSTOMER_ID;\n\n if (customerId) {\n if (!/^\\d{10}$/.test(customerId)) {\n writeAdsJson({\n ok: false,\n error: {\n code: \"INVALID_CUSTOMER_ID\",\n message:\n \"Customer ID must be exactly 10 digits without dashes. Pass --customer-id or set BAKER_GOOGLE_ADS_CUSTOMER_ID.\",\n },\n });\n process.exit(1);\n }\n return customerId;\n }\n\n const useCache = !args[\"no-cache\"];\n\n try {\n const accounts = await fetchAccounts(useCache);\n const [single] = accounts;\n if (accounts.length === 1 && single) {\n process.stderr.write(`Using account \"${single.name}\" (${single.id})\\n`);\n return single.id;\n }\n if (accounts.length === 0) {\n handleConnectionError(\"google_ads\");\n }\n const list = accounts.map((a) => ` ${a.id} ${a.name}`).join(\"\\n\");\n writeAdsJson({\n ok: false,\n error: {\n code: \"MULTIPLE_ACCOUNTS\",\n message: `Multiple accounts found. Pass --customer-id or set BAKER_GOOGLE_ADS_CUSTOMER_ID:\\n${list}`,\n },\n });\n process.exit(1);\n } catch (err) {\n // \"No connection\" reaches here as a 404/403 from the accounts lookup. Reporting it as\n // an unusable customer id sends the agent hunting for an id that cannot exist yet.\n if (err instanceof ApiError && isNotConnectedError(err.code, err.message)) {\n handleConnectionError(\"google_ads\", err.message);\n }\n writeAdsJson({\n ok: false,\n error: {\n code: \"INVALID_CUSTOMER_ID\",\n message: \"Could not auto-detect account. Pass --customer-id or set BAKER_GOOGLE_ADS_CUSTOMER_ID.\",\n },\n });\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
|