@koda-sl/baker-cli 0.507.0 → 0.508.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.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/dist/{ads-T5RPISTB.js → ads-BOLVA4YI.js} +7 -7
  3. package/dist/{analytics-XW4ZEEAR.js → analytics-YIRKA7OC.js} +2 -2
  4. package/dist/{chatgpt-SVGWMEQD.js → chatgpt-DEG26ES2.js} +2 -2
  5. package/dist/{chunk-LLBB6OAB.js → chunk-GACSA5A6.js} +2 -2
  6. package/dist/{chunk-TWKZVPOA.js → chunk-Y2FAUFWK.js} +2 -2
  7. package/dist/chunk-Y2FAUFWK.js.map +1 -0
  8. package/dist/cli.js +1 -1
  9. package/dist/{google-3GSINSOV.js → google-X6LRA27Z.js} +4 -4
  10. package/dist/{insights-EQW34IL4.js → insights-M3CYB22P.js} +2 -2
  11. package/dist/{insights-JZVXC7IJ.js → insights-XCT3ONC7.js} +2 -2
  12. package/dist/{linkedin-4MGRSWXZ.js → linkedin-AWUXPBIV.js} +2 -2
  13. package/dist/{meta-KS3SQSYZ.js → meta-VL5RNWCW.js} +2 -2
  14. package/dist/{performance-WPFGEJTE.js → performance-34XRXF2I.js} +117 -33
  15. package/dist/performance-34XRXF2I.js.map +1 -0
  16. package/dist/{query-42QT56FJ.js → query-QM7MBQD7.js} +3 -3
  17. package/dist/{stats-LNEQJ42S.js → stats-HUWCLN7J.js} +2 -2
  18. package/dist/{sync-YOKQEZ46.js → sync-PHH65JFS.js} +2 -2
  19. package/dist/{x-7VY6K7JS.js → x-AHQPDNB4.js} +2 -2
  20. package/package.json +1 -1
  21. package/dist/chunk-TWKZVPOA.js.map +0 -1
  22. package/dist/performance-WPFGEJTE.js.map +0 -1
  23. /package/dist/{ads-T5RPISTB.js.map → ads-BOLVA4YI.js.map} +0 -0
  24. /package/dist/{analytics-XW4ZEEAR.js.map → analytics-YIRKA7OC.js.map} +0 -0
  25. /package/dist/{chatgpt-SVGWMEQD.js.map → chatgpt-DEG26ES2.js.map} +0 -0
  26. /package/dist/{chunk-LLBB6OAB.js.map → chunk-GACSA5A6.js.map} +0 -0
  27. /package/dist/{google-3GSINSOV.js.map → google-X6LRA27Z.js.map} +0 -0
  28. /package/dist/{insights-EQW34IL4.js.map → insights-M3CYB22P.js.map} +0 -0
  29. /package/dist/{insights-JZVXC7IJ.js.map → insights-XCT3ONC7.js.map} +0 -0
  30. /package/dist/{linkedin-4MGRSWXZ.js.map → linkedin-AWUXPBIV.js.map} +0 -0
  31. /package/dist/{meta-KS3SQSYZ.js.map → meta-VL5RNWCW.js.map} +0 -0
  32. /package/dist/{query-42QT56FJ.js.map → query-QM7MBQD7.js.map} +0 -0
  33. /package/dist/{stats-LNEQJ42S.js.map → stats-HUWCLN7J.js.map} +0 -0
  34. /package/dist/{sync-YOKQEZ46.js.map → sync-PHH65JFS.js.map} +0 -0
  35. /package/dist/{x-7VY6K7JS.js.map → x-AHQPDNB4.js.map} +0 -0
package/README.md CHANGED
@@ -182,7 +182,7 @@ Multi-platform ad commands. Google, Meta, and LinkedIn support both reads and **
182
182
 
183
183
  Every connected ad platform's numbers from Baker's stored copy (refreshed every few hours, full history), without calling the platforms. Read-only. Results are the company's chosen conversion goals, else each platform's headline conversions; money is shown in the company's reporting currency at the period's average ECB rate (`--currency <ISO>` for another, `--currency native` for each ad account's own), and comparisons use only days whose conversions have settled.
184
184
 
185
- It is the first read for any ads report, analysis or alert: no rate limit and the whole history, a few hours behind (`status` says how fresh). The live `baker ads <platform>` reads are for today's numbers and what this lacks, and their answers name the synced command that covers the same question.
185
+ It is the first read for any ads report, analysis or alert: no rate limit and the whole history, a few hours behind. Every read's first hint says when each platform's copy was last updated and which days it holds; when an update failed or history is still loading, it names the days the copy lacks and the live read that fills them. The live `baker ads <platform>` reads are for today's numbers and what this lacks, and their answers name the synced command that covers the same question.
186
186
 
187
187
  ```bash
188
188
  baker ads performance overview --days 30 # spend, results, cost per result per platform, vs the period before
@@ -33,16 +33,16 @@ Examples:
33
33
  Full guides: __tooling__/docs/tools/baker/ads-<platform>.md (google|meta|linkedin|x|chatgpt|performance|terms)`
34
34
  },
35
35
  subCommands: {
36
- google: () => import("./google-3GSINSOV.js").then((m) => m.googleCommand),
37
- meta: () => import("./meta-KS3SQSYZ.js").then((m) => m.metaCommand),
38
- linkedin: () => import("./linkedin-4MGRSWXZ.js").then((m) => m.linkedinCommand),
39
- x: () => import("./x-7VY6K7JS.js").then((m) => m.xCommand),
40
- chatgpt: () => import("./chatgpt-SVGWMEQD.js").then((m) => m.chatgptCommand),
41
- performance: () => import("./performance-WPFGEJTE.js").then((m) => m.performanceCommand),
36
+ google: () => import("./google-X6LRA27Z.js").then((m) => m.googleCommand),
37
+ meta: () => import("./meta-VL5RNWCW.js").then((m) => m.metaCommand),
38
+ linkedin: () => import("./linkedin-AWUXPBIV.js").then((m) => m.linkedinCommand),
39
+ x: () => import("./x-AHQPDNB4.js").then((m) => m.xCommand),
40
+ chatgpt: () => import("./chatgpt-DEG26ES2.js").then((m) => m.chatgptCommand),
41
+ performance: () => import("./performance-34XRXF2I.js").then((m) => m.performanceCommand),
42
42
  terms: () => import("./terms-O6ODXEOK.js").then((m) => m.termsCommand)
43
43
  }
44
44
  });
45
45
  export {
46
46
  adsCommand
47
47
  };
48
- //# sourceMappingURL=ads-T5RPISTB.js.map
48
+ //# sourceMappingURL=ads-BOLVA4YI.js.map
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-CLUJWAQE.js";
8
8
  import {
9
9
  syncedFirstHint
10
- } from "./chunk-TWKZVPOA.js";
10
+ } from "./chunk-Y2FAUFWK.js";
11
11
  import {
12
12
  writeAdsJson,
13
13
  writeAdsOutput
@@ -442,4 +442,4 @@ Examples \u2014 common AI questions:
442
442
  export {
443
443
  analyticsCommand
444
444
  };
445
- //# sourceMappingURL=analytics-XW4ZEEAR.js.map
445
+ //# sourceMappingURL=analytics-YIRKA7OC.js.map
@@ -36,7 +36,7 @@ Full guide: __tooling__/docs/tools/baker/ads-chatgpt.md`
36
36
  audiences: () => import("./audiences-WCDWN64L.js").then((m) => m.audiencesCommand),
37
37
  catalogs: () => import("./catalogs-T23RKYFC.js").then((m) => m.catalogsCommand),
38
38
  history: () => import("./history-5MRLNTCI.js").then((m) => m.historyCommand),
39
- insights: () => import("./insights-JZVXC7IJ.js").then((m) => m.insightsCommand),
39
+ insights: () => import("./insights-XCT3ONC7.js").then((m) => m.insightsCommand),
40
40
  locations: () => import("./locations-UKBOT6SB.js").then((m) => m.locationsCommand),
41
41
  draft: () => import("./draft-LPVK6YHI.js").then((m) => m.draftCommand)
42
42
  }
@@ -44,4 +44,4 @@ Full guide: __tooling__/docs/tools/baker/ads-chatgpt.md`
44
44
  export {
45
45
  chatgptCommand
46
46
  };
47
- //# sourceMappingURL=chatgpt-SVGWMEQD.js.map
47
+ //# sourceMappingURL=chatgpt-DEG26ES2.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  googlePresetSyncedHint
3
- } from "./chunk-TWKZVPOA.js";
3
+ } from "./chunk-Y2FAUFWK.js";
4
4
  import {
5
5
  getFieldDescriptions
6
6
  } from "./chunk-JPK34QPS.js";
@@ -1643,4 +1643,4 @@ export {
1643
1643
  runQuery,
1644
1644
  queryCommand
1645
1645
  };
1646
- //# sourceMappingURL=chunk-LLBB6OAB.js.map
1646
+ //# sourceMappingURL=chunk-GACSA5A6.js.map
@@ -1,6 +1,6 @@
1
1
  // src/commands/ads/synced-first.ts
2
2
  function hint(command) {
3
- return `Synced copy first: \`${command}\` answers this with no rate limit and full history, a few hours behind (\`baker ads performance status\` says how fresh). Read live only for today's numbers, a field the synced copy lacks, or right before a change.`;
3
+ return `Synced copy first: \`${command}\` answers this with no rate limit and full history, a few hours behind (every synced read says how fresh, and which days it lacks). Read live only for today's numbers, a field the synced copy lacks, or right before a change.`;
4
4
  }
5
5
  function syncedCommand(platform, level) {
6
6
  const scope = `--platforms ${platform}`;
@@ -47,4 +47,4 @@ export {
47
47
  metaBreakdownSyncedHint,
48
48
  googlePresetSyncedHint
49
49
  };
50
- //# sourceMappingURL=chunk-TWKZVPOA.js.map
50
+ //# sourceMappingURL=chunk-Y2FAUFWK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/ads/synced-first.ts"],"sourcesContent":["/**\n * The nudge on every live performance read (`baker ads <platform> insights`,\n * `analytics`, `stats`, GAQL performance presets): Baker's synced copy\n * (`baker ads performance`) answers the same question with no rate limit and\n * the whole history, so it is the first read; live is for today's numbers, a\n * field the copy lacks, or the read right before a change.\n */\n\nexport type SyncedPlatform = \"meta\" | \"google\" | \"linkedin\" | \"x\" | \"chatgpt\";\nexport type SyncedLevel = \"account\" | \"campaign\" | \"ad_group\" | \"ad\" | \"keyword\";\n\nfunction hint(command: string): string {\n return `Synced copy first: \\`${command}\\` answers this with no rate limit and full history, a few hours behind (every synced read says how fresh, and which days it lacks). Read live only for today's numbers, a field the synced copy lacks, or right before a change.`;\n}\n\nfunction syncedCommand(platform: SyncedPlatform, level: SyncedLevel): string {\n const scope = `--platforms ${platform}`;\n if (level === \"account\") return `baker ads performance overview ${scope}`;\n return `baker ads performance entities ${scope} --level ${level}`;\n}\n\nexport function syncedFirstHint(platform: SyncedPlatform, level: SyncedLevel): string {\n return hint(syncedCommand(platform, level));\n}\n\n/** Meta insights breakdowns the synced copy keeps, and the `breakdown --dimension` that reads each. */\nconst META_BREAKDOWN_DIMENSION: Record<string, string> = {\n age: \"age\",\n gender: \"gender\",\n country: \"country\",\n publisher_platform: \"placement\",\n platform_position: \"placement\",\n impression_device: \"device\",\n device_platform: \"device\",\n};\n\n/** The synced read for a Meta breakdown; null when the copy does not keep it or would need two dimensions at once. */\nexport function metaBreakdownSyncedHint(breakdowns: readonly string[]): string | null {\n const dimensions = new Set(breakdowns.map((breakdown) => META_BREAKDOWN_DIMENSION[breakdown]));\n const [dimension] = dimensions;\n if (dimensions.size !== 1 || dimension === undefined) return null;\n return hint(`baker ads performance breakdown --dimension ${dimension} --platforms meta`);\n}\n\n/** Google Ads presets the synced copy answers, and the read that does. Inventories and live-only reports are absent. */\nconst GOOGLE_PRESET_READS: Record<string, string> = {\n \"account-summary\": \"baker ads performance overview --platforms google\",\n \"campaign-performance\": \"baker ads performance entities --platforms google --level campaign\",\n \"budget-limited\": \"baker ads performance impression-share\",\n \"keyword-analysis\": \"baker ads performance entities --platforms google --level keyword\",\n \"search-terms\": \"baker ads performance search-terms\",\n \"ad-copy-performance\": \"baker ads performance entities --platforms google --level ad\",\n \"geo-performance\": \"baker ads performance breakdown --dimension country --platforms google\",\n \"landing-pages\": \"baker ads performance landings --platforms google\",\n \"conversions-by-action\": \"baker ads performance goals\",\n \"account-budget\": \"baker ads performance pacing --platforms google\",\n};\n\nexport function googlePresetSyncedHint(preset: string | undefined): string | null {\n const command = preset === undefined ? undefined : GOOGLE_PRESET_READS[preset];\n return command === undefined ? null : hint(command);\n}\n"],"mappings":";AAWA,SAAS,KAAK,SAAyB;AACrC,SAAO,wBAAwB,OAAO;AACxC;AAEA,SAAS,cAAc,UAA0B,OAA4B;AAC3E,QAAM,QAAQ,eAAe,QAAQ;AACrC,MAAI,UAAU,UAAW,QAAO,kCAAkC,KAAK;AACvE,SAAO,kCAAkC,KAAK,YAAY,KAAK;AACjE;AAEO,SAAS,gBAAgB,UAA0B,OAA4B;AACpF,SAAO,KAAK,cAAc,UAAU,KAAK,CAAC;AAC5C;AAGA,IAAM,2BAAmD;AAAA,EACvD,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,oBAAoB;AAAA,EACpB,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,iBAAiB;AACnB;AAGO,SAAS,wBAAwB,YAA8C;AACpF,QAAM,aAAa,IAAI,IAAI,WAAW,IAAI,CAAC,cAAc,yBAAyB,SAAS,CAAC,CAAC;AAC7F,QAAM,CAAC,SAAS,IAAI;AACpB,MAAI,WAAW,SAAS,KAAK,cAAc,OAAW,QAAO;AAC7D,SAAO,KAAK,+CAA+C,SAAS,mBAAmB;AACzF;AAGA,IAAM,sBAA8C;AAAA,EAClD,mBAAmB;AAAA,EACnB,wBAAwB;AAAA,EACxB,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,uBAAuB;AAAA,EACvB,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,yBAAyB;AAAA,EACzB,kBAAkB;AACpB;AAEO,SAAS,uBAAuB,QAA2C;AAChF,QAAM,UAAU,WAAW,SAAY,SAAY,oBAAoB,MAAM;AAC7E,SAAO,YAAY,SAAY,OAAO,KAAK,OAAO;AACpD;","names":[]}
package/dist/cli.js CHANGED
@@ -117,7 +117,7 @@ Introspection: Run 'baker schema <command>' to inspect argument schemas.`
117
117
  capabilities: () => import("./capabilities-QREOIRIH.js").then((m) => m.capabilitiesCommand),
118
118
  actions: () => import("./actions-IG2D6NRC.js").then((m) => m.actionsCommand),
119
119
  "scheduled-agents": () => import("./scheduled-agents-7JW3JIOL.js").then((m) => m.scheduledAgentsCommand),
120
- ads: () => import("./ads-T5RPISTB.js").then((m) => m.adsCommand),
120
+ ads: () => import("./ads-BOLVA4YI.js").then((m) => m.adsCommand),
121
121
  brand: () => import("./brand-JB6NXAAL.js").then((m) => m.brandCommand),
122
122
  analytics: () => import("./analytics-P5UFA6I4.js").then((m) => m.analyticsCommand),
123
123
  experiment: () => import("./experiment-I5CRLOVN.js").then((m) => m.experimentCommand),
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  runQuery
3
- } from "./chunk-LLBB6OAB.js";
4
- import "./chunk-TWKZVPOA.js";
3
+ } from "./chunk-GACSA5A6.js";
4
+ import "./chunk-Y2FAUFWK.js";
5
5
  import "./chunk-JPK34QPS.js";
6
6
  import {
7
7
  AD_COPY_LIST_USAGE,
@@ -3254,7 +3254,7 @@ Full guide: __tooling__/docs/tools/baker/ads-google.md`
3254
3254
  "identity-verification": () => import("./account-reads-KJHPUVEV.js").then((m) => m.identityVerificationCommand),
3255
3255
  previews: () => import("./account-reads-KJHPUVEV.js").then((m) => m.previewsCommand),
3256
3256
  brands: () => import("./account-reads-KJHPUVEV.js").then((m) => m.brandsCommand),
3257
- query: () => import("./query-42QT56FJ.js").then((m) => m.queryCommand),
3257
+ query: () => import("./query-QM7MBQD7.js").then((m) => m.queryCommand),
3258
3258
  keywords: keywordsWithWrites,
3259
3259
  ...googleWriteCommandsWithLists,
3260
3260
  "merchant-center": merchantCenterCommand,
@@ -3264,4 +3264,4 @@ Full guide: __tooling__/docs/tools/baker/ads-google.md`
3264
3264
  export {
3265
3265
  googleCommand
3266
3266
  };
3267
- //# sourceMappingURL=google-3GSINSOV.js.map
3267
+ //# sourceMappingURL=google-X6LRA27Z.js.map
@@ -11,7 +11,7 @@ import {
11
11
  import {
12
12
  metaBreakdownSyncedHint,
13
13
  syncedFirstHint
14
- } from "./chunk-TWKZVPOA.js";
14
+ } from "./chunk-Y2FAUFWK.js";
15
15
  import "./chunk-Y2JZX6JB.js";
16
16
  import {
17
17
  writeAdsJson,
@@ -434,4 +434,4 @@ export {
434
434
  insightsCommand,
435
435
  wantsAsync
436
436
  };
437
- //# sourceMappingURL=insights-EQW34IL4.js.map
437
+ //# sourceMappingURL=insights-M3CYB22P.js.map
@@ -10,7 +10,7 @@ import {
10
10
  } from "./chunk-M7HD42M6.js";
11
11
  import {
12
12
  syncedFirstHint
13
- } from "./chunk-TWKZVPOA.js";
13
+ } from "./chunk-Y2FAUFWK.js";
14
14
  import {
15
15
  writeJsonEnvelope
16
16
  } from "./chunk-ZRP7Y2DM.js";
@@ -127,4 +127,4 @@ export {
127
127
  chatgptInsightsHints,
128
128
  insightsCommand
129
129
  };
130
- //# sourceMappingURL=insights-JZVXC7IJ.js.map
130
+ //# sourceMappingURL=insights-XCT3ONC7.js.map
@@ -1291,7 +1291,7 @@ Full guide: __tooling__/docs/tools/baker/ads-linkedin.md`
1291
1291
  "lead-forms": () => import("./write-commands-ZQ5JYOUB.js").then((m) => m.leadFormsCommand),
1292
1292
  experiments: () => import("./experiments-PJWBABLJ.js").then((m) => m.experimentsCommand),
1293
1293
  draft: () => import("./draft-RTZBROFS.js").then((m) => m.linkedinDraftCommand),
1294
- analytics: () => import("./analytics-XW4ZEEAR.js").then((m) => m.analyticsCommand),
1294
+ analytics: () => import("./analytics-YIRKA7OC.js").then((m) => m.analyticsCommand),
1295
1295
  demographics: () => import("./demographics-MZBHPCT3.js").then((m) => m.demographicsCommand),
1296
1296
  companies: () => import("./companies-2DHNNS34.js").then((m) => m.companiesCommand),
1297
1297
  "top-companies": () => import("./top-companies-HZQTHV2H.js").then((m) => m.topCompaniesCommand),
@@ -1311,4 +1311,4 @@ Full guide: __tooling__/docs/tools/baker/ads-linkedin.md`
1311
1311
  export {
1312
1312
  linkedinCommand
1313
1313
  };
1314
- //# sourceMappingURL=linkedin-4MGRSWXZ.js.map
1314
+ //# sourceMappingURL=linkedin-AWUXPBIV.js.map
@@ -92,7 +92,7 @@ Full guide: __tooling__/docs/tools/baker/ads-meta.md`
92
92
  "custom-conversions": () => import("./custom-conversions-6KYF26SK.js").then((m) => m.customConversionsCommand),
93
93
  pixels: () => import("./pixels-ZZTB6NWF.js").then((m) => m.pixelsCommand),
94
94
  activities: () => import("./activities-LKLPORDI.js").then((m) => m.activitiesCommand),
95
- insights: () => import("./insights-EQW34IL4.js").then((m) => m.insightsCommand),
95
+ insights: () => import("./insights-M3CYB22P.js").then((m) => m.insightsCommand),
96
96
  preview: () => import("./preview-CWRGDFCP.js").then((m) => m.previewCommand),
97
97
  targeting: () => import("./targeting-SOMGPSKV.js").then((m) => m.targetingCommand),
98
98
  "instagram-accounts": () => import("./account-assets-WZOYMT3C.js").then((m) => m.instagramAccountsCommand),
@@ -119,4 +119,4 @@ Full guide: __tooling__/docs/tools/baker/ads-meta.md`
119
119
  export {
120
120
  metaCommand
121
121
  };
122
- //# sourceMappingURL=meta-KS3SQSYZ.js.map
122
+ //# sourceMappingURL=meta-VL5RNWCW.js.map
@@ -1469,15 +1469,119 @@ import { defineCommand as defineCommand2 } from "citty";
1469
1469
 
1470
1470
  // src/commands/ads/performance/reportCommand.ts
1471
1471
  import { defineCommand } from "citty";
1472
+
1473
+ // src/commands/ads/performance/syncCoverage.ts
1474
+ var HOUR_MS = 36e5;
1475
+ var DAY_MS = 24 * HOUR_MS;
1476
+ var BEHIND_AFTER_MS = 26 * HOUR_MS;
1477
+ var LIVE_READ = {
1478
+ google: (from, to) => `baker ads google query --preset campaign-performance --date-range "BETWEEN '${from}' AND '${to}'"`,
1479
+ meta: (from, to) => `baker ads meta insights --level campaign --since ${from} --until ${to}`,
1480
+ linkedin: (from, to) => `baker ads linkedin analytics --level campaign --start ${from} --end ${to}`,
1481
+ x: () => "baker ads x stats",
1482
+ chatgpt: () => "baker ads chatgpt insights"
1483
+ };
1484
+ function ago(ms) {
1485
+ const hours = Math.max(0, Math.floor(ms / HOUR_MS));
1486
+ if (hours < 1) return "under an hour";
1487
+ if (hours < 48) return `${hours}h`;
1488
+ return `${Math.floor(hours / 24)} days`;
1489
+ }
1490
+ function utc(at) {
1491
+ return `${new Date(at).toISOString().slice(0, 16).replace("T", " ")} UTC`;
1492
+ }
1493
+ function dayOf(at) {
1494
+ return new Date(at).toISOString().slice(0, 10);
1495
+ }
1496
+ function dayAfter(day, days = 1) {
1497
+ return dayOf(Date.parse(`${day}T00:00:00Z`) + days * DAY_MS);
1498
+ }
1499
+ function earliest(dates) {
1500
+ const known = dates.filter((date) => date !== null);
1501
+ return known.length === 0 ? null : known.reduce((a, b) => b < a ? b : a);
1502
+ }
1503
+ function liveRead(platform, from, to, why) {
1504
+ const command = LIVE_READ[platform]?.(from, to);
1505
+ return command ? `: read live with \`${command}\` (live reads spend the platform's rate limit \u2014 ${why})` : "";
1506
+ }
1507
+ function headline(platform, accounts, now) {
1508
+ const updatedAt = platform.lastSuccessAt;
1509
+ const from = earliest(accounts.map((account) => account.historyFrom));
1510
+ const through = earliest(accounts.map((account) => account.newestDate));
1511
+ const updated = updatedAt === null ? "never updated yet" : `last updated ${ago(now - updatedAt)} ago (${utc(updatedAt)})`;
1512
+ const holds = from !== null && through !== null ? `; holds ${from} \u2192 ${through}` : "";
1513
+ return `${platformLabel(platform.platform)} synced copy: ${updated}${holds}.`;
1514
+ }
1515
+ function loadingSentences(platform, accounts) {
1516
+ return accounts.filter((account) => !account.backfillComplete && account.historyFrom !== null).map((account) => {
1517
+ const before = account.historyFrom ?? "";
1518
+ const live = LIVE_READ[platform.platform]?.("<from>", dayAfter(before, -1));
1519
+ return `History is still loading for ${account.name || account.adAccountId}: days before ${before} are not in the copy yet${live ? `; for an older window read live with \`${live}\`` : ""}.`;
1520
+ });
1521
+ }
1522
+ function emptySentences(accounts) {
1523
+ if (accounts.every((account) => account.newestDate === null)) return [];
1524
+ return accounts.filter((account) => account.newestDate === null).map((account) => `Nothing is stored yet for ${account.name || account.adAccountId}; read it live.`);
1525
+ }
1526
+ function failureSentences(platform, accounts) {
1527
+ const byError = /* @__PURE__ */ new Map();
1528
+ for (const account of accounts) {
1529
+ if (account.error === null) continue;
1530
+ byError.set(account.error, [...byError.get(account.error) ?? [], account.name || account.adAccountId]);
1531
+ }
1532
+ const sentences = [...byError].map(([error, names]) => `Last update failed for ${names.join(", ")}: ${error}`);
1533
+ if (sentences.length === 0 && platform.error) sentences.push(`Last update failed: ${platform.error}`);
1534
+ return sentences;
1535
+ }
1536
+ function behindSentences(platform, accounts, now) {
1537
+ const sentences = failureSentences(platform, accounts);
1538
+ const updatedAt = platform.lastSuccessAt;
1539
+ const overdue = updatedAt === null || now - updatedAt > BEHIND_AFTER_MS;
1540
+ if (!overdue && sentences.length === 0) return sentences;
1541
+ const through = earliest(accounts.map((account) => account.newestDate));
1542
+ const today = dayOf(now);
1543
+ if (through === null) {
1544
+ const live = liveRead(platform.platform, dayOf(now - 29 * DAY_MS), today, "read only what the question needs");
1545
+ sentences.push(`Nothing is in the copy yet${live}.`);
1546
+ } else if (through < today) {
1547
+ const live = liveRead(platform.platform, dayAfter(through), today, "read only the missing days");
1548
+ sentences.push(
1549
+ `Days after ${through} are not in the copy${live}, or tell the person the numbers stop at ${through}.`
1550
+ );
1551
+ }
1552
+ return sentences;
1553
+ }
1554
+ var GOOGLE_ONLY_READS = /* @__PURE__ */ new Set(["brand-split", "impression-share", "keyword-quality", "google-report", "segments"]);
1555
+ function platformsOfRead(path, body) {
1556
+ const named = typeof body === "object" && body !== null ? body : {};
1557
+ if (Array.isArray(named.platforms) && named.platforms.length > 0) return named.platforms.map(String);
1558
+ if (typeof named.platform === "string") return [named.platform];
1559
+ return GOOGLE_ONLY_READS.has(path) ? ["google"] : void 0;
1560
+ }
1561
+ function syncCoverageHints(status, now, platforms) {
1562
+ return status.platforms.filter((platform) => platform.connected && (platforms === void 0 || platforms.includes(platform.platform))).map((platform) => {
1563
+ const accounts = platform.accounts.filter((account) => account.selected);
1564
+ return [
1565
+ headline(platform, accounts, now),
1566
+ ...loadingSentences(platform, accounts),
1567
+ ...emptySentences(accounts),
1568
+ ...behindSentences(platform, accounts, now)
1569
+ ].join(" ");
1570
+ });
1571
+ }
1572
+
1573
+ // src/commands/ads/performance/reportCommand.ts
1472
1574
  function failInvalid(issues, args) {
1473
1575
  writeJson({ ok: false, error: { code: "INVALID_ARGS", message: invalidArgsMessage(issues, args) } });
1474
1576
  process.exit(1);
1475
1577
  }
1476
- function postRead(path, body) {
1477
- return retryOnceIfBusy(
1578
+ async function postRead(path, body) {
1579
+ const response = await retryOnceIfBusy(
1478
1580
  () => apiPost(`/api/ads/performance/${path}`, body),
1479
1581
  (caught) => caught instanceof Error ? caught.message : ""
1480
1582
  );
1583
+ const coverage = response.dataStatus ? syncCoverageHints(response.dataStatus, Date.now(), platformsOfRead(path, body)) : [];
1584
+ return { ok: true, data: response.data, coverage };
1481
1585
  }
1482
1586
  function failRead(err) {
1483
1587
  const refusal = err instanceof ApiError ? adPerformanceRefusal(err.code, err.message) : null;
@@ -1507,7 +1611,7 @@ function report(spec) {
1507
1611
  if (!parsed.success) failInvalid(parsed.error.issues, spec.args);
1508
1612
  try {
1509
1613
  const response = await postRead(spec.path, parsed.data);
1510
- const hints = spec.hints?.(response.data, parsed.data, args) ?? [];
1614
+ const hints = [...response.coverage, ...spec.hints?.(response.data, parsed.data, args) ?? []];
1511
1615
  const data = args.full === true ? response.data : (spec.compact ?? roundDeep)(response.data);
1512
1616
  writeJson({ ok: true, data, ...hints.length > 0 ? { hints } : {} });
1513
1617
  } catch (err) {
@@ -1901,8 +2005,8 @@ function evidenceWords(evidence) {
1901
2005
  evidence.format === "different" ? "different format" : null
1902
2006
  ].filter((part) => part !== null).join(", ");
1903
2007
  }
1904
- var titleOf = (concept, headline) => {
1905
- const words = (concept ?? headline).trim();
2008
+ var titleOf = (concept, headline2) => {
2009
+ const words = (concept ?? headline2).trim();
1906
2010
  return words.length <= 120 ? words : `${words.slice(0, 119).trimEnd()}\u2026`;
1907
2011
  };
1908
2012
  function whyWords(choice) {
@@ -2410,16 +2514,6 @@ var PLATFORM_LABEL = {
2410
2514
  function platformLabel(platform) {
2411
2515
  return PLATFORM_LABEL[platform] ?? platform;
2412
2516
  }
2413
- function historyLoadingHint(status) {
2414
- if (!status.historyLoading) return null;
2415
- const loading = status.platforms.flatMap(
2416
- (platform) => platform.accounts.filter((account) => account.selected && !account.backfillComplete).map(
2417
- (account) => `${account.name || account.adAccountId} (${platformLabel(platform.platform)}${account.historyFrom ? `, stored from ${account.historyFrom}` : ""})`
2418
- )
2419
- );
2420
- const which = loading.length > 0 ? loading.join(", ") : "some ad accounts";
2421
- return `History is still loading for ${which}; older periods are incomplete. Say so before comparing them, or read a recent window.`;
2422
- }
2423
2517
  var NO_GOALS_HINT = "No result goals are set, so results are each platform's headline conversions. Run `baker ads performance goals` to see every conversion action each platform reported.";
2424
2518
  function resultNounHint(noun) {
2425
2519
  if (noun.source === "company")
@@ -2430,8 +2524,6 @@ function resultNounHint(noun) {
2430
2524
  }
2431
2525
  function overviewHints(data) {
2432
2526
  const hints = [];
2433
- const loading = historyLoadingHint(data.dataStatus);
2434
- if (loading !== null) hints.push(loading);
2435
2527
  if (data.goals.length === 0 && data.platforms.length > 0) hints.push(NO_GOALS_HINT);
2436
2528
  const noun = resultNounHint(data.resultNoun);
2437
2529
  if (noun !== null) hints.push(noun);
@@ -2616,15 +2708,7 @@ function splitAdsHints(status) {
2616
2708
  );
2617
2709
  }
2618
2710
  function statusHints(status) {
2619
- const hints = [];
2620
- const loading = historyLoadingHint(status);
2621
- if (loading !== null) hints.push(loading);
2622
- for (const platform of status.platforms) {
2623
- if (platform.connected && platform.error)
2624
- hints.push(`${platformLabel(platform.platform)} last sync failed: ${platform.error}`);
2625
- }
2626
- hints.push(...splitAdsHints(status));
2627
- return hints;
2711
+ return splitAdsHints(status);
2628
2712
  }
2629
2713
 
2630
2714
  // src/commands/ads/performance/accounts.ts
@@ -2966,7 +3050,7 @@ function targetCommand(spec) {
2966
3050
  if (!parsed.success) failInvalid(parsed.error.issues, spec.args);
2967
3051
  try {
2968
3052
  const response = await postRead(spec.name, parsed.data);
2969
- const hints = spec.hints(response.data);
3053
+ const hints = [...response.coverage, ...spec.hints(response.data)];
2970
3054
  const data = args.full === true ? response.data : spec.compact(response.data);
2971
3055
  writeJson({ ok: true, data, ...hints.length > 0 ? { hints } : {} });
2972
3056
  } catch (err) {
@@ -3324,7 +3408,7 @@ var creativeAssetsCommand = defineCommand4({
3324
3408
  if (!parsed.success) failInvalid(parsed.error.issues, CREATIVE_ASSETS_ARGS);
3325
3409
  try {
3326
3410
  const response = await postRead("creative-assets", parsed.data);
3327
- const hints = creativeAssetsHints(response.data, parsed.data);
3411
+ const hints = [...response.coverage, ...creativeAssetsHints(response.data, parsed.data)];
3328
3412
  const data = args.full === true ? response.data : compactCreativeAssets(response.data);
3329
3413
  writeJson({ ok: true, data, ...hints.length > 0 ? { hints } : {} });
3330
3414
  } catch (err) {
@@ -3544,7 +3628,7 @@ var creativeHealthCommand = defineCommand5({
3544
3628
  }
3545
3629
  try {
3546
3630
  const response = await postRead("creative-health", parsed.data);
3547
- const hints = creativeHealthHints(response.data, choice);
3631
+ const hints = [...response.coverage, ...creativeHealthHints(response.data, choice)];
3548
3632
  const data = args.full === true ? response.data : compactCreativeHealth(response.data, choice);
3549
3633
  writeJson({ ok: true, data, ...hints.length > 0 ? { hints } : {} });
3550
3634
  } catch (err) {
@@ -3776,7 +3860,7 @@ function diagnosticCommand(spec) {
3776
3860
  if (!parsed.success) failInvalid(parsed.error.issues, spec.args);
3777
3861
  try {
3778
3862
  const response = await postRead(spec.path, parsed.data);
3779
- const hints = spec.hints(response.data);
3863
+ const hints = [...response.coverage, ...spec.hints(response.data)];
3780
3864
  const data = args.full === true ? response.data : spec.compact(response.data);
3781
3865
  writeJson({ ok: true, data, ...hints.length > 0 ? { hints } : {} });
3782
3866
  } catch (err) {
@@ -3957,7 +4041,7 @@ var healthCommand = defineCommand7({
3957
4041
  if (!parsed.success) failInvalid(parsed.error.issues, HEALTH_ARGS);
3958
4042
  try {
3959
4043
  const response = await postRead("health", parsed.data);
3960
- const hints = healthHints(response.data);
4044
+ const hints = [...response.coverage, ...healthHints(response.data)];
3961
4045
  const data = args.full === true ? response.data : compactHealth(response.data);
3962
4046
  writeJson({ ok: true, data, ...hints.length > 0 ? { hints } : {} });
3963
4047
  } catch (err) {
@@ -4231,7 +4315,7 @@ function peopleCommand(spec) {
4231
4315
  if (!parsed.success) failInvalid(parsed.error.issues, spec.args);
4232
4316
  try {
4233
4317
  const response = await postRead(spec.path, parsed.data);
4234
- const hints = spec.hints(response.data);
4318
+ const hints = [...response.coverage, ...spec.hints(response.data)];
4235
4319
  const data = args.full === true ? response.data : spec.compact(response.data);
4236
4320
  writeJson({ ok: true, data, ...hints.length > 0 ? { hints } : {} });
4237
4321
  } catch (err) {
@@ -5452,4 +5536,4 @@ Full guide: __tooling__/docs/tools/baker/ads-performance.md`
5452
5536
  export {
5453
5537
  performanceCommand
5454
5538
  };
5455
- //# sourceMappingURL=performance-WPFGEJTE.js.map
5539
+ //# sourceMappingURL=performance-34XRXF2I.js.map