@koda-sl/baker-cli 0.347.0-dev.043fc924c → 0.348.0-dev.78a35388f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -15
- package/dist/{chunk-KGDL7F7M.js → chunk-2IC3CGQO.js} +3 -3
- package/dist/chunk-2OKSJNLA.js +855 -0
- package/dist/chunk-2OKSJNLA.js.map +1 -0
- package/dist/{chunk-D5P4COPD.js → chunk-4QG7SF4U.js} +85 -11
- package/dist/chunk-4QG7SF4U.js.map +1 -0
- package/dist/{chunk-XPUSSDLR.js → chunk-4QXKWIFC.js} +3 -3
- package/dist/{chunk-JWMLQSGK.js → chunk-NA5CPMUA.js} +4 -4
- package/dist/{chunk-PWGY347J.js → chunk-XWHHYHKQ.js} +2 -2
- package/dist/cli.js +560 -784
- package/dist/cli.js.map +1 -1
- package/dist/{client-PWRZOA3Q.js → client-ODCNYCP4.js} +4 -4
- package/dist/engine/index.js +3 -3
- package/dist/{env-TQTHWLBN.js → env-BSG7TY5Y.js} +6 -2
- package/dist/{output-HD63GTWI.js → output-2XBCYF7L.js} +5 -5
- package/dist/{shared-5662SBDW.js → shared-RYAF7Y7H.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-4VP5WWPR.js +0 -376
- package/dist/chunk-4VP5WWPR.js.map +0 -1
- package/dist/chunk-D5P4COPD.js.map +0 -1
- /package/dist/{chunk-KGDL7F7M.js.map → chunk-2IC3CGQO.js.map} +0 -0
- /package/dist/{chunk-XPUSSDLR.js.map → chunk-4QXKWIFC.js.map} +0 -0
- /package/dist/{chunk-JWMLQSGK.js.map → chunk-NA5CPMUA.js.map} +0 -0
- /package/dist/{chunk-PWGY347J.js.map → chunk-XWHHYHKQ.js.map} +0 -0
- /package/dist/{client-PWRZOA3Q.js.map → client-ODCNYCP4.js.map} +0 -0
- /package/dist/{env-TQTHWLBN.js.map → env-BSG7TY5Y.js.map} +0 -0
- /package/dist/{output-HD63GTWI.js.map → output-2XBCYF7L.js.map} +0 -0
- /package/dist/{shared-5662SBDW.js.map → shared-RYAF7Y7H.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
YtDlpError,
|
|
24
24
|
bestSupportedQuality,
|
|
25
25
|
canonicalUrl,
|
|
26
|
+
catalogView,
|
|
26
27
|
clipAudioMode,
|
|
27
28
|
clipDurationsFor,
|
|
28
29
|
clipParamRecipe,
|
|
@@ -67,7 +68,7 @@ import {
|
|
|
67
68
|
validateCanvasDeep,
|
|
68
69
|
withoutCastName,
|
|
69
70
|
ytDlpBlockSignal
|
|
70
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-4QG7SF4U.js";
|
|
71
72
|
import {
|
|
72
73
|
csvOrJson,
|
|
73
74
|
daysAgoIso,
|
|
@@ -76,7 +77,7 @@ import {
|
|
|
76
77
|
resolveAccountIdArg,
|
|
77
78
|
resolveEffectiveStatus,
|
|
78
79
|
todayIso
|
|
79
|
-
} from "./chunk-
|
|
80
|
+
} from "./chunk-NA5CPMUA.js";
|
|
80
81
|
import {
|
|
81
82
|
buildQueryCacheKey,
|
|
82
83
|
cacheGet,
|
|
@@ -92,33 +93,42 @@ import {
|
|
|
92
93
|
writeAdsJson,
|
|
93
94
|
writeAdsOutput,
|
|
94
95
|
writeJsonEnvelope
|
|
95
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-2IC3CGQO.js";
|
|
96
97
|
import {
|
|
97
98
|
ApiError,
|
|
98
99
|
apiGet,
|
|
99
100
|
apiGetWithHeaders,
|
|
100
101
|
apiPost,
|
|
101
102
|
validateConvexId
|
|
102
|
-
} from "./chunk-
|
|
103
|
+
} from "./chunk-4QXKWIFC.js";
|
|
103
104
|
import {
|
|
104
105
|
installExitRecord,
|
|
105
106
|
installStreamTaps,
|
|
106
107
|
logInvocation
|
|
107
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-XWHHYHKQ.js";
|
|
108
109
|
import {
|
|
110
|
+
adContentNormalizer,
|
|
109
111
|
captureBudgetMs,
|
|
110
112
|
captureProxyCredentials,
|
|
111
113
|
captureVimeoCookie,
|
|
114
|
+
compactImage,
|
|
115
|
+
compactVideo,
|
|
112
116
|
getEnv,
|
|
113
117
|
hasOpenRouterKey,
|
|
114
118
|
isProxyFailure,
|
|
115
119
|
openRouterKey,
|
|
120
|
+
optionalChatId,
|
|
116
121
|
plannedRoutes,
|
|
117
122
|
refuseNonPublicUrl,
|
|
118
123
|
requireChatId,
|
|
119
124
|
resolveChatId,
|
|
120
|
-
shouldEscalate
|
|
121
|
-
|
|
125
|
+
shouldEscalate,
|
|
126
|
+
testimonialNormalizer,
|
|
127
|
+
videoNormalizer,
|
|
128
|
+
winningAdNormalizer,
|
|
129
|
+
writeJson,
|
|
130
|
+
writeOutput
|
|
131
|
+
} from "./chunk-2OKSJNLA.js";
|
|
122
132
|
|
|
123
133
|
// src/cli.ts
|
|
124
134
|
import { defineCommand as defineCommand245, runMain } from "citty";
|
|
@@ -159,420 +169,6 @@ import { defineCommand as defineCommand18 } from "citty";
|
|
|
159
169
|
// src/commands/actions/claim.ts
|
|
160
170
|
import { defineCommand } from "citty";
|
|
161
171
|
|
|
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
172
|
// src/schemas.ts
|
|
577
173
|
var registry = /* @__PURE__ */ new Map();
|
|
578
174
|
function registerSchema(schema) {
|
|
@@ -789,50 +385,6 @@ var actionsUpdateRequestSchema = z.object({
|
|
|
789
385
|
var actionsUpdateResponseSchema = okOnlySchema();
|
|
790
386
|
var ACTION_COMPLETE_NOTE_MIN = 20;
|
|
791
387
|
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
388
|
var actionsCompleteRequestSchema = z.object({
|
|
837
389
|
chatId: z.string(),
|
|
838
390
|
actionRef: z.string().min(1, "actionRef is required"),
|
|
@@ -840,11 +392,6 @@ var actionsCompleteRequestSchema = z.object({
|
|
|
840
392
|
}).transform((body) => ({ ...body, note: (body.note ?? "").trim() })).refine((body) => body.note.length >= ACTION_COMPLETE_NOTE_MIN, {
|
|
841
393
|
message: actionsCompleteNoteMessage,
|
|
842
394
|
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
395
|
});
|
|
849
396
|
var actionsCompleteResponseSchema = okOnlySchema();
|
|
850
397
|
var actionsDiscardRequestSchema = z.object({
|
|
@@ -2411,6 +1958,7 @@ var GOOGLE_ADS_LIMITS = {
|
|
|
2411
1958
|
businessNameMax: 25
|
|
2412
1959
|
}
|
|
2413
1960
|
};
|
|
1961
|
+
var GOOGLE_ADS_QUERY_MAX_PAGE_SIZE = 8192;
|
|
2414
1962
|
var KEYWORD_MATCH_TYPES = ["EXACT", "PHRASE", "BROAD"];
|
|
2415
1963
|
var ADVERTISING_CHANNEL_TYPES = [
|
|
2416
1964
|
"SEARCH",
|
|
@@ -8509,12 +8057,12 @@ var claimCommand = defineCommand({
|
|
|
8509
8057
|
import { defineCommand as defineCommand2 } from "citty";
|
|
8510
8058
|
registerSchema({
|
|
8511
8059
|
command: "actions.complete",
|
|
8512
|
-
description:
|
|
8060
|
+
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
8061
|
args: {
|
|
8514
8062
|
id: { type: "string", description: "Action ID or temp ID", required: true },
|
|
8515
8063
|
note: {
|
|
8516
8064
|
type: "string",
|
|
8517
|
-
description: "REQUIRED.
|
|
8065
|
+
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
8066
|
required: true
|
|
8519
8067
|
}
|
|
8520
8068
|
}
|
|
@@ -8529,7 +8077,7 @@ var completeCommand = defineCommand2({
|
|
|
8529
8077
|
"action-id": { type: "string", description: "Action ID or temp ID", required: false },
|
|
8530
8078
|
note: {
|
|
8531
8079
|
type: "string",
|
|
8532
|
-
description: "REQUIRED. What you did
|
|
8080
|
+
description: "REQUIRED. What you did to resolve this: what changed, where, and the outcome.",
|
|
8533
8081
|
required: false
|
|
8534
8082
|
}
|
|
8535
8083
|
},
|
|
@@ -8548,10 +8096,6 @@ var completeCommand = defineCommand2({
|
|
|
8548
8096
|
`--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
8097
|
);
|
|
8550
8098
|
}
|
|
8551
|
-
const internals = completionNoteInternals(note);
|
|
8552
|
-
if (internals.length > 0) {
|
|
8553
|
-
failValidation(`--note: ${completionNoteInternalsMessage(internals)} Then run the same command again.`);
|
|
8554
|
-
}
|
|
8555
8099
|
const chatId = requireChatId();
|
|
8556
8100
|
await apiPost("/api/actions/complete", { chatId, actionRef: id, note });
|
|
8557
8101
|
writeOk();
|
|
@@ -10132,7 +9676,76 @@ function urlOptionsHints(report) {
|
|
|
10132
9676
|
return hints2;
|
|
10133
9677
|
}
|
|
10134
9678
|
|
|
9679
|
+
// src/commands/ads/google/during-literals.ts
|
|
9680
|
+
var GAQL_DURING_LITERALS = [
|
|
9681
|
+
"TODAY",
|
|
9682
|
+
"YESTERDAY",
|
|
9683
|
+
"LAST_7_DAYS",
|
|
9684
|
+
"LAST_14_DAYS",
|
|
9685
|
+
"LAST_30_DAYS",
|
|
9686
|
+
"LAST_BUSINESS_WEEK",
|
|
9687
|
+
"THIS_MONTH",
|
|
9688
|
+
"LAST_MONTH",
|
|
9689
|
+
"THIS_WEEK_SUN_TODAY",
|
|
9690
|
+
"THIS_WEEK_MON_TODAY",
|
|
9691
|
+
"LAST_WEEK_SUN_SAT",
|
|
9692
|
+
"LAST_WEEK_MON_SUN"
|
|
9693
|
+
];
|
|
9694
|
+
function isDuringLiteral(value) {
|
|
9695
|
+
return GAQL_DURING_LITERALS.includes(value.toUpperCase());
|
|
9696
|
+
}
|
|
9697
|
+
function isoDay(date) {
|
|
9698
|
+
return date.toISOString().slice(0, 10);
|
|
9699
|
+
}
|
|
9700
|
+
function utcDate(year, month, day) {
|
|
9701
|
+
return new Date(Date.UTC(year, month, day));
|
|
9702
|
+
}
|
|
9703
|
+
function monthsBefore(date, months) {
|
|
9704
|
+
const lastDayOfTarget = utcDate(date.getUTCFullYear(), date.getUTCMonth() - months + 1, 0).getUTCDate();
|
|
9705
|
+
return utcDate(date.getUTCFullYear(), date.getUTCMonth() - months, Math.min(date.getUTCDate(), lastDayOfTarget));
|
|
9706
|
+
}
|
|
9707
|
+
function equivalentBetween(literal, now) {
|
|
9708
|
+
const upper2 = literal.toUpperCase();
|
|
9709
|
+
const today = utcDate(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
|
|
9710
|
+
const yesterday = utcDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate() - 1);
|
|
9711
|
+
const range = (from, to) => `BETWEEN '${isoDay(from)}' AND '${isoDay(to)}'`;
|
|
9712
|
+
const year = today.getUTCFullYear();
|
|
9713
|
+
const quarterStart = Math.floor(today.getUTCMonth() / 3) * 3;
|
|
9714
|
+
const rolling = upper2.match(/^LAST_(\d+)_(DAYS|WEEKS|MONTHS|YEARS)$/);
|
|
9715
|
+
if (rolling) {
|
|
9716
|
+
const n = Number(rolling[1]);
|
|
9717
|
+
const unit = rolling[2];
|
|
9718
|
+
if (n < 1) return null;
|
|
9719
|
+
if (unit === "DAYS" || unit === "WEEKS") {
|
|
9720
|
+
const days = unit === "DAYS" ? n : n * 7;
|
|
9721
|
+
return range(utcDate(year, today.getUTCMonth(), today.getUTCDate() - days), yesterday);
|
|
9722
|
+
}
|
|
9723
|
+
const months = unit === "MONTHS" ? n : n * 12;
|
|
9724
|
+
return range(monthsBefore(today, months), yesterday);
|
|
9725
|
+
}
|
|
9726
|
+
switch (upper2) {
|
|
9727
|
+
case "THIS_YEAR":
|
|
9728
|
+
return range(utcDate(year, 0, 1), today);
|
|
9729
|
+
case "LAST_YEAR":
|
|
9730
|
+
return range(utcDate(year - 1, 0, 1), utcDate(year - 1, 11, 31));
|
|
9731
|
+
case "THIS_QUARTER":
|
|
9732
|
+
return range(utcDate(year, quarterStart, 1), today);
|
|
9733
|
+
case "LAST_QUARTER":
|
|
9734
|
+
return range(utcDate(year, quarterStart - 3, 1), utcDate(year, quarterStart, 0));
|
|
9735
|
+
default:
|
|
9736
|
+
return null;
|
|
9737
|
+
}
|
|
9738
|
+
}
|
|
9739
|
+
function duringLiteralExplanation(literal, between) {
|
|
9740
|
+
const accepted = GAQL_DURING_LITERALS.join(", ");
|
|
9741
|
+
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.`;
|
|
9742
|
+
return `${lead} DURING accepts only: ${accepted}. Any other window needs BETWEEN.`;
|
|
9743
|
+
}
|
|
9744
|
+
|
|
10135
9745
|
// src/commands/ads/google/changes-window.ts
|
|
9746
|
+
function monthlySpendTrendQuery(now = /* @__PURE__ */ new Date()) {
|
|
9747
|
+
return `SELECT campaign.name, segments.month, metrics.cost_micros, metrics.conversions FROM campaign WHERE segments.date ${equivalentBetween("LAST_12_MONTHS", now)}`;
|
|
9748
|
+
}
|
|
10136
9749
|
function isChangeScope(value) {
|
|
10137
9750
|
return CHANGE_SCOPES.includes(value);
|
|
10138
9751
|
}
|
|
@@ -10186,7 +9799,7 @@ function lookbackTooLong(scope, days, max) {
|
|
|
10186
9799
|
message: `Google's account history API reaches back ${CHANGE_SCOPE_MAX_DAYS.summary} days at most; ${days} days is outside that window.`,
|
|
10187
9800
|
fix: {
|
|
10188
9801
|
action: "use_different_resource",
|
|
10189
|
-
explanation:
|
|
9802
|
+
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
9803
|
}
|
|
10191
9804
|
};
|
|
10192
9805
|
}
|
|
@@ -10545,7 +10158,7 @@ var CORRECTION_RULES = [
|
|
|
10545
10158
|
matchApiError: /changeStatusError\W{0,4}START_DATE_TOO_OLD/i,
|
|
10546
10159
|
fix: () => ({
|
|
10547
10160
|
action: "use_different_resource",
|
|
10548
|
-
explanation:
|
|
10161
|
+
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
10162
|
})
|
|
10550
10163
|
},
|
|
10551
10164
|
// 18c. change_status rejects a date range it cannot bound. Google raises this both for
|
|
@@ -12372,9 +11985,16 @@ import { resolve } from "path";
|
|
|
12372
11985
|
import { defineCommand as defineCommand28 } from "citty";
|
|
12373
11986
|
|
|
12374
11987
|
// src/commands/ads/google/presets.ts
|
|
12375
|
-
var
|
|
11988
|
+
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
11989
|
function isValidDateRange(value) {
|
|
12377
|
-
|
|
11990
|
+
const trimmed = value.trim();
|
|
11991
|
+
return isDuringLiteral(trimmed) || BETWEEN_RE.test(trimmed);
|
|
11992
|
+
}
|
|
11993
|
+
function dateRangeError(dateRange, now) {
|
|
11994
|
+
if (/^[A-Za-z0-9_]+$/.test(dateRange.trim())) {
|
|
11995
|
+
return `Invalid date range: "${dateRange}". ${duringLiteralExplanation(dateRange, equivalentBetween(dateRange, now))} Pass it as --date-range "BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'".`;
|
|
11996
|
+
}
|
|
11997
|
+
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
11998
|
}
|
|
12379
11999
|
var SERVING_CHAINS = {
|
|
12380
12000
|
keyword_view: ["campaign.status", "ad_group.status", "ad_group_criterion.status"],
|
|
@@ -12510,17 +12130,15 @@ function buildStatusScope(statusFields, includePaused) {
|
|
|
12510
12130
|
const predicate = includePaused ? "!= 'REMOVED'" : "= 'ENABLED'";
|
|
12511
12131
|
return statusFields.map((field) => ` AND ${field} ${predicate}`).join("");
|
|
12512
12132
|
}
|
|
12513
|
-
function expandPreset(preset, params) {
|
|
12133
|
+
function expandPreset(preset, params, now = /* @__PURE__ */ new Date()) {
|
|
12514
12134
|
const dateRange = params.dateRange ?? preset.defaultDateRange;
|
|
12515
12135
|
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
|
-
};
|
|
12136
|
+
if (preset.gaqlTemplate.includes("{dateRange}") && !isValidDateRange(dateRange)) {
|
|
12137
|
+
return { query: "", dateRangeError: dateRangeError(dateRange, now) };
|
|
12521
12138
|
}
|
|
12522
12139
|
const statusScope = buildStatusScope(preset.statusFields, params.includePaused ?? false);
|
|
12523
|
-
const
|
|
12140
|
+
const range = dateRange.trim();
|
|
12141
|
+
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
12142
|
return { query };
|
|
12525
12143
|
}
|
|
12526
12144
|
|
|
@@ -12679,6 +12297,49 @@ function applyAutoFixes(query, limit) {
|
|
|
12679
12297
|
}
|
|
12680
12298
|
return { query: corrected, warnings };
|
|
12681
12299
|
}
|
|
12300
|
+
var DURING_CLAUSE = /(?<=[a-z_]\.[a-z_.]+\s+)DURING\s+([A-Za-z0-9_]+)/gi;
|
|
12301
|
+
function rejectUnsupportedDuring(query, customerId) {
|
|
12302
|
+
const unsupported = [...query.matchAll(DURING_CLAUSE)].map((match) => match[1] ?? "").filter((literal2) => !isDuringLiteral(literal2));
|
|
12303
|
+
const literal = unsupported[0];
|
|
12304
|
+
if (literal === void 0) return null;
|
|
12305
|
+
const now = /* @__PURE__ */ new Date();
|
|
12306
|
+
const between = equivalentBetween(literal, now);
|
|
12307
|
+
const fixed = unsupported.every((l) => equivalentBetween(l, now) !== null) ? query.replace(
|
|
12308
|
+
DURING_CLAUSE,
|
|
12309
|
+
(clause, l) => isDuringLiteral(l) ? clause : equivalentBetween(l, now) ?? clause
|
|
12310
|
+
) : void 0;
|
|
12311
|
+
return {
|
|
12312
|
+
ok: false,
|
|
12313
|
+
error: {
|
|
12314
|
+
code: "INVALID_DATE_RANGE",
|
|
12315
|
+
message: `DURING ${literal} is not a Google Ads date range, so Google would refuse this query.`,
|
|
12316
|
+
fix: {
|
|
12317
|
+
action: "narrow_date_range",
|
|
12318
|
+
...fixed ? { correctedCommand: buildCommand2(fixed, customerId) } : {},
|
|
12319
|
+
explanation: duringLiteralExplanation(literal, between)
|
|
12320
|
+
},
|
|
12321
|
+
retryable: false
|
|
12322
|
+
}
|
|
12323
|
+
};
|
|
12324
|
+
}
|
|
12325
|
+
function rejectOversizedLimit(query, customerId) {
|
|
12326
|
+
const match = query.match(/\bLIMIT\s+(\d+)\s*$/i);
|
|
12327
|
+
if (!match || Number(match[1]) <= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE) return null;
|
|
12328
|
+
const fixed = query.replace(/\bLIMIT\s+\d+\s*$/i, `LIMIT ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}`);
|
|
12329
|
+
return {
|
|
12330
|
+
ok: false,
|
|
12331
|
+
error: {
|
|
12332
|
+
code: "VALIDATION_ERROR",
|
|
12333
|
+
message: `LIMIT ${match[1]} is more rows than one read can return (${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}).`,
|
|
12334
|
+
fix: {
|
|
12335
|
+
action: "retry_with_flag",
|
|
12336
|
+
correctedCommand: buildCommand2(fixed, customerId),
|
|
12337
|
+
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.`
|
|
12338
|
+
},
|
|
12339
|
+
retryable: false
|
|
12340
|
+
}
|
|
12341
|
+
};
|
|
12342
|
+
}
|
|
12682
12343
|
function validatePreflight(query, customerId, limit) {
|
|
12683
12344
|
const warnings = [];
|
|
12684
12345
|
const corrected = query;
|
|
@@ -12750,6 +12411,10 @@ function validatePreflight(query, customerId, limit) {
|
|
|
12750
12411
|
}
|
|
12751
12412
|
};
|
|
12752
12413
|
}
|
|
12414
|
+
const duringRejection = rejectUnsupportedDuring(corrected, customerId);
|
|
12415
|
+
if (duringRejection) return { valid: false, warnings: [], error: duringRejection };
|
|
12416
|
+
const limitRejection = rejectOversizedLimit(corrected, customerId);
|
|
12417
|
+
if (limitRejection) return { valid: false, warnings: [], error: limitRejection };
|
|
12753
12418
|
if (/\bsegments\.conversion_action_name\b/i.test(corrected) && /\bmetrics\.cost_micros\b/i.test(corrected)) {
|
|
12754
12419
|
return {
|
|
12755
12420
|
valid: false,
|
|
@@ -12958,7 +12623,7 @@ registerSchema({
|
|
|
12958
12623
|
},
|
|
12959
12624
|
"date-range": {
|
|
12960
12625
|
type: "string",
|
|
12961
|
-
description: "Date range for presets (LAST_7_DAYS, LAST_30_DAYS,
|
|
12626
|
+
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
12627
|
required: false
|
|
12963
12628
|
},
|
|
12964
12629
|
"include-paused": {
|
|
@@ -12968,7 +12633,7 @@ registerSchema({
|
|
|
12968
12633
|
},
|
|
12969
12634
|
limit: {
|
|
12970
12635
|
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
|
|
12636
|
+
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
12637
|
required: false,
|
|
12973
12638
|
default: 200
|
|
12974
12639
|
},
|
|
@@ -12976,7 +12641,7 @@ registerSchema({
|
|
|
12976
12641
|
cursor: { type: "string", description: "Pagination cursor from previous response", required: false },
|
|
12977
12642
|
all: {
|
|
12978
12643
|
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
|
|
12644
|
+
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
12645
|
required: false
|
|
12981
12646
|
},
|
|
12982
12647
|
out: {
|
|
@@ -13063,7 +12728,8 @@ function completenessWarnings(read) {
|
|
|
13063
12728
|
const filledTheCap = cap3 !== void 0 && cap3 > 0 && read.rowCount >= cap3;
|
|
13064
12729
|
if (!read.hasMore && !filledTheCap) return [];
|
|
13065
12730
|
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
|
|
12731
|
+
const atPageCap = cap3 !== void 0 && cap3 >= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE;
|
|
12732
|
+
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
12733
|
return [
|
|
13068
12734
|
{
|
|
13069
12735
|
code: "TRUNCATED_RESULTS",
|
|
@@ -13135,8 +12801,8 @@ var writeMdRows = (rows, fields, notices) => {
|
|
|
13135
12801
|
};
|
|
13136
12802
|
var ROW_WRITERS = { csv: writeCsvRows, jsonl: writeJsonlRows, md: writeMdRows };
|
|
13137
12803
|
function writeRows(format, rows, columns, notices) {
|
|
13138
|
-
const
|
|
13139
|
-
|
|
12804
|
+
const write = ROW_WRITERS[format] ?? writeMdRows;
|
|
12805
|
+
write(rows, columns, notices);
|
|
13140
12806
|
if (rows.length > REPEAT_NOTICE_ABOVE_ROWS) {
|
|
13141
12807
|
for (const line of notices) writeLine(line);
|
|
13142
12808
|
}
|
|
@@ -13397,9 +13063,35 @@ async function executeQuery(finalQuery, args5, customerId, limit, warnings, useC
|
|
|
13397
13063
|
function shouldReadCache(args5) {
|
|
13398
13064
|
return !args5["no-cache"] && !args5.out;
|
|
13399
13065
|
}
|
|
13066
|
+
var DEFAULT_QUERY_LIMIT = 200;
|
|
13067
|
+
function parseQueryLimit(raw) {
|
|
13068
|
+
if (raw === void 0 || raw === null || raw === "") return { ok: true, limit: DEFAULT_QUERY_LIMIT };
|
|
13069
|
+
const limit = Number(raw);
|
|
13070
|
+
if (Number.isInteger(limit) && limit >= 1 && limit <= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE) return { ok: true, limit };
|
|
13071
|
+
return {
|
|
13072
|
+
ok: false,
|
|
13073
|
+
error: {
|
|
13074
|
+
ok: false,
|
|
13075
|
+
error: {
|
|
13076
|
+
code: "VALIDATION_ERROR",
|
|
13077
|
+
message: `--limit must be a whole number from 1 to ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}, got '${String(raw)}'.`,
|
|
13078
|
+
fix: {
|
|
13079
|
+
action: "retry_with_flag",
|
|
13080
|
+
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.`
|
|
13081
|
+
},
|
|
13082
|
+
retryable: false
|
|
13083
|
+
}
|
|
13084
|
+
}
|
|
13085
|
+
};
|
|
13086
|
+
}
|
|
13400
13087
|
async function validateArgs(args5) {
|
|
13088
|
+
const parsedLimit = parseQueryLimit(args5.limit);
|
|
13089
|
+
if (!parsedLimit.ok) {
|
|
13090
|
+
writeAdsJson(parsedLimit.error);
|
|
13091
|
+
process.exit(1);
|
|
13092
|
+
}
|
|
13093
|
+
const { limit } = parsedLimit;
|
|
13401
13094
|
const customerId = await resolveCustomerId(args5);
|
|
13402
|
-
const limit = args5.limit ? Number(args5.limit) : 200;
|
|
13403
13095
|
const gaql = resolveGaql(args5, limit);
|
|
13404
13096
|
if (!gaql) {
|
|
13405
13097
|
process.exit(1);
|
|
@@ -22339,7 +22031,7 @@ Subcommands:
|
|
|
22339
22031
|
|
|
22340
22032
|
// src/commands/ads/linkedin/creatives.ts
|
|
22341
22033
|
import { defineCommand as defineCommand44 } from "citty";
|
|
22342
|
-
function
|
|
22034
|
+
function asRecord(value) {
|
|
22343
22035
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
22344
22036
|
}
|
|
22345
22037
|
function asString(value) {
|
|
@@ -22349,12 +22041,12 @@ function creativeDestination(content) {
|
|
|
22349
22041
|
if (!content) return { format: "unknown" };
|
|
22350
22042
|
const reference = asString(content.reference);
|
|
22351
22043
|
if (reference) return { format: "post", postUrn: reference };
|
|
22352
|
-
const textAd =
|
|
22044
|
+
const textAd = asRecord(content.textAd);
|
|
22353
22045
|
if (textAd) return { format: "text", landingUrl: asString(textAd.landingPage) };
|
|
22354
|
-
const spotlight =
|
|
22046
|
+
const spotlight = asRecord(content.spotlight);
|
|
22355
22047
|
if (spotlight) return { format: "spotlight", landingUrl: asString(spotlight.landingPage) };
|
|
22356
|
-
if (
|
|
22357
|
-
if (
|
|
22048
|
+
if (asRecord(content.follower)) return { format: "follower" };
|
|
22049
|
+
if (asRecord(content.jobs)) return { format: "jobs" };
|
|
22358
22050
|
return { format: "unknown" };
|
|
22359
22051
|
}
|
|
22360
22052
|
var COMPACT_COLUMNS = [
|
|
@@ -22385,7 +22077,7 @@ function compactRow(row) {
|
|
|
22385
22077
|
function readPath(row, path48) {
|
|
22386
22078
|
let current = row;
|
|
22387
22079
|
for (const segment of path48.split(".")) {
|
|
22388
|
-
const record =
|
|
22080
|
+
const record = asRecord(current);
|
|
22389
22081
|
if (!record) return void 0;
|
|
22390
22082
|
current = record[segment];
|
|
22391
22083
|
}
|
|
@@ -25758,10 +25450,10 @@ Example: baker ads meta ads retag 120252158038400247 --url-tags "utm_source={{si
|
|
|
25758
25450
|
}
|
|
25759
25451
|
},
|
|
25760
25452
|
run: async ({ args: args5 }) => {
|
|
25761
|
-
const { apiPost: apiPost2 } = await import("./client-
|
|
25762
|
-
const { requireChatId: requireChatId2 } = await import("./env-
|
|
25763
|
-
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-
|
|
25764
|
-
const { handleMetaError: handleMetaError2 } = await import("./shared-
|
|
25453
|
+
const { apiPost: apiPost2 } = await import("./client-ODCNYCP4.js");
|
|
25454
|
+
const { requireChatId: requireChatId2 } = await import("./env-BSG7TY5Y.js");
|
|
25455
|
+
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-2XBCYF7L.js");
|
|
25456
|
+
const { handleMetaError: handleMetaError2 } = await import("./shared-RYAF7Y7H.js");
|
|
25765
25457
|
try {
|
|
25766
25458
|
const accountId = bareAccountId2(args5);
|
|
25767
25459
|
const urlTags = typeof args5["url-tags"] === "string" ? args5["url-tags"] : void 0;
|
|
@@ -25906,10 +25598,10 @@ function duplicateCommand2(entity, label) {
|
|
|
25906
25598
|
replace: { type: "boolean", description: "Pause the original once the copy publishes" }
|
|
25907
25599
|
},
|
|
25908
25600
|
run: async ({ args: args5 }) => {
|
|
25909
|
-
const { apiPost: apiPost2 } = await import("./client-
|
|
25910
|
-
const { requireChatId: requireChatId2 } = await import("./env-
|
|
25911
|
-
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-
|
|
25912
|
-
const { handleMetaError: handleMetaError2 } = await import("./shared-
|
|
25601
|
+
const { apiPost: apiPost2 } = await import("./client-ODCNYCP4.js");
|
|
25602
|
+
const { requireChatId: requireChatId2 } = await import("./env-BSG7TY5Y.js");
|
|
25603
|
+
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-2XBCYF7L.js");
|
|
25604
|
+
const { handleMetaError: handleMetaError2 } = await import("./shared-RYAF7Y7H.js");
|
|
25913
25605
|
try {
|
|
25914
25606
|
const accountId = bareAccountId2(args5);
|
|
25915
25607
|
const chatId = requireChatId2();
|
|
@@ -31085,6 +30777,286 @@ function mergeStoredWithCounts(stored, reported) {
|
|
|
31085
30777
|
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
31086
30778
|
}
|
|
31087
30779
|
|
|
30780
|
+
// src/commands/analytics/render.ts
|
|
30781
|
+
var SPARKLINE_POINTS = 30;
|
|
30782
|
+
var DASHBOARD_ONLY_FIELDS = /* @__PURE__ */ new Set([
|
|
30783
|
+
// The Convex row id. It exists so a dashboard can link to the row; an agent
|
|
30784
|
+
// addresses a landing by `landingId`, which is the id in its `_definition.md`.
|
|
30785
|
+
"landingRef"
|
|
30786
|
+
]);
|
|
30787
|
+
function isAbsent(value) {
|
|
30788
|
+
if (value === "") return true;
|
|
30789
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
30790
|
+
if (typeof value === "object" && value !== null) return Object.keys(value).length === 0;
|
|
30791
|
+
return false;
|
|
30792
|
+
}
|
|
30793
|
+
function resample(series, points) {
|
|
30794
|
+
if (series.length <= points || points < 1) return [...series];
|
|
30795
|
+
const out = [];
|
|
30796
|
+
for (let i = 0; i < points; i += 1) {
|
|
30797
|
+
const from = Math.floor(i * series.length / points);
|
|
30798
|
+
const to = Math.floor((i + 1) * series.length / points);
|
|
30799
|
+
let sum = 0;
|
|
30800
|
+
for (let j = from; j < to; j += 1) sum += series[j] ?? 0;
|
|
30801
|
+
out.push(sum);
|
|
30802
|
+
}
|
|
30803
|
+
return out;
|
|
30804
|
+
}
|
|
30805
|
+
function pruneValue(value, full) {
|
|
30806
|
+
if (Array.isArray(value)) return value.map((entry) => pruneValue(entry, full));
|
|
30807
|
+
if (typeof value === "object" && value !== null) {
|
|
30808
|
+
return pruneRow(value, full);
|
|
30809
|
+
}
|
|
30810
|
+
return value;
|
|
30811
|
+
}
|
|
30812
|
+
function pruneRow(row, full) {
|
|
30813
|
+
const out = {};
|
|
30814
|
+
for (const [key, value] of Object.entries(row)) {
|
|
30815
|
+
if (DASHBOARD_ONLY_FIELDS.has(key)) continue;
|
|
30816
|
+
if (key === "sparkline" && Array.isArray(value)) {
|
|
30817
|
+
if (!full) continue;
|
|
30818
|
+
out[key] = resample(value, SPARKLINE_POINTS);
|
|
30819
|
+
continue;
|
|
30820
|
+
}
|
|
30821
|
+
if (isAbsent(value)) continue;
|
|
30822
|
+
out[key] = pruneValue(value, full);
|
|
30823
|
+
}
|
|
30824
|
+
return out;
|
|
30825
|
+
}
|
|
30826
|
+
function pruneForAgent(data, full) {
|
|
30827
|
+
return pruneRow(data, full);
|
|
30828
|
+
}
|
|
30829
|
+
function isTabular(rows) {
|
|
30830
|
+
return rows.every(
|
|
30831
|
+
(row) => typeof row === "object" && row !== null && !Array.isArray(row) && Object.values(row).every(
|
|
30832
|
+
(value) => value === null || typeof value !== "object" || Array.isArray(value) && value.every((e) => typeof e !== "object")
|
|
30833
|
+
)
|
|
30834
|
+
);
|
|
30835
|
+
}
|
|
30836
|
+
function cell2(value, key) {
|
|
30837
|
+
if (value === null || value === void 0) return "";
|
|
30838
|
+
if (Array.isArray(value)) return value.join(" ");
|
|
30839
|
+
if (typeof value === "number" && !Number.isInteger(value)) {
|
|
30840
|
+
if (key?.endsWith("Ms")) return String(Math.round(value));
|
|
30841
|
+
return value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
|
|
30842
|
+
}
|
|
30843
|
+
return String(value);
|
|
30844
|
+
}
|
|
30845
|
+
var IDENTITY_COLUMNS = [
|
|
30846
|
+
"landingId",
|
|
30847
|
+
"name",
|
|
30848
|
+
"slug",
|
|
30849
|
+
"path",
|
|
30850
|
+
"flowSlug",
|
|
30851
|
+
"stepId",
|
|
30852
|
+
"value",
|
|
30853
|
+
"key",
|
|
30854
|
+
"bucket",
|
|
30855
|
+
"tag",
|
|
30856
|
+
"eventName",
|
|
30857
|
+
"release",
|
|
30858
|
+
"place",
|
|
30859
|
+
"bot",
|
|
30860
|
+
"source",
|
|
30861
|
+
"medium",
|
|
30862
|
+
"campaign",
|
|
30863
|
+
"platform",
|
|
30864
|
+
"sessionId",
|
|
30865
|
+
"eventId"
|
|
30866
|
+
];
|
|
30867
|
+
function byIdentityFirst(a, b) {
|
|
30868
|
+
const ia = IDENTITY_COLUMNS.indexOf(a);
|
|
30869
|
+
const ib = IDENTITY_COLUMNS.indexOf(b);
|
|
30870
|
+
if (ia !== -1 || ib !== -1)
|
|
30871
|
+
return (ia === -1 ? Number.MAX_SAFE_INTEGER : ia) - (ib === -1 ? Number.MAX_SAFE_INTEGER : ib);
|
|
30872
|
+
return 0;
|
|
30873
|
+
}
|
|
30874
|
+
var BLOCK_ORDER = ["window", "totals", "previous", "pageInfo"];
|
|
30875
|
+
function byBlockOrder(a, b) {
|
|
30876
|
+
const rank = (key) => {
|
|
30877
|
+
const at = BLOCK_ORDER.indexOf(key);
|
|
30878
|
+
if (at !== -1) return at;
|
|
30879
|
+
return key === "preset" ? BLOCK_ORDER.length + 1 : BLOCK_ORDER.length;
|
|
30880
|
+
};
|
|
30881
|
+
return rank(a) - rank(b);
|
|
30882
|
+
}
|
|
30883
|
+
function columnsOf(rows) {
|
|
30884
|
+
const seen = [];
|
|
30885
|
+
for (const row of rows) {
|
|
30886
|
+
for (const key of Object.keys(row)) {
|
|
30887
|
+
if (!seen.includes(key)) seen.push(key);
|
|
30888
|
+
}
|
|
30889
|
+
}
|
|
30890
|
+
return seen.filter((key) => rows.some((row) => !isAbsent(row[key]) && row[key] !== null && row[key] !== void 0)).sort(byIdentityFirst);
|
|
30891
|
+
}
|
|
30892
|
+
function table2(rows) {
|
|
30893
|
+
const cols = columnsOf(rows);
|
|
30894
|
+
if (cols.length === 0) return "";
|
|
30895
|
+
const head = `| ${cols.join(" | ")} |`;
|
|
30896
|
+
const rule = `| ${cols.map(() => "---").join(" | ")} |`;
|
|
30897
|
+
const body = rows.map((row) => `| ${cols.map((col) => cell2(row[col], col)).join(" | ")} |`);
|
|
30898
|
+
return [head, rule, ...body].join("\n");
|
|
30899
|
+
}
|
|
30900
|
+
function isRowList(value) {
|
|
30901
|
+
return Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry));
|
|
30902
|
+
}
|
|
30903
|
+
function labelOf(row, index) {
|
|
30904
|
+
for (const key of IDENTITY_COLUMNS) {
|
|
30905
|
+
const value = row[key];
|
|
30906
|
+
if (typeof value === "string" && value !== "") return value;
|
|
30907
|
+
}
|
|
30908
|
+
return `#${index + 1}`;
|
|
30909
|
+
}
|
|
30910
|
+
function nestedBlock(name, rows) {
|
|
30911
|
+
const parents = rows.map(
|
|
30912
|
+
(row) => Object.fromEntries(Object.entries(row).filter(([, value]) => !isRowList(value) && !isPlainRow(value)))
|
|
30913
|
+
);
|
|
30914
|
+
const parts = [`### ${name}`, table2(parents)];
|
|
30915
|
+
rows.forEach((row, index) => {
|
|
30916
|
+
for (const [key, value] of Object.entries(row)) {
|
|
30917
|
+
if (isRowList(value)) parts.push(`#### ${name}[${labelOf(row, index)}].${key}`, table2(value));
|
|
30918
|
+
else if (isPlainRow(value)) parts.push(`#### ${name}[${labelOf(row, index)}].${key}`, table2([value]));
|
|
30919
|
+
}
|
|
30920
|
+
});
|
|
30921
|
+
return parts.filter((part) => part !== "").join("\n");
|
|
30922
|
+
}
|
|
30923
|
+
function isPlainRow(value) {
|
|
30924
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30925
|
+
}
|
|
30926
|
+
function block(name, value) {
|
|
30927
|
+
if (Array.isArray(value)) {
|
|
30928
|
+
if (value.length === 0) return "";
|
|
30929
|
+
if (isTabular(value)) return `### ${name}
|
|
30930
|
+
${table2(value)}`;
|
|
30931
|
+
if (isRowList(value)) return nestedBlock(name, value);
|
|
30932
|
+
return `### ${name}
|
|
30933
|
+
${JSON.stringify(value)}`;
|
|
30934
|
+
}
|
|
30935
|
+
if (typeof value === "object" && value !== null) {
|
|
30936
|
+
const entries = Object.entries(value);
|
|
30937
|
+
if (entries.length === 0) return "";
|
|
30938
|
+
if (entries.every(([, v]) => v === null || typeof v !== "object")) {
|
|
30939
|
+
return `### ${name}
|
|
30940
|
+
${entries.map(([k, v]) => `- ${k}: ${v === null ? "null" : cell2(v, k)}`).join("\n")}`;
|
|
30941
|
+
}
|
|
30942
|
+
return `### ${name}
|
|
30943
|
+
${JSON.stringify(value)}`;
|
|
30944
|
+
}
|
|
30945
|
+
return `### ${name}
|
|
30946
|
+
${cell2(value)}`;
|
|
30947
|
+
}
|
|
30948
|
+
function renderMarkdown3(envelope) {
|
|
30949
|
+
const parts = [];
|
|
30950
|
+
for (const warning of envelope.warnings ?? []) parts.push(`> **${warning.code}** \u2014 ${warning.message}`);
|
|
30951
|
+
for (const hint of envelope.hints ?? []) parts.push(`> ${hint}`);
|
|
30952
|
+
for (const [name, value] of Object.entries(envelope.data).sort(([a], [b]) => byBlockOrder(a, b))) {
|
|
30953
|
+
const rendered = block(name, value);
|
|
30954
|
+
if (rendered !== "") parts.push(rendered);
|
|
30955
|
+
}
|
|
30956
|
+
return `${parts.join("\n\n")}
|
|
30957
|
+
`;
|
|
30958
|
+
}
|
|
30959
|
+
function missingBreakdownHint(data, asked) {
|
|
30960
|
+
if (asked.eventName === void 0) return [];
|
|
30961
|
+
const breakdown = data.eventBreakdown;
|
|
30962
|
+
if (Array.isArray(breakdown) && breakdown.length > 0) return [];
|
|
30963
|
+
const known = Array.isArray(data.customEvents) ? data.customEvents.flatMap((row) => isPlainRow(row) && typeof row.eventName === "string" ? [row.eventName] : []) : [];
|
|
30964
|
+
return [
|
|
30965
|
+
`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" : ""}` : ""}.`
|
|
30966
|
+
];
|
|
30967
|
+
}
|
|
30968
|
+
var ANALYTICS_OUTPUT_FORMATS = ["json", "md"];
|
|
30969
|
+
function isAnalyticsOutputFormat(value) {
|
|
30970
|
+
return ANALYTICS_OUTPUT_FORMATS.includes(value);
|
|
30971
|
+
}
|
|
30972
|
+
function writeAnalyticsReport(envelope, format) {
|
|
30973
|
+
if (format === "md") {
|
|
30974
|
+
process.stdout.write(renderMarkdown3(envelope));
|
|
30975
|
+
return;
|
|
30976
|
+
}
|
|
30977
|
+
process.stdout.write(`${JSON.stringify(envelope)}
|
|
30978
|
+
`);
|
|
30979
|
+
}
|
|
30980
|
+
var ROWS_BEFORE_FORMAT_HINT = 25;
|
|
30981
|
+
function rowCount(data) {
|
|
30982
|
+
let total = 0;
|
|
30983
|
+
for (const value of Object.values(data)) {
|
|
30984
|
+
if (Array.isArray(value)) total += value.length;
|
|
30985
|
+
}
|
|
30986
|
+
return total;
|
|
30987
|
+
}
|
|
30988
|
+
function formatHint(data, format) {
|
|
30989
|
+
if (format === "md") return [];
|
|
30990
|
+
const rows = rowCount(data);
|
|
30991
|
+
if (rows < ROWS_BEFORE_FORMAT_HINT) return [];
|
|
30992
|
+
return [
|
|
30993
|
+
`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.`
|
|
30994
|
+
];
|
|
30995
|
+
}
|
|
30996
|
+
|
|
30997
|
+
// src/commands/analytics/conversionsMarkdown.ts
|
|
30998
|
+
function doorsCell(doors) {
|
|
30999
|
+
return doors.map((door) => `${door.door}=${door.events}/${door.sessions}`).join(" ");
|
|
31000
|
+
}
|
|
31001
|
+
function eventKeysCell(eventKeys) {
|
|
31002
|
+
const formKeys = eventKeys.filter((key) => key.startsWith("form:")).length;
|
|
31003
|
+
const others = eventKeys.filter((key) => !key.startsWith("form:"));
|
|
31004
|
+
return [...others, ...formKeys > 0 ? [`${formKeys} Form ending(s)`] : []].join(" + ");
|
|
31005
|
+
}
|
|
31006
|
+
function trafficByForm(candidates) {
|
|
31007
|
+
const byForm = /* @__PURE__ */ new Map();
|
|
31008
|
+
for (const row of candidates) {
|
|
31009
|
+
const form = byForm.get(row.flowSlug) ?? { "endings seen": 0, "endings counted": 0, events: 0, lastSeen: "" };
|
|
31010
|
+
if (row.events > 0) form["endings seen"] += 1;
|
|
31011
|
+
if (row.isCounted) form["endings counted"] += 1;
|
|
31012
|
+
form.events += row.events;
|
|
31013
|
+
if (row.lastSeen && row.lastSeen.slice(0, 10) > form.lastSeen) form.lastSeen = row.lastSeen.slice(0, 10);
|
|
31014
|
+
byForm.set(row.flowSlug, form);
|
|
31015
|
+
}
|
|
31016
|
+
return byForm;
|
|
31017
|
+
}
|
|
31018
|
+
var NO_TRAFFIC = { "endings seen": 0, "endings counted": 0, events: 0, lastSeen: "" };
|
|
31019
|
+
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.";
|
|
31020
|
+
function renderConversionsMarkdown(envelope) {
|
|
31021
|
+
const { definitions, flow, flows, candidates } = envelope.data;
|
|
31022
|
+
const data = {
|
|
31023
|
+
definitions: definitions.map(({ name, eventKeys, ...row }) => ({
|
|
31024
|
+
name,
|
|
31025
|
+
eventKeys: eventKeysCell(eventKeys),
|
|
31026
|
+
...row
|
|
31027
|
+
}))
|
|
31028
|
+
};
|
|
31029
|
+
if (flow) {
|
|
31030
|
+
data.flow = { slug: flow.slug, noConversionReason: flow.noConversionReason };
|
|
31031
|
+
data.flowEndings = flow.endings;
|
|
31032
|
+
}
|
|
31033
|
+
const traffic = candidates ? trafficByForm(candidates.filter((row) => row.flowSlug !== "")) : /* @__PURE__ */ new Map();
|
|
31034
|
+
if (flows) {
|
|
31035
|
+
data.flows = flows.map((row) => ({
|
|
31036
|
+
slug: row.slug,
|
|
31037
|
+
counted: row.counted.map((entry) => `${entry.name} @ ${entry.step}`).join("; "),
|
|
31038
|
+
noConversion: row.noConversion,
|
|
31039
|
+
...candidates ? traffic.get(row.slug) ?? NO_TRAFFIC : {}
|
|
31040
|
+
}));
|
|
31041
|
+
}
|
|
31042
|
+
const hints2 = [...envelope.hints ?? []];
|
|
31043
|
+
if (candidates) {
|
|
31044
|
+
const listed2 = new Set(flows?.map((row) => row.slug));
|
|
31045
|
+
const unlisted = [...traffic].filter(([slug]) => !listed2.has(slug));
|
|
31046
|
+
if (unlisted.length > 0) data.formCandidates = unlisted.map(([form, row]) => ({ form, ...row }));
|
|
31047
|
+
data.candidates = candidates.filter((row) => row.flowSlug === "").map(({ doors, flowSlug: _flowSlug, nodeType: _nodeType, ...row }) => ({
|
|
31048
|
+
...row,
|
|
31049
|
+
"doors (events/sessions)": doorsCell(doors)
|
|
31050
|
+
}));
|
|
31051
|
+
if (traffic.size > 0) hints2.push(FORM_KEYS_HINT);
|
|
31052
|
+
}
|
|
31053
|
+
return renderMarkdown3({ data, ...hints2.length > 0 ? { hints: hints2 } : {} });
|
|
31054
|
+
}
|
|
31055
|
+
function withFormatHint(envelope, format) {
|
|
31056
|
+
const hints2 = [...envelope.hints ?? [], ...formatHint(envelope.data, format)];
|
|
31057
|
+
return { ...envelope, ...hints2.length > 0 ? { hints: hints2 } : {} };
|
|
31058
|
+
}
|
|
31059
|
+
|
|
31088
31060
|
// src/commands/analytics/errorEnvelope.ts
|
|
31089
31061
|
function analyticsErrorEnvelope(err) {
|
|
31090
31062
|
if (err instanceof ApiError) {
|
|
@@ -31576,223 +31548,6 @@ var ANALYTICS_PRESET_INFO = [
|
|
|
31576
31548
|
}
|
|
31577
31549
|
];
|
|
31578
31550
|
|
|
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
31551
|
// src/commands/analytics/sends.ts
|
|
31797
31552
|
import { defineCommand as defineCommand97 } from "citty";
|
|
31798
31553
|
var SET_FLAGS = ["--source-event", "--platform", "--tag", "--event"];
|
|
@@ -32967,17 +32722,7 @@ async function runPreset(args5, options) {
|
|
|
32967
32722
|
const response = await apiPost("/api/analytics/query", requestBody(args5, options));
|
|
32968
32723
|
const requestedPlatform = args5.platform === void 0 ? "" : String(args5.platform);
|
|
32969
32724
|
const adPlatformScope = isAdPlatform(requestedPlatform) ? requestedPlatform : void 0;
|
|
32970
|
-
const format =
|
|
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
|
-
}
|
|
32725
|
+
const format = outputFormatOrExit(args5.output);
|
|
32981
32726
|
const data = pruneForAgent(response.data, args5.full === true);
|
|
32982
32727
|
const feed = "stream" in data ? shapeFeed(data.stream, args5.everything === true) : void 0;
|
|
32983
32728
|
if (feed !== void 0) data.stream = feed.rows;
|
|
@@ -33007,6 +32752,20 @@ async function runPreset(args5, options) {
|
|
|
33007
32752
|
handleError3(err);
|
|
33008
32753
|
}
|
|
33009
32754
|
}
|
|
32755
|
+
function outputFormatOrExit(raw) {
|
|
32756
|
+
const format = raw === void 0 ? "json" : String(raw);
|
|
32757
|
+
if (!isAnalyticsOutputFormat(format)) {
|
|
32758
|
+
writeJsonEnvelope({
|
|
32759
|
+
ok: false,
|
|
32760
|
+
error: {
|
|
32761
|
+
code: "VALIDATION_ERROR",
|
|
32762
|
+
message: `Unknown --output "${format}". Use one of: ${ANALYTICS_OUTPUT_FORMATS.join(", ")}.`
|
|
32763
|
+
}
|
|
32764
|
+
});
|
|
32765
|
+
process.exit(1);
|
|
32766
|
+
}
|
|
32767
|
+
return format;
|
|
32768
|
+
}
|
|
33010
32769
|
function presetCommand(options) {
|
|
33011
32770
|
registerSchema({
|
|
33012
32771
|
command: `analytics.${options.name}`,
|
|
@@ -33508,7 +33267,8 @@ var conversionsCommand3 = (() => {
|
|
|
33508
33267
|
type: "string",
|
|
33509
33268
|
description: "Lookback for the counts and for --candidates. Default 30",
|
|
33510
33269
|
required: false
|
|
33511
|
-
}
|
|
33270
|
+
},
|
|
33271
|
+
output: SHARED_ARGS.output
|
|
33512
33272
|
};
|
|
33513
33273
|
registerSchema({
|
|
33514
33274
|
command: "analytics.conversions",
|
|
@@ -33523,22 +33283,23 @@ var conversionsCommand3 = (() => {
|
|
|
33523
33283
|
args: args5,
|
|
33524
33284
|
run: async ({ args: raw }) => {
|
|
33525
33285
|
try {
|
|
33286
|
+
const format = outputFormatOrExit(raw.output);
|
|
33526
33287
|
const args6 = parseConversionsArgs(raw, process.argv);
|
|
33527
33288
|
for (const eventKey of args6.eventKeys) refuseUnmatchableKey(eventKey);
|
|
33528
33289
|
const before = await apiPost("/api/analytics/conversions", {});
|
|
33529
|
-
const
|
|
33530
|
-
const stored =
|
|
33290
|
+
const write = conversionsWrite(args6, before.data.definitions);
|
|
33291
|
+
const stored = write ? (await apiPost("/api/analytics/conversions", write)).data.definitions : before.data.definitions;
|
|
33531
33292
|
const report = await apiPost("/api/analytics/query", {
|
|
33532
33293
|
preset: "conversions",
|
|
33533
33294
|
days: args6.days
|
|
33534
33295
|
});
|
|
33535
|
-
|
|
33536
|
-
|
|
33537
|
-
|
|
33538
|
-
|
|
33539
|
-
report.data.eventCatalog ?? []
|
|
33540
|
-
)
|
|
33296
|
+
const envelope = conversionsEnvelope(
|
|
33297
|
+
args6,
|
|
33298
|
+
mergeStoredWithCounts(stored, report.data.conversions ?? []),
|
|
33299
|
+
report.data.eventCatalog ?? []
|
|
33541
33300
|
);
|
|
33301
|
+
if (format === "md") process.stdout.write(renderConversionsMarkdown(envelope));
|
|
33302
|
+
else writeJsonEnvelope(withFormatHint(envelope, format));
|
|
33542
33303
|
} catch (err) {
|
|
33543
33304
|
if (err instanceof ConversionArgsError) {
|
|
33544
33305
|
handleError3(new ApiError("VALIDATION_ERROR", err.message));
|
|
@@ -33903,11 +33664,7 @@ function rosterRow(avatar) {
|
|
|
33903
33664
|
};
|
|
33904
33665
|
}
|
|
33905
33666
|
function chatIdFromEnv() {
|
|
33906
|
-
|
|
33907
|
-
return getEnv().BAKER_CHAT_ID || void 0;
|
|
33908
|
-
} catch {
|
|
33909
|
-
return void 0;
|
|
33910
|
-
}
|
|
33667
|
+
return optionalChatId();
|
|
33911
33668
|
}
|
|
33912
33669
|
function rosterHints(roster, statusFilter) {
|
|
33913
33670
|
if (roster.length === 0) {
|
|
@@ -35761,15 +35518,38 @@ import { defineCommand as defineCommand119 } from "citty";
|
|
|
35761
35518
|
|
|
35762
35519
|
// src/commands/canvas/catalog.ts
|
|
35763
35520
|
import { defineCommand as defineCommand108 } from "citty";
|
|
35764
|
-
var
|
|
35765
|
-
|
|
35766
|
-
|
|
35767
|
-
|
|
35521
|
+
var DESCRIPTION = "The canvas node catalog. Start here: `baker canvas catalog` lists every node by category with one line each (a few KB). Then `--node video_generate` prints that node's full JSON Schema \u2014 params, inputs, outputs, models, when to use it. `--category image` narrows the list; add `--full` for that category's schemas. `--full` alone prints every schema (~160 KB) \u2014 prefer `--node`.";
|
|
35522
|
+
var ARGS = {
|
|
35523
|
+
node: {
|
|
35524
|
+
type: "string",
|
|
35525
|
+
required: false,
|
|
35526
|
+
description: "Node id(s) whose full schema to print, comma-separated. Example: --node video_generate,tts"
|
|
35768
35527
|
},
|
|
35769
|
-
|
|
35770
|
-
|
|
35771
|
-
|
|
35528
|
+
category: {
|
|
35529
|
+
type: "string",
|
|
35530
|
+
required: false,
|
|
35531
|
+
description: "Only this category (audio, video, image, \u2026). Listed with one line per node unless --full."
|
|
35532
|
+
},
|
|
35533
|
+
full: {
|
|
35534
|
+
type: "boolean",
|
|
35535
|
+
required: false,
|
|
35536
|
+
default: false,
|
|
35537
|
+
description: "Print full JSON Schemas instead of the one-line index (every node, or the --category)."
|
|
35538
|
+
}
|
|
35539
|
+
};
|
|
35540
|
+
registerSchema({ command: "canvas.catalog", description: DESCRIPTION, args: ARGS });
|
|
35541
|
+
var catalogCommand = defineCommand108({
|
|
35542
|
+
meta: { name: "catalog", description: DESCRIPTION },
|
|
35543
|
+
args: ARGS,
|
|
35544
|
+
async run({ args: args5 }) {
|
|
35545
|
+
const view = catalogView(generateCatalog(defaultRegistry()), {
|
|
35546
|
+
node: typeof args5.node === "string" ? args5.node : void 0,
|
|
35547
|
+
category: typeof args5.category === "string" ? args5.category : void 0,
|
|
35548
|
+
full: args5.full === true
|
|
35549
|
+
});
|
|
35550
|
+
process.stdout.write(`${JSON.stringify(view, null, 2)}
|
|
35772
35551
|
`);
|
|
35552
|
+
if (!view.ok) process.exit(1);
|
|
35773
35553
|
}
|
|
35774
35554
|
});
|
|
35775
35555
|
|
|
@@ -39436,7 +39216,7 @@ function sceneSpokenText(scene) {
|
|
|
39436
39216
|
return (scene.dialogue ?? []).map((d) => d.line?.trim()).filter((l) => Boolean(l)).join(" ") || null;
|
|
39437
39217
|
}
|
|
39438
39218
|
function buildMotionBoard(blueprint) {
|
|
39439
|
-
const
|
|
39219
|
+
const round4 = (n) => Math.round(n * 100) / 100;
|
|
39440
39220
|
let cursor = 0;
|
|
39441
39221
|
return blueprint.scenes.map((scene, i) => {
|
|
39442
39222
|
const start_s = scene.start_s ?? cursor;
|
|
@@ -39448,15 +39228,15 @@ function buildMotionBoard(blueprint) {
|
|
|
39448
39228
|
const graphics = [
|
|
39449
39229
|
...(overlays.success ? overlays.data : []).filter((ov) => ov.text?.trim()).map((ov) => ({
|
|
39450
39230
|
kind: "text",
|
|
39451
|
-
at_s:
|
|
39452
|
-
dur_s:
|
|
39231
|
+
at_s: round4(ov.appears_at_s ?? start_s),
|
|
39232
|
+
dur_s: round4(ov.duration_s ?? 2.5),
|
|
39453
39233
|
position: ov.position ?? "bottom_center",
|
|
39454
39234
|
text: ov.text?.trim()
|
|
39455
39235
|
})),
|
|
39456
39236
|
...(floats.success ? floats.data : []).map((fe) => ({
|
|
39457
39237
|
kind: "graphic",
|
|
39458
|
-
at_s:
|
|
39459
|
-
dur_s:
|
|
39238
|
+
at_s: round4(fe.appears_at_s ?? start_s),
|
|
39239
|
+
dur_s: round4(fe.duration_s ?? 2.5),
|
|
39460
39240
|
position: fe.position ?? "bottom_center",
|
|
39461
39241
|
label: fe.brand_name || fe.what_it_represents || fe.description || fe.kind || "element"
|
|
39462
39242
|
}))
|
|
@@ -39464,7 +39244,7 @@ function buildMotionBoard(blueprint) {
|
|
|
39464
39244
|
return {
|
|
39465
39245
|
scene: i,
|
|
39466
39246
|
role: resolveSceneRole(scene, i, blueprint.scenes.length),
|
|
39467
|
-
window_s: [
|
|
39247
|
+
window_s: [round4(start_s), round4(end_s)],
|
|
39468
39248
|
// A continuation b-roll scene shares the previous scene's end frame as its start
|
|
39469
39249
|
// (no own `s<i>_start` node), so point the storyboard at that shared keyframe.
|
|
39470
39250
|
storyboard_frames: [scene.continues_previous && i > 0 ? `s${i - 1}_end` : `s${i}_start`],
|
|
@@ -41227,7 +41007,7 @@ ${describeRewrites(healed.rewrites)}
|
|
|
41227
41007
|
// "edited since last render". Computed from the on-disk canvas.json +
|
|
41228
41008
|
// prompt.json — the same helper the edit-sync uses, so the two are comparable.
|
|
41229
41009
|
sourceSha: await computeSourceSha(filePath),
|
|
41230
|
-
chatId:
|
|
41010
|
+
chatId: optionalChatId(),
|
|
41231
41011
|
canvasSnapshotUrl
|
|
41232
41012
|
};
|
|
41233
41013
|
if (client) {
|
|
@@ -44737,7 +44517,7 @@ Subcommands:
|
|
|
44737
44517
|
baker canvas run <file.json> \u2014 execute the canvas, write outputs to ./canvas/<run_id>/
|
|
44738
44518
|
baker canvas rerun <slug> \u2014 restore a creative's latest recorded canvas from run history and run it (works in a fresh workspace)
|
|
44739
44519
|
baker canvas sync-definition <path> \u2014 push a creative's current node-graph + input thumbnails to the dashboard without rendering (runs automatically on canvas/prompt edits)
|
|
44740
|
-
baker canvas catalog \u2014
|
|
44520
|
+
baker canvas catalog \u2014 one line per node; --node <id> for its full schema
|
|
44741
44521
|
baker canvas inspect <run_id> \u2014 one-page summary of a completed run
|
|
44742
44522
|
baker canvas scaffold-ad --spec <spec.json> \u2014 DEFAULT for a new ad: write beats (one spoken line + one shot each), get the whole runnable canvas
|
|
44743
44523
|
baker canvas scaffold-video <video> \u2014 reproduce a video the USER gave you (an ad you found is inspiration, not a copy target)
|
|
@@ -45218,11 +44998,7 @@ function detectCreativeContentType(filePath) {
|
|
|
45218
44998
|
});
|
|
45219
44999
|
}
|
|
45220
45000
|
function chatIdFromEnv2() {
|
|
45221
|
-
|
|
45222
|
-
return getEnv().BAKER_CHAT_ID || void 0;
|
|
45223
|
-
} catch {
|
|
45224
|
-
return void 0;
|
|
45225
|
-
}
|
|
45001
|
+
return optionalChatId();
|
|
45226
45002
|
}
|
|
45227
45003
|
function parseOptionalUrl(value) {
|
|
45228
45004
|
if (value === void 0 || value.trim() === "") {
|
|
@@ -53139,7 +52915,7 @@ var extractCommand = defineCommand149({
|
|
|
53139
52915
|
import { defineCommand as defineCommand150 } from "citty";
|
|
53140
52916
|
|
|
53141
52917
|
// src/commands/images/providerHits.ts
|
|
53142
|
-
function
|
|
52918
|
+
function asRecord2(value) {
|
|
53143
52919
|
return value && typeof value === "object" ? value : {};
|
|
53144
52920
|
}
|
|
53145
52921
|
function str(value) {
|
|
@@ -53164,7 +52940,7 @@ function providerHitNormalizer(record, full) {
|
|
|
53164
52940
|
if (!full) {
|
|
53165
52941
|
return compact3;
|
|
53166
52942
|
}
|
|
53167
|
-
const group =
|
|
52943
|
+
const group = asRecord2(record.group);
|
|
53168
52944
|
return {
|
|
53169
52945
|
...compact3,
|
|
53170
52946
|
externalUrl: str(record.externalUrl),
|
|
@@ -57069,7 +56845,7 @@ function dedupe(findings) {
|
|
|
57069
56845
|
|
|
57070
56846
|
// src/engine/landing/lib/critique.ts
|
|
57071
56847
|
var FAMILIES = ["typography", "color", "borders_depth", "motion", "spacing", "copy", "integrity"];
|
|
57072
|
-
function
|
|
56848
|
+
function round2(n) {
|
|
57073
56849
|
return Math.round(n * 100) / 100;
|
|
57074
56850
|
}
|
|
57075
56851
|
function clamp012(n) {
|
|
@@ -57106,11 +56882,11 @@ function critiqueLanding(input) {
|
|
|
57106
56882
|
const dimensions = FAMILIES.map((family) => {
|
|
57107
56883
|
const fam = findings.filter((f) => f.family === family);
|
|
57108
56884
|
const penalty = fam.reduce((s, f) => s + SEVERITY_WEIGHT[f.severity], 0);
|
|
57109
|
-
const score =
|
|
56885
|
+
const score = round2(clamp012(1 - penalty));
|
|
57110
56886
|
const note = fam.length === 0 ? "clean" : `${fam.length} finding(s): ${describeCounts(fam)}`;
|
|
57111
56887
|
return { dimension: family, score, note };
|
|
57112
56888
|
});
|
|
57113
|
-
const overall =
|
|
56889
|
+
const overall = round2(dimensions.reduce((s, d) => s + d.score, 0) / dimensions.length);
|
|
57114
56890
|
const counts = {
|
|
57115
56891
|
block: findings.filter((f) => f.severity === "block").length,
|
|
57116
56892
|
warn: findings.filter((f) => f.severity === "warn").length,
|
|
@@ -61269,7 +61045,7 @@ function writeJsonl(data) {
|
|
|
61269
61045
|
`);
|
|
61270
61046
|
}
|
|
61271
61047
|
}
|
|
61272
|
-
function
|
|
61048
|
+
function writeMd(data, fields) {
|
|
61273
61049
|
if (data.length === 0) return;
|
|
61274
61050
|
const cols = fields ?? Object.keys(data[0] ?? {});
|
|
61275
61051
|
process.stdout.write(`| ${cols.join(" | ")} |
|
|
@@ -61291,7 +61067,7 @@ function writeResearchOutput(data, format, fields) {
|
|
|
61291
61067
|
writeJsonl(data);
|
|
61292
61068
|
break;
|
|
61293
61069
|
case "md":
|
|
61294
|
-
|
|
61070
|
+
writeMd(data, fields);
|
|
61295
61071
|
break;
|
|
61296
61072
|
default:
|
|
61297
61073
|
writeResearchJson({ ok: true, data });
|
|
@@ -64077,10 +63853,10 @@ var getCommand5 = defineCommand208({
|
|
|
64077
63853
|
|
|
64078
63854
|
// src/commands/studio/improve.ts
|
|
64079
63855
|
import { defineCommand as defineCommand209 } from "citty";
|
|
64080
|
-
var
|
|
63856
|
+
var DESCRIPTION2 = "Sharpen a rough brief into directed art direction \u2014 the same rewrite the client gets from the wand in the Studio prompt bar. Reach for it when you are relaying the CLIENT's own words and want them shaped without substituting your voice; when you are writing the art direction yourself, just write it, because you will do a better job than this does.";
|
|
64081
63857
|
registerSchema({
|
|
64082
63858
|
command: "studio.improve",
|
|
64083
|
-
description:
|
|
63859
|
+
description: DESCRIPTION2,
|
|
64084
63860
|
args: {
|
|
64085
63861
|
prompt: { type: "string", description: "The rough brief to sharpen", required: true },
|
|
64086
63862
|
kind: {
|
|
@@ -64104,7 +63880,7 @@ registerSchema({
|
|
|
64104
63880
|
var improveCommand = defineCommand209({
|
|
64105
63881
|
meta: {
|
|
64106
63882
|
name: "improve",
|
|
64107
|
-
description: `${
|
|
63883
|
+
description: `${DESCRIPTION2}
|
|
64108
63884
|
|
|
64109
63885
|
Both the rewrite and the original come back, so you can show the client what changed rather than silently replacing their words. Nothing is generated \u2014 feed the result to \`baker studio generate\` yourself.
|
|
64110
63886
|
|
|
@@ -64252,10 +64028,10 @@ var listCommand18 = defineCommand211({
|
|
|
64252
64028
|
|
|
64253
64029
|
// src/commands/studio/models.ts
|
|
64254
64030
|
import { defineCommand as defineCommand212 } from "citty";
|
|
64255
|
-
var
|
|
64031
|
+
var DESCRIPTION3 = "What each Studio model actually accepts: its shapes, resolutions, clip lengths, prompt character cap, how many reference images it takes, and which knobs it has. Read this before a batch you care about \u2014 the models disagree far more than they look like they do, and a setting the chosen model does not have is REFUSED, not ignored.";
|
|
64256
64032
|
registerSchema({
|
|
64257
64033
|
command: "studio.models",
|
|
64258
|
-
description:
|
|
64034
|
+
description: DESCRIPTION3,
|
|
64259
64035
|
args: {
|
|
64260
64036
|
kind: {
|
|
64261
64037
|
type: "string",
|
|
@@ -64341,7 +64117,7 @@ function buildModelCards(kind, model) {
|
|
|
64341
64117
|
var modelsCommand = defineCommand212({
|
|
64342
64118
|
meta: {
|
|
64343
64119
|
name: "models",
|
|
64344
|
-
description: `${
|
|
64120
|
+
description: `${DESCRIPTION3}
|
|
64345
64121
|
|
|
64346
64122
|
Everything here is read from the same table the backend validates against, so it cannot drift from what a request will actually be allowed to do.
|
|
64347
64123
|
|