@koda-sl/baker-cli 0.155.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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  handleConnectionError,
3
3
  writeAdsJson
4
- } from "./chunk-FBUVCOH6.js";
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-UFBKUMN2.js.map
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-FBUVCOH6.js.map
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":[]}
package/dist/cli.js CHANGED
@@ -45,20 +45,22 @@ import {
45
45
  resolveAccountIdArg,
46
46
  resolveEffectiveStatus,
47
47
  todayIso
48
- } from "./chunk-UFBKUMN2.js";
48
+ } from "./chunk-JOPBGFXC.js";
49
49
  import {
50
50
  buildQueryCacheKey,
51
51
  cacheGet,
52
52
  cacheSet,
53
+ connectionFixExplanation,
53
54
  getManagerIdForCustomer,
54
55
  getQueryTtl,
55
56
  handleConnectionError,
57
+ isNotConnectedError,
56
58
  resolveCustomerId,
57
59
  toCsvRow,
58
60
  writeAdsJson,
59
61
  writeAdsOutput,
60
62
  writeJsonEnvelope
61
- } from "./chunk-FBUVCOH6.js";
63
+ } from "./chunk-R4PG6VCS.js";
62
64
  import {
63
65
  ApiError,
64
66
  apiGet,
@@ -5825,12 +5827,23 @@ function getRetryDelay(code) {
5825
5827
  if (code === "TIMEOUT") return 5e3;
5826
5828
  return void 0;
5827
5829
  }
5828
- function parseApiError(errorMessage, originalQuery, customerId) {
5830
+ function parseApiError(errorMessage, originalQuery, customerId, apiCode) {
5829
5831
  const ctx = {
5830
5832
  originalQuery,
5831
5833
  customerId,
5832
5834
  apiErrorMessage: errorMessage
5833
5835
  };
5836
+ if (apiCode && isNotConnectedError(apiCode, errorMessage)) {
5837
+ return {
5838
+ ok: false,
5839
+ error: {
5840
+ code: "NO_GOOGLE_ADS_CONNECTION",
5841
+ message: errorMessage,
5842
+ fix: { action: "request_connection", explanation: connectionFixExplanation("google_ads") },
5843
+ retryable: false
5844
+ }
5845
+ };
5846
+ }
5834
5847
  for (const rule of CORRECTION_RULES) {
5835
5848
  if (rule.matchApiError?.test(errorMessage)) {
5836
5849
  const fix = rule.fix(ctx);
@@ -5970,7 +5983,7 @@ Examples:
5970
5983
  writeJsonEnvelope({ ok: true, data, fields: fieldDescs, hints: window.hints });
5971
5984
  } catch (err) {
5972
5985
  if (err instanceof ApiError) {
5973
- writeAdsJson(parseApiError(err.message, "", customerId));
5986
+ writeAdsJson(parseApiError(err.message, "", customerId, err.code));
5974
5987
  process.exit(1);
5975
5988
  }
5976
5989
  writeAdsJson({ ok: false, error: { code: "NETWORK_ERROR", message: "Unexpected error" } });
@@ -6031,7 +6044,7 @@ Examples:
6031
6044
  writeAdsJson({ ok: true, data });
6032
6045
  } catch (err) {
6033
6046
  if (err instanceof ApiError) {
6034
- writeAdsJson(parseApiError(err.message, "", customerId));
6047
+ writeAdsJson(parseApiError(err.message, "", customerId, err.code));
6035
6048
  process.exit(1);
6036
6049
  }
6037
6050
  writeAdsJson({ ok: false, error: { code: "NETWORK_ERROR", message: "Unexpected error" } });
@@ -6285,6 +6298,9 @@ function loadPatchArg(args) {
6285
6298
  }
6286
6299
  function handleGoogleError(err) {
6287
6300
  if (err instanceof ApiError) {
6301
+ if (isNotConnectedError(err.code, err.message)) {
6302
+ handleConnectionError("google_ads", err.message);
6303
+ }
6288
6304
  writeJsonEnvelope({ ok: false, error: { code: err.code ?? "API_ERROR", message: err.message } });
6289
6305
  process.exit(1);
6290
6306
  }
@@ -6723,6 +6739,9 @@ function buildDiscoverBody(customerId, args) {
6723
6739
  }
6724
6740
  function handleKeywordError(err) {
6725
6741
  if (err instanceof ApiError) {
6742
+ if (isNotConnectedError(err.code, err.message)) {
6743
+ handleConnectionError("google_ads", err.message);
6744
+ }
6726
6745
  const envelope = {
6727
6746
  ok: false,
6728
6747
  error: {
@@ -6866,6 +6885,26 @@ registerSchema({
6866
6885
  output: { type: "string", description: "Format: json|csv|jsonl|md", required: false, default: "json" }
6867
6886
  }
6868
6887
  });
6888
+ function handleMetricsError(err) {
6889
+ if (err instanceof ApiError) {
6890
+ if (isNotConnectedError(err.code, err.message)) {
6891
+ handleConnectionError("google_ads", err.message);
6892
+ }
6893
+ const envelope = {
6894
+ ok: false,
6895
+ error: {
6896
+ code: err.code,
6897
+ message: err.message,
6898
+ fix: { action: "reject", explanation: err.message },
6899
+ retryable: err.code === "RATE_LIMITED"
6900
+ }
6901
+ };
6902
+ writeAdsJson(envelope);
6903
+ process.exit(1);
6904
+ }
6905
+ writeAdsJson({ ok: false, error: { code: "NETWORK_ERROR", message: "Unexpected error" } });
6906
+ process.exit(1);
6907
+ }
6869
6908
  var metricsCommand = defineCommand26({
6870
6909
  meta: {
6871
6910
  name: "metrics",
@@ -6933,21 +6972,7 @@ Examples:
6933
6972
  };
6934
6973
  writeAdsJson({ ok: true, data, fields, query_context: queryContext });
6935
6974
  } catch (err) {
6936
- if (err instanceof ApiError) {
6937
- const envelope = {
6938
- ok: false,
6939
- error: {
6940
- code: err.code,
6941
- message: err.message,
6942
- fix: { action: "reject", explanation: err.message },
6943
- retryable: err.code === "RATE_LIMITED"
6944
- }
6945
- };
6946
- writeAdsJson(envelope);
6947
- process.exit(1);
6948
- }
6949
- writeAdsJson({ ok: false, error: { code: "NETWORK_ERROR", message: "Unexpected error" } });
6950
- process.exit(1);
6975
+ handleMetricsError(err);
6951
6976
  }
6952
6977
  }
6953
6978
  });
@@ -7514,7 +7539,7 @@ async function validateArgs(args) {
7514
7539
  }
7515
7540
  function handleQueryError(err, finalQuery, customerId) {
7516
7541
  if (err instanceof ApiError) {
7517
- const envelope = parseApiError(err.message, finalQuery, customerId);
7542
+ const envelope = parseApiError(err.message, finalQuery, customerId, err.code);
7518
7543
  envelope.error.gaqlExecuted = finalQuery;
7519
7544
  writeAdsJson(envelope);
7520
7545
  process.exit(1);
@@ -9119,6 +9144,9 @@ function handleLinkedinError(err) {
9119
9144
  if (err.code === "NOT_FOUND") {
9120
9145
  handleConnectionError("linkedin_ads", err.message);
9121
9146
  }
9147
+ if (isNoAccountsSelectedError(err.code, err.message)) {
9148
+ handleConnectionError("linkedin_ads", err.message);
9149
+ }
9122
9150
  const explanation = explainLinkedinError(err);
9123
9151
  const envelope = {
9124
9152
  ok: false,
@@ -9140,6 +9168,9 @@ function handleLinkedinError(err) {
9140
9168
  writeAdsJson({ ok: false, error: { code: "NETWORK_ERROR", message } });
9141
9169
  process.exit(1);
9142
9170
  }
9171
+ function isNoAccountsSelectedError(code, message) {
9172
+ return code === "VALIDATION_ERROR" && /no linkedin ad accounts selected/i.test(message);
9173
+ }
9143
9174
  function explainLinkedinError(err) {
9144
9175
  const lower = err.message.toLowerCase();
9145
9176
  if (err.code === "VALIDATION_ERROR" && lower.includes("granularity") && lower.includes("daily")) {
@@ -13511,8 +13542,8 @@ function duplicateCommand2(entity, label) {
13511
13542
  run: async ({ args }) => {
13512
13543
  const { apiPost: apiPost2 } = await import("./client-R5ZSVIK3.js");
13513
13544
  const { requireChatId: requireChatId2 } = await import("./env-3JMYIH25.js");
13514
- const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-R534QTMO.js");
13515
- const { handleMetaError: handleMetaError2 } = await import("./shared-MUWU7BK5.js");
13545
+ const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-KLK2GZXR.js");
13546
+ const { handleMetaError: handleMetaError2 } = await import("./shared-AXAXNKGE.js");
13516
13547
  try {
13517
13548
  const accountId = bareAccountId2(args);
13518
13549
  const chatId = requireChatId2();
@@ -23679,7 +23710,10 @@ ${list}`
23679
23710
  }
23680
23711
  });
23681
23712
  process.exit(1);
23682
- } catch {
23713
+ } catch (err) {
23714
+ if (err instanceof ApiError && (err.code === "UNAUTHORIZED" || err.code === "NOT_FOUND")) {
23715
+ handleConnectionError("ga4", err.message);
23716
+ }
23683
23717
  writeJsonEnvelope({
23684
23718
  ok: false,
23685
23719
  error: {
@@ -23742,6 +23776,9 @@ Examples:
23742
23776
  writeJsonEnvelope(data);
23743
23777
  } catch (err) {
23744
23778
  if (err instanceof ApiError) {
23779
+ if (isNotConnectedError(err.code, err.message)) {
23780
+ handleConnectionError("ga4", err.message);
23781
+ }
23745
23782
  writeJsonEnvelope({ ok: false, error: { code: err.code, message: err.message } });
23746
23783
  process.exit(1);
23747
23784
  }
@@ -23933,6 +23970,9 @@ function buildRequestBody(args, propertyId, useCache) {
23933
23970
  }
23934
23971
  function handleError(err) {
23935
23972
  if (err instanceof ApiError) {
23973
+ if (isNotConnectedError(err.code, err.message)) {
23974
+ handleConnectionError("ga4", err.message);
23975
+ }
23936
23976
  writeJsonEnvelope({ ok: false, error: { code: err.code, message: err.message } });
23937
23977
  process.exit(1);
23938
23978
  }
@@ -24119,7 +24159,10 @@ ${list}`
24119
24159
  }
24120
24160
  });
24121
24161
  process.exit(1);
24122
- } catch {
24162
+ } catch (err) {
24163
+ if (err instanceof ApiError && (err.code === "UNAUTHORIZED" || err.code === "NOT_FOUND")) {
24164
+ handleConnectionError("gsc", err.message);
24165
+ }
24123
24166
  writeJsonEnvelope({
24124
24167
  ok: false,
24125
24168
  error: {
@@ -24221,6 +24264,9 @@ function buildRequestBody2(args, siteUrl, useCache) {
24221
24264
  }
24222
24265
  function handleError2(err) {
24223
24266
  if (err instanceof ApiError) {
24267
+ if (isNotConnectedError(err.code, err.message)) {
24268
+ handleConnectionError("gsc", err.message);
24269
+ }
24224
24270
  writeJsonEnvelope({ ok: false, error: { code: err.code, message: err.message } });
24225
24271
  process.exit(1);
24226
24272
  }
@@ -24357,6 +24403,9 @@ Examples:
24357
24403
  writeJsonEnvelope({ ok: true, data });
24358
24404
  } catch (err) {
24359
24405
  if (err instanceof ApiError) {
24406
+ if (isNotConnectedError(err.code, err.message)) {
24407
+ handleConnectionError("gsc", err.message);
24408
+ }
24360
24409
  writeJsonEnvelope({ ok: false, error: { code: err.code, message: err.message } });
24361
24410
  process.exit(1);
24362
24411
  }
@@ -30410,8 +30459,8 @@ function handleError3(err) {
30410
30459
  message: err.message,
30411
30460
  ...err.code === "UNAUTHORIZED" ? {
30412
30461
  fix: {
30413
- action: "ask_user",
30414
- explanation: 'Tag Manager is not connected for this company yet, or no container has been picked. Do NOT send the user to Settings \u2014 call the `request_connection` tool with { platform: "google-tag-manager", reason } and they connect and pick the container from inside the chat. Carry on with the rest of the task meanwhile.'
30462
+ action: "request_connection",
30463
+ explanation: 'Tag Manager is not connected for this company yet, or no container has been picked. Do NOT send the user to Settings \u2014 call the `request_connection` tool with { platform: "google-tag-manager", reason } and they connect and pick the container from inside the chat. `request_tag_input` is NOT the route: that form manages the container snippet on the site, not what runs inside the container, and it cannot pick a container. If `request_connection` is not available to you, say so plainly and point them at dashboard \u2192 Brain \u2192 Integrations \u2192 Tools \u2192 Google Tag Manager, where they also pick the container. Carry on with the rest of the task meanwhile.'
30415
30464
  }
30416
30465
  } : {},
30417
30466
  retryable: RETRYABLE_CODES.has(err.code)