@koda-sl/baker-cli 0.347.0-dev.043fc924c → 0.348.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 (28) hide show
  1. package/README.md +10 -8
  2. package/dist/{chunk-KGDL7F7M.js → chunk-2IC3CGQO.js} +3 -3
  3. package/dist/chunk-2OKSJNLA.js +855 -0
  4. package/dist/chunk-2OKSJNLA.js.map +1 -0
  5. package/dist/{chunk-XPUSSDLR.js → chunk-4QXKWIFC.js} +3 -3
  6. package/dist/{chunk-JWMLQSGK.js → chunk-NA5CPMUA.js} +4 -4
  7. package/dist/{chunk-PWGY347J.js → chunk-XWHHYHKQ.js} +2 -2
  8. package/dist/{chunk-D5P4COPD.js → chunk-YBD3W4M7.js} +9 -8
  9. package/dist/chunk-YBD3W4M7.js.map +1 -0
  10. package/dist/cli.js +522 -770
  11. package/dist/cli.js.map +1 -1
  12. package/dist/{client-PWRZOA3Q.js → client-ODCNYCP4.js} +4 -4
  13. package/dist/engine/index.js +3 -3
  14. package/dist/{env-TQTHWLBN.js → env-BSG7TY5Y.js} +6 -2
  15. package/dist/{output-HD63GTWI.js → output-2XBCYF7L.js} +5 -5
  16. package/dist/{shared-5662SBDW.js → shared-RYAF7Y7H.js} +6 -6
  17. package/package.json +1 -1
  18. package/dist/chunk-4VP5WWPR.js +0 -376
  19. package/dist/chunk-4VP5WWPR.js.map +0 -1
  20. package/dist/chunk-D5P4COPD.js.map +0 -1
  21. /package/dist/{chunk-KGDL7F7M.js.map → chunk-2IC3CGQO.js.map} +0 -0
  22. /package/dist/{chunk-XPUSSDLR.js.map → chunk-4QXKWIFC.js.map} +0 -0
  23. /package/dist/{chunk-JWMLQSGK.js.map → chunk-NA5CPMUA.js.map} +0 -0
  24. /package/dist/{chunk-PWGY347J.js.map → chunk-XWHHYHKQ.js.map} +0 -0
  25. /package/dist/{client-PWRZOA3Q.js.map → client-ODCNYCP4.js.map} +0 -0
  26. /package/dist/{env-TQTHWLBN.js.map → env-BSG7TY5Y.js.map} +0 -0
  27. /package/dist/{output-HD63GTWI.js.map → output-2XBCYF7L.js.map} +0 -0
  28. /package/dist/{shared-5662SBDW.js.map → shared-RYAF7Y7H.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -67,7 +67,7 @@ import {
67
67
  validateCanvasDeep,
68
68
  withoutCastName,
69
69
  ytDlpBlockSignal
70
- } from "./chunk-D5P4COPD.js";
70
+ } from "./chunk-YBD3W4M7.js";
71
71
  import {
72
72
  csvOrJson,
73
73
  daysAgoIso,
@@ -76,7 +76,7 @@ import {
76
76
  resolveAccountIdArg,
77
77
  resolveEffectiveStatus,
78
78
  todayIso
79
- } from "./chunk-JWMLQSGK.js";
79
+ } from "./chunk-NA5CPMUA.js";
80
80
  import {
81
81
  buildQueryCacheKey,
82
82
  cacheGet,
@@ -92,33 +92,42 @@ import {
92
92
  writeAdsJson,
93
93
  writeAdsOutput,
94
94
  writeJsonEnvelope
95
- } from "./chunk-KGDL7F7M.js";
95
+ } from "./chunk-2IC3CGQO.js";
96
96
  import {
97
97
  ApiError,
98
98
  apiGet,
99
99
  apiGetWithHeaders,
100
100
  apiPost,
101
101
  validateConvexId
102
- } from "./chunk-XPUSSDLR.js";
102
+ } from "./chunk-4QXKWIFC.js";
103
103
  import {
104
104
  installExitRecord,
105
105
  installStreamTaps,
106
106
  logInvocation
107
- } from "./chunk-PWGY347J.js";
107
+ } from "./chunk-XWHHYHKQ.js";
108
108
  import {
109
+ adContentNormalizer,
109
110
  captureBudgetMs,
110
111
  captureProxyCredentials,
111
112
  captureVimeoCookie,
113
+ compactImage,
114
+ compactVideo,
112
115
  getEnv,
113
116
  hasOpenRouterKey,
114
117
  isProxyFailure,
115
118
  openRouterKey,
119
+ optionalChatId,
116
120
  plannedRoutes,
117
121
  refuseNonPublicUrl,
118
122
  requireChatId,
119
123
  resolveChatId,
120
- shouldEscalate
121
- } from "./chunk-4VP5WWPR.js";
124
+ shouldEscalate,
125
+ testimonialNormalizer,
126
+ videoNormalizer,
127
+ winningAdNormalizer,
128
+ writeJson,
129
+ writeOutput
130
+ } from "./chunk-2OKSJNLA.js";
122
131
 
123
132
  // src/cli.ts
124
133
  import { defineCommand as defineCommand245, runMain } from "citty";
@@ -159,420 +168,6 @@ import { defineCommand as defineCommand18 } from "citty";
159
168
  // src/commands/actions/claim.ts
160
169
  import { defineCommand } from "citty";
161
170
 
162
- // src/output.ts
163
- var ASPECT_RATIO_MAP = {
164
- "1:1": 1,
165
- "16:9": 16 / 9,
166
- "9:16": 9 / 16,
167
- "2:3": 2 / 3,
168
- "3:4": 3 / 4,
169
- "1:2": 1 / 2,
170
- "2:1": 2,
171
- "4:5": 4 / 5,
172
- "3:2": 3 / 2,
173
- "4:3": 4 / 3
174
- };
175
- var ASPECT_RATIO_TOLERANCE = 0.08;
176
- function formatAspectRatio(ratio) {
177
- if (ratio === void 0 || ratio === null) {
178
- return null;
179
- }
180
- for (const [label, value] of Object.entries(ASPECT_RATIO_MAP)) {
181
- if (Math.abs(ratio - value) / value < ASPECT_RATIO_TOLERANCE) {
182
- return label;
183
- }
184
- }
185
- return null;
186
- }
187
- function groupSummary(record) {
188
- const key = record.groupKey;
189
- if (typeof key !== "string" || !key) {
190
- return null;
191
- }
192
- const index = typeof record.groupIndex === "number" ? record.groupIndex : null;
193
- const size = typeof record.groupSize === "number" ? record.groupSize : null;
194
- return {
195
- key,
196
- kind: typeof record.groupKind === "string" ? record.groupKind : null,
197
- position: index === null ? null : size === null ? `${index + 1}` : `${index + 1} of ${size}`,
198
- title: typeof record.groupTitle === "string" ? record.groupTitle : null,
199
- url: typeof record.groupUrl === "string" ? record.groupUrl : null
200
- };
201
- }
202
- function peopleLines(value) {
203
- if (!Array.isArray(value)) {
204
- return [];
205
- }
206
- return value.flatMap((entry) => {
207
- const person = asRecord(entry);
208
- const role = typeof person.role === "string" ? person.role : "";
209
- if (!role) {
210
- return [];
211
- }
212
- const identity = typeof person.identity === "string" && person.identity ? ` \u2014 ${person.identity}` : "";
213
- const appearance = typeof person.appearance === "string" && person.appearance ? ` (${person.appearance})` : "";
214
- const speaking = person.speaking === true ? " [speaking]" : "";
215
- return [`${role}${identity}${appearance}${speaking}`];
216
- });
217
- }
218
- function compactImage(image) {
219
- const analysis = asRecord(image.analysis);
220
- return {
221
- _id: String(image._id ?? ""),
222
- name: String(image.name ?? ""),
223
- imageUrl: String(image.imageUrl ?? ""),
224
- description: String(image.description ?? ""),
225
- tags: Array.isArray(image.tags) ? image.tags : [],
226
- // Two numbers that are not about choosing the image but about placing it.
227
- // A remote <BakerImage> with no width/height makes Astro DOWNLOAD the file
228
- // at build time just to measure it — one request per image per build, on a
229
- // build that is otherwise offline, and a single dropped connection there
230
- // fails a client's whole verify. They are cheap enough to always carry, and
231
- // burying them behind --full meant the agent placing the image never had
232
- // them.
233
- width: typeof image.width === "number" ? image.width : null,
234
- height: typeof image.height === "number" ? image.height : null,
235
- // The facts that decide a pick, so choosing does not need a second call:
236
- // whose brand it is, who is in it, and what text is burned in.
237
- subject: typeof analysis.subject === "string" ? analysis.subject : null,
238
- brandOwnership: typeof analysis.brandOwnership === "string" ? analysis.brandOwnership : null,
239
- people: peopleLines(analysis.people),
240
- textInImage: Array.isArray(analysis.textInImage) ? analysis.textInImage : [],
241
- group: groupSummary(image)
242
- };
243
- }
244
- function fullImage(image) {
245
- const compact3 = compactImage(image);
246
- return {
247
- ...compact3,
248
- aspectRatio: formatAspectRatio(image.aspectRatio),
249
- dominantColor: image.dominantColor ?? null,
250
- imagePalette: Array.isArray(image.imagePalette) ? image.imagePalette : [],
251
- source: String(image.source ?? ""),
252
- externalUrl: image.externalUrl ?? null,
253
- originalFilename: image.originalFilename ?? null,
254
- descriptionContext: image.descriptionContext ?? null,
255
- style: typeof asRecord(image.analysis).style === "string" ? asRecord(image.analysis).style : null,
256
- composition: typeof asRecord(image.analysis).composition === "string" ? asRecord(image.analysis).composition : null,
257
- usageNotes: typeof asRecord(image.analysis).usageNotes === "string" ? asRecord(image.analysis).usageNotes : null,
258
- createdAt: image.createdAt ?? null
259
- };
260
- }
261
- function imageNormalizer(record, full) {
262
- if (full) {
263
- return fullImage(record);
264
- }
265
- return compactImage(record);
266
- }
267
- function compactVideo(video) {
268
- const analysis = asRecord(video.analysis);
269
- const sceneCount = typeof video.sceneCount === "number" ? video.sceneCount : Array.isArray(video.scenes) ? video.scenes.length : 0;
270
- return {
271
- _id: String(video._id ?? ""),
272
- name: String(video.name ?? ""),
273
- thumbnailUrl: String(video.thumbnailUrl ?? ""),
274
- description: String(video.description ?? ""),
275
- tags: Array.isArray(video.tags) ? video.tags : [],
276
- status: String(video.status ?? ""),
277
- // What decides whether this clip is usable, without a second call: who is
278
- // on camera and in what role, what language they speak, how it opens, and
279
- // how many beats there are to cut from.
280
- subject: typeof analysis.subject === "string" ? analysis.subject : null,
281
- brandOwnership: typeof analysis.brandOwnership === "string" ? analysis.brandOwnership : null,
282
- people: peopleLines(analysis.people),
283
- spokenLanguages: Array.isArray(analysis.spokenLanguages) ? analysis.spokenLanguages : [],
284
- hook: typeof analysis.hook === "string" ? analysis.hook : null,
285
- sceneCount,
286
- group: groupSummary(video)
287
- };
288
- }
289
- function sceneDecoration(scene) {
290
- const shot = typeof scene.shot === "string" && scene.shot ? ` [${scene.shot}]` : "";
291
- const change = typeof scene.visualChange === "string" && scene.visualChange ? ` <${scene.visualChange}>` : "";
292
- const overlayText = Array.isArray(scene.onScreenText) ? scene.onScreenText.filter(Boolean) : [];
293
- let overlay = "";
294
- if (scene.hasOverlay === true) {
295
- overlay = overlayText.length > 0 ? ` \u2014 banner: "${overlayText.join('" / "')}"` : " \u2014 banner";
296
- } else if (scene.hasOverlay === false) {
297
- overlay = " \u2014 clean frame";
298
- }
299
- const spoken = typeof scene.spoken === "string" && scene.spoken ? ` \u2014 says: "${scene.spoken}"` : "";
300
- return `${shot}${change}${overlay}${spoken}`;
301
- }
302
- function sceneLines(value) {
303
- if (!Array.isArray(value)) {
304
- return [];
305
- }
306
- return value.flatMap((s) => {
307
- const scene = asRecord(s);
308
- const title = typeof scene.title === "string" ? scene.title : "";
309
- if (!title) {
310
- return [];
311
- }
312
- const start = round2(scene.startSecond);
313
- const end = round2(scene.endSecond);
314
- const range = start !== null && end !== null ? ` (${start}-${end}s)` : "";
315
- return [`${title}${range}${sceneDecoration(scene)}`];
316
- });
317
- }
318
- function fullVideo(video) {
319
- const compact3 = compactVideo(video);
320
- return {
321
- ...compact3,
322
- duration: video.duration ?? null,
323
- muxPlaybackId: String(video.muxPlaybackId ?? ""),
324
- source: String(video.source ?? ""),
325
- // The spoken transcript + AI scene breakdown of the client's own clip — the
326
- // "understand what's in this video" payload. Kept out of the compact/search
327
- // projection (too large for a list) and surfaced here on --full.
328
- transcript: video.transcript ?? null,
329
- transcript_segment_count: Array.isArray(video.transcriptSegments) ? video.transcriptSegments.length : 0,
330
- scenes: sceneLines(video.scenes),
331
- originalFilename: video.originalFilename ?? null,
332
- descriptionContext: video.descriptionContext ?? null,
333
- shotStyle: typeof asRecord(video.analysis).shotStyle === "string" ? asRecord(video.analysis).shotStyle : null,
334
- audioStyle: typeof asRecord(video.analysis).audioStyle === "string" ? asRecord(video.analysis).audioStyle : null,
335
- onScreenText: Array.isArray(asRecord(video.analysis).onScreenText) ? asRecord(video.analysis).onScreenText : [],
336
- usageNotes: typeof asRecord(video.analysis).usageNotes === "string" ? asRecord(video.analysis).usageNotes : null,
337
- createdAt: video.createdAt ?? null
338
- };
339
- }
340
- function videoNormalizer(record, full) {
341
- if (full) {
342
- return fullVideo(record);
343
- }
344
- return compactVideo(record);
345
- }
346
- function truncate(text2, maxLength) {
347
- if (text2.length <= maxLength) return text2;
348
- return `${text2.slice(0, maxLength)}...`;
349
- }
350
- function compactTestimonial(record) {
351
- return {
352
- _id: String(record._id ?? ""),
353
- reviewerName: String(record.reviewerName ?? ""),
354
- reviewText: truncate(String(record.reviewText ?? ""), 200),
355
- rating: record.rating ?? null,
356
- sentiment: record.sentiment ?? null,
357
- tags: Array.isArray(record.tags) ? record.tags : [],
358
- sourceType: String(record.sourceType ?? "")
359
- };
360
- }
361
- function fullTestimonial(record) {
362
- const compact3 = compactTestimonial(record);
363
- return {
364
- ...compact3,
365
- reviewText: String(record.reviewText ?? ""),
366
- highlight: record.highlight ?? null,
367
- summary: record.summary ?? null,
368
- language: record.language ?? null,
369
- reviewerImageUrl: record.reviewerImageUrl ?? null,
370
- sourceUrl: record.sourceUrl ?? null,
371
- reviewDate: record.reviewDate ?? null,
372
- createdAt: record.createdAt ?? null,
373
- status: String(record.status ?? "")
374
- };
375
- }
376
- function testimonialNormalizer(record, full) {
377
- if (full) {
378
- return fullTestimonial(record);
379
- }
380
- return compactTestimonial(record);
381
- }
382
- function round2(value) {
383
- if (typeof value !== "number" || Number.isNaN(value)) {
384
- return null;
385
- }
386
- return Math.round(value * 100) / 100;
387
- }
388
- function asRecord(value) {
389
- return value && typeof value === "object" ? value : {};
390
- }
391
- function compactWinningAd(record) {
392
- const dna = asRecord(record.dna);
393
- return {
394
- advertiser: String(record.advertiser ?? ""),
395
- platform: String(record.platform ?? ""),
396
- format: String(record.format ?? ""),
397
- relevance: round2(record.relevance),
398
- winner_score: round2(record.winner_score),
399
- summary: String(dna.creative_concept ?? ""),
400
- advertiser_id: String(record.advertiser_id ?? ""),
401
- ad_id: String(record.ad_id ?? ""),
402
- media_url: String(record.media_url ?? "")
403
- };
404
- }
405
- function fullWinningAd(record) {
406
- const compact3 = compactWinningAd(record);
407
- const dna = asRecord(record.dna);
408
- return {
409
- ...compact3,
410
- winner_category: String(record.winner_category ?? ""),
411
- media_kind: record.media_kind ?? null,
412
- days_active: record.days_active ?? null,
413
- reach: record.reach ?? null,
414
- active: typeof record.active === "boolean" ? record.active : null,
415
- angle: dna.angle ?? null,
416
- awareness_stage: dna.awareness_stage ?? null,
417
- target_persona: dna.target_persona ?? null,
418
- hook_archetype: dna.hook_archetype ?? null,
419
- industry: dna.industry ?? null
420
- };
421
- }
422
- function winningAdNormalizer(record, full) {
423
- if (full) {
424
- return fullWinningAd(record);
425
- }
426
- return compactWinningAd(record);
427
- }
428
- function overlayLines(value, detailed = false) {
429
- if (!Array.isArray(value)) {
430
- return [];
431
- }
432
- return value.flatMap((o) => {
433
- const item = asRecord(o);
434
- const text2 = typeof item.text === "string" ? item.text : "";
435
- if (!text2) {
436
- return [];
437
- }
438
- const at = round2(item.appears_at_s);
439
- let line = at !== null ? `${text2} @${at}s` : text2;
440
- if (detailed) {
441
- const tags = [item.role, item.position].filter((t) => typeof t === "string" && t.length > 0);
442
- if (tags.length) {
443
- line += ` [${tags.join(", ")}]`;
444
- }
445
- }
446
- return [line];
447
- });
448
- }
449
- function compactAdContent(record) {
450
- return {
451
- ad_id: record.ad_id ?? null,
452
- advertiser: record.advertiser ?? null,
453
- format: record.format ?? null,
454
- media_kind: record.media_kind ?? null,
455
- media_url: record.media_url ?? null,
456
- summary: record.summary ?? null,
457
- transcript: record.transcript ?? null,
458
- on_screen_text: overlayLines(record.on_screen_text),
459
- has_captions: typeof record.has_captions === "boolean" ? record.has_captions : null,
460
- primary_text: record.primary_text ?? null,
461
- headline: record.headline ?? null,
462
- cta: record.cta ?? null
463
- };
464
- }
465
- function fullAdContent(record) {
466
- const speech = asRecord(record.speech);
467
- const pacing = asRecord(record.pacing);
468
- const music = asRecord(record.music);
469
- return {
470
- ...compactAdContent(record),
471
- platform: record.platform ?? null,
472
- // Re-derive with role/position tags — the lean compact version dropped them.
473
- on_screen_text: overlayLines(record.on_screen_text, true),
474
- winner_score: round2(record.winner_score),
475
- winner_category: record.winner_category ?? null,
476
- angle: record.angle ?? null,
477
- hook_archetype: record.hook_archetype ?? null,
478
- caption_style: record.caption_style ?? null,
479
- has_speech: typeof record.has_speech === "boolean" ? record.has_speech : null,
480
- speech_pct_of_runtime: round2(speech.speech_pct_of_runtime),
481
- speaker_count: speech.speaker_count ?? null,
482
- voiceover_vs_sync: speech.voiceover_vs_sync ?? null,
483
- is_direct_address: typeof speech.is_direct_address === "boolean" ? speech.is_direct_address : null,
484
- speech_emotion_dominant: speech.speech_emotion_dominant ?? null,
485
- scene_count: pacing.scene_count ?? null,
486
- avg_shot_length_s: round2(pacing.avg_shot_length_s),
487
- pacing_shape: pacing.pacing_shape ?? null,
488
- hook_ends_at_s: round2(pacing.hook_ends_at_s),
489
- cta_first_shown_at_s: round2(pacing.cta_first_shown_at_s),
490
- product_first_appears_at_s: round2(pacing.product_first_appears_at_s),
491
- music_track_title: music.track_title ?? null,
492
- music_track_artist: music.track_artist ?? null
493
- };
494
- }
495
- function adContentNormalizer(record, full) {
496
- return full ? fullAdContent(record) : compactAdContent(record);
497
- }
498
- function applyFieldMask(data, fields) {
499
- const result = {};
500
- for (const field of fields) {
501
- if (field in data) {
502
- result[field] = data[field];
503
- }
504
- }
505
- return result;
506
- }
507
- function write(text2) {
508
- process.stdout.write(text2);
509
- }
510
- function writeJson(envelope) {
511
- write(`${JSON.stringify(envelope, null, 2)}
512
- `);
513
- }
514
- function writeFiles(data, fields) {
515
- write(`${fields.join(" ")}
516
- `);
517
- for (const item of data) {
518
- const values = fields.map((f) => String(item[f] ?? ""));
519
- write(`${values.join(" ")}
520
- `);
521
- }
522
- }
523
- function writeMd(data, fields) {
524
- write(`| ${fields.join(" | ")} |
525
- `);
526
- write(`| ${fields.map(() => "---").join(" | ")} |
527
- `);
528
- for (const item of data) {
529
- const values = fields.map((f) => String(item[f] ?? ""));
530
- write(`| ${values.join(" | ")} |
531
- `);
532
- }
533
- }
534
- function normalizeData(envelope, fields, full, normalizer) {
535
- const raw = Array.isArray(envelope.data) ? envelope.data : [envelope.data];
536
- const items = raw.map((item) => {
537
- const record = item;
538
- return normalizer(record, full);
539
- });
540
- if (fields) {
541
- return items.map((item) => applyFieldMask(item, fields));
542
- }
543
- return items;
544
- }
545
- function writeOutput(envelope, format, fields, full, normalizer) {
546
- if (!envelope.ok || "dryRun" in envelope) {
547
- writeJson(envelope);
548
- return;
549
- }
550
- if (format === "json") {
551
- writeJson(envelope);
552
- return;
553
- }
554
- if (format !== "files" && format !== "md") {
555
- process.stderr.write(`Unknown --output "${String(format)}". Valid formats: json, files, md. Using json.
556
- `);
557
- writeJson(envelope);
558
- return;
559
- }
560
- const items = normalizeData(envelope, fields, full ?? false, normalizer ?? imageNormalizer);
561
- const displayFields = fields ?? Object.keys(items[0] ?? {});
562
- if (format === "files") {
563
- writeFiles(items, displayFields);
564
- } else {
565
- writeMd(items, displayFields);
566
- }
567
- writeHints(envelope.hints);
568
- }
569
- function writeHints(hints2) {
570
- for (const hint of hints2 ?? []) {
571
- process.stderr.write(`${hint}
572
- `);
573
- }
574
- }
575
-
576
171
  // src/schemas.ts
577
172
  var registry = /* @__PURE__ */ new Map();
578
173
  function registerSchema(schema) {
@@ -789,50 +384,6 @@ var actionsUpdateRequestSchema = z.object({
789
384
  var actionsUpdateResponseSchema = okOnlySchema();
790
385
  var ACTION_COMPLETE_NOTE_MIN = 20;
791
386
  var actionsCompleteNoteMessage = `A completion note (\u2265${ACTION_COMPLETE_NOTE_MIN} chars) describing what was done is required: what changed, where, and the outcome.`;
792
- var ACCOUNT_CONTEXT = "(?:account|accounts|cuenta|cuentas|customer|cliente|property|propiedad|meta|facebook|google|ads|linkedin|ga4|analytics|gtm|tag manager|id)";
793
- var COMPLETION_NOTE_INTERNALS = [
794
- // Repo paths: rooted in a workspace directory, or a path or lowercase file name
795
- // ending in a source extension (a product name like "Next.js" is not a file).
796
- /(?:\.{0,2}\/)?(?:src|public|__tooling__|\.claude)\/[^\s,;:)`'"]*[^\s,;:)`'".]/g,
797
- /(?:[\w.-]*\/[\w./-]*|\b[a-z_][\w-]*)\.(?:mdx?|astro|tsx?|jsx?|mjs|json|ya?ml)\b/g,
798
- // Meta ad-account ids, Tag Manager containers and GA4 measurement ids.
799
- /\bact_\d+\b/g,
800
- /\bGTM-[A-Z0-9]{4,}\b/g,
801
- /\bG-[A-Z0-9]{6,}\b/g,
802
- // Google Ads customer ids (386-508-3115), LinkedIn, GA4 and bare Google Ads ids:
803
- // a long number named next to a platform or an account, so a phone number or
804
- // a figure elsewhere in the note is not one.
805
- new RegExp(
806
- String.raw`(?<=\b${ACCOUNT_CONTEXT}\b[^.\n\d]{0,20})(?<![\w-])(?:\d{3}-\d{3}-\d{4}|\d{7,})(?![\w-])`,
807
- "gi"
808
- ),
809
- // Error codes: SCREAMING_SNAKE in the shapes platforms and Baker return them
810
- // (NO_GOOGLE_ADS_CONNECTION, PERMISSION_DENIED, INVALID_ACCOUNT_ID).
811
- /\b(?:(?:ERR|INVALID|MISSING)(?:_[A-Z0-9]+)+|[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*_(?:ERROR|CONNECTION|FAILED|NOT_FOUND|DENIED|REQUIRED|INVALID|EXPIRED|UNAVAILABLE|EXCEEDED|UNREACHABLE|UNAUTHORIZED|FORBIDDEN|TIMEOUT|TOKEN)|BAD_REQUEST|NOT_FOUND|RATE_LIMITED)\b/g,
812
- // CLI invocations (`baker analytics flow`).
813
- /\bbaker [a-z][a-z-]*/g
814
- ];
815
- function completionNoteInternals(note) {
816
- const hits = [];
817
- for (const pattern of COMPLETION_NOTE_INTERNALS) {
818
- for (const match of note.matchAll(pattern)) {
819
- const index = match.index ?? 0;
820
- hits.push({ index, end: index + match[0].length, text: match[0] });
821
- }
822
- }
823
- hits.sort((a, b) => a.index - b.index || b.end - a.end);
824
- const found = [];
825
- let coveredUntil = -1;
826
- for (const hit of hits) {
827
- if (hit.index < coveredUntil) continue;
828
- found.push(hit.text);
829
- coveredUntil = hit.end;
830
- }
831
- return found;
832
- }
833
- function completionNoteInternalsMessage(internals) {
834
- return `The completion note is read by the client on the Task, and it carries internal details they cannot use: ${internals.join(", ")}. Rewrite it in product terms \u2014 name the page, campaign or Knowledge entry instead of a file, the platform instead of an account id, and say what is missing instead of an error code.`;
835
- }
836
387
  var actionsCompleteRequestSchema = z.object({
837
388
  chatId: z.string(),
838
389
  actionRef: z.string().min(1, "actionRef is required"),
@@ -840,11 +391,6 @@ var actionsCompleteRequestSchema = z.object({
840
391
  }).transform((body) => ({ ...body, note: (body.note ?? "").trim() })).refine((body) => body.note.length >= ACTION_COMPLETE_NOTE_MIN, {
841
392
  message: actionsCompleteNoteMessage,
842
393
  path: ["note"]
843
- }).superRefine((body, ctx) => {
844
- const internals = completionNoteInternals(body.note);
845
- if (internals.length > 0) {
846
- ctx.addIssue({ code: "custom", message: completionNoteInternalsMessage(internals), path: ["note"] });
847
- }
848
394
  });
849
395
  var actionsCompleteResponseSchema = okOnlySchema();
850
396
  var actionsDiscardRequestSchema = z.object({
@@ -2411,6 +1957,7 @@ var GOOGLE_ADS_LIMITS = {
2411
1957
  businessNameMax: 25
2412
1958
  }
2413
1959
  };
1960
+ var GOOGLE_ADS_QUERY_MAX_PAGE_SIZE = 8192;
2414
1961
  var KEYWORD_MATCH_TYPES = ["EXACT", "PHRASE", "BROAD"];
2415
1962
  var ADVERTISING_CHANNEL_TYPES = [
2416
1963
  "SEARCH",
@@ -8509,12 +8056,12 @@ var claimCommand = defineCommand({
8509
8056
  import { defineCommand as defineCommand2 } from "citty";
8510
8057
  registerSchema({
8511
8058
  command: "actions.complete",
8512
- description: 'Stage completion of an action (it was DONE). Accepts a real action ID (must be claimed) or a temp ID from the same draft. The action becomes completed when the chat is published. --note is REQUIRED and must describe what you actually did to resolve it (what changed, where, and the outcome) \u2014 it is the client-facing record shown on the Task in Tasks and surfaced by `baker actions log`, so write it for a marketer, in the client\'s language and product terms: a few lines, no file paths, account ids, error codes or `baker` commands ("Weekly review saved in your Knowledge; Google Ads and LinkedIn are not connected yet", not "written to src/content/audits/\u2026md; NO_GOOGLE_ADS_CONNECTION"). A note carrying those is refused. Do NOT use this to drop an action you no longer want \u2014 to remove a staged op use `actions draft remove`, to close an unwanted published action use `actions discard`.',
8059
+ description: "Stage completion of an action (it was DONE). Accepts a real action ID (must be claimed) or a temp ID from the same draft. The action becomes completed when the chat is published. --note is REQUIRED and must describe what you actually did to resolve it (what changed, where, and the outcome) \u2014 it is the client-facing record surfaced by `baker actions log`. Do NOT use this to drop an action you no longer want \u2014 to remove a staged op use `actions draft remove`, to close an unwanted published action use `actions discard`.",
8513
8060
  args: {
8514
8061
  id: { type: "string", description: "Action ID or temp ID", required: true },
8515
8062
  note: {
8516
8063
  type: "string",
8517
- description: "REQUIRED. What you did to resolve this \u2014 what changed, where (in product terms: the page, the campaign, Knowledge), and the outcome \u2014 in a few lines. The client reads it on the Task: no file paths, account ids, error codes or `baker` commands.",
8064
+ description: "REQUIRED. Detailed description of what you did to resolve this: what changed, where, and the outcome. This is the durable, client-facing record shown by `baker actions log`.",
8518
8065
  required: true
8519
8066
  }
8520
8067
  }
@@ -8529,7 +8076,7 @@ var completeCommand = defineCommand2({
8529
8076
  "action-id": { type: "string", description: "Action ID or temp ID", required: false },
8530
8077
  note: {
8531
8078
  type: "string",
8532
- description: "REQUIRED. What you did, where (in product terms) and the outcome \u2014 read by the client, so no file paths, account ids or error codes.",
8079
+ description: "REQUIRED. What you did to resolve this: what changed, where, and the outcome.",
8533
8080
  required: false
8534
8081
  }
8535
8082
  },
@@ -8548,10 +8095,6 @@ var completeCommand = defineCommand2({
8548
8095
  `--note is required and must describe what you did (\u2265${ACTION_COMPLETE_NOTE_MIN} chars): what changed, where, and the outcome. This becomes the client-facing record surfaced by \`baker actions log\`.`
8549
8096
  );
8550
8097
  }
8551
- const internals = completionNoteInternals(note);
8552
- if (internals.length > 0) {
8553
- failValidation(`--note: ${completionNoteInternalsMessage(internals)} Then run the same command again.`);
8554
- }
8555
8098
  const chatId = requireChatId();
8556
8099
  await apiPost("/api/actions/complete", { chatId, actionRef: id, note });
8557
8100
  writeOk();
@@ -10132,7 +9675,76 @@ function urlOptionsHints(report) {
10132
9675
  return hints2;
10133
9676
  }
10134
9677
 
9678
+ // src/commands/ads/google/during-literals.ts
9679
+ var GAQL_DURING_LITERALS = [
9680
+ "TODAY",
9681
+ "YESTERDAY",
9682
+ "LAST_7_DAYS",
9683
+ "LAST_14_DAYS",
9684
+ "LAST_30_DAYS",
9685
+ "LAST_BUSINESS_WEEK",
9686
+ "THIS_MONTH",
9687
+ "LAST_MONTH",
9688
+ "THIS_WEEK_SUN_TODAY",
9689
+ "THIS_WEEK_MON_TODAY",
9690
+ "LAST_WEEK_SUN_SAT",
9691
+ "LAST_WEEK_MON_SUN"
9692
+ ];
9693
+ function isDuringLiteral(value) {
9694
+ return GAQL_DURING_LITERALS.includes(value.toUpperCase());
9695
+ }
9696
+ function isoDay(date) {
9697
+ return date.toISOString().slice(0, 10);
9698
+ }
9699
+ function utcDate(year, month, day) {
9700
+ return new Date(Date.UTC(year, month, day));
9701
+ }
9702
+ function monthsBefore(date, months) {
9703
+ const lastDayOfTarget = utcDate(date.getUTCFullYear(), date.getUTCMonth() - months + 1, 0).getUTCDate();
9704
+ return utcDate(date.getUTCFullYear(), date.getUTCMonth() - months, Math.min(date.getUTCDate(), lastDayOfTarget));
9705
+ }
9706
+ function equivalentBetween(literal, now) {
9707
+ const upper2 = literal.toUpperCase();
9708
+ const today = utcDate(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
9709
+ const yesterday = utcDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate() - 1);
9710
+ const range = (from, to) => `BETWEEN '${isoDay(from)}' AND '${isoDay(to)}'`;
9711
+ const year = today.getUTCFullYear();
9712
+ const quarterStart = Math.floor(today.getUTCMonth() / 3) * 3;
9713
+ const rolling = upper2.match(/^LAST_(\d+)_(DAYS|WEEKS|MONTHS|YEARS)$/);
9714
+ if (rolling) {
9715
+ const n = Number(rolling[1]);
9716
+ const unit = rolling[2];
9717
+ if (n < 1) return null;
9718
+ if (unit === "DAYS" || unit === "WEEKS") {
9719
+ const days = unit === "DAYS" ? n : n * 7;
9720
+ return range(utcDate(year, today.getUTCMonth(), today.getUTCDate() - days), yesterday);
9721
+ }
9722
+ const months = unit === "MONTHS" ? n : n * 12;
9723
+ return range(monthsBefore(today, months), yesterday);
9724
+ }
9725
+ switch (upper2) {
9726
+ case "THIS_YEAR":
9727
+ return range(utcDate(year, 0, 1), today);
9728
+ case "LAST_YEAR":
9729
+ return range(utcDate(year - 1, 0, 1), utcDate(year - 1, 11, 31));
9730
+ case "THIS_QUARTER":
9731
+ return range(utcDate(year, quarterStart, 1), today);
9732
+ case "LAST_QUARTER":
9733
+ return range(utcDate(year, quarterStart - 3, 1), utcDate(year, quarterStart, 0));
9734
+ default:
9735
+ return null;
9736
+ }
9737
+ }
9738
+ function duringLiteralExplanation(literal, between) {
9739
+ const accepted = GAQL_DURING_LITERALS.join(", ");
9740
+ const lead = between ? `Google does not accept ${literal} after DURING. The same window as explicit dates is ${between}.` : `Google does not accept ${literal} after DURING, and it names no fixed window \u2014 use BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD', or drop the date filter.`;
9741
+ return `${lead} DURING accepts only: ${accepted}. Any other window needs BETWEEN.`;
9742
+ }
9743
+
10135
9744
  // src/commands/ads/google/changes-window.ts
9745
+ function monthlySpendTrendQuery(now = /* @__PURE__ */ new Date()) {
9746
+ return `SELECT campaign.name, segments.month, metrics.cost_micros, metrics.conversions FROM campaign WHERE segments.date ${equivalentBetween("LAST_12_MONTHS", now)}`;
9747
+ }
10136
9748
  function isChangeScope(value) {
10137
9749
  return CHANGE_SCOPES.includes(value);
10138
9750
  }
@@ -10186,7 +9798,7 @@ function lookbackTooLong(scope, days, max) {
10186
9798
  message: `Google's account history API reaches back ${CHANGE_SCOPE_MAX_DAYS.summary} days at most; ${days} days is outside that window.`,
10187
9799
  fix: {
10188
9800
  action: "use_different_resource",
10189
- explanation: "Do not abort \u2014 infer the change from performance instead. Query monthly metrics per campaign (SELECT campaign.name, segments.month, metrics.cost_micros, metrics.conversions FROM campaign WHERE segments.date DURING LAST_12_MONTHS) to see which campaigns stopped or started spending. That shows what changed and when, though not who changed it. Older history exists only in the Google Ads web UI, which has no API \u2014 ask the user to check it if the 'who' matters."
9801
+ explanation: `Do not abort \u2014 infer the change from performance instead. Query monthly metrics per campaign (${monthlySpendTrendQuery()}) to see which campaigns stopped or started spending. That shows what changed and when, though not who changed it. Older history exists only in the Google Ads web UI, which has no API \u2014 ask the user to check it if the 'who' matters.`
10190
9802
  }
10191
9803
  };
10192
9804
  }
@@ -10545,7 +10157,7 @@ var CORRECTION_RULES = [
10545
10157
  matchApiError: /changeStatusError\W{0,4}START_DATE_TOO_OLD/i,
10546
10158
  fix: () => ({
10547
10159
  action: "use_different_resource",
10548
- explanation: "Google's account history reaches back 90 days at most, and this query asks for more. Do not abort \u2014 infer the change from performance: SELECT campaign.name, segments.month, metrics.cost_micros, metrics.conversions FROM campaign WHERE segments.date DURING LAST_12_MONTHS shows which campaigns started or stopped spending and when, though not who changed them. Anything older with attribution exists only in the Google Ads web UI, which has no API."
10160
+ explanation: `Google's account history reaches back 90 days at most, and this query asks for more. Do not abort \u2014 infer the change from performance: ${monthlySpendTrendQuery()} shows which campaigns started or stopped spending and when, though not who changed them. Anything older with attribution exists only in the Google Ads web UI, which has no API.`
10549
10161
  })
10550
10162
  },
10551
10163
  // 18c. change_status rejects a date range it cannot bound. Google raises this both for
@@ -12372,9 +11984,16 @@ import { resolve } from "path";
12372
11984
  import { defineCommand as defineCommand28 } from "citty";
12373
11985
 
12374
11986
  // src/commands/ads/google/presets.ts
12375
- var GAQL_DATE_RANGE_RE = /^(?:TODAY|YESTERDAY|LAST_7_DAYS|LAST_14_DAYS|LAST_30_DAYS|LAST_90_DAYS|THIS_MONTH|LAST_MONTH|THIS_QUARTER|LAST_QUARTER|THIS_YEAR|LAST_YEAR|ALL_TIME|BETWEEN\s+'[0-9]{4}-[0-9]{2}-[0-9]{2}'\s+AND\s+'[0-9]{4}-[0-9]{2}-[0-9]{2}')$/i;
11987
+ var BETWEEN_RE = /^BETWEEN\s+'[0-9]{4}-[0-9]{2}-[0-9]{2}'\s+AND\s+'[0-9]{4}-[0-9]{2}-[0-9]{2}'$/i;
12376
11988
  function isValidDateRange(value) {
12377
- return GAQL_DATE_RANGE_RE.test(value.trim());
11989
+ const trimmed = value.trim();
11990
+ return isDuringLiteral(trimmed) || BETWEEN_RE.test(trimmed);
11991
+ }
11992
+ function dateRangeError(dateRange, now) {
11993
+ if (/^[A-Za-z0-9_]+$/.test(dateRange.trim())) {
11994
+ return `Invalid date range: "${dateRange}". ${duringLiteralExplanation(dateRange, equivalentBetween(dateRange, now))} Pass it as --date-range "BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'".`;
11995
+ }
11996
+ return `Invalid date range: "${dateRange}". Use a GAQL date literal (e.g. LAST_30_DAYS, TODAY) or BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'.`;
12378
11997
  }
12379
11998
  var SERVING_CHAINS = {
12380
11999
  keyword_view: ["campaign.status", "ad_group.status", "ad_group_criterion.status"],
@@ -12510,17 +12129,15 @@ function buildStatusScope(statusFields, includePaused) {
12510
12129
  const predicate = includePaused ? "!= 'REMOVED'" : "= 'ENABLED'";
12511
12130
  return statusFields.map((field) => ` AND ${field} ${predicate}`).join("");
12512
12131
  }
12513
- function expandPreset(preset, params) {
12132
+ function expandPreset(preset, params, now = /* @__PURE__ */ new Date()) {
12514
12133
  const dateRange = params.dateRange ?? preset.defaultDateRange;
12515
12134
  const limit = params.limit ?? preset.defaultLimit;
12516
- if (!isValidDateRange(dateRange)) {
12517
- return {
12518
- query: "",
12519
- dateRangeError: `Invalid date range: "${dateRange}". Use a GAQL date literal (e.g. LAST_30_DAYS, TODAY) or BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'.`
12520
- };
12135
+ if (preset.gaqlTemplate.includes("{dateRange}") && !isValidDateRange(dateRange)) {
12136
+ return { query: "", dateRangeError: dateRangeError(dateRange, now) };
12521
12137
  }
12522
12138
  const statusScope = buildStatusScope(preset.statusFields, params.includePaused ?? false);
12523
- const query = preset.gaqlTemplate.replace(/\{dateRange\}/g, dateRange).replace(/\{limit\}/g, String(limit)).replace(/\{statusScope\}/g, statusScope);
12139
+ const range = dateRange.trim();
12140
+ const query = preset.gaqlTemplate.replace(/DURING\s+\{dateRange\}/g, BETWEEN_RE.test(range) ? range : `DURING ${range}`).replace(/\{dateRange\}/g, range).replace(/\{limit\}/g, String(limit)).replace(/\{statusScope\}/g, statusScope);
12524
12141
  return { query };
12525
12142
  }
12526
12143
 
@@ -12679,6 +12296,49 @@ function applyAutoFixes(query, limit) {
12679
12296
  }
12680
12297
  return { query: corrected, warnings };
12681
12298
  }
12299
+ var DURING_CLAUSE = /(?<=[a-z_]\.[a-z_.]+\s+)DURING\s+([A-Za-z0-9_]+)/gi;
12300
+ function rejectUnsupportedDuring(query, customerId) {
12301
+ const unsupported = [...query.matchAll(DURING_CLAUSE)].map((match) => match[1] ?? "").filter((literal2) => !isDuringLiteral(literal2));
12302
+ const literal = unsupported[0];
12303
+ if (literal === void 0) return null;
12304
+ const now = /* @__PURE__ */ new Date();
12305
+ const between = equivalentBetween(literal, now);
12306
+ const fixed = unsupported.every((l) => equivalentBetween(l, now) !== null) ? query.replace(
12307
+ DURING_CLAUSE,
12308
+ (clause, l) => isDuringLiteral(l) ? clause : equivalentBetween(l, now) ?? clause
12309
+ ) : void 0;
12310
+ return {
12311
+ ok: false,
12312
+ error: {
12313
+ code: "INVALID_DATE_RANGE",
12314
+ message: `DURING ${literal} is not a Google Ads date range, so Google would refuse this query.`,
12315
+ fix: {
12316
+ action: "narrow_date_range",
12317
+ ...fixed ? { correctedCommand: buildCommand2(fixed, customerId) } : {},
12318
+ explanation: duringLiteralExplanation(literal, between)
12319
+ },
12320
+ retryable: false
12321
+ }
12322
+ };
12323
+ }
12324
+ function rejectOversizedLimit(query, customerId) {
12325
+ const match = query.match(/\bLIMIT\s+(\d+)\s*$/i);
12326
+ if (!match || Number(match[1]) <= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE) return null;
12327
+ const fixed = query.replace(/\bLIMIT\s+\d+\s*$/i, `LIMIT ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}`);
12328
+ return {
12329
+ ok: false,
12330
+ error: {
12331
+ code: "VALIDATION_ERROR",
12332
+ message: `LIMIT ${match[1]} is more rows than one read can return (${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}).`,
12333
+ fix: {
12334
+ action: "retry_with_flag",
12335
+ correctedCommand: buildCommand2(fixed, customerId),
12336
+ explanation: `One query returns at most ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE} rows. For more, split the read into narrower queries \u2014 a shorter BETWEEN range each, or one campaign each \u2014 write each with --out, and combine the files.`
12337
+ },
12338
+ retryable: false
12339
+ }
12340
+ };
12341
+ }
12682
12342
  function validatePreflight(query, customerId, limit) {
12683
12343
  const warnings = [];
12684
12344
  const corrected = query;
@@ -12750,6 +12410,10 @@ function validatePreflight(query, customerId, limit) {
12750
12410
  }
12751
12411
  };
12752
12412
  }
12413
+ const duringRejection = rejectUnsupportedDuring(corrected, customerId);
12414
+ if (duringRejection) return { valid: false, warnings: [], error: duringRejection };
12415
+ const limitRejection = rejectOversizedLimit(corrected, customerId);
12416
+ if (limitRejection) return { valid: false, warnings: [], error: limitRejection };
12753
12417
  if (/\bsegments\.conversion_action_name\b/i.test(corrected) && /\bmetrics\.cost_micros\b/i.test(corrected)) {
12754
12418
  return {
12755
12419
  valid: false,
@@ -12958,7 +12622,7 @@ registerSchema({
12958
12622
  },
12959
12623
  "date-range": {
12960
12624
  type: "string",
12961
- description: "Date range for presets (LAST_7_DAYS, LAST_30_DAYS, LAST_90_DAYS, or BETWEEN 'X' AND 'Y')",
12625
+ description: "Date range for presets: a Google date literal (TODAY, YESTERDAY, LAST_7_DAYS, LAST_14_DAYS, LAST_30_DAYS, THIS_MONTH, LAST_MONTH \u2026) or BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'. Google has no LAST_90_DAYS \u2014 use BETWEEN for any other window.",
12962
12626
  required: false
12963
12627
  },
12964
12628
  "include-paused": {
@@ -12968,7 +12632,7 @@ registerSchema({
12968
12632
  },
12969
12633
  limit: {
12970
12634
  type: "number",
12971
- description: "Max rows in ONE page (default 200), AND the LIMIT added to a query that has none \u2014 so it caps the whole read with --all too. Values up to 10000 are accepted. Any read that fills this cap comes back flagged TRUNCATED_RESULTS \u2014 never count or total from one.",
12635
+ description: "Max rows in ONE page (default 200), AND the LIMIT added to a query that has none \u2014 so it caps the whole read with --all too. Values up to 8192 are accepted \u2014 the most one read can return. Any read that fills this cap comes back flagged TRUNCATED_RESULTS \u2014 never count or total from one.",
12972
12636
  required: false,
12973
12637
  default: 200
12974
12638
  },
@@ -12976,7 +12640,7 @@ registerSchema({
12976
12640
  cursor: { type: "string", description: "Pagination cursor from previous response", required: false },
12977
12641
  all: {
12978
12642
  type: "boolean",
12979
- description: "Follow every page until the data set is exhausted (use with --out for large datasets). Needed for a read you may count or total, but not sufficient: it exhausts the pages, not the statement's LIMIT, so pair it with a --limit above the row count you expect (or your own LIMIT) and check the read came back without TRUNCATED_RESULTS.",
12643
+ description: "Follow every page until the data set is exhausted (use with --out for large datasets). Needed for a read you may count or total, but not sufficient: it exhausts the pages, not the statement's LIMIT, so pair it with a LIMIT in the statement above the row count you expect (at most 8192 \u2014 split bigger reads into narrower queries) and check the read came back without TRUNCATED_RESULTS.",
12980
12644
  required: false
12981
12645
  },
12982
12646
  out: {
@@ -13063,7 +12727,8 @@ function completenessWarnings(read) {
13063
12727
  const filledTheCap = cap3 !== void 0 && cap3 > 0 && read.rowCount >= cap3;
13064
12728
  if (!read.hasMore && !filledTheCap) return [];
13065
12729
  const cause = read.hasMore ? `More rows are waiting: the account returned a next-page cursor this run did not follow.` : `The ${read.rowCount} rows returned exactly fill the row cap in force (${cap3}).`;
13066
- const fix = read.all ? `Every page was followed, so the cap is the LIMIT in the statement itself \u2014 raise it above ${cap3}, or drop it and pass a larger --limit, then re-run.` : `Re-run with --all --out <file> to page through everything, or raise --limit above ${cap3}.`;
12730
+ const atPageCap = cap3 !== void 0 && cap3 >= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE;
12731
+ const fix = atPageCap ? `${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE} rows is the most one read can return \u2014 split it into narrower queries (a shorter BETWEEN range each, or one campaign each), write each with --out, and combine the files.` : read.all ? `Every page was followed, so the cap is the LIMIT in the statement itself \u2014 raise it above ${cap3}, or drop it and pass a larger --limit (up to ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}), then re-run.` : `Re-run with --all --out <file> to page through everything, or raise --limit above ${cap3} (up to ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}).`;
13067
12732
  return [
13068
12733
  {
13069
12734
  code: "TRUNCATED_RESULTS",
@@ -13135,8 +12800,8 @@ var writeMdRows = (rows, fields, notices) => {
13135
12800
  };
13136
12801
  var ROW_WRITERS = { csv: writeCsvRows, jsonl: writeJsonlRows, md: writeMdRows };
13137
12802
  function writeRows(format, rows, columns, notices) {
13138
- const write2 = ROW_WRITERS[format] ?? writeMdRows;
13139
- write2(rows, columns, notices);
12803
+ const write = ROW_WRITERS[format] ?? writeMdRows;
12804
+ write(rows, columns, notices);
13140
12805
  if (rows.length > REPEAT_NOTICE_ABOVE_ROWS) {
13141
12806
  for (const line of notices) writeLine(line);
13142
12807
  }
@@ -13397,9 +13062,35 @@ async function executeQuery(finalQuery, args5, customerId, limit, warnings, useC
13397
13062
  function shouldReadCache(args5) {
13398
13063
  return !args5["no-cache"] && !args5.out;
13399
13064
  }
13065
+ var DEFAULT_QUERY_LIMIT = 200;
13066
+ function parseQueryLimit(raw) {
13067
+ if (raw === void 0 || raw === null || raw === "") return { ok: true, limit: DEFAULT_QUERY_LIMIT };
13068
+ const limit = Number(raw);
13069
+ if (Number.isInteger(limit) && limit >= 1 && limit <= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE) return { ok: true, limit };
13070
+ return {
13071
+ ok: false,
13072
+ error: {
13073
+ ok: false,
13074
+ error: {
13075
+ code: "VALIDATION_ERROR",
13076
+ message: `--limit must be a whole number from 1 to ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}, got '${String(raw)}'.`,
13077
+ fix: {
13078
+ action: "retry_with_flag",
13079
+ explanation: `One read returns at most ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE} rows. For more, split the read into narrower queries (a shorter BETWEEN range each, or one campaign each), write each with --out, and combine the files.`
13080
+ },
13081
+ retryable: false
13082
+ }
13083
+ }
13084
+ };
13085
+ }
13400
13086
  async function validateArgs(args5) {
13087
+ const parsedLimit = parseQueryLimit(args5.limit);
13088
+ if (!parsedLimit.ok) {
13089
+ writeAdsJson(parsedLimit.error);
13090
+ process.exit(1);
13091
+ }
13092
+ const { limit } = parsedLimit;
13401
13093
  const customerId = await resolveCustomerId(args5);
13402
- const limit = args5.limit ? Number(args5.limit) : 200;
13403
13094
  const gaql = resolveGaql(args5, limit);
13404
13095
  if (!gaql) {
13405
13096
  process.exit(1);
@@ -22339,7 +22030,7 @@ Subcommands:
22339
22030
 
22340
22031
  // src/commands/ads/linkedin/creatives.ts
22341
22032
  import { defineCommand as defineCommand44 } from "citty";
22342
- function asRecord2(value) {
22033
+ function asRecord(value) {
22343
22034
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
22344
22035
  }
22345
22036
  function asString(value) {
@@ -22349,12 +22040,12 @@ function creativeDestination(content) {
22349
22040
  if (!content) return { format: "unknown" };
22350
22041
  const reference = asString(content.reference);
22351
22042
  if (reference) return { format: "post", postUrn: reference };
22352
- const textAd = asRecord2(content.textAd);
22043
+ const textAd = asRecord(content.textAd);
22353
22044
  if (textAd) return { format: "text", landingUrl: asString(textAd.landingPage) };
22354
- const spotlight = asRecord2(content.spotlight);
22045
+ const spotlight = asRecord(content.spotlight);
22355
22046
  if (spotlight) return { format: "spotlight", landingUrl: asString(spotlight.landingPage) };
22356
- if (asRecord2(content.follower)) return { format: "follower" };
22357
- if (asRecord2(content.jobs)) return { format: "jobs" };
22047
+ if (asRecord(content.follower)) return { format: "follower" };
22048
+ if (asRecord(content.jobs)) return { format: "jobs" };
22358
22049
  return { format: "unknown" };
22359
22050
  }
22360
22051
  var COMPACT_COLUMNS = [
@@ -22385,7 +22076,7 @@ function compactRow(row) {
22385
22076
  function readPath(row, path48) {
22386
22077
  let current = row;
22387
22078
  for (const segment of path48.split(".")) {
22388
- const record = asRecord2(current);
22079
+ const record = asRecord(current);
22389
22080
  if (!record) return void 0;
22390
22081
  current = record[segment];
22391
22082
  }
@@ -25758,10 +25449,10 @@ Example: baker ads meta ads retag 120252158038400247 --url-tags "utm_source={{si
25758
25449
  }
25759
25450
  },
25760
25451
  run: async ({ args: args5 }) => {
25761
- const { apiPost: apiPost2 } = await import("./client-PWRZOA3Q.js");
25762
- const { requireChatId: requireChatId2 } = await import("./env-TQTHWLBN.js");
25763
- const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-HD63GTWI.js");
25764
- const { handleMetaError: handleMetaError2 } = await import("./shared-5662SBDW.js");
25452
+ const { apiPost: apiPost2 } = await import("./client-ODCNYCP4.js");
25453
+ const { requireChatId: requireChatId2 } = await import("./env-BSG7TY5Y.js");
25454
+ const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-2XBCYF7L.js");
25455
+ const { handleMetaError: handleMetaError2 } = await import("./shared-RYAF7Y7H.js");
25765
25456
  try {
25766
25457
  const accountId = bareAccountId2(args5);
25767
25458
  const urlTags = typeof args5["url-tags"] === "string" ? args5["url-tags"] : void 0;
@@ -25906,10 +25597,10 @@ function duplicateCommand2(entity, label) {
25906
25597
  replace: { type: "boolean", description: "Pause the original once the copy publishes" }
25907
25598
  },
25908
25599
  run: async ({ args: args5 }) => {
25909
- const { apiPost: apiPost2 } = await import("./client-PWRZOA3Q.js");
25910
- const { requireChatId: requireChatId2 } = await import("./env-TQTHWLBN.js");
25911
- const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-HD63GTWI.js");
25912
- const { handleMetaError: handleMetaError2 } = await import("./shared-5662SBDW.js");
25600
+ const { apiPost: apiPost2 } = await import("./client-ODCNYCP4.js");
25601
+ const { requireChatId: requireChatId2 } = await import("./env-BSG7TY5Y.js");
25602
+ const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-2XBCYF7L.js");
25603
+ const { handleMetaError: handleMetaError2 } = await import("./shared-RYAF7Y7H.js");
25913
25604
  try {
25914
25605
  const accountId = bareAccountId2(args5);
25915
25606
  const chatId = requireChatId2();
@@ -31085,6 +30776,286 @@ function mergeStoredWithCounts(stored, reported) {
31085
30776
  }).sort((a, b) => a.name.localeCompare(b.name));
31086
30777
  }
31087
30778
 
30779
+ // src/commands/analytics/render.ts
30780
+ var SPARKLINE_POINTS = 30;
30781
+ var DASHBOARD_ONLY_FIELDS = /* @__PURE__ */ new Set([
30782
+ // The Convex row id. It exists so a dashboard can link to the row; an agent
30783
+ // addresses a landing by `landingId`, which is the id in its `_definition.md`.
30784
+ "landingRef"
30785
+ ]);
30786
+ function isAbsent(value) {
30787
+ if (value === "") return true;
30788
+ if (Array.isArray(value)) return value.length === 0;
30789
+ if (typeof value === "object" && value !== null) return Object.keys(value).length === 0;
30790
+ return false;
30791
+ }
30792
+ function resample(series, points) {
30793
+ if (series.length <= points || points < 1) return [...series];
30794
+ const out = [];
30795
+ for (let i = 0; i < points; i += 1) {
30796
+ const from = Math.floor(i * series.length / points);
30797
+ const to = Math.floor((i + 1) * series.length / points);
30798
+ let sum = 0;
30799
+ for (let j = from; j < to; j += 1) sum += series[j] ?? 0;
30800
+ out.push(sum);
30801
+ }
30802
+ return out;
30803
+ }
30804
+ function pruneValue(value, full) {
30805
+ if (Array.isArray(value)) return value.map((entry) => pruneValue(entry, full));
30806
+ if (typeof value === "object" && value !== null) {
30807
+ return pruneRow(value, full);
30808
+ }
30809
+ return value;
30810
+ }
30811
+ function pruneRow(row, full) {
30812
+ const out = {};
30813
+ for (const [key, value] of Object.entries(row)) {
30814
+ if (DASHBOARD_ONLY_FIELDS.has(key)) continue;
30815
+ if (key === "sparkline" && Array.isArray(value)) {
30816
+ if (!full) continue;
30817
+ out[key] = resample(value, SPARKLINE_POINTS);
30818
+ continue;
30819
+ }
30820
+ if (isAbsent(value)) continue;
30821
+ out[key] = pruneValue(value, full);
30822
+ }
30823
+ return out;
30824
+ }
30825
+ function pruneForAgent(data, full) {
30826
+ return pruneRow(data, full);
30827
+ }
30828
+ function isTabular(rows) {
30829
+ return rows.every(
30830
+ (row) => typeof row === "object" && row !== null && !Array.isArray(row) && Object.values(row).every(
30831
+ (value) => value === null || typeof value !== "object" || Array.isArray(value) && value.every((e) => typeof e !== "object")
30832
+ )
30833
+ );
30834
+ }
30835
+ function cell2(value, key) {
30836
+ if (value === null || value === void 0) return "";
30837
+ if (Array.isArray(value)) return value.join(" ");
30838
+ if (typeof value === "number" && !Number.isInteger(value)) {
30839
+ if (key?.endsWith("Ms")) return String(Math.round(value));
30840
+ return value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
30841
+ }
30842
+ return String(value);
30843
+ }
30844
+ var IDENTITY_COLUMNS = [
30845
+ "landingId",
30846
+ "name",
30847
+ "slug",
30848
+ "path",
30849
+ "flowSlug",
30850
+ "stepId",
30851
+ "value",
30852
+ "key",
30853
+ "bucket",
30854
+ "tag",
30855
+ "eventName",
30856
+ "release",
30857
+ "place",
30858
+ "bot",
30859
+ "source",
30860
+ "medium",
30861
+ "campaign",
30862
+ "platform",
30863
+ "sessionId",
30864
+ "eventId"
30865
+ ];
30866
+ function byIdentityFirst(a, b) {
30867
+ const ia = IDENTITY_COLUMNS.indexOf(a);
30868
+ const ib = IDENTITY_COLUMNS.indexOf(b);
30869
+ if (ia !== -1 || ib !== -1)
30870
+ return (ia === -1 ? Number.MAX_SAFE_INTEGER : ia) - (ib === -1 ? Number.MAX_SAFE_INTEGER : ib);
30871
+ return 0;
30872
+ }
30873
+ var BLOCK_ORDER = ["window", "totals", "previous", "pageInfo"];
30874
+ function byBlockOrder(a, b) {
30875
+ const rank = (key) => {
30876
+ const at = BLOCK_ORDER.indexOf(key);
30877
+ if (at !== -1) return at;
30878
+ return key === "preset" ? BLOCK_ORDER.length + 1 : BLOCK_ORDER.length;
30879
+ };
30880
+ return rank(a) - rank(b);
30881
+ }
30882
+ function columnsOf(rows) {
30883
+ const seen = [];
30884
+ for (const row of rows) {
30885
+ for (const key of Object.keys(row)) {
30886
+ if (!seen.includes(key)) seen.push(key);
30887
+ }
30888
+ }
30889
+ return seen.filter((key) => rows.some((row) => !isAbsent(row[key]) && row[key] !== null && row[key] !== void 0)).sort(byIdentityFirst);
30890
+ }
30891
+ function table2(rows) {
30892
+ const cols = columnsOf(rows);
30893
+ if (cols.length === 0) return "";
30894
+ const head = `| ${cols.join(" | ")} |`;
30895
+ const rule = `| ${cols.map(() => "---").join(" | ")} |`;
30896
+ const body = rows.map((row) => `| ${cols.map((col) => cell2(row[col], col)).join(" | ")} |`);
30897
+ return [head, rule, ...body].join("\n");
30898
+ }
30899
+ function isRowList(value) {
30900
+ return Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry));
30901
+ }
30902
+ function labelOf(row, index) {
30903
+ for (const key of IDENTITY_COLUMNS) {
30904
+ const value = row[key];
30905
+ if (typeof value === "string" && value !== "") return value;
30906
+ }
30907
+ return `#${index + 1}`;
30908
+ }
30909
+ function nestedBlock(name, rows) {
30910
+ const parents = rows.map(
30911
+ (row) => Object.fromEntries(Object.entries(row).filter(([, value]) => !isRowList(value) && !isPlainRow(value)))
30912
+ );
30913
+ const parts = [`### ${name}`, table2(parents)];
30914
+ rows.forEach((row, index) => {
30915
+ for (const [key, value] of Object.entries(row)) {
30916
+ if (isRowList(value)) parts.push(`#### ${name}[${labelOf(row, index)}].${key}`, table2(value));
30917
+ else if (isPlainRow(value)) parts.push(`#### ${name}[${labelOf(row, index)}].${key}`, table2([value]));
30918
+ }
30919
+ });
30920
+ return parts.filter((part) => part !== "").join("\n");
30921
+ }
30922
+ function isPlainRow(value) {
30923
+ return typeof value === "object" && value !== null && !Array.isArray(value);
30924
+ }
30925
+ function block(name, value) {
30926
+ if (Array.isArray(value)) {
30927
+ if (value.length === 0) return "";
30928
+ if (isTabular(value)) return `### ${name}
30929
+ ${table2(value)}`;
30930
+ if (isRowList(value)) return nestedBlock(name, value);
30931
+ return `### ${name}
30932
+ ${JSON.stringify(value)}`;
30933
+ }
30934
+ if (typeof value === "object" && value !== null) {
30935
+ const entries = Object.entries(value);
30936
+ if (entries.length === 0) return "";
30937
+ if (entries.every(([, v]) => v === null || typeof v !== "object")) {
30938
+ return `### ${name}
30939
+ ${entries.map(([k, v]) => `- ${k}: ${v === null ? "null" : cell2(v, k)}`).join("\n")}`;
30940
+ }
30941
+ return `### ${name}
30942
+ ${JSON.stringify(value)}`;
30943
+ }
30944
+ return `### ${name}
30945
+ ${cell2(value)}`;
30946
+ }
30947
+ function renderMarkdown3(envelope) {
30948
+ const parts = [];
30949
+ for (const warning of envelope.warnings ?? []) parts.push(`> **${warning.code}** \u2014 ${warning.message}`);
30950
+ for (const hint of envelope.hints ?? []) parts.push(`> ${hint}`);
30951
+ for (const [name, value] of Object.entries(envelope.data).sort(([a], [b]) => byBlockOrder(a, b))) {
30952
+ const rendered = block(name, value);
30953
+ if (rendered !== "") parts.push(rendered);
30954
+ }
30955
+ return `${parts.join("\n\n")}
30956
+ `;
30957
+ }
30958
+ function missingBreakdownHint(data, asked) {
30959
+ if (asked.eventName === void 0) return [];
30960
+ const breakdown = data.eventBreakdown;
30961
+ if (Array.isArray(breakdown) && breakdown.length > 0) return [];
30962
+ const known = Array.isArray(data.customEvents) ? data.customEvents.flatMap((row) => isPlainRow(row) && typeof row.eventName === "string" ? [row.eventName] : []) : [];
30963
+ return [
30964
+ `No rows came back for --event ${asked.eventName}${asked.property ? ` --property ${asked.property}` : ""}. That event or property does not exist on these pages${known.length > 0 ? `; the ones that do are: ${known.slice(0, 12).join(", ")}${known.length > 12 ? ", \u2026" : ""}` : ""}.`
30965
+ ];
30966
+ }
30967
+ var ANALYTICS_OUTPUT_FORMATS = ["json", "md"];
30968
+ function isAnalyticsOutputFormat(value) {
30969
+ return ANALYTICS_OUTPUT_FORMATS.includes(value);
30970
+ }
30971
+ function writeAnalyticsReport(envelope, format) {
30972
+ if (format === "md") {
30973
+ process.stdout.write(renderMarkdown3(envelope));
30974
+ return;
30975
+ }
30976
+ process.stdout.write(`${JSON.stringify(envelope)}
30977
+ `);
30978
+ }
30979
+ var ROWS_BEFORE_FORMAT_HINT = 25;
30980
+ function rowCount(data) {
30981
+ let total = 0;
30982
+ for (const value of Object.values(data)) {
30983
+ if (Array.isArray(value)) total += value.length;
30984
+ }
30985
+ return total;
30986
+ }
30987
+ function formatHint(data, format) {
30988
+ if (format === "md") return [];
30989
+ const rows = rowCount(data);
30990
+ if (rows < ROWS_BEFORE_FORMAT_HINT) return [];
30991
+ return [
30992
+ `This report is ${rows} rows. If you are going to read the numbers rather than pick one field out of them, --output md returns the same answer as tables, typically a third to a half smaller.`
30993
+ ];
30994
+ }
30995
+
30996
+ // src/commands/analytics/conversionsMarkdown.ts
30997
+ function doorsCell(doors) {
30998
+ return doors.map((door) => `${door.door}=${door.events}/${door.sessions}`).join(" ");
30999
+ }
31000
+ function eventKeysCell(eventKeys) {
31001
+ const formKeys = eventKeys.filter((key) => key.startsWith("form:")).length;
31002
+ const others = eventKeys.filter((key) => !key.startsWith("form:"));
31003
+ return [...others, ...formKeys > 0 ? [`${formKeys} Form ending(s)`] : []].join(" + ");
31004
+ }
31005
+ function trafficByForm(candidates) {
31006
+ const byForm = /* @__PURE__ */ new Map();
31007
+ for (const row of candidates) {
31008
+ const form = byForm.get(row.flowSlug) ?? { "endings seen": 0, "endings counted": 0, events: 0, lastSeen: "" };
31009
+ if (row.events > 0) form["endings seen"] += 1;
31010
+ if (row.isCounted) form["endings counted"] += 1;
31011
+ form.events += row.events;
31012
+ if (row.lastSeen && row.lastSeen.slice(0, 10) > form.lastSeen) form.lastSeen = row.lastSeen.slice(0, 10);
31013
+ byForm.set(row.flowSlug, form);
31014
+ }
31015
+ return byForm;
31016
+ }
31017
+ var NO_TRAFFIC = { "endings seen": 0, "endings counted": 0, events: 0, lastSeen: "" };
31018
+ var FORM_KEYS_HINT = "A Form's candidates are one row per Form: `baker analytics conversions --flow <slug>` lists that Form's event keys, and `--candidates --output json` lists every key.";
31019
+ function renderConversionsMarkdown(envelope) {
31020
+ const { definitions, flow, flows, candidates } = envelope.data;
31021
+ const data = {
31022
+ definitions: definitions.map(({ name, eventKeys, ...row }) => ({
31023
+ name,
31024
+ eventKeys: eventKeysCell(eventKeys),
31025
+ ...row
31026
+ }))
31027
+ };
31028
+ if (flow) {
31029
+ data.flow = { slug: flow.slug, noConversionReason: flow.noConversionReason };
31030
+ data.flowEndings = flow.endings;
31031
+ }
31032
+ const traffic = candidates ? trafficByForm(candidates.filter((row) => row.flowSlug !== "")) : /* @__PURE__ */ new Map();
31033
+ if (flows) {
31034
+ data.flows = flows.map((row) => ({
31035
+ slug: row.slug,
31036
+ counted: row.counted.map((entry) => `${entry.name} @ ${entry.step}`).join("; "),
31037
+ noConversion: row.noConversion,
31038
+ ...candidates ? traffic.get(row.slug) ?? NO_TRAFFIC : {}
31039
+ }));
31040
+ }
31041
+ const hints2 = [...envelope.hints ?? []];
31042
+ if (candidates) {
31043
+ const listed2 = new Set(flows?.map((row) => row.slug));
31044
+ const unlisted = [...traffic].filter(([slug]) => !listed2.has(slug));
31045
+ if (unlisted.length > 0) data.formCandidates = unlisted.map(([form, row]) => ({ form, ...row }));
31046
+ data.candidates = candidates.filter((row) => row.flowSlug === "").map(({ doors, flowSlug: _flowSlug, nodeType: _nodeType, ...row }) => ({
31047
+ ...row,
31048
+ "doors (events/sessions)": doorsCell(doors)
31049
+ }));
31050
+ if (traffic.size > 0) hints2.push(FORM_KEYS_HINT);
31051
+ }
31052
+ return renderMarkdown3({ data, ...hints2.length > 0 ? { hints: hints2 } : {} });
31053
+ }
31054
+ function withFormatHint(envelope, format) {
31055
+ const hints2 = [...envelope.hints ?? [], ...formatHint(envelope.data, format)];
31056
+ return { ...envelope, ...hints2.length > 0 ? { hints: hints2 } : {} };
31057
+ }
31058
+
31088
31059
  // src/commands/analytics/errorEnvelope.ts
31089
31060
  function analyticsErrorEnvelope(err) {
31090
31061
  if (err instanceof ApiError) {
@@ -31576,223 +31547,6 @@ var ANALYTICS_PRESET_INFO = [
31576
31547
  }
31577
31548
  ];
31578
31549
 
31579
- // src/commands/analytics/render.ts
31580
- var SPARKLINE_POINTS = 30;
31581
- var DASHBOARD_ONLY_FIELDS = /* @__PURE__ */ new Set([
31582
- // The Convex row id. It exists so a dashboard can link to the row; an agent
31583
- // addresses a landing by `landingId`, which is the id in its `_definition.md`.
31584
- "landingRef"
31585
- ]);
31586
- function isAbsent(value) {
31587
- if (value === "") return true;
31588
- if (Array.isArray(value)) return value.length === 0;
31589
- if (typeof value === "object" && value !== null) return Object.keys(value).length === 0;
31590
- return false;
31591
- }
31592
- function resample(series, points) {
31593
- if (series.length <= points || points < 1) return [...series];
31594
- const out = [];
31595
- for (let i = 0; i < points; i += 1) {
31596
- const from = Math.floor(i * series.length / points);
31597
- const to = Math.floor((i + 1) * series.length / points);
31598
- let sum = 0;
31599
- for (let j = from; j < to; j += 1) sum += series[j] ?? 0;
31600
- out.push(sum);
31601
- }
31602
- return out;
31603
- }
31604
- function pruneValue(value, full) {
31605
- if (Array.isArray(value)) return value.map((entry) => pruneValue(entry, full));
31606
- if (typeof value === "object" && value !== null) {
31607
- return pruneRow(value, full);
31608
- }
31609
- return value;
31610
- }
31611
- function pruneRow(row, full) {
31612
- const out = {};
31613
- for (const [key, value] of Object.entries(row)) {
31614
- if (DASHBOARD_ONLY_FIELDS.has(key)) continue;
31615
- if (key === "sparkline" && Array.isArray(value)) {
31616
- if (!full) continue;
31617
- out[key] = resample(value, SPARKLINE_POINTS);
31618
- continue;
31619
- }
31620
- if (isAbsent(value)) continue;
31621
- out[key] = pruneValue(value, full);
31622
- }
31623
- return out;
31624
- }
31625
- function pruneForAgent(data, full) {
31626
- return pruneRow(data, full);
31627
- }
31628
- function isTabular(rows) {
31629
- return rows.every(
31630
- (row) => typeof row === "object" && row !== null && !Array.isArray(row) && Object.values(row).every(
31631
- (value) => value === null || typeof value !== "object" || Array.isArray(value) && value.every((e) => typeof e !== "object")
31632
- )
31633
- );
31634
- }
31635
- function cell2(value, key) {
31636
- if (value === null || value === void 0) return "";
31637
- if (Array.isArray(value)) return value.join(" ");
31638
- if (typeof value === "number" && !Number.isInteger(value)) {
31639
- if (key?.endsWith("Ms")) return String(Math.round(value));
31640
- return value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
31641
- }
31642
- return String(value);
31643
- }
31644
- var IDENTITY_COLUMNS = [
31645
- "landingId",
31646
- "name",
31647
- "slug",
31648
- "path",
31649
- "flowSlug",
31650
- "stepId",
31651
- "value",
31652
- "key",
31653
- "bucket",
31654
- "tag",
31655
- "eventName",
31656
- "release",
31657
- "place",
31658
- "bot",
31659
- "source",
31660
- "medium",
31661
- "campaign",
31662
- "platform",
31663
- "sessionId",
31664
- "eventId"
31665
- ];
31666
- function byIdentityFirst(a, b) {
31667
- const ia = IDENTITY_COLUMNS.indexOf(a);
31668
- const ib = IDENTITY_COLUMNS.indexOf(b);
31669
- if (ia !== -1 || ib !== -1)
31670
- return (ia === -1 ? Number.MAX_SAFE_INTEGER : ia) - (ib === -1 ? Number.MAX_SAFE_INTEGER : ib);
31671
- return 0;
31672
- }
31673
- var BLOCK_ORDER = ["window", "totals", "previous", "pageInfo"];
31674
- function byBlockOrder(a, b) {
31675
- const rank = (key) => {
31676
- const at = BLOCK_ORDER.indexOf(key);
31677
- if (at !== -1) return at;
31678
- return key === "preset" ? BLOCK_ORDER.length + 1 : BLOCK_ORDER.length;
31679
- };
31680
- return rank(a) - rank(b);
31681
- }
31682
- function columnsOf(rows) {
31683
- const seen = [];
31684
- for (const row of rows) {
31685
- for (const key of Object.keys(row)) {
31686
- if (!seen.includes(key)) seen.push(key);
31687
- }
31688
- }
31689
- return seen.filter((key) => rows.some((row) => !isAbsent(row[key]) && row[key] !== null && row[key] !== void 0)).sort(byIdentityFirst);
31690
- }
31691
- function table2(rows) {
31692
- const cols = columnsOf(rows);
31693
- if (cols.length === 0) return "";
31694
- const head = `| ${cols.join(" | ")} |`;
31695
- const rule = `| ${cols.map(() => "---").join(" | ")} |`;
31696
- const body = rows.map((row) => `| ${cols.map((col) => cell2(row[col], col)).join(" | ")} |`);
31697
- return [head, rule, ...body].join("\n");
31698
- }
31699
- function isRowList(value) {
31700
- return Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry));
31701
- }
31702
- function labelOf(row, index) {
31703
- for (const key of IDENTITY_COLUMNS) {
31704
- const value = row[key];
31705
- if (typeof value === "string" && value !== "") return value;
31706
- }
31707
- return `#${index + 1}`;
31708
- }
31709
- function nestedBlock(name, rows) {
31710
- const parents = rows.map(
31711
- (row) => Object.fromEntries(Object.entries(row).filter(([, value]) => !isRowList(value) && !isPlainRow(value)))
31712
- );
31713
- const parts = [`### ${name}`, table2(parents)];
31714
- rows.forEach((row, index) => {
31715
- for (const [key, value] of Object.entries(row)) {
31716
- if (isRowList(value)) parts.push(`#### ${name}[${labelOf(row, index)}].${key}`, table2(value));
31717
- else if (isPlainRow(value)) parts.push(`#### ${name}[${labelOf(row, index)}].${key}`, table2([value]));
31718
- }
31719
- });
31720
- return parts.filter((part) => part !== "").join("\n");
31721
- }
31722
- function isPlainRow(value) {
31723
- return typeof value === "object" && value !== null && !Array.isArray(value);
31724
- }
31725
- function block(name, value) {
31726
- if (Array.isArray(value)) {
31727
- if (value.length === 0) return "";
31728
- if (isTabular(value)) return `### ${name}
31729
- ${table2(value)}`;
31730
- if (isRowList(value)) return nestedBlock(name, value);
31731
- return `### ${name}
31732
- ${JSON.stringify(value)}`;
31733
- }
31734
- if (typeof value === "object" && value !== null) {
31735
- const entries = Object.entries(value);
31736
- if (entries.length === 0) return "";
31737
- if (entries.every(([, v]) => v === null || typeof v !== "object")) {
31738
- return `### ${name}
31739
- ${entries.map(([k, v]) => `- ${k}: ${v === null ? "null" : cell2(v, k)}`).join("\n")}`;
31740
- }
31741
- return `### ${name}
31742
- ${JSON.stringify(value)}`;
31743
- }
31744
- return `### ${name}
31745
- ${cell2(value)}`;
31746
- }
31747
- function renderMarkdown3(envelope) {
31748
- const parts = [];
31749
- for (const warning of envelope.warnings ?? []) parts.push(`> **${warning.code}** \u2014 ${warning.message}`);
31750
- for (const hint of envelope.hints ?? []) parts.push(`> ${hint}`);
31751
- for (const [name, value] of Object.entries(envelope.data).sort(([a], [b]) => byBlockOrder(a, b))) {
31752
- const rendered = block(name, value);
31753
- if (rendered !== "") parts.push(rendered);
31754
- }
31755
- return `${parts.join("\n\n")}
31756
- `;
31757
- }
31758
- function missingBreakdownHint(data, asked) {
31759
- if (asked.eventName === void 0) return [];
31760
- const breakdown = data.eventBreakdown;
31761
- if (Array.isArray(breakdown) && breakdown.length > 0) return [];
31762
- const known = Array.isArray(data.customEvents) ? data.customEvents.flatMap((row) => isPlainRow(row) && typeof row.eventName === "string" ? [row.eventName] : []) : [];
31763
- return [
31764
- `No rows came back for --event ${asked.eventName}${asked.property ? ` --property ${asked.property}` : ""}. That event or property does not exist on these pages${known.length > 0 ? `; the ones that do are: ${known.slice(0, 12).join(", ")}${known.length > 12 ? ", \u2026" : ""}` : ""}.`
31765
- ];
31766
- }
31767
- var ANALYTICS_OUTPUT_FORMATS = ["json", "md"];
31768
- function isAnalyticsOutputFormat(value) {
31769
- return ANALYTICS_OUTPUT_FORMATS.includes(value);
31770
- }
31771
- function writeAnalyticsReport(envelope, format) {
31772
- if (format === "md") {
31773
- process.stdout.write(renderMarkdown3(envelope));
31774
- return;
31775
- }
31776
- process.stdout.write(`${JSON.stringify(envelope)}
31777
- `);
31778
- }
31779
- var ROWS_BEFORE_FORMAT_HINT = 25;
31780
- function rowCount(data) {
31781
- let total = 0;
31782
- for (const value of Object.values(data)) {
31783
- if (Array.isArray(value)) total += value.length;
31784
- }
31785
- return total;
31786
- }
31787
- function formatHint(data, format) {
31788
- if (format === "md") return [];
31789
- const rows = rowCount(data);
31790
- if (rows < ROWS_BEFORE_FORMAT_HINT) return [];
31791
- return [
31792
- `This report is ${rows} rows. If you are going to read the numbers rather than pick one field out of them, --output md returns the same answer as tables, typically a third to a half smaller.`
31793
- ];
31794
- }
31795
-
31796
31550
  // src/commands/analytics/sends.ts
31797
31551
  import { defineCommand as defineCommand97 } from "citty";
31798
31552
  var SET_FLAGS = ["--source-event", "--platform", "--tag", "--event"];
@@ -32967,17 +32721,7 @@ async function runPreset(args5, options) {
32967
32721
  const response = await apiPost("/api/analytics/query", requestBody(args5, options));
32968
32722
  const requestedPlatform = args5.platform === void 0 ? "" : String(args5.platform);
32969
32723
  const adPlatformScope = isAdPlatform(requestedPlatform) ? requestedPlatform : void 0;
32970
- const format = args5.output === void 0 ? "json" : String(args5.output);
32971
- if (!isAnalyticsOutputFormat(format)) {
32972
- writeJsonEnvelope({
32973
- ok: false,
32974
- error: {
32975
- code: "VALIDATION_ERROR",
32976
- message: `Unknown --output "${format}". Use one of: ${ANALYTICS_OUTPUT_FORMATS.join(", ")}.`
32977
- }
32978
- });
32979
- process.exit(1);
32980
- }
32724
+ const format = outputFormatOrExit(args5.output);
32981
32725
  const data = pruneForAgent(response.data, args5.full === true);
32982
32726
  const feed = "stream" in data ? shapeFeed(data.stream, args5.everything === true) : void 0;
32983
32727
  if (feed !== void 0) data.stream = feed.rows;
@@ -33007,6 +32751,20 @@ async function runPreset(args5, options) {
33007
32751
  handleError3(err);
33008
32752
  }
33009
32753
  }
32754
+ function outputFormatOrExit(raw) {
32755
+ const format = raw === void 0 ? "json" : String(raw);
32756
+ if (!isAnalyticsOutputFormat(format)) {
32757
+ writeJsonEnvelope({
32758
+ ok: false,
32759
+ error: {
32760
+ code: "VALIDATION_ERROR",
32761
+ message: `Unknown --output "${format}". Use one of: ${ANALYTICS_OUTPUT_FORMATS.join(", ")}.`
32762
+ }
32763
+ });
32764
+ process.exit(1);
32765
+ }
32766
+ return format;
32767
+ }
33010
32768
  function presetCommand(options) {
33011
32769
  registerSchema({
33012
32770
  command: `analytics.${options.name}`,
@@ -33508,7 +33266,8 @@ var conversionsCommand3 = (() => {
33508
33266
  type: "string",
33509
33267
  description: "Lookback for the counts and for --candidates. Default 30",
33510
33268
  required: false
33511
- }
33269
+ },
33270
+ output: SHARED_ARGS.output
33512
33271
  };
33513
33272
  registerSchema({
33514
33273
  command: "analytics.conversions",
@@ -33523,22 +33282,23 @@ var conversionsCommand3 = (() => {
33523
33282
  args: args5,
33524
33283
  run: async ({ args: raw }) => {
33525
33284
  try {
33285
+ const format = outputFormatOrExit(raw.output);
33526
33286
  const args6 = parseConversionsArgs(raw, process.argv);
33527
33287
  for (const eventKey of args6.eventKeys) refuseUnmatchableKey(eventKey);
33528
33288
  const before = await apiPost("/api/analytics/conversions", {});
33529
- const write2 = conversionsWrite(args6, before.data.definitions);
33530
- const stored = write2 ? (await apiPost("/api/analytics/conversions", write2)).data.definitions : before.data.definitions;
33289
+ const write = conversionsWrite(args6, before.data.definitions);
33290
+ const stored = write ? (await apiPost("/api/analytics/conversions", write)).data.definitions : before.data.definitions;
33531
33291
  const report = await apiPost("/api/analytics/query", {
33532
33292
  preset: "conversions",
33533
33293
  days: args6.days
33534
33294
  });
33535
- writeJsonEnvelope(
33536
- conversionsEnvelope(
33537
- args6,
33538
- mergeStoredWithCounts(stored, report.data.conversions ?? []),
33539
- report.data.eventCatalog ?? []
33540
- )
33295
+ const envelope = conversionsEnvelope(
33296
+ args6,
33297
+ mergeStoredWithCounts(stored, report.data.conversions ?? []),
33298
+ report.data.eventCatalog ?? []
33541
33299
  );
33300
+ if (format === "md") process.stdout.write(renderConversionsMarkdown(envelope));
33301
+ else writeJsonEnvelope(withFormatHint(envelope, format));
33542
33302
  } catch (err) {
33543
33303
  if (err instanceof ConversionArgsError) {
33544
33304
  handleError3(new ApiError("VALIDATION_ERROR", err.message));
@@ -33903,11 +33663,7 @@ function rosterRow(avatar) {
33903
33663
  };
33904
33664
  }
33905
33665
  function chatIdFromEnv() {
33906
- try {
33907
- return getEnv().BAKER_CHAT_ID || void 0;
33908
- } catch {
33909
- return void 0;
33910
- }
33666
+ return optionalChatId();
33911
33667
  }
33912
33668
  function rosterHints(roster, statusFilter) {
33913
33669
  if (roster.length === 0) {
@@ -39436,7 +39192,7 @@ function sceneSpokenText(scene) {
39436
39192
  return (scene.dialogue ?? []).map((d) => d.line?.trim()).filter((l) => Boolean(l)).join(" ") || null;
39437
39193
  }
39438
39194
  function buildMotionBoard(blueprint) {
39439
- const round5 = (n) => Math.round(n * 100) / 100;
39195
+ const round4 = (n) => Math.round(n * 100) / 100;
39440
39196
  let cursor = 0;
39441
39197
  return blueprint.scenes.map((scene, i) => {
39442
39198
  const start_s = scene.start_s ?? cursor;
@@ -39448,15 +39204,15 @@ function buildMotionBoard(blueprint) {
39448
39204
  const graphics = [
39449
39205
  ...(overlays.success ? overlays.data : []).filter((ov) => ov.text?.trim()).map((ov) => ({
39450
39206
  kind: "text",
39451
- at_s: round5(ov.appears_at_s ?? start_s),
39452
- dur_s: round5(ov.duration_s ?? 2.5),
39207
+ at_s: round4(ov.appears_at_s ?? start_s),
39208
+ dur_s: round4(ov.duration_s ?? 2.5),
39453
39209
  position: ov.position ?? "bottom_center",
39454
39210
  text: ov.text?.trim()
39455
39211
  })),
39456
39212
  ...(floats.success ? floats.data : []).map((fe) => ({
39457
39213
  kind: "graphic",
39458
- at_s: round5(fe.appears_at_s ?? start_s),
39459
- dur_s: round5(fe.duration_s ?? 2.5),
39214
+ at_s: round4(fe.appears_at_s ?? start_s),
39215
+ dur_s: round4(fe.duration_s ?? 2.5),
39460
39216
  position: fe.position ?? "bottom_center",
39461
39217
  label: fe.brand_name || fe.what_it_represents || fe.description || fe.kind || "element"
39462
39218
  }))
@@ -39464,7 +39220,7 @@ function buildMotionBoard(blueprint) {
39464
39220
  return {
39465
39221
  scene: i,
39466
39222
  role: resolveSceneRole(scene, i, blueprint.scenes.length),
39467
- window_s: [round5(start_s), round5(end_s)],
39223
+ window_s: [round4(start_s), round4(end_s)],
39468
39224
  // A continuation b-roll scene shares the previous scene's end frame as its start
39469
39225
  // (no own `s<i>_start` node), so point the storyboard at that shared keyframe.
39470
39226
  storyboard_frames: [scene.continues_previous && i > 0 ? `s${i - 1}_end` : `s${i}_start`],
@@ -41227,7 +40983,7 @@ ${describeRewrites(healed.rewrites)}
41227
40983
  // "edited since last render". Computed from the on-disk canvas.json +
41228
40984
  // prompt.json — the same helper the edit-sync uses, so the two are comparable.
41229
40985
  sourceSha: await computeSourceSha(filePath),
41230
- chatId: getEnv().BAKER_CHAT_ID || void 0,
40986
+ chatId: optionalChatId(),
41231
40987
  canvasSnapshotUrl
41232
40988
  };
41233
40989
  if (client) {
@@ -45218,11 +44974,7 @@ function detectCreativeContentType(filePath) {
45218
44974
  });
45219
44975
  }
45220
44976
  function chatIdFromEnv2() {
45221
- try {
45222
- return getEnv().BAKER_CHAT_ID || void 0;
45223
- } catch {
45224
- return void 0;
45225
- }
44977
+ return optionalChatId();
45226
44978
  }
45227
44979
  function parseOptionalUrl(value) {
45228
44980
  if (value === void 0 || value.trim() === "") {
@@ -53139,7 +52891,7 @@ var extractCommand = defineCommand149({
53139
52891
  import { defineCommand as defineCommand150 } from "citty";
53140
52892
 
53141
52893
  // src/commands/images/providerHits.ts
53142
- function asRecord3(value) {
52894
+ function asRecord2(value) {
53143
52895
  return value && typeof value === "object" ? value : {};
53144
52896
  }
53145
52897
  function str(value) {
@@ -53164,7 +52916,7 @@ function providerHitNormalizer(record, full) {
53164
52916
  if (!full) {
53165
52917
  return compact3;
53166
52918
  }
53167
- const group = asRecord3(record.group);
52919
+ const group = asRecord2(record.group);
53168
52920
  return {
53169
52921
  ...compact3,
53170
52922
  externalUrl: str(record.externalUrl),
@@ -57069,7 +56821,7 @@ function dedupe(findings) {
57069
56821
 
57070
56822
  // src/engine/landing/lib/critique.ts
57071
56823
  var FAMILIES = ["typography", "color", "borders_depth", "motion", "spacing", "copy", "integrity"];
57072
- function round4(n) {
56824
+ function round2(n) {
57073
56825
  return Math.round(n * 100) / 100;
57074
56826
  }
57075
56827
  function clamp012(n) {
@@ -57106,11 +56858,11 @@ function critiqueLanding(input) {
57106
56858
  const dimensions = FAMILIES.map((family) => {
57107
56859
  const fam = findings.filter((f) => f.family === family);
57108
56860
  const penalty = fam.reduce((s, f) => s + SEVERITY_WEIGHT[f.severity], 0);
57109
- const score = round4(clamp012(1 - penalty));
56861
+ const score = round2(clamp012(1 - penalty));
57110
56862
  const note = fam.length === 0 ? "clean" : `${fam.length} finding(s): ${describeCounts(fam)}`;
57111
56863
  return { dimension: family, score, note };
57112
56864
  });
57113
- const overall = round4(dimensions.reduce((s, d) => s + d.score, 0) / dimensions.length);
56865
+ const overall = round2(dimensions.reduce((s, d) => s + d.score, 0) / dimensions.length);
57114
56866
  const counts = {
57115
56867
  block: findings.filter((f) => f.severity === "block").length,
57116
56868
  warn: findings.filter((f) => f.severity === "warn").length,
@@ -61269,7 +61021,7 @@ function writeJsonl(data) {
61269
61021
  `);
61270
61022
  }
61271
61023
  }
61272
- function writeMd2(data, fields) {
61024
+ function writeMd(data, fields) {
61273
61025
  if (data.length === 0) return;
61274
61026
  const cols = fields ?? Object.keys(data[0] ?? {});
61275
61027
  process.stdout.write(`| ${cols.join(" | ")} |
@@ -61291,7 +61043,7 @@ function writeResearchOutput(data, format, fields) {
61291
61043
  writeJsonl(data);
61292
61044
  break;
61293
61045
  case "md":
61294
- writeMd2(data, fields);
61046
+ writeMd(data, fields);
61295
61047
  break;
61296
61048
  default:
61297
61049
  writeResearchJson({ ok: true, data });