@koda-sl/baker-cli 0.68.0 → 0.71.2
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 +76 -3
- package/canvas/video-overlay-composition/index.html +65 -191
- package/canvas/video-overlay-composition/meta.json +4 -15
- package/dist/{chunk-K6LHXCKD.js → chunk-JIDZ37KG.js} +77 -5
- package/dist/chunk-JIDZ37KG.js.map +1 -0
- package/dist/cli.js +714 -195
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +14 -0
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-K6LHXCKD.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ELEVENLABS_MAX_MUSIC_LENGTH_MS,
|
|
4
|
+
IMAGE_GENERATE_MODELS,
|
|
4
5
|
MODEL_REGISTRY,
|
|
5
6
|
SEEDANCE_DURATIONS,
|
|
6
7
|
ValidationError,
|
|
@@ -8,10 +9,10 @@ import {
|
|
|
8
9
|
defaultRegistry,
|
|
9
10
|
generateCatalog,
|
|
10
11
|
validateCanvasDeep
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-JIDZ37KG.js";
|
|
12
13
|
|
|
13
14
|
// src/cli.ts
|
|
14
|
-
import { defineCommand as
|
|
15
|
+
import { defineCommand as defineCommand138, runMain } from "citty";
|
|
15
16
|
|
|
16
17
|
// src/commands/actions/index.ts
|
|
17
18
|
import { defineCommand as defineCommand12 } from "citty";
|
|
@@ -146,9 +147,9 @@ async function handleResponse(response) {
|
|
|
146
147
|
throw new ApiError("INTERNAL_ERROR", "Failed to parse API response as JSON");
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
|
-
async function apiGet(
|
|
150
|
+
async function apiGet(path7, params) {
|
|
150
151
|
const env = getEnv();
|
|
151
|
-
const url = new URL(
|
|
152
|
+
const url = new URL(path7, env.BAKER_API_URL);
|
|
152
153
|
if (params) {
|
|
153
154
|
const clean = sanitizeParams(params);
|
|
154
155
|
for (const [key, value] of Object.entries(clean)) {
|
|
@@ -173,12 +174,12 @@ async function apiGet(path6, params) {
|
|
|
173
174
|
}
|
|
174
175
|
return handleResponse(response);
|
|
175
176
|
}
|
|
176
|
-
async function apiPost(
|
|
177
|
+
async function apiPost(path7, body, opts) {
|
|
177
178
|
const env = getEnv();
|
|
178
179
|
const timeoutMs = opts?.timeoutMs ?? 6e4;
|
|
179
180
|
let response;
|
|
180
181
|
try {
|
|
181
|
-
response = await fetchWithRateLimitRetry(new URL(
|
|
182
|
+
response = await fetchWithRateLimitRetry(new URL(path7, env.BAKER_API_URL).toString(), {
|
|
182
183
|
method: "POST",
|
|
183
184
|
headers: {
|
|
184
185
|
Authorization: `Bearer ${env.BAKER_API_KEY}`,
|
|
@@ -917,31 +918,31 @@ function cachePath(category, key) {
|
|
|
917
918
|
return join(dir, `${hashKey(key)}.json`);
|
|
918
919
|
}
|
|
919
920
|
function cacheGet(category, key) {
|
|
920
|
-
const
|
|
921
|
-
if (!existsSync(
|
|
921
|
+
const path7 = cachePath(category, key);
|
|
922
|
+
if (!existsSync(path7)) {
|
|
922
923
|
return null;
|
|
923
924
|
}
|
|
924
925
|
try {
|
|
925
|
-
const raw = readFileSync(
|
|
926
|
+
const raw = readFileSync(path7, "utf-8");
|
|
926
927
|
const entry = JSON.parse(raw);
|
|
927
928
|
if (entry.expiresAt < Date.now()) {
|
|
928
|
-
rmSync(
|
|
929
|
+
rmSync(path7, { force: true });
|
|
929
930
|
return null;
|
|
930
931
|
}
|
|
931
932
|
return entry;
|
|
932
933
|
} catch {
|
|
933
|
-
rmSync(
|
|
934
|
+
rmSync(path7, { force: true });
|
|
934
935
|
return null;
|
|
935
936
|
}
|
|
936
937
|
}
|
|
937
938
|
function cacheSet(category, key, data, ttlMs, fields) {
|
|
938
|
-
const
|
|
939
|
+
const path7 = cachePath(category, key);
|
|
939
940
|
const entry = {
|
|
940
941
|
expiresAt: Date.now() + ttlMs,
|
|
941
942
|
data,
|
|
942
943
|
fields
|
|
943
944
|
};
|
|
944
|
-
writeFileSync(
|
|
945
|
+
writeFileSync(path7, JSON.stringify(entry), "utf-8");
|
|
945
946
|
}
|
|
946
947
|
var HOUR = 60 * 60 * 1e3;
|
|
947
948
|
var MINUTE = 60 * 1e3;
|
|
@@ -7713,6 +7714,24 @@ async function probeDuration(filePath) {
|
|
|
7713
7714
|
import { readFile as readFile2 } from "fs/promises";
|
|
7714
7715
|
import path2 from "path";
|
|
7715
7716
|
import { defineCommand as defineCommand74 } from "citty";
|
|
7717
|
+
|
|
7718
|
+
// src/commands/canvas/placeholders.ts
|
|
7719
|
+
function unsuppliedPlaceholderAssets(canvas) {
|
|
7720
|
+
const nodes = canvas?.nodes;
|
|
7721
|
+
if (!Array.isArray(nodes)) return [];
|
|
7722
|
+
const out = [];
|
|
7723
|
+
for (const n of nodes) {
|
|
7724
|
+
const node = n;
|
|
7725
|
+
if (node?.type !== "ingest") continue;
|
|
7726
|
+
const params = node.params;
|
|
7727
|
+
if (params?.source === "path" && typeof params.path === "string" && params.path.includes("[TODO")) {
|
|
7728
|
+
out.push({ node: String(node.id ?? "?"), placeholder: params.path });
|
|
7729
|
+
}
|
|
7730
|
+
}
|
|
7731
|
+
return out;
|
|
7732
|
+
}
|
|
7733
|
+
|
|
7734
|
+
// src/commands/canvas/run.ts
|
|
7716
7735
|
var runCommand = defineCommand74({
|
|
7717
7736
|
meta: { name: "run", description: "Validate and execute a canvas JSON file." },
|
|
7718
7737
|
args: {
|
|
@@ -7734,6 +7753,25 @@ var runCommand = defineCommand74({
|
|
|
7734
7753
|
`);
|
|
7735
7754
|
process.exit(2);
|
|
7736
7755
|
}
|
|
7756
|
+
const pending = unsuppliedPlaceholderAssets(parsed);
|
|
7757
|
+
if (pending.length > 0) {
|
|
7758
|
+
process.stderr.write(
|
|
7759
|
+
`${JSON.stringify(
|
|
7760
|
+
{
|
|
7761
|
+
ok: false,
|
|
7762
|
+
error: {
|
|
7763
|
+
code: "unsupplied_assets",
|
|
7764
|
+
message: "This canvas still has placeholder asset slots \u2014 supply a real image (or video) at each before running. Each `el_*` ingest is a [TODO] you fill with the real logo/subject/product, then re-run.",
|
|
7765
|
+
assets: pending
|
|
7766
|
+
}
|
|
7767
|
+
},
|
|
7768
|
+
null,
|
|
7769
|
+
2
|
|
7770
|
+
)}
|
|
7771
|
+
`
|
|
7772
|
+
);
|
|
7773
|
+
process.exit(2);
|
|
7774
|
+
}
|
|
7737
7775
|
const engine = createEngineFromEnv({
|
|
7738
7776
|
cacheDir: args["cache-dir"] ? String(args["cache-dir"]) : void 0,
|
|
7739
7777
|
outputsDir: args["outputs-dir"] ? String(args["outputs-dir"]) : void 0,
|
|
@@ -8191,7 +8229,7 @@ var scaffoldStaticAdCommand = defineCommand75({
|
|
|
8191
8229
|
|
|
8192
8230
|
// src/commands/canvas/scaffold-video.ts
|
|
8193
8231
|
import { cp, mkdir, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
8194
|
-
import
|
|
8232
|
+
import path5 from "path";
|
|
8195
8233
|
import { defineCommand as defineCommand76 } from "citty";
|
|
8196
8234
|
|
|
8197
8235
|
// src/engine/scaffold/video.ts
|
|
@@ -8199,7 +8237,19 @@ import { z as z3 } from "zod";
|
|
|
8199
8237
|
var FIXED_TTS_MODEL = "elevenlabs/eleven_v3";
|
|
8200
8238
|
var FIXED_SFX_MODEL = "elevenlabs/eleven_text_to_sound_v2";
|
|
8201
8239
|
var FIXED_MUSIC_MODEL = "elevenlabs/music-v1";
|
|
8240
|
+
var FIXED_LIPSYNC_MODEL = "fal/veed-lipsync";
|
|
8202
8241
|
var MUSIC_BED_GAIN_DB = -12;
|
|
8242
|
+
var NARRATOR_SPEAKERS = /* @__PURE__ */ new Set([
|
|
8243
|
+
"voiceover",
|
|
8244
|
+
"voice_over",
|
|
8245
|
+
"narrator",
|
|
8246
|
+
"narration",
|
|
8247
|
+
"vo",
|
|
8248
|
+
"announcer",
|
|
8249
|
+
"off_screen",
|
|
8250
|
+
"offscreen",
|
|
8251
|
+
"off-screen"
|
|
8252
|
+
]);
|
|
8203
8253
|
var SHARED_ASPECT_RATIOS = /* @__PURE__ */ new Set(["1:1", "16:9", "9:16", "4:3", "3:4", "21:9"]);
|
|
8204
8254
|
var EDGES = ["start", "end"];
|
|
8205
8255
|
function snapToSeedance(durationS) {
|
|
@@ -8215,11 +8265,39 @@ function snapToSeedance(durationS) {
|
|
|
8215
8265
|
}
|
|
8216
8266
|
return best;
|
|
8217
8267
|
}
|
|
8268
|
+
function ceilToSeedance(durationS) {
|
|
8269
|
+
const max = SEEDANCE_DURATIONS[SEEDANCE_DURATIONS.length - 1];
|
|
8270
|
+
if (!Number.isFinite(durationS) || durationS <= 0) return SEEDANCE_DURATIONS[0];
|
|
8271
|
+
for (const d of SEEDANCE_DURATIONS) if (d >= durationS) return d;
|
|
8272
|
+
return max;
|
|
8273
|
+
}
|
|
8274
|
+
function sceneDurationS(scene) {
|
|
8275
|
+
const raw = scene.duration_s ?? (scene.end_s != null && scene.start_s != null ? scene.end_s - scene.start_s : 5);
|
|
8276
|
+
const max = SEEDANCE_DURATIONS[SEEDANCE_DURATIONS.length - 1];
|
|
8277
|
+
return Math.min(Math.max(raw, 0.5), max);
|
|
8278
|
+
}
|
|
8279
|
+
function trimArgs(durationS) {
|
|
8280
|
+
return [
|
|
8281
|
+
"-i",
|
|
8282
|
+
"{{in.clip}}",
|
|
8283
|
+
"-t",
|
|
8284
|
+
durationS.toFixed(3),
|
|
8285
|
+
"-an",
|
|
8286
|
+
"-c:v",
|
|
8287
|
+
"libx264",
|
|
8288
|
+
"-pix_fmt",
|
|
8289
|
+
"yuv420p",
|
|
8290
|
+
"{{out.video}}"
|
|
8291
|
+
];
|
|
8292
|
+
}
|
|
8218
8293
|
var FrameAsset = z3.object({ url: z3.string().optional() }).loose().optional();
|
|
8219
8294
|
var DialogueLine = z3.object({
|
|
8220
8295
|
speaker: z3.string().optional(),
|
|
8221
8296
|
line: z3.string().optional(),
|
|
8297
|
+
// Absolute seconds on the source timeline (the deconstruct emits both).
|
|
8222
8298
|
start_s: z3.number().optional(),
|
|
8299
|
+
end_s: z3.number().optional(),
|
|
8300
|
+
delivery: z3.string().optional(),
|
|
8223
8301
|
voice_description: z3.string().optional()
|
|
8224
8302
|
}).loose();
|
|
8225
8303
|
var Sfx = z3.object({
|
|
@@ -8259,7 +8337,12 @@ var VideoBlueprint = z3.object({
|
|
|
8259
8337
|
identified_track: z3.object({ title: z3.string().optional(), artist: z3.string().optional() }).loose().nullish()
|
|
8260
8338
|
}).loose().optional(),
|
|
8261
8339
|
cast: z3.array(z3.object({ id: z3.string().optional(), description: z3.string().optional() }).loose()).optional(),
|
|
8262
|
-
voiceover: z3.object({
|
|
8340
|
+
voiceover: z3.object({
|
|
8341
|
+
// on_camera | mixed → mouths are on screen (lip-sync candidates);
|
|
8342
|
+
// voiceover | none → narration over the picture (no lip-sync).
|
|
8343
|
+
mode: z3.string().optional(),
|
|
8344
|
+
voice_description: z3.string().optional()
|
|
8345
|
+
}).loose().optional()
|
|
8263
8346
|
}).loose().optional(),
|
|
8264
8347
|
scenes: z3.array(Scene).min(1)
|
|
8265
8348
|
}).loose();
|
|
@@ -8391,7 +8474,17 @@ function buildFramePrompt(edge, sceneIndex, framePrompt, present, hasAnchor) {
|
|
|
8391
8474
|
].join("\n");
|
|
8392
8475
|
const description = framePrompt?.trim() || `the ${edge} frame of scene ${sceneIndex + 1} \u2014 describe the full composition, subjects, setting, action, lighting, and palette here. (Edit this line to change ONLY this frame.)`;
|
|
8393
8476
|
return [
|
|
8394
|
-
`Render the ${EDGE} frame of scene ${sceneIndex + 1} as a single still image. This prompt is self-contained and edit-per-frame: change the FRAME DESCRIPTION below to alter ONLY this frame
|
|
8477
|
+
`Render the ${EDGE} frame of scene ${sceneIndex + 1} as a single still image. This prompt is self-contained and edit-per-frame: change the FRAME DESCRIPTION below to alter ONLY this frame.`,
|
|
8478
|
+
"",
|
|
8479
|
+
"CRITICAL \u2014 RENDER A CLEAN PLATE WITH ZERO TEXT OR GRAPHICS:",
|
|
8480
|
+
"This frame is a background plate. ALL words, captions, headlines, lower-third bars,",
|
|
8481
|
+
"news tickers/crawls, chyrons, on-screen logos/wordmarks, station bugs, watermarks,",
|
|
8482
|
+
"subtitles, UI and numbers are added afterwards as a separate HTML layer. Render NONE",
|
|
8483
|
+
"of them \u2014 no legible text anywhere in the image, not even in the background, on the",
|
|
8484
|
+
"news desk, on screens, or as part of a 'broadcast look'. If a reference image (a logo,",
|
|
8485
|
+
"a desk, a studio) contains any text or graphics, DO NOT reproduce that text \u2014 render",
|
|
8486
|
+
"the subject/scene only, with blank surfaces where text would be. Imperfect/garbled",
|
|
8487
|
+
"letterforms are the worst outcome; leave those areas clean.",
|
|
8395
8488
|
"",
|
|
8396
8489
|
"REFERENCE IMAGES (in the order provided):",
|
|
8397
8490
|
legend,
|
|
@@ -8399,7 +8492,7 @@ function buildFramePrompt(edge, sceneIndex, framePrompt, present, hasAnchor) {
|
|
|
8399
8492
|
"FRAME DESCRIPTION (this frame's editable prompt):",
|
|
8400
8493
|
description,
|
|
8401
8494
|
"",
|
|
8402
|
-
"Keep every recurring element identical to its reference image across all frames. Use the GLOBAL STYLE REFERENCE only for shared cast identity, palette, typography mood, and aspect ratio \u2014 do NOT copy another scene's composition from it; this frame's content is the FRAME DESCRIPTION above.",
|
|
8495
|
+
"Keep every recurring element identical to its reference image across all frames. Use the GLOBAL STYLE REFERENCE only for shared cast identity, palette, typography mood, and aspect ratio \u2014 do NOT copy another scene's composition from it; this frame's content is the FRAME DESCRIPTION above. Again: NO rendered text or graphic overlays \u2014 clean plate only.",
|
|
8403
8496
|
"",
|
|
8404
8497
|
"GLOBAL STYLE REFERENCE (shared across frames; not this frame's content):",
|
|
8405
8498
|
"{{target_blueprint}}"
|
|
@@ -8450,7 +8543,7 @@ function buildSeedancePrompt(scene, sceneIndex, present) {
|
|
|
8450
8543
|
if (transcript) parts.push(`Transcript: ${transcript}`);
|
|
8451
8544
|
return parts.join("\n");
|
|
8452
8545
|
}
|
|
8453
|
-
function buildSceneVisuals(blueprint, slots, opts, nodes) {
|
|
8546
|
+
function buildSceneVisuals(blueprint, slots, opts, nodes, sceneTurns) {
|
|
8454
8547
|
const ar = aspectRatioParam(blueprint);
|
|
8455
8548
|
const reuse = opts.frames === "reuse";
|
|
8456
8549
|
const clipRefs = [];
|
|
@@ -8472,10 +8565,11 @@ function buildSceneVisuals(blueprint, slots, opts, nodes) {
|
|
|
8472
8565
|
ctx,
|
|
8473
8566
|
nodes
|
|
8474
8567
|
);
|
|
8568
|
+
const dur = sceneDurationS(scene);
|
|
8475
8569
|
const clipParams = {
|
|
8476
8570
|
model: opts.videoModel,
|
|
8477
8571
|
prompt: buildSeedancePrompt(scene, i, slotsForScene(slots, i)),
|
|
8478
|
-
duration:
|
|
8572
|
+
duration: ceilToSeedance(dur)
|
|
8479
8573
|
};
|
|
8480
8574
|
if (ar) clipParams.aspect_ratio = ar;
|
|
8481
8575
|
nodes.push({
|
|
@@ -8484,7 +8578,29 @@ function buildSceneVisuals(blueprint, slots, opts, nodes) {
|
|
|
8484
8578
|
inputs: { first_frame: firstFrame, last_frame: lastFrame },
|
|
8485
8579
|
params: clipParams
|
|
8486
8580
|
});
|
|
8487
|
-
|
|
8581
|
+
let base = `$ref:s${i}_clip.video`;
|
|
8582
|
+
const onCam = (sceneTurns.get(i) ?? []).filter((t) => t.onCamera);
|
|
8583
|
+
const solo = onCam.length === 1 ? onCam[0] : void 0;
|
|
8584
|
+
if (solo) {
|
|
8585
|
+
nodes.push({
|
|
8586
|
+
id: `s${i}_lipsync`,
|
|
8587
|
+
type: "video_lipsync",
|
|
8588
|
+
inputs: { video: base, audio: solo.audioRef },
|
|
8589
|
+
params: { model: FIXED_LIPSYNC_MODEL }
|
|
8590
|
+
});
|
|
8591
|
+
base = `$ref:s${i}_lipsync.video`;
|
|
8592
|
+
}
|
|
8593
|
+
if (ceilToSeedance(dur) === dur) {
|
|
8594
|
+
clipRefs.push(base);
|
|
8595
|
+
} else {
|
|
8596
|
+
nodes.push({
|
|
8597
|
+
id: `s${i}_trim`,
|
|
8598
|
+
type: "ffmpeg",
|
|
8599
|
+
inputs: { clip: base },
|
|
8600
|
+
params: { args: trimArgs(dur), outputs: { video: { kind: "video", ext: "mp4" } } }
|
|
8601
|
+
});
|
|
8602
|
+
clipRefs.push(`$ref:s${i}_trim.video`);
|
|
8603
|
+
}
|
|
8488
8604
|
});
|
|
8489
8605
|
return clipRefs;
|
|
8490
8606
|
}
|
|
@@ -8497,8 +8613,21 @@ function musicBedPrompt(blueprint, musicPrompt) {
|
|
|
8497
8613
|
|
|
8498
8614
|
Reference vibe: the original used "${title}"${by} (identified via AudD). Match its mood, tempo, and energy with ORIGINAL music \u2014 do not reproduce the track.`;
|
|
8499
8615
|
}
|
|
8500
|
-
function
|
|
8616
|
+
function onCameraDialogue(blueprint) {
|
|
8617
|
+
const mode = blueprint.global?.voiceover?.mode;
|
|
8618
|
+
return mode !== "voiceover" && mode !== "none";
|
|
8619
|
+
}
|
|
8620
|
+
var castIdSet = (blueprint) => new Set((blueprint.global?.cast ?? []).map((c) => c.id).filter((id) => Boolean(id)));
|
|
8621
|
+
function isOnCameraSpeaker(speaker, casts, cameraOn) {
|
|
8622
|
+
if (!cameraOn) return false;
|
|
8623
|
+
if (NARRATOR_SPEAKERS.has(speaker.toLowerCase())) return false;
|
|
8624
|
+
return casts.has(speaker);
|
|
8625
|
+
}
|
|
8626
|
+
function buildDialogue(blueprint, nodes) {
|
|
8501
8627
|
const tracks = [];
|
|
8628
|
+
const sceneTurns = /* @__PURE__ */ new Map();
|
|
8629
|
+
const casts = castIdSet(blueprint);
|
|
8630
|
+
const cameraOn = onCameraDialogue(blueprint);
|
|
8502
8631
|
const voiceNodeBySpeaker = /* @__PURE__ */ new Map();
|
|
8503
8632
|
const speakerDescription = (speaker) => {
|
|
8504
8633
|
for (const scene of blueprint.scenes) {
|
|
@@ -8517,41 +8646,62 @@ function buildAudio(blueprint, nodes) {
|
|
|
8517
8646
|
voiceNodeBySpeaker.set(speaker, id);
|
|
8518
8647
|
return id;
|
|
8519
8648
|
};
|
|
8520
|
-
const scriptBySpeaker = /* @__PURE__ */ new Map();
|
|
8521
|
-
const orderedSpeakers = [];
|
|
8522
|
-
for (const scene of blueprint.scenes) {
|
|
8523
|
-
for (const line of scene.dialogue ?? []) {
|
|
8524
|
-
if (!line.line) continue;
|
|
8525
|
-
const speaker = line.speaker ?? "voiceover";
|
|
8526
|
-
const start = line.start_s ?? scene.start_s ?? 0;
|
|
8527
|
-
const existing = scriptBySpeaker.get(speaker);
|
|
8528
|
-
if (existing) {
|
|
8529
|
-
existing.lines.push(line.line);
|
|
8530
|
-
existing.start = Math.min(existing.start, start);
|
|
8531
|
-
} else {
|
|
8532
|
-
scriptBySpeaker.set(speaker, { lines: [line.line], start });
|
|
8533
|
-
orderedSpeakers.push(speaker);
|
|
8534
|
-
}
|
|
8535
|
-
}
|
|
8536
|
-
}
|
|
8537
8649
|
const usedVoIds = /* @__PURE__ */ new Set();
|
|
8538
|
-
|
|
8539
|
-
const
|
|
8540
|
-
if (
|
|
8541
|
-
const
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8650
|
+
blueprint.scenes.forEach((scene, sceneIndex) => {
|
|
8651
|
+
const lines = (scene.dialogue ?? []).filter((l) => Boolean(l.line?.trim())).slice().sort((a, b) => (a.start_s ?? 0) - (b.start_s ?? 0));
|
|
8652
|
+
if (lines.length === 0) return;
|
|
8653
|
+
const groups = [];
|
|
8654
|
+
for (const line of lines) {
|
|
8655
|
+
const speaker = line.speaker ?? "voiceover";
|
|
8656
|
+
const last = groups[groups.length - 1];
|
|
8657
|
+
if (last && last.speaker === speaker) last.lines.push(line);
|
|
8658
|
+
else groups.push({ speaker, lines: [line] });
|
|
8659
|
+
}
|
|
8660
|
+
const list = [];
|
|
8661
|
+
groups.forEach((group, gi) => {
|
|
8662
|
+
const first = group.lines[0];
|
|
8663
|
+
const last = group.lines[group.lines.length - 1];
|
|
8664
|
+
if (!first || !last) return;
|
|
8665
|
+
const start = first.start_s ?? scene.start_s ?? 0;
|
|
8666
|
+
const end = last.end_s ?? last.start_s ?? scene.end_s ?? start;
|
|
8667
|
+
const voiceNode = ensureVoiceNode(group.speaker);
|
|
8668
|
+
let id = sanitizeId2(`vo_s${sceneIndex}_${group.speaker}`, `vo_${sceneIndex}_${gi}`);
|
|
8669
|
+
if (usedVoIds.has(id)) {
|
|
8670
|
+
let n = 2;
|
|
8671
|
+
while (usedVoIds.has(`${id}_${n}`)) n++;
|
|
8672
|
+
id = `${id}_${n}`;
|
|
8673
|
+
}
|
|
8674
|
+
usedVoIds.add(id);
|
|
8675
|
+
nodes.push({
|
|
8676
|
+
id,
|
|
8677
|
+
type: "tts",
|
|
8678
|
+
inputs: { voice_ref: `$ref:${voiceNode}.voice_id` },
|
|
8679
|
+
// Lines join with a space; each keeps its terminal punctuation so eleven_v3
|
|
8680
|
+
// reads the sentence boundaries (and their pauses) within the one turn.
|
|
8681
|
+
params: {
|
|
8682
|
+
model: FIXED_TTS_MODEL,
|
|
8683
|
+
text: group.lines.map((l) => l.line.trim()).join(" "),
|
|
8684
|
+
voice: "{{voice_ref}}"
|
|
8685
|
+
}
|
|
8686
|
+
});
|
|
8687
|
+
const turn = {
|
|
8688
|
+
sceneIndex,
|
|
8689
|
+
speaker: group.speaker,
|
|
8690
|
+
onCamera: isOnCameraSpeaker(group.speaker, casts, cameraOn),
|
|
8691
|
+
start_s: start,
|
|
8692
|
+
end_s: end,
|
|
8693
|
+
ttsId: id,
|
|
8694
|
+
audioRef: `$ref:${id}.audio`
|
|
8695
|
+
};
|
|
8696
|
+
list.push(turn);
|
|
8697
|
+
tracks.push({ slot: id, ref: turn.audioRef, start_s: start, end_s: end, kind: "vo" });
|
|
8552
8698
|
});
|
|
8553
|
-
|
|
8699
|
+
sceneTurns.set(sceneIndex, list);
|
|
8554
8700
|
});
|
|
8701
|
+
return { tracks, sceneTurns };
|
|
8702
|
+
}
|
|
8703
|
+
function buildSfxMusic(blueprint, nodes) {
|
|
8704
|
+
const tracks = [];
|
|
8555
8705
|
blueprint.scenes.forEach((scene, i) => {
|
|
8556
8706
|
(scene.sfx ?? []).forEach((sfx, k) => {
|
|
8557
8707
|
const text = sfx.sound_effect_prompt ?? sfx.description;
|
|
@@ -8560,7 +8710,12 @@ function buildAudio(blueprint, nodes) {
|
|
|
8560
8710
|
const params = { model: FIXED_SFX_MODEL, text };
|
|
8561
8711
|
if (typeof sfx.duration_s === "number") params.duration_seconds = Math.min(Math.max(sfx.duration_s, 0.5), 30);
|
|
8562
8712
|
nodes.push({ id, type: "sound_effect", params });
|
|
8563
|
-
tracks.push({
|
|
8713
|
+
tracks.push({
|
|
8714
|
+
slot: `sfx_s${i}_${k}`,
|
|
8715
|
+
ref: `$ref:${id}.audio`,
|
|
8716
|
+
start_s: sfx.at_s ?? scene.start_s ?? 0,
|
|
8717
|
+
kind: "sfx"
|
|
8718
|
+
});
|
|
8564
8719
|
});
|
|
8565
8720
|
});
|
|
8566
8721
|
const musicPrompt = blueprint.global?.music?.music_prompt;
|
|
@@ -8572,10 +8727,90 @@ function buildAudio(blueprint, nodes) {
|
|
|
8572
8727
|
type: "music",
|
|
8573
8728
|
params: { model: FIXED_MUSIC_MODEL, prompt: musicBedPrompt(blueprint, musicPrompt), music_length_ms: musicMs }
|
|
8574
8729
|
});
|
|
8575
|
-
tracks.
|
|
8730
|
+
tracks.push({ slot: "music", ref: "$ref:music_bed.audio", start_s: 0, gain_db: MUSIC_BED_GAIN_DB, kind: "music" });
|
|
8576
8731
|
}
|
|
8577
8732
|
return tracks;
|
|
8578
8733
|
}
|
|
8734
|
+
var OverlayStyle = z3.object({ color_hex: z3.string().optional(), background: z3.string().optional(), size: z3.string().optional() }).loose();
|
|
8735
|
+
var Overlay = z3.object({
|
|
8736
|
+
text: z3.string().optional(),
|
|
8737
|
+
appears_at_s: z3.number().optional(),
|
|
8738
|
+
duration_s: z3.number().optional(),
|
|
8739
|
+
position: z3.string().optional(),
|
|
8740
|
+
role: z3.string().optional(),
|
|
8741
|
+
animation: z3.string().optional(),
|
|
8742
|
+
animation_detail: z3.string().optional(),
|
|
8743
|
+
style: OverlayStyle.optional()
|
|
8744
|
+
}).loose();
|
|
8745
|
+
var FloatingElement = z3.object({
|
|
8746
|
+
kind: z3.string().optional(),
|
|
8747
|
+
description: z3.string().optional(),
|
|
8748
|
+
brand_name: z3.string().nullish(),
|
|
8749
|
+
what_it_represents: z3.string().optional(),
|
|
8750
|
+
appears_at_s: z3.number().optional(),
|
|
8751
|
+
duration_s: z3.number().optional(),
|
|
8752
|
+
position: z3.string().optional()
|
|
8753
|
+
}).loose();
|
|
8754
|
+
function escapeHtml(s) {
|
|
8755
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
8756
|
+
}
|
|
8757
|
+
function commentSafe(s) {
|
|
8758
|
+
return escapeHtml(s).replace(/-{2,}/g, "\u2013");
|
|
8759
|
+
}
|
|
8760
|
+
var SUPPORTED_ANIMS = /* @__PURE__ */ new Set(["fade", "slide_up", "slide_down", "pop"]);
|
|
8761
|
+
function normalizeAnim(animation) {
|
|
8762
|
+
if (!animation || animation === "none") return void 0;
|
|
8763
|
+
const mapped = animation === "slide" ? "slide_up" : animation;
|
|
8764
|
+
return SUPPORTED_ANIMS.has(mapped) ? mapped : void 0;
|
|
8765
|
+
}
|
|
8766
|
+
function positionClass(position) {
|
|
8767
|
+
const p = (position ?? "bottom_center").toLowerCase().replace(/[^a-z]+/g, "-");
|
|
8768
|
+
return `pos-${p}`;
|
|
8769
|
+
}
|
|
8770
|
+
function overlayElement(ov, sceneStart) {
|
|
8771
|
+
if (!ov.text?.trim()) return "";
|
|
8772
|
+
const at = ov.appears_at_s ?? sceneStart;
|
|
8773
|
+
const dur = ov.duration_s ?? 2.5;
|
|
8774
|
+
const role = ov.role ? ` data-role="${escapeHtml(ov.role)}"` : "";
|
|
8775
|
+
const normAnim = normalizeAnim(ov.animation);
|
|
8776
|
+
const anim = normAnim ? ` data-anim="${normAnim}"` : "";
|
|
8777
|
+
const detail = ov.animation_detail ? ` data-anim-detail="${escapeHtml(ov.animation_detail)}"` : "";
|
|
8778
|
+
return `<div class="ov ${positionClass(ov.position)}" data-start="${at}" data-dur="${dur}"${role}${anim}${detail}>${escapeHtml(ov.text.trim())}</div>`;
|
|
8779
|
+
}
|
|
8780
|
+
function floatingStub(fe, sceneStart) {
|
|
8781
|
+
const at = fe.appears_at_s ?? sceneStart;
|
|
8782
|
+
const dur = fe.duration_s ?? 2.5;
|
|
8783
|
+
const kind = commentSafe(fe.kind ?? "element");
|
|
8784
|
+
const label = commentSafe(fe.brand_name || fe.what_it_represents || fe.description || fe.kind || "element");
|
|
8785
|
+
const slug = (fe.kind ?? "element").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "element";
|
|
8786
|
+
return [
|
|
8787
|
+
`<!-- ${kind}: ${label} @ ${at}s for ${dur}s (${positionClass(fe.position)}). Drop an image in this dir and uncomment:`,
|
|
8788
|
+
`<img class="ov ${positionClass(fe.position)}" src="your-${slug}.png" data-start="${at}" data-dur="${dur}" alt="" /> -->`
|
|
8789
|
+
].join("\n");
|
|
8790
|
+
}
|
|
8791
|
+
function buildOverlayHtml(input) {
|
|
8792
|
+
const blueprint = VideoBlueprint.parse(input);
|
|
8793
|
+
const blocks = [
|
|
8794
|
+
[
|
|
8795
|
+
"<!-- \u2B07 OVERLAY LAYER \u2014 this is YOUR HTML to paint. The reference's overlays are",
|
|
8796
|
+
" seeded below as plain elements (text + position class + data-start/data-dur).",
|
|
8797
|
+
" Restyle freely in <style>, regroup, animate, swap a logo placeholder for a",
|
|
8798
|
+
" real <img> you drop in this dir. The runtime only shows/hides by timestamp;",
|
|
8799
|
+
" it makes NO styling decisions. Positions: edit the .pos-* classes or add your own. -->"
|
|
8800
|
+
].join("\n")
|
|
8801
|
+
];
|
|
8802
|
+
for (const scene of blueprint.scenes) {
|
|
8803
|
+
const sceneStart = scene.start_s ?? 0;
|
|
8804
|
+
const overlays = z3.array(Overlay).safeParse(scene.overlays ?? []);
|
|
8805
|
+
const floats = z3.array(FloatingElement).safeParse(scene.floating_elements ?? []);
|
|
8806
|
+
const parts = [
|
|
8807
|
+
...overlays.success ? overlays.data.map((ov) => overlayElement(ov, sceneStart)) : [],
|
|
8808
|
+
...floats.success ? floats.data.map((fe) => floatingStub(fe, sceneStart)) : []
|
|
8809
|
+
].filter(Boolean);
|
|
8810
|
+
if (parts.length > 0) blocks.push(parts.join("\n"));
|
|
8811
|
+
}
|
|
8812
|
+
return blocks.join("\n\n");
|
|
8813
|
+
}
|
|
8579
8814
|
function lastSceneEnd(blueprint) {
|
|
8580
8815
|
let end = 0;
|
|
8581
8816
|
for (const s of blueprint.scenes) end = Math.max(end, s.end_s ?? 0);
|
|
@@ -8607,7 +8842,8 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
|
|
|
8607
8842
|
params: { source: "path", path: todoPath2(elements[i], slot.label), expect: "image" }
|
|
8608
8843
|
});
|
|
8609
8844
|
});
|
|
8610
|
-
const
|
|
8845
|
+
const { tracks: voTracks, sceneTurns } = buildDialogue(blueprint, nodes);
|
|
8846
|
+
const clipRefs = buildSceneVisuals(blueprint, slots, opts, nodes, sceneTurns);
|
|
8611
8847
|
const concatInputs = {};
|
|
8612
8848
|
clipRefs.forEach((ref, i) => {
|
|
8613
8849
|
concatInputs[`c${i}`] = ref;
|
|
@@ -8627,12 +8863,12 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
|
|
|
8627
8863
|
id: "overlaid",
|
|
8628
8864
|
type: "hyperframe_render",
|
|
8629
8865
|
inputs: { background: videoRef },
|
|
8630
|
-
params: { composition: opts.overlayCompositionPath
|
|
8866
|
+
params: { composition: opts.overlayCompositionPath }
|
|
8631
8867
|
});
|
|
8632
8868
|
videoRef = "$ref:overlaid.video";
|
|
8633
8869
|
videoNode = "overlaid";
|
|
8634
8870
|
}
|
|
8635
|
-
const tracks =
|
|
8871
|
+
const tracks = [...voTracks, ...buildSfxMusic(blueprint, nodes)];
|
|
8636
8872
|
if (tracks.length > 0) {
|
|
8637
8873
|
const mixInputs = {};
|
|
8638
8874
|
for (const t of tracks) mixInputs[t.slot] = t.ref;
|
|
@@ -8665,8 +8901,18 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
|
|
|
8665
8901
|
"1:a:0",
|
|
8666
8902
|
"-c:v",
|
|
8667
8903
|
"copy",
|
|
8904
|
+
// The raw mix is a quiet mono track (tts + ducked bed), which reads as
|
|
8905
|
+
// "no sound" in casual players. Normalize integrated loudness to the
|
|
8906
|
+
// social/broadcast target (-14 LUFS, -1.5 dBTP) and upmix to stereo so
|
|
8907
|
+
// every rendered ad is loud and plays everywhere.
|
|
8908
|
+
"-af",
|
|
8909
|
+
"loudnorm=I=-14:TP=-1.5:LRA=11,aformat=channel_layouts=stereo",
|
|
8668
8910
|
"-c:a",
|
|
8669
8911
|
"aac",
|
|
8912
|
+
"-b:a",
|
|
8913
|
+
"192k",
|
|
8914
|
+
"-ar",
|
|
8915
|
+
"48000",
|
|
8670
8916
|
"-shortest",
|
|
8671
8917
|
"{{out.video}}"
|
|
8672
8918
|
],
|
|
@@ -8680,43 +8926,69 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
|
|
|
8680
8926
|
metadata: {
|
|
8681
8927
|
name: "video reproduction",
|
|
8682
8928
|
description: VIDEO_GUIDE,
|
|
8683
|
-
todo: buildVideoTodo(videoReport(input, elementsInput), overlays.length, floating.length, opts)
|
|
8929
|
+
todo: buildVideoTodo(videoReport(input, elementsInput), overlays.length, floating.length, opts),
|
|
8930
|
+
// The timing plan `baker canvas validate` checks before any billed render:
|
|
8931
|
+
// sequenced voiceover turns (no overlap), audio ≈ video length, and which
|
|
8932
|
+
// scenes must be lip-synced.
|
|
8933
|
+
video: buildVideoMeta(blueprint, sceneTurns)
|
|
8684
8934
|
},
|
|
8685
8935
|
nodes,
|
|
8686
8936
|
output: { node: videoNode, output: "video" }
|
|
8687
8937
|
};
|
|
8688
8938
|
}
|
|
8939
|
+
function buildVideoMeta(blueprint, sceneTurns) {
|
|
8940
|
+
const vo_segments = [];
|
|
8941
|
+
const talking_scenes = [];
|
|
8942
|
+
for (const [scene, turns] of [...sceneTurns.entries()].sort((a, b) => a[0] - b[0])) {
|
|
8943
|
+
for (const t of turns) {
|
|
8944
|
+
vo_segments.push({ slot: t.ttsId, start_s: t.start_s, end_s: t.end_s, scene, speaker: t.speaker });
|
|
8945
|
+
}
|
|
8946
|
+
if (turns.filter((t) => t.onCamera).length === 1) {
|
|
8947
|
+
talking_scenes.push({ scene, lipsync_node: `s${scene}_lipsync` });
|
|
8948
|
+
}
|
|
8949
|
+
}
|
|
8950
|
+
return {
|
|
8951
|
+
duration_s: blueprint.source?.duration_s ?? lastSceneEnd(blueprint),
|
|
8952
|
+
vo_segments,
|
|
8953
|
+
talking_scenes
|
|
8954
|
+
};
|
|
8955
|
+
}
|
|
8689
8956
|
var VIDEO_GUIDE = [
|
|
8690
|
-
"Scaffolded by `baker canvas scaffold-video` \u2014 a runnable reproduction of your reference video.
|
|
8957
|
+
"Scaffolded by `baker canvas scaffold-video` \u2014 a runnable reproduction of your reference video. Per scene: two AI-generated CLEAN-PLATE frames (no baked text) \u2192 a clip \u2192 trimmed to the real scene length so the picture stays on the audio timeline \u2192 optional lip-sync \u2192 concatenated. On-screen text is a separate HTML layer you paint; audio is sequenced voiceover + SFX + a ducked music bed, normalized stereo. It is a DRAFT: edit it, supply the real assets, then validate and run.",
|
|
8691
8958
|
"",
|
|
8692
8959
|
"WHAT TO DO NEXT:",
|
|
8693
8960
|
"1. Edit each frame's prompt IN PLACE. Every `s<i>_start` / `s<i>_end` node has its OWN self-contained `params.prompt` (the FRAME DESCRIPTION) \u2014 editing one changes only that frame. Rewrite the cast, product, claims, palette into the ad you want.",
|
|
8694
|
-
"2. Drop ONE real source image at each `el_*` ingest `[TODO]` path. Each recurring element (person/product/logo) is reused across every frame it appears in, so the same identity stays consistent.",
|
|
8695
|
-
"3. Confirm the `voice_select` casting (one per speaker).
|
|
8696
|
-
"4.
|
|
8697
|
-
"5.
|
|
8961
|
+
"2. Drop ONE real source image at each `el_*` ingest `[TODO]` path. Each recurring element (person/product/logo) is reused across every frame it appears in, so the same identity stays consistent. `baker canvas run` REFUSES to start until every `[TODO]` slot holds a real source \u2014 so this is mandatory, not optional.",
|
|
8962
|
+
"3. Confirm the `voice_select` casting (one per speaker). Voiceover is SEQUENCED: each contiguous same-speaker turn is its own `tts` placed at its real start, so dialogue alternates instead of stacking. Edit a turn's `params.text` (punctuation / ALL-CAPS / line breaks are read verbatim by eleven_v3 for emphasis and pauses) to shape delivery; re-author the words to be TRUE for your brand.",
|
|
8963
|
+
"4. Lip-sync: scenes with a single on-camera speaker route their clip through `video_lipsync` (~20 cr each) so the mouth matches the line. Two-speaker scenes are left un-synced \u2014 split them or pick a primary speaker if you want sync. Drop the node to skip.",
|
|
8964
|
+
"5. Overlays are REAL HTML you paint. Open `video-overlay-composition/index.html`: the reference's overlays are seeded inside `#overlay-root` as plain elements (text + a `.pos-*` class + `data-start`/`data-dur`). Restyle the CSS freely \u2014 build lower-thirds, a ticker, whatever the look needs \u2014 and replace a logo placeholder with a real `<img>` you drop in that dir. The runtime only shows/hides by timestamp; it makes no styling decisions. Drop `brand-bold.otf` / `brand-regular.otf` there for on-brand type.",
|
|
8965
|
+
"6. `baker canvas validate` (proves audio/lip-sync timing for free) then `baker canvas run` (generates many billed image/video/audio assets \u2014 not free).",
|
|
8698
8966
|
"",
|
|
8699
8967
|
"Tip: `prompt.json` is the deconstruction provenance + the demoted GLOBAL STYLE REFERENCE each frame reads for shared palette/cast cohesion. It is NOT the per-frame editing surface \u2014 the frame nodes are."
|
|
8700
8968
|
].join("\n");
|
|
8701
8969
|
function buildVideoTodo(report, overlayCount, floatingCount, opts) {
|
|
8702
8970
|
return {
|
|
8703
|
-
edit_frames_in_place: "Each s<i>_start / s<i>_end node has its own editable params.prompt (FRAME DESCRIPTION). Edit per frame; the blueprint is only a shared style reference.",
|
|
8971
|
+
edit_frames_in_place: "Each s<i>_start / s<i>_end node has its own editable params.prompt (FRAME DESCRIPTION). Edit per frame; the blueprint is only a shared style reference. Frames are CLEAN PLATES \u2014 they render no on-screen text; all text is the overlay HTML layer.",
|
|
8704
8972
|
frames_mode: opts.frames ?? "generate",
|
|
8973
|
+
assets_required: "MANDATORY: drop a real image at every el_* [TODO] ingest before running \u2014 `baker canvas run` refuses to start (and bills nothing) until each placeholder holds a real source.",
|
|
8705
8974
|
recurring_elements_to_supply: report.elements,
|
|
8975
|
+
text_strategy: "Decide per ad: text is either baked by the generated creative OR painted via the overlay HTML \u2014 not both. Default here is clean text-free frames + the HTML overlay layer (video-overlay-composition/index.html) as the single text source, which you fully control.",
|
|
8976
|
+
timeline: "Automatic: each clip is generated at >= its scene length then trimmed back to the real scene duration, so the concatenated picture stays on the same timeline as the absolute-timed audio (this is what makes the lips line up). You don't manage it.",
|
|
8706
8977
|
voices_to_confirm: report.dialogue.map((d) => ({
|
|
8707
8978
|
scene: d.scene,
|
|
8708
8979
|
speaker: d.speaker,
|
|
8709
8980
|
voice_description: d.voice_description,
|
|
8710
8981
|
line: d.line
|
|
8711
8982
|
})),
|
|
8712
|
-
voiceover_note: "
|
|
8983
|
+
voiceover_note: "Sequenced: one tts per contiguous same-speaker TURN, placed at its real start_s so turns alternate (no parallel monologues); same voice locked via voice_select.voice_id. Edit a turn's params.text (punctuation / ALL CAPS / line breaks read verbatim) to shape delivery.",
|
|
8984
|
+
lip_sync_note: "Scenes with a single on-camera speaker route their clip through video_lipsync (~20 cr each) so the mouth matches the line. Two-speaker scenes are left un-synced (one track can't drive two faces) \u2014 split or pick a primary. `baker canvas validate` checks every talking scene is synced.",
|
|
8713
8985
|
text_overlays: {
|
|
8714
8986
|
count: overlayCount,
|
|
8715
|
-
note: "
|
|
8987
|
+
note: "Seeded as editable HTML inside `#overlay-root` in video-overlay-composition/index.html (text + a .pos-* class + data-start/data-dur). PAINT it: restyle the CSS, build lower-thirds/tickers, drop brand-*.otf for on-brand type. The runtime only shows/hides by timestamp."
|
|
8716
8988
|
},
|
|
8717
8989
|
floating_elements: {
|
|
8718
8990
|
count: floatingCount,
|
|
8719
|
-
note: floatingCount > 0 ? "
|
|
8991
|
+
note: floatingCount > 0 ? "Seeded as labeled placeholders in index.html \u2014 replace each with a real <img> you drop into video-overlay-composition/. Recurring logos are also handled well as an el_* element baked into frames." : "none detected"
|
|
8720
8992
|
},
|
|
8721
8993
|
sound_effects: { count: report.sfx_count },
|
|
8722
8994
|
music: {
|
|
@@ -8768,8 +9040,24 @@ function videoReport(input, elementsInput) {
|
|
|
8768
9040
|
};
|
|
8769
9041
|
}
|
|
8770
9042
|
|
|
9043
|
+
// src/commands/canvas/composition-path.ts
|
|
9044
|
+
import { existsSync as existsSync3 } from "fs";
|
|
9045
|
+
import path4 from "path";
|
|
9046
|
+
function resolveShippedCanvasDir(name, startDir, exists = existsSync3, maxDepth = 8) {
|
|
9047
|
+
const rel = path4.join("canvas", name);
|
|
9048
|
+
let dir = startDir;
|
|
9049
|
+
for (let i = 0; i < maxDepth; i++) {
|
|
9050
|
+
const candidate = path4.join(dir, rel);
|
|
9051
|
+
if (exists(path4.join(candidate, "meta.json"))) return candidate;
|
|
9052
|
+
const parent = path4.dirname(dir);
|
|
9053
|
+
if (parent === dir) break;
|
|
9054
|
+
dir = parent;
|
|
9055
|
+
}
|
|
9056
|
+
return path4.resolve(startDir, "../../../", rel);
|
|
9057
|
+
}
|
|
9058
|
+
|
|
8771
9059
|
// src/commands/canvas/scaffold-video.ts
|
|
8772
|
-
var SHIPPED_COMPOSITION_DIR =
|
|
9060
|
+
var SHIPPED_COMPOSITION_DIR = resolveShippedCanvasDir("video-overlay-composition", import.meta.dirname);
|
|
8773
9061
|
function resolveModel2(kind, preferred) {
|
|
8774
9062
|
const ids = Object.keys(MODEL_REGISTRY[kind]);
|
|
8775
9063
|
return ids.includes(preferred) ? preferred : ids[0] ?? preferred;
|
|
@@ -8890,13 +9178,19 @@ var scaffoldVideoCommand = defineCommand76({
|
|
|
8890
9178
|
"video-model": { type: "string", description: "Override the video_generate model id for clips" }
|
|
8891
9179
|
},
|
|
8892
9180
|
async run({ args }) {
|
|
8893
|
-
const videoPath =
|
|
8894
|
-
const base =
|
|
8895
|
-
const outPath = args.out ?
|
|
8896
|
-
const outDir =
|
|
8897
|
-
const blueprintPath =
|
|
9181
|
+
const videoPath = path5.resolve(String(args.file));
|
|
9182
|
+
const base = path5.basename(videoPath, path5.extname(videoPath));
|
|
9183
|
+
const outPath = args.out ? path5.resolve(String(args.out)) : path5.join(path5.dirname(videoPath), `${base}.video.canvas.json`);
|
|
9184
|
+
const outDir = path5.dirname(outPath);
|
|
9185
|
+
const blueprintPath = path5.join(outDir, "prompt.json");
|
|
8898
9186
|
const frames = args.frames === "reuse" ? "reuse" : "generate";
|
|
8899
9187
|
const maxScenes = args["max-scenes"] ? Number(args["max-scenes"]) : void 0;
|
|
9188
|
+
if (Number.isFinite(maxScenes)) {
|
|
9189
|
+
process.stderr.write(
|
|
9190
|
+
`\u26A0\uFE0F --max-scenes ${maxScenes} caps the deconstruct: any scenes beyond ${maxScenes} are MERGED away, reducing fidelity (fewer cuts, lost beats). Omit it to reproduce every scene.
|
|
9191
|
+
`
|
|
9192
|
+
);
|
|
9193
|
+
}
|
|
8900
9194
|
const { deconstructModel, selectModel, imageModel, videoModel } = resolveModels2(args);
|
|
8901
9195
|
const analysisCanvas = buildAnalysisCanvas(videoPath, deconstructModel, selectModel, {
|
|
8902
9196
|
maxScenes: Number.isFinite(maxScenes) ? maxScenes : void 0,
|
|
@@ -8908,8 +9202,19 @@ var scaffoldVideoCommand = defineCommand76({
|
|
|
8908
9202
|
const annotated = annotateBlueprintWithElements(blueprint, elements);
|
|
8909
9203
|
await writeFile2(blueprintPath, `${JSON.stringify(annotated, null, 2)}
|
|
8910
9204
|
`, "utf8");
|
|
8911
|
-
const compositionDest =
|
|
9205
|
+
const compositionDest = path5.join(outDir, "video-overlay-composition");
|
|
8912
9206
|
await cp(SHIPPED_COMPOSITION_DIR, compositionDest, { recursive: true });
|
|
9207
|
+
const indexPath = path5.join(compositionDest, "index.html");
|
|
9208
|
+
const overlayHtml = buildOverlayHtml(blueprint);
|
|
9209
|
+
const indexHtml = await readFile4(indexPath, "utf8");
|
|
9210
|
+
const injected = indexHtml.replace("<!--OVERLAYS-->", () => overlayHtml);
|
|
9211
|
+
if (injected === indexHtml && overlayHtml.trim()) {
|
|
9212
|
+
fail2(
|
|
9213
|
+
"composition_marker_missing",
|
|
9214
|
+
`video-overlay-composition/index.html is missing the <!--OVERLAYS--> marker \u2014 cannot inject the overlay layer`
|
|
9215
|
+
);
|
|
9216
|
+
}
|
|
9217
|
+
await writeFile2(indexPath, injected, "utf8");
|
|
8913
9218
|
const opts = {
|
|
8914
9219
|
imageModel,
|
|
8915
9220
|
videoModel,
|
|
@@ -8952,7 +9257,7 @@ var scaffoldVideoCommand = defineCommand76({
|
|
|
8952
9257
|
run_estimated_credits: validation.estimatedCredits
|
|
8953
9258
|
},
|
|
8954
9259
|
checklist: {
|
|
8955
|
-
edit_prompt: `Edit ${
|
|
9260
|
+
edit_prompt: `Edit ${path5.basename(blueprintPath)} \u2014 the blueprint deconstructed from your video; rewrite it into the ad you want (cast, palette, copy, claims). Every scene frame reads it via target_blueprint.`,
|
|
8956
9261
|
recurring_elements_to_supply: report.elements,
|
|
8957
9262
|
voices_to_confirm: report.dialogue.map((d) => ({
|
|
8958
9263
|
scene: d.scene,
|
|
@@ -8977,7 +9282,7 @@ var scaffoldVideoCommand = defineCommand76({
|
|
|
8977
9282
|
|
|
8978
9283
|
// src/commands/canvas/validate.ts
|
|
8979
9284
|
import { readFile as readFile5 } from "fs/promises";
|
|
8980
|
-
import
|
|
9285
|
+
import path6 from "path";
|
|
8981
9286
|
import { defineCommand as defineCommand77 } from "citty";
|
|
8982
9287
|
var validateCommand = defineCommand77({
|
|
8983
9288
|
meta: {
|
|
@@ -8986,7 +9291,7 @@ var validateCommand = defineCommand77({
|
|
|
8986
9291
|
},
|
|
8987
9292
|
args: { file: { type: "positional", required: true, description: "Path to canvas JSON" } },
|
|
8988
9293
|
async run({ args }) {
|
|
8989
|
-
const filePath =
|
|
9294
|
+
const filePath = path6.resolve(String(args.file));
|
|
8990
9295
|
const raw = await readFile5(filePath, "utf8");
|
|
8991
9296
|
let parsed;
|
|
8992
9297
|
try {
|
|
@@ -9221,7 +9526,7 @@ Examples:
|
|
|
9221
9526
|
});
|
|
9222
9527
|
|
|
9223
9528
|
// src/commands/ga4/query.ts
|
|
9224
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
9529
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
9225
9530
|
import { resolve as resolve2 } from "path";
|
|
9226
9531
|
import { defineCommand as defineCommand81 } from "citty";
|
|
9227
9532
|
|
|
@@ -9295,7 +9600,7 @@ function writeRowsToFile2(filePath, rows, append) {
|
|
|
9295
9600
|
const fields = extractFields2(rows);
|
|
9296
9601
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
9297
9602
|
if (ext === "csv") {
|
|
9298
|
-
if (!append || !
|
|
9603
|
+
if (!append || !existsSync4(filePath)) {
|
|
9299
9604
|
writeFileSync4(filePath, `${toCsvRow(fields)}
|
|
9300
9605
|
`, "utf-8");
|
|
9301
9606
|
}
|
|
@@ -9306,12 +9611,12 @@ function writeRowsToFile2(filePath, rows, append) {
|
|
|
9306
9611
|
const lines = rows.map((row) => JSON.stringify(row));
|
|
9307
9612
|
const content = `${lines.join("\n")}
|
|
9308
9613
|
`;
|
|
9309
|
-
if (append &&
|
|
9614
|
+
if (append && existsSync4(filePath)) {
|
|
9310
9615
|
appendFileSync2(filePath, content, "utf-8");
|
|
9311
9616
|
} else {
|
|
9312
9617
|
writeFileSync4(filePath, content, "utf-8");
|
|
9313
9618
|
}
|
|
9314
|
-
} else if (append &&
|
|
9619
|
+
} else if (append && existsSync4(filePath)) {
|
|
9315
9620
|
const existing = JSON.parse(readFileSync6(filePath, "utf-8"));
|
|
9316
9621
|
writeFileSync4(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
|
|
9317
9622
|
} else {
|
|
@@ -9452,7 +9757,7 @@ Examples:
|
|
|
9452
9757
|
import { defineCommand as defineCommand86 } from "citty";
|
|
9453
9758
|
|
|
9454
9759
|
// src/commands/gsc/query.ts
|
|
9455
|
-
import { appendFileSync as appendFileSync3, existsSync as
|
|
9760
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
9456
9761
|
import { resolve as resolve3 } from "path";
|
|
9457
9762
|
import { defineCommand as defineCommand83 } from "citty";
|
|
9458
9763
|
|
|
@@ -9581,7 +9886,7 @@ function writeRowsToFile3(filePath, rows, append) {
|
|
|
9581
9886
|
const fields = extractFields3(rows);
|
|
9582
9887
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
9583
9888
|
if (ext === "csv") {
|
|
9584
|
-
if (!append || !
|
|
9889
|
+
if (!append || !existsSync5(filePath)) {
|
|
9585
9890
|
writeFileSync5(filePath, `${toCsvRow(fields)}
|
|
9586
9891
|
`, "utf-8");
|
|
9587
9892
|
}
|
|
@@ -9591,12 +9896,12 @@ function writeRowsToFile3(filePath, rows, append) {
|
|
|
9591
9896
|
} else if (ext === "jsonl") {
|
|
9592
9897
|
const content = `${rows.map((row) => JSON.stringify(row)).join("\n")}
|
|
9593
9898
|
`;
|
|
9594
|
-
if (append &&
|
|
9899
|
+
if (append && existsSync5(filePath)) {
|
|
9595
9900
|
appendFileSync3(filePath, content, "utf-8");
|
|
9596
9901
|
} else {
|
|
9597
9902
|
writeFileSync5(filePath, content, "utf-8");
|
|
9598
9903
|
}
|
|
9599
|
-
} else if (append &&
|
|
9904
|
+
} else if (append && existsSync5(filePath)) {
|
|
9600
9905
|
const existing = JSON.parse(readFileSync7(filePath, "utf-8"));
|
|
9601
9906
|
writeFileSync5(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
|
|
9602
9907
|
} else {
|
|
@@ -9858,7 +10163,7 @@ Examples:
|
|
|
9858
10163
|
});
|
|
9859
10164
|
|
|
9860
10165
|
// src/commands/images/index.ts
|
|
9861
|
-
import { defineCommand as
|
|
10166
|
+
import { defineCommand as defineCommand109 } from "citty";
|
|
9862
10167
|
|
|
9863
10168
|
// src/commands/images/crop.ts
|
|
9864
10169
|
import { defineCommand as defineCommand87 } from "citty";
|
|
@@ -9914,9 +10219,9 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
9914
10219
|
}
|
|
9915
10220
|
return readFile6(pathOrUrl);
|
|
9916
10221
|
}
|
|
9917
|
-
async function isDirectory(
|
|
10222
|
+
async function isDirectory(path7) {
|
|
9918
10223
|
try {
|
|
9919
|
-
const s = await stat2(
|
|
10224
|
+
const s = await stat2(path7);
|
|
9920
10225
|
return s.isDirectory();
|
|
9921
10226
|
} catch {
|
|
9922
10227
|
return false;
|
|
@@ -10275,8 +10580,157 @@ var findCommand = defineCommand91({
|
|
|
10275
10580
|
}
|
|
10276
10581
|
});
|
|
10277
10582
|
|
|
10278
|
-
// src/commands/images/
|
|
10583
|
+
// src/commands/images/generate.ts
|
|
10584
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
10279
10585
|
import { defineCommand as defineCommand92 } from "citty";
|
|
10586
|
+
import sharp2 from "sharp";
|
|
10587
|
+
var GENERATE_TIMEOUT_MS = 18e4;
|
|
10588
|
+
var REFERENCE_MAX_EDGE = 1536;
|
|
10589
|
+
var MODEL_LIST = [...IMAGE_GENERATE_MODELS];
|
|
10590
|
+
registerSchema({
|
|
10591
|
+
command: "images.generate",
|
|
10592
|
+
description: "Generate an image with AI (OpenRouter image models) and ingest it into the library. Default model openai/gpt-5.4-image-2 \u2014 photo-realistic, cleanest text rendering. Pass --reference with image URLs AND/OR local file paths (brand logo, product shot, Pinterest pin, cropped photo) to ground the generation on real imagery for on-brand, realistic output.",
|
|
10593
|
+
args: {
|
|
10594
|
+
prompt: { type: "string", description: "What to generate", required: true },
|
|
10595
|
+
model: {
|
|
10596
|
+
type: "string",
|
|
10597
|
+
description: `Model (default openai/gpt-5.4-image-2). One of: ${MODEL_LIST.join(", ")}`,
|
|
10598
|
+
required: false,
|
|
10599
|
+
enum: MODEL_LIST
|
|
10600
|
+
},
|
|
10601
|
+
"aspect-ratio": {
|
|
10602
|
+
type: "string",
|
|
10603
|
+
description: "Aspect ratio, e.g. 1:1 (default), 16:9, 9:16, 4:5, 3:2 (Gemini flash also supports 1:4/8:1)",
|
|
10604
|
+
required: false
|
|
10605
|
+
},
|
|
10606
|
+
"image-size": {
|
|
10607
|
+
type: "string",
|
|
10608
|
+
description: "Output resolution: 1K (default) | 2K | 4K (Gemini flash also 0.5K)",
|
|
10609
|
+
required: false
|
|
10610
|
+
},
|
|
10611
|
+
reference: {
|
|
10612
|
+
type: "string",
|
|
10613
|
+
description: "Comma-separated visual references \u2014 each is a public image URL OR a local file path (a sandbox image: brand logo, cropped photo, screenshot). Local files are downscaled and inlined automatically. Grounds output in real imagery; applied in order.",
|
|
10614
|
+
required: false
|
|
10615
|
+
},
|
|
10616
|
+
strength: {
|
|
10617
|
+
type: "number",
|
|
10618
|
+
description: "Recraft v4.1 Pro Vector only: vectorization strength 0-1",
|
|
10619
|
+
required: false
|
|
10620
|
+
},
|
|
10621
|
+
"rgb-colors": {
|
|
10622
|
+
type: "string",
|
|
10623
|
+
description: 'Recraft only: JSON array of [r,g,b] palette triples, e.g. "[[10,10,10],[255,80,0]]"',
|
|
10624
|
+
required: false
|
|
10625
|
+
},
|
|
10626
|
+
"bg-rgb": {
|
|
10627
|
+
type: "string",
|
|
10628
|
+
description: 'Recraft only: JSON [r,g,b] background color, e.g. "[255,255,255]"',
|
|
10629
|
+
required: false
|
|
10630
|
+
},
|
|
10631
|
+
context: {
|
|
10632
|
+
type: "string",
|
|
10633
|
+
description: "Description context hint for the ingested row (overrides the prompt as the describe hint)",
|
|
10634
|
+
required: false
|
|
10635
|
+
}
|
|
10636
|
+
}
|
|
10637
|
+
});
|
|
10638
|
+
function parseJsonArg(raw, flag) {
|
|
10639
|
+
try {
|
|
10640
|
+
return JSON.parse(raw);
|
|
10641
|
+
} catch {
|
|
10642
|
+
throw new ApiError("VALIDATION_ERROR", `--${flag} must be valid JSON (got: ${raw})`);
|
|
10643
|
+
}
|
|
10644
|
+
}
|
|
10645
|
+
function buildGenerateBody(args, prompt) {
|
|
10646
|
+
const body = { prompt };
|
|
10647
|
+
if (args.model) body.model = args.model;
|
|
10648
|
+
if (args["aspect-ratio"]) body.aspectRatio = args["aspect-ratio"];
|
|
10649
|
+
if (args["image-size"]) body.imageSize = args["image-size"];
|
|
10650
|
+
if (args.strength !== void 0) body.strength = Number(args.strength);
|
|
10651
|
+
if (args["rgb-colors"]) body.rgbColors = parseJsonArg(args["rgb-colors"], "rgb-colors");
|
|
10652
|
+
if (args["bg-rgb"]) body.backgroundRgbColor = parseJsonArg(args["bg-rgb"], "bg-rgb");
|
|
10653
|
+
if (args.context) body.descriptionContext = args.context;
|
|
10654
|
+
return body;
|
|
10655
|
+
}
|
|
10656
|
+
async function resolveReferences(spec) {
|
|
10657
|
+
const trimmed = spec.trim();
|
|
10658
|
+
const entries = trimmed.startsWith("data:") ? [trimmed] : trimmed.split(",").map((s) => s.trim()).filter(Boolean);
|
|
10659
|
+
const out = [];
|
|
10660
|
+
for (const entry of entries) {
|
|
10661
|
+
if (isRemoteUrl(entry) || entry.startsWith("data:")) {
|
|
10662
|
+
out.push(entry);
|
|
10663
|
+
continue;
|
|
10664
|
+
}
|
|
10665
|
+
let raw;
|
|
10666
|
+
try {
|
|
10667
|
+
raw = await readFile7(entry);
|
|
10668
|
+
} catch {
|
|
10669
|
+
throw new ApiError("VALIDATION_ERROR", `Reference file not found: ${entry}`);
|
|
10670
|
+
}
|
|
10671
|
+
let webp;
|
|
10672
|
+
try {
|
|
10673
|
+
webp = await sharp2(raw).resize({ width: REFERENCE_MAX_EDGE, height: REFERENCE_MAX_EDGE, fit: "inside", withoutEnlargement: true }).webp({ quality: 82 }).toBuffer();
|
|
10674
|
+
} catch {
|
|
10675
|
+
throw new ApiError("VALIDATION_ERROR", `Reference is not a readable image: ${entry}`);
|
|
10676
|
+
}
|
|
10677
|
+
out.push(`data:image/webp;base64,${webp.toString("base64")}`);
|
|
10678
|
+
}
|
|
10679
|
+
return out;
|
|
10680
|
+
}
|
|
10681
|
+
var generateCommand = defineCommand92({
|
|
10682
|
+
meta: {
|
|
10683
|
+
name: "generate",
|
|
10684
|
+
description: "Generate an image with AI and store it in the library (cost-tracked per request via OpenRouter usage). Models mirror the canvas: openai/gpt-5.4-image-2 (default \u2014 photoreal, cleanest text, best for ad/landing reproduction), google/gemini-3-pro-image-preview (Nano Banana Pro), google/gemini-3.5-flash & google/gemini-3.1-flash-image-preview (fast, extreme aspect ratios), recraft/recraft-v4.1-pro-vector (vector/SVG-style with palette control). The result is auto-ingested (describe + embed), so the next `baker images library` query finds it. Pass --reference with image URLs and/or local file paths (Pinterest, stock, brand assets, sandbox files) to ground generation in reality.\n\nExamples:\n baker images generate 'a friendly golden retriever sitting in a bright modern living room' --aspect-ratio 16:9\n baker images generate 'hero shot of a matte black water bottle on marble' --model google/gemini-3-pro-image-preview --image-size 2K\n baker images generate 'lifestyle photo matching this mood' --reference 'https://\u2026/ref1.jpg,https://\u2026/ref2.jpg'\n baker images generate 'put this product on a marble countertop, soft daylight' --reference './src/brand/logos/product.png,./refs/kitchen-mood.jpg'\n baker images generate 'flat geometric mascot, brand palette' --model recraft/recraft-v4.1-pro-vector --rgb-colors '[[10,10,10],[255,80,0]]'"
|
|
10685
|
+
},
|
|
10686
|
+
args: {
|
|
10687
|
+
prompt: { type: "positional", description: "What to generate", required: false },
|
|
10688
|
+
model: { type: "string", description: "Model id (default openai/gpt-5.4-image-2)", required: false },
|
|
10689
|
+
"aspect-ratio": { type: "string", description: "Aspect ratio (default 1:1)", required: false },
|
|
10690
|
+
"image-size": { type: "string", description: "1K (default) | 2K | 4K | 0.5K", required: false },
|
|
10691
|
+
reference: {
|
|
10692
|
+
type: "string",
|
|
10693
|
+
description: "Comma-separated reference image URLs and/or local file paths",
|
|
10694
|
+
required: false
|
|
10695
|
+
},
|
|
10696
|
+
strength: { type: "string", description: "Recraft vectorization strength 0-1", required: false },
|
|
10697
|
+
"rgb-colors": {
|
|
10698
|
+
type: "string",
|
|
10699
|
+
description: "Recraft palette JSON, e.g. [[10,10,10],[255,80,0]]",
|
|
10700
|
+
required: false
|
|
10701
|
+
},
|
|
10702
|
+
"bg-rgb": { type: "string", description: "Recraft background JSON, e.g. [255,255,255]", required: false },
|
|
10703
|
+
context: { type: "string", description: "Describe-hint override for the ingested row", required: false }
|
|
10704
|
+
},
|
|
10705
|
+
run: async ({ args }) => {
|
|
10706
|
+
try {
|
|
10707
|
+
const prompt = args.prompt;
|
|
10708
|
+
if (!prompt) {
|
|
10709
|
+
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "Prompt is required" } });
|
|
10710
|
+
process.exit(1);
|
|
10711
|
+
}
|
|
10712
|
+
const body = buildGenerateBody(args, prompt);
|
|
10713
|
+
if (args.reference) {
|
|
10714
|
+
const refs = await resolveReferences(args.reference);
|
|
10715
|
+
if (refs.length > 0) body.referenceUrls = refs;
|
|
10716
|
+
}
|
|
10717
|
+
const data = await apiPost("/api/images/generate", body, {
|
|
10718
|
+
timeoutMs: GENERATE_TIMEOUT_MS
|
|
10719
|
+
});
|
|
10720
|
+
writeJson({ ok: true, data });
|
|
10721
|
+
} catch (err) {
|
|
10722
|
+
if (err instanceof ApiError) {
|
|
10723
|
+
writeJson({ ok: false, error: { code: err.code, message: err.message } });
|
|
10724
|
+
process.exit(1);
|
|
10725
|
+
}
|
|
10726
|
+
writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
|
|
10727
|
+
process.exit(1);
|
|
10728
|
+
}
|
|
10729
|
+
}
|
|
10730
|
+
});
|
|
10731
|
+
|
|
10732
|
+
// src/commands/images/get.ts
|
|
10733
|
+
import { defineCommand as defineCommand93 } from "citty";
|
|
10280
10734
|
registerSchema({
|
|
10281
10735
|
command: "images.get",
|
|
10282
10736
|
description: "Get a single image by ID",
|
|
@@ -10284,7 +10738,7 @@ registerSchema({
|
|
|
10284
10738
|
id: { type: "string", description: "Image ID", required: true }
|
|
10285
10739
|
}
|
|
10286
10740
|
});
|
|
10287
|
-
var getCommand2 =
|
|
10741
|
+
var getCommand2 = defineCommand93({
|
|
10288
10742
|
meta: { name: "get", description: "Get a single image by ID. Example: baker images get j571abc123" },
|
|
10289
10743
|
args: {
|
|
10290
10744
|
id: { type: "positional", description: "Image ID", required: false },
|
|
@@ -10320,7 +10774,7 @@ var getCommand2 = defineCommand92({
|
|
|
10320
10774
|
});
|
|
10321
10775
|
|
|
10322
10776
|
// src/commands/images/gif.ts
|
|
10323
|
-
import { defineCommand as
|
|
10777
|
+
import { defineCommand as defineCommand94 } from "citty";
|
|
10324
10778
|
registerSchema({
|
|
10325
10779
|
command: "images.gif",
|
|
10326
10780
|
description: "Search Giphy for GIFs / reaction memes (paid social creative).",
|
|
@@ -10352,7 +10806,7 @@ registerSchema({
|
|
|
10352
10806
|
}
|
|
10353
10807
|
}
|
|
10354
10808
|
});
|
|
10355
|
-
var gifCommand =
|
|
10809
|
+
var gifCommand = defineCommand94({
|
|
10356
10810
|
meta: {
|
|
10357
10811
|
name: "gif",
|
|
10358
10812
|
description: "Search Giphy for GIFs / reaction memes \u2014 built for paid-social creative (Meta, TikTok, LinkedIn, X). Free API. Each hit carries WebP + GIF + MP4 URLs in providerMeta so you can pick the right format per platform.\n\nExample: baker images gif 'this is fine' --limit 10\nExample: baker images gif 'office reaction' --rating pg --auto-ingest 2\nExample: baker images gif --trending --limit 25"
|
|
@@ -10399,7 +10853,7 @@ var gifCommand = defineCommand93({
|
|
|
10399
10853
|
});
|
|
10400
10854
|
|
|
10401
10855
|
// src/commands/images/google.ts
|
|
10402
|
-
import { defineCommand as
|
|
10856
|
+
import { defineCommand as defineCommand95 } from "citty";
|
|
10403
10857
|
registerSchema({
|
|
10404
10858
|
command: "images.google",
|
|
10405
10859
|
description: "Google Images search via the official Custom Search JSON API. Unverified source \u2014 inspect before placing.",
|
|
@@ -10435,7 +10889,7 @@ registerSchema({
|
|
|
10435
10889
|
}
|
|
10436
10890
|
}
|
|
10437
10891
|
});
|
|
10438
|
-
var googleCommand2 =
|
|
10892
|
+
var googleCommand2 = defineCommand95({
|
|
10439
10893
|
meta: {
|
|
10440
10894
|
name: "google",
|
|
10441
10895
|
description: "Google Images via the official Custom Search JSON API ($0.005/query, free 100/day). \u26A0 Source unverified \u2014 watermarks, low-res, mislabeled results are common. Use as last resort. With --auto-ingest, ingested hits return Baker-owned URLs.\n\nExample: baker images google 'industrial workshop' --type photo --size large --limit 20"
|
|
@@ -10483,7 +10937,7 @@ var googleCommand2 = defineCommand94({
|
|
|
10483
10937
|
});
|
|
10484
10938
|
|
|
10485
10939
|
// src/commands/images/icon.ts
|
|
10486
|
-
import { defineCommand as
|
|
10940
|
+
import { defineCommand as defineCommand96 } from "citty";
|
|
10487
10941
|
registerSchema({
|
|
10488
10942
|
command: "images.icon",
|
|
10489
10943
|
description: "Icon lookup via Iconify (200+ icon sets, free CDN).",
|
|
@@ -10509,7 +10963,7 @@ registerSchema({
|
|
|
10509
10963
|
}
|
|
10510
10964
|
}
|
|
10511
10965
|
});
|
|
10512
|
-
var iconCommand =
|
|
10966
|
+
var iconCommand = defineCommand96({
|
|
10513
10967
|
meta: {
|
|
10514
10968
|
name: "icon",
|
|
10515
10969
|
description: "Icon via Iconify (simple-icons, logos, lucide, devicon, heroicons, tabler, phosphor, material-symbols, \u2026). Free CDN, no API key.\n\nExample: baker images icon react --set devicon\nExample: baker images icon lucide:check --color '#0a0a0a'"
|
|
@@ -10549,7 +11003,7 @@ var iconCommand = defineCommand95({
|
|
|
10549
11003
|
});
|
|
10550
11004
|
|
|
10551
11005
|
// src/commands/images/ingest.ts
|
|
10552
|
-
import { defineCommand as
|
|
11006
|
+
import { defineCommand as defineCommand97 } from "citty";
|
|
10553
11007
|
registerSchema({
|
|
10554
11008
|
command: "images.ingest",
|
|
10555
11009
|
description: "Ingest a remote image URL into the library (full describe + embed).",
|
|
@@ -10561,7 +11015,7 @@ registerSchema({
|
|
|
10561
11015
|
context: { type: "string", description: "Description context hint", required: false }
|
|
10562
11016
|
}
|
|
10563
11017
|
});
|
|
10564
|
-
var ingestCommand =
|
|
11018
|
+
var ingestCommand = defineCommand97({
|
|
10565
11019
|
meta: {
|
|
10566
11020
|
name: "ingest",
|
|
10567
11021
|
description: "Download a remote URL and store it in the library. Hash-deduped on bytes + externalId.\n\nExample: baker images ingest https://img.freepik.com/free-photo/xyz.jpg --source magnific --external-id 12345"
|
|
@@ -10603,7 +11057,7 @@ var ingestCommand = defineCommand96({
|
|
|
10603
11057
|
});
|
|
10604
11058
|
|
|
10605
11059
|
// src/commands/images/library.ts
|
|
10606
|
-
import { defineCommand as
|
|
11060
|
+
import { defineCommand as defineCommand98 } from "citty";
|
|
10607
11061
|
registerSchema({
|
|
10608
11062
|
command: "images.library",
|
|
10609
11063
|
description: "Search the company image library. Returns only ready images.",
|
|
@@ -10629,7 +11083,7 @@ registerSchema({
|
|
|
10629
11083
|
}
|
|
10630
11084
|
}
|
|
10631
11085
|
});
|
|
10632
|
-
var libraryCommand =
|
|
11086
|
+
var libraryCommand = defineCommand98({
|
|
10633
11087
|
meta: {
|
|
10634
11088
|
name: "library",
|
|
10635
11089
|
description: "Search the company image library (hybrid BM25 + vector + Cohere rerank). Use this BEFORE any external provider.\n\nExample: baker images library 'hero banner' --aspect-ratio 16:9 --source magnific"
|
|
@@ -10686,7 +11140,7 @@ var libraryCommand = defineCommand97({
|
|
|
10686
11140
|
});
|
|
10687
11141
|
|
|
10688
11142
|
// src/commands/images/logo.ts
|
|
10689
|
-
import { defineCommand as
|
|
11143
|
+
import { defineCommand as defineCommand99 } from "citty";
|
|
10690
11144
|
registerSchema({
|
|
10691
11145
|
command: "images.logo",
|
|
10692
11146
|
description: "Brand logo lookup via Brandfetch CDN (fallback/404). Auto-ingests by default.",
|
|
@@ -10711,7 +11165,7 @@ registerSchema({
|
|
|
10711
11165
|
}
|
|
10712
11166
|
}
|
|
10713
11167
|
});
|
|
10714
|
-
var logoCommand =
|
|
11168
|
+
var logoCommand = defineCommand99({
|
|
10715
11169
|
meta: {
|
|
10716
11170
|
name: "logo",
|
|
10717
11171
|
description: "Brand logo via Brandfetch CDN. Returns up to 5 variants (icon, light/dark logo, light/dark symbol). Auto-ingests the first variant.\n\nExample: baker images logo stripe.com --variant logo"
|
|
@@ -10749,7 +11203,7 @@ var logoCommand = defineCommand98({
|
|
|
10749
11203
|
});
|
|
10750
11204
|
|
|
10751
11205
|
// src/commands/images/normalize.ts
|
|
10752
|
-
import { defineCommand as
|
|
11206
|
+
import { defineCommand as defineCommand100 } from "citty";
|
|
10753
11207
|
|
|
10754
11208
|
// src/lib/image/color-changer.ts
|
|
10755
11209
|
import quantize from "quantize";
|
|
@@ -11097,7 +11551,7 @@ function solidifyEdges(data, saturationBoost = 0.25) {
|
|
|
11097
11551
|
}
|
|
11098
11552
|
|
|
11099
11553
|
// src/lib/image/image-processor.ts
|
|
11100
|
-
import
|
|
11554
|
+
import sharp3 from "sharp";
|
|
11101
11555
|
function hasGradient(data) {
|
|
11102
11556
|
const uniqueColors = /* @__PURE__ */ new Set();
|
|
11103
11557
|
for (let i = 0; i < data.length; i += 4) {
|
|
@@ -11129,10 +11583,10 @@ function createSvgRecolorer(target, palette) {
|
|
|
11129
11583
|
async function processGradient(data, info) {
|
|
11130
11584
|
if (!hasGradient(data)) return { data, info };
|
|
11131
11585
|
const solidified = solidifyEdges(data, 0.1);
|
|
11132
|
-
const blurred = await
|
|
11586
|
+
const blurred = await sharp3(solidified, {
|
|
11133
11587
|
raw: { channels: 4, width: info.width, height: info.height }
|
|
11134
11588
|
}).png().blur(0.3).toBuffer();
|
|
11135
|
-
const result = await
|
|
11589
|
+
const result = await sharp3(blurred).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
11136
11590
|
return { data: result.data, info: result.info };
|
|
11137
11591
|
}
|
|
11138
11592
|
function isSvgBuffer(buffer) {
|
|
@@ -11141,13 +11595,13 @@ function isSvgBuffer(buffer) {
|
|
|
11141
11595
|
}
|
|
11142
11596
|
async function processInternal(inputBuffer, isSVG, options) {
|
|
11143
11597
|
const stages = [];
|
|
11144
|
-
const metadata = await
|
|
11598
|
+
const metadata = await sharp3(inputBuffer).metadata();
|
|
11145
11599
|
let alreadyTransparent = false;
|
|
11146
11600
|
if (metadata.hasAlpha) {
|
|
11147
|
-
const { data: alphaData } = await
|
|
11601
|
+
const { data: alphaData } = await sharp3(inputBuffer).raw().toBuffer({ resolveWithObject: true });
|
|
11148
11602
|
alreadyTransparent = hasTransparency(alphaData, 0.05);
|
|
11149
11603
|
}
|
|
11150
|
-
let { data: processedData, info } = await
|
|
11604
|
+
let { data: processedData, info } = await sharp3(inputBuffer).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
11151
11605
|
if (options.color) {
|
|
11152
11606
|
stages.push("recolor");
|
|
11153
11607
|
processedData = applyQuantization(processedData, 8);
|
|
@@ -11218,7 +11672,7 @@ async function processInternal(inputBuffer, isSVG, options) {
|
|
|
11218
11672
|
processedData = result.data;
|
|
11219
11673
|
info = result.info;
|
|
11220
11674
|
}
|
|
11221
|
-
let pipeline =
|
|
11675
|
+
let pipeline = sharp3(processedData, {
|
|
11222
11676
|
raw: { channels: 4, width: info.width, height: info.height }
|
|
11223
11677
|
}).png();
|
|
11224
11678
|
if (options.shrinkToContent) {
|
|
@@ -11231,12 +11685,12 @@ async function processInternal(inputBuffer, isSVG, options) {
|
|
|
11231
11685
|
function applyResize(buffer, resize) {
|
|
11232
11686
|
const transparent = { r: 0, g: 0, b: 0, alpha: 0 };
|
|
11233
11687
|
if (resize.size) {
|
|
11234
|
-
return
|
|
11688
|
+
return sharp3(buffer).resize(resize.size.width, resize.size.height, {
|
|
11235
11689
|
fit: resize.fit ?? "contain",
|
|
11236
11690
|
background: transparent
|
|
11237
11691
|
}).toBuffer();
|
|
11238
11692
|
}
|
|
11239
|
-
return
|
|
11693
|
+
return sharp3(buffer).resize(resize.width ?? null, resize.height ?? null, { fit: "inside", withoutEnlargement: false }).toBuffer();
|
|
11240
11694
|
}
|
|
11241
11695
|
async function processImage(inputBuffer, options = {}) {
|
|
11242
11696
|
const stages = [];
|
|
@@ -11245,7 +11699,7 @@ async function processImage(inputBuffer, options = {}) {
|
|
|
11245
11699
|
if (options.resize) {
|
|
11246
11700
|
stages.push("resize");
|
|
11247
11701
|
const resized = await applyResize(inputBuffer, options.resize);
|
|
11248
|
-
const meta3 = await
|
|
11702
|
+
const meta3 = await sharp3(resized).metadata();
|
|
11249
11703
|
return {
|
|
11250
11704
|
buffer: resized,
|
|
11251
11705
|
format: "png",
|
|
@@ -11254,7 +11708,7 @@ async function processImage(inputBuffer, options = {}) {
|
|
|
11254
11708
|
stages
|
|
11255
11709
|
};
|
|
11256
11710
|
}
|
|
11257
|
-
const meta2 = await
|
|
11711
|
+
const meta2 = await sharp3(inputBuffer).metadata();
|
|
11258
11712
|
return {
|
|
11259
11713
|
buffer: inputBuffer,
|
|
11260
11714
|
format: "svg",
|
|
@@ -11266,7 +11720,7 @@ async function processImage(inputBuffer, options = {}) {
|
|
|
11266
11720
|
let workingBuffer = inputBuffer;
|
|
11267
11721
|
if (isSVG) {
|
|
11268
11722
|
stages.push("rasterize-svg");
|
|
11269
|
-
workingBuffer = await
|
|
11723
|
+
workingBuffer = await sharp3(inputBuffer).png({ compressionLevel: 0, force: true, palette: false, quality: 100 }).toBuffer();
|
|
11270
11724
|
}
|
|
11271
11725
|
const pass = await processInternal(workingBuffer, isSVG, options);
|
|
11272
11726
|
stages.push(...pass.stages);
|
|
@@ -11275,7 +11729,7 @@ async function processImage(inputBuffer, options = {}) {
|
|
|
11275
11729
|
stages.push("resize");
|
|
11276
11730
|
processed = await applyResize(processed, options.resize);
|
|
11277
11731
|
}
|
|
11278
|
-
const meta = await
|
|
11732
|
+
const meta = await sharp3(processed).metadata();
|
|
11279
11733
|
return {
|
|
11280
11734
|
buffer: processed,
|
|
11281
11735
|
format: "png",
|
|
@@ -11481,7 +11935,7 @@ function coerceRawArgs(args) {
|
|
|
11481
11935
|
"dry-run": bool(args["dry-run"])
|
|
11482
11936
|
};
|
|
11483
11937
|
}
|
|
11484
|
-
var normalizeCommand =
|
|
11938
|
+
var normalizeCommand = defineCommand100({
|
|
11485
11939
|
meta: {
|
|
11486
11940
|
name: "normalize",
|
|
11487
11941
|
description: `Normalize logos / images: declarative recolor + bg removal + trim + resize. Operates on local files; writes in-place by default.
|
|
@@ -11535,8 +11989,68 @@ Examples:
|
|
|
11535
11989
|
}
|
|
11536
11990
|
});
|
|
11537
11991
|
|
|
11992
|
+
// src/commands/images/pinterest.ts
|
|
11993
|
+
import { defineCommand as defineCommand101 } from "citty";
|
|
11994
|
+
registerSchema({
|
|
11995
|
+
command: "images.pinterest",
|
|
11996
|
+
description: "Pinterest image search via ScrapeCreators. Reference-grade real-world photography, product styling, interiors, fashion, food, and aesthetic mood boards. Inspect before placing \u2014 Pinterest is unverified, trademark-bearing web content.",
|
|
11997
|
+
args: {
|
|
11998
|
+
query: { type: "string", description: "Search query", required: true },
|
|
11999
|
+
limit: { type: "number", description: "Max results (1-20, default 5)", required: false, default: 5 },
|
|
12000
|
+
"auto-ingest": {
|
|
12001
|
+
type: "number",
|
|
12002
|
+
description: "Auto-ingest top N and return Baker-owned URLs on ingested hits",
|
|
12003
|
+
required: false,
|
|
12004
|
+
default: 0
|
|
12005
|
+
},
|
|
12006
|
+
context: {
|
|
12007
|
+
type: "string",
|
|
12008
|
+
description: "Free-text hint applied to every auto-ingested hit (overrides the pin title)",
|
|
12009
|
+
required: false
|
|
12010
|
+
}
|
|
12011
|
+
}
|
|
12012
|
+
});
|
|
12013
|
+
var pinterestCommand = defineCommand101({
|
|
12014
|
+
meta: {
|
|
12015
|
+
name: "pinterest",
|
|
12016
|
+
description: "Pinterest image search via ScrapeCreators ($0.00188/request). Best for photo-realistic reference imagery \u2014 lifestyle, interiors, fashion, food, product styling, and mood boards to brief AI generation against. \u26A0 Unverified, trademark-bearing web content \u2014 inspect and respect rights before placing on a customer page. Browse first; auto-ingest only the pins you commit to.\n\nExamples:\n baker images pinterest 'scandinavian living room'\n baker images pinterest 'minimalist skincare product photography' --limit 20\n baker images pinterest 'cozy coffee shop interior' --auto-ingest 2 --context 'Mood reference for hero photography'"
|
|
12017
|
+
},
|
|
12018
|
+
args: {
|
|
12019
|
+
query: { type: "positional", description: "Search query", required: false },
|
|
12020
|
+
limit: { type: "string", description: "Max results (1-20)", required: false },
|
|
12021
|
+
"auto-ingest": {
|
|
12022
|
+
type: "string",
|
|
12023
|
+
description: "Auto-ingest top N and return Baker-owned URLs on ingested hits",
|
|
12024
|
+
required: false
|
|
12025
|
+
},
|
|
12026
|
+
context: { type: "string", description: "Description context hint applied to auto-ingested hits", required: false }
|
|
12027
|
+
},
|
|
12028
|
+
run: async ({ args }) => {
|
|
12029
|
+
try {
|
|
12030
|
+
const query = args.query;
|
|
12031
|
+
if (!query) {
|
|
12032
|
+
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "Query is required" } });
|
|
12033
|
+
process.exit(1);
|
|
12034
|
+
}
|
|
12035
|
+
const body = { query };
|
|
12036
|
+
if (args.limit) body.limit = Number(args.limit);
|
|
12037
|
+
if (args["auto-ingest"]) body.autoIngest = Number(args["auto-ingest"]);
|
|
12038
|
+
if (args.context) body.descriptionContext = args.context;
|
|
12039
|
+
const data = await apiPost("/api/images/pinterest", body);
|
|
12040
|
+
writeJson({ ok: true, data });
|
|
12041
|
+
} catch (err) {
|
|
12042
|
+
if (err instanceof ApiError) {
|
|
12043
|
+
writeJson({ ok: false, error: { code: err.code, message: err.message } });
|
|
12044
|
+
process.exit(1);
|
|
12045
|
+
}
|
|
12046
|
+
writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
|
|
12047
|
+
process.exit(1);
|
|
12048
|
+
}
|
|
12049
|
+
}
|
|
12050
|
+
});
|
|
12051
|
+
|
|
11538
12052
|
// src/commands/images/screenshot.ts
|
|
11539
|
-
import { defineCommand as
|
|
12053
|
+
import { defineCommand as defineCommand102 } from "citty";
|
|
11540
12054
|
registerSchema({
|
|
11541
12055
|
command: "images.screenshot",
|
|
11542
12056
|
description: "Capture a website screenshot via ScreenshotOne. Auto-ingests on success.",
|
|
@@ -11552,7 +12066,7 @@ registerSchema({
|
|
|
11552
12066
|
}
|
|
11553
12067
|
}
|
|
11554
12068
|
});
|
|
11555
|
-
var screenshotCommand =
|
|
12069
|
+
var screenshotCommand = defineCommand102({
|
|
11556
12070
|
meta: {
|
|
11557
12071
|
name: "screenshot",
|
|
11558
12072
|
description: "Screenshot a URL via ScreenshotOne. $0.009/capture. Auto-ingests to library.\n\nExample: baker images screenshot https://stripe.com --full-page"
|
|
@@ -11602,7 +12116,7 @@ var screenshotCommand = defineCommand100({
|
|
|
11602
12116
|
});
|
|
11603
12117
|
|
|
11604
12118
|
// src/commands/images/search.ts
|
|
11605
|
-
import { defineCommand as
|
|
12119
|
+
import { defineCommand as defineCommand103 } from "citty";
|
|
11606
12120
|
registerSchema({
|
|
11607
12121
|
command: "images.search",
|
|
11608
12122
|
description: "Search images by text query. Only returns ready images.",
|
|
@@ -11618,7 +12132,7 @@ registerSchema({
|
|
|
11618
12132
|
tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
|
|
11619
12133
|
}
|
|
11620
12134
|
});
|
|
11621
|
-
var searchCommand =
|
|
12135
|
+
var searchCommand = defineCommand103({
|
|
11622
12136
|
meta: {
|
|
11623
12137
|
name: "search",
|
|
11624
12138
|
description: "Semantic search images by text query. Uses hybrid BM25 + vector + reranking. Example: baker images search 'hero banner' --aspect-ratio 16:9 --tags logo"
|
|
@@ -11678,7 +12192,7 @@ var searchCommand = defineCommand101({
|
|
|
11678
12192
|
});
|
|
11679
12193
|
|
|
11680
12194
|
// src/commands/images/sticker.ts
|
|
11681
|
-
import { defineCommand as
|
|
12195
|
+
import { defineCommand as defineCommand104 } from "citty";
|
|
11682
12196
|
registerSchema({
|
|
11683
12197
|
command: "images.sticker",
|
|
11684
12198
|
description: "Search Giphy stickers \u2014 transparent-background overlays for ad creative.",
|
|
@@ -11710,7 +12224,7 @@ registerSchema({
|
|
|
11710
12224
|
}
|
|
11711
12225
|
}
|
|
11712
12226
|
});
|
|
11713
|
-
var stickerCommand =
|
|
12227
|
+
var stickerCommand = defineCommand104({
|
|
11714
12228
|
meta: {
|
|
11715
12229
|
name: "sticker",
|
|
11716
12230
|
description: "Search Giphy's sticker corpus \u2014 transparent-background WebPs / GIFs ideal for overlaying on ad creative (Meta, TikTok, Stories). Same Giphy free API as `baker images gif`; results carry WebP + GIF + MP4 URLs in providerMeta.\n\nExample: baker images sticker 'thumbs up' --limit 10\nExample: baker images sticker celebration --rating g --auto-ingest 3\nExample: baker images sticker --trending --limit 25"
|
|
@@ -11757,7 +12271,7 @@ var stickerCommand = defineCommand102({
|
|
|
11757
12271
|
});
|
|
11758
12272
|
|
|
11759
12273
|
// src/commands/images/stock.ts
|
|
11760
|
-
import { defineCommand as
|
|
12274
|
+
import { defineCommand as defineCommand105 } from "citty";
|
|
11761
12275
|
registerSchema({
|
|
11762
12276
|
command: "images.stock",
|
|
11763
12277
|
description: "Stock photo, vector illustration, icon-set, and PSD search via Magnific (Freepik's developer API).",
|
|
@@ -11815,7 +12329,7 @@ registerSchema({
|
|
|
11815
12329
|
}
|
|
11816
12330
|
}
|
|
11817
12331
|
});
|
|
11818
|
-
var stockCommand =
|
|
12332
|
+
var stockCommand = defineCommand105({
|
|
11819
12333
|
meta: {
|
|
11820
12334
|
name: "stock",
|
|
11821
12335
|
description: "Stock search via Magnific \u2014 Freepik's developer API (~250M assets: photos, vectors, illustrations, icons, PSDs). $0.002/req. With --auto-ingest, ingested hits return Baker-owned URLs.\n\nExamples:\n baker images stock 'minimalist office'\n baker images stock 'flat office workers' --type vector\n baker images stock 'hero photo of a kitchen' --type photo --orientation landscape --ai exclude\n baker images stock 'brand pattern' --color '#0a0a0a' --license freemium --auto-ingest 2"
|
|
@@ -11871,9 +12385,9 @@ var stockCommand = defineCommand103({
|
|
|
11871
12385
|
});
|
|
11872
12386
|
|
|
11873
12387
|
// src/commands/images/upload.ts
|
|
11874
|
-
import { readFile as
|
|
12388
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
11875
12389
|
import { extname as extname2 } from "path";
|
|
11876
|
-
import { defineCommand as
|
|
12390
|
+
import { defineCommand as defineCommand106 } from "citty";
|
|
11877
12391
|
var MIME_MAP = {
|
|
11878
12392
|
".png": "image/png",
|
|
11879
12393
|
".jpg": "image/jpeg",
|
|
@@ -11928,7 +12442,7 @@ function detectContentType(filePath) {
|
|
|
11928
12442
|
}
|
|
11929
12443
|
return mime;
|
|
11930
12444
|
}
|
|
11931
|
-
var uploadCommand =
|
|
12445
|
+
var uploadCommand = defineCommand106({
|
|
11932
12446
|
meta: {
|
|
11933
12447
|
name: "upload",
|
|
11934
12448
|
description: "Upload an image to the library \u2014 accepts a local file path OR a remote http(s) URL.\n\nLocal: reads bytes, sends to /api/images/upload, content-type auto-detected from extension.\nRemote: dispatches to /api/images/ingest with hash-dedup on bytes + externalId.\n\nExamples:\n baker images upload ./logo.png --source uploaded\n baker images upload ./cert.png --context 'ISO 27001 badge \u2014 enterprise tier'\n baker images upload https://acme.com/hero.png --source firecrawl --context 'Acme competitor pricing hero'"
|
|
@@ -12011,7 +12525,7 @@ async function uploadLocal(target, args) {
|
|
|
12011
12525
|
});
|
|
12012
12526
|
return;
|
|
12013
12527
|
}
|
|
12014
|
-
const fileBuffer = await
|
|
12528
|
+
const fileBuffer = await readFile8(target);
|
|
12015
12529
|
const base64 = fileBuffer.toString("base64");
|
|
12016
12530
|
const body = { base64, contentType };
|
|
12017
12531
|
if (args.source) body.source = args.source;
|
|
@@ -12021,7 +12535,7 @@ async function uploadLocal(target, args) {
|
|
|
12021
12535
|
}
|
|
12022
12536
|
|
|
12023
12537
|
// src/commands/images/upscale.ts
|
|
12024
|
-
import { defineCommand as
|
|
12538
|
+
import { defineCommand as defineCommand107 } from "citty";
|
|
12025
12539
|
registerSchema({
|
|
12026
12540
|
command: "images.upscale",
|
|
12027
12541
|
description: "Upscale a library image via the backend (Replicate, cost-tracked). Waits for completion by default. The image must be status 'ready' and raster (not SVG/AVIF).",
|
|
@@ -12036,7 +12550,7 @@ registerSchema({
|
|
|
12036
12550
|
}
|
|
12037
12551
|
});
|
|
12038
12552
|
var POLL_INTERVAL_MS3 = 1500;
|
|
12039
|
-
var upscaleCommand =
|
|
12553
|
+
var upscaleCommand = defineCommand107({
|
|
12040
12554
|
meta: {
|
|
12041
12555
|
name: "upscale",
|
|
12042
12556
|
description: "Upscale a library image via the Convex backend (Replicate, cost-tracked at $0.05/image). Waits for completion by default.\n\nExample: baker images upscale j571abc123def\nExample: baker images upscale j571abc123def --max-wait 0 # fire-and-forget"
|
|
@@ -12091,7 +12605,7 @@ var upscaleCommand = defineCommand105({
|
|
|
12091
12605
|
});
|
|
12092
12606
|
|
|
12093
12607
|
// src/commands/images/use.ts
|
|
12094
|
-
import { defineCommand as
|
|
12608
|
+
import { defineCommand as defineCommand108 } from "citty";
|
|
12095
12609
|
registerSchema({
|
|
12096
12610
|
command: "images.use",
|
|
12097
12611
|
description: "Ingest a URL and wait for the library record to be ready.",
|
|
@@ -12107,7 +12621,7 @@ registerSchema({
|
|
|
12107
12621
|
}
|
|
12108
12622
|
});
|
|
12109
12623
|
var POLL_INTERVAL_MS4 = 1500;
|
|
12110
|
-
var useCommand =
|
|
12624
|
+
var useCommand = defineCommand108({
|
|
12111
12625
|
meta: {
|
|
12112
12626
|
name: "use",
|
|
12113
12627
|
description: "Sugar over `ingest`: download \u2192 store \u2192 wait until describe + embed complete \u2192 return ready library record.\n\nExample: baker images use https://cdn.example.com/hero.png --source uploaded"
|
|
@@ -12153,7 +12667,7 @@ var useCommand = defineCommand106({
|
|
|
12153
12667
|
});
|
|
12154
12668
|
|
|
12155
12669
|
// src/commands/images/index.ts
|
|
12156
|
-
var imagesCommand =
|
|
12670
|
+
var imagesCommand = defineCommand109({
|
|
12157
12671
|
meta: {
|
|
12158
12672
|
name: "images",
|
|
12159
12673
|
description: `Find, source, and normalize images. Subcommands route by provider so cost + license are explicit.
|
|
@@ -12165,6 +12679,7 @@ Library + search:
|
|
|
12165
12679
|
External providers:
|
|
12166
12680
|
baker images stock <q> [--type photo|vector|psd] Magnific (Freepik's dev API) \u2014 photos, vectors, illustrations, PSDs
|
|
12167
12681
|
baker images google <q> Google Images via the official Custom Search API
|
|
12682
|
+
baker images pinterest <q> Pinterest reference imagery via ScrapeCreators (photo-real mood boards)
|
|
12168
12683
|
baker images logo <domain> Brand logo via Brandfetch CDN
|
|
12169
12684
|
baker images icon <name> [--set \u2026] Iconify (200+ sets)
|
|
12170
12685
|
baker images gif <q> [--trending] Giphy GIFs / reaction memes (paid-social creative)
|
|
@@ -12172,7 +12687,9 @@ External providers:
|
|
|
12172
12687
|
baker images extract <url> Firecrawl page extract
|
|
12173
12688
|
baker images screenshot <url> ScreenshotOne capture
|
|
12174
12689
|
|
|
12175
|
-
|
|
12690
|
+
AI generation (run on the Convex backend, cost-tracked):
|
|
12691
|
+
baker images generate <prompt> [--model \u2026] [--aspect-ratio \u2026] [--reference url1,url2]
|
|
12692
|
+
Generate an image with AI (OpenRouter models) and ingest it
|
|
12176
12693
|
baker images ingest <url> --source <enum> Save remote URL to library
|
|
12177
12694
|
baker images use <url> Ingest and wait for ready
|
|
12178
12695
|
baker images upload ./file Upload local file
|
|
@@ -12195,6 +12712,8 @@ Paid transforms (run on the Convex backend, cost-tracked):
|
|
|
12195
12712
|
find: findCommand,
|
|
12196
12713
|
stock: stockCommand,
|
|
12197
12714
|
google: googleCommand2,
|
|
12715
|
+
pinterest: pinterestCommand,
|
|
12716
|
+
generate: generateCommand,
|
|
12198
12717
|
logo: logoCommand,
|
|
12199
12718
|
icon: iconCommand,
|
|
12200
12719
|
gif: gifCommand,
|
|
@@ -12214,10 +12733,10 @@ Paid transforms (run on the Convex backend, cost-tracked):
|
|
|
12214
12733
|
});
|
|
12215
12734
|
|
|
12216
12735
|
// src/commands/research/index.ts
|
|
12217
|
-
import { defineCommand as
|
|
12736
|
+
import { defineCommand as defineCommand120 } from "citty";
|
|
12218
12737
|
|
|
12219
12738
|
// src/commands/research/advertisers.ts
|
|
12220
|
-
import { defineCommand as
|
|
12739
|
+
import { defineCommand as defineCommand110 } from "citty";
|
|
12221
12740
|
|
|
12222
12741
|
// src/commands/research/output.ts
|
|
12223
12742
|
var RESEARCH_DATA_NOTE = "Estimates based on third-party SERP data \u2014 not exact figures. Use for directional insights, not precise measurement.";
|
|
@@ -12330,7 +12849,7 @@ var FIELDS3 = {
|
|
|
12330
12849
|
etv: "Estimated traffic value (USD)",
|
|
12331
12850
|
visibility: "SERP visibility score (0-1)"
|
|
12332
12851
|
};
|
|
12333
|
-
var advertisersCommand =
|
|
12852
|
+
var advertisersCommand = defineCommand110({
|
|
12334
12853
|
meta: {
|
|
12335
12854
|
name: "advertisers",
|
|
12336
12855
|
description: `Find domains competing for a keyword in Google SERPs.
|
|
@@ -12377,7 +12896,7 @@ Examples:
|
|
|
12377
12896
|
});
|
|
12378
12897
|
|
|
12379
12898
|
// src/commands/research/autocomplete.ts
|
|
12380
|
-
import { defineCommand as
|
|
12899
|
+
import { defineCommand as defineCommand111 } from "citty";
|
|
12381
12900
|
registerSchema({
|
|
12382
12901
|
command: "research.autocomplete",
|
|
12383
12902
|
description: "Get Google Autocomplete suggestions for a seed keyword. Useful for keyword expansion and discovering what people actually search for. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en).",
|
|
@@ -12400,7 +12919,7 @@ registerSchema({
|
|
|
12400
12919
|
var FIELDS4 = {
|
|
12401
12920
|
suggestion: "Autocomplete suggestion from Google"
|
|
12402
12921
|
};
|
|
12403
|
-
var autocompleteCommand =
|
|
12922
|
+
var autocompleteCommand = defineCommand111({
|
|
12404
12923
|
meta: {
|
|
12405
12924
|
name: "autocomplete",
|
|
12406
12925
|
description: `Get Google Autocomplete suggestions for keyword expansion.
|
|
@@ -12446,7 +12965,7 @@ Examples:
|
|
|
12446
12965
|
});
|
|
12447
12966
|
|
|
12448
12967
|
// src/commands/research/countries.ts
|
|
12449
|
-
import { defineCommand as
|
|
12968
|
+
import { defineCommand as defineCommand112 } from "citty";
|
|
12450
12969
|
registerSchema({
|
|
12451
12970
|
command: "research.countries",
|
|
12452
12971
|
description: "List all supported country codes for --location flag in research commands.",
|
|
@@ -12503,7 +13022,7 @@ var FIELDS5 = {
|
|
|
12503
13022
|
code: "Country code to pass as --location",
|
|
12504
13023
|
name: "Country name"
|
|
12505
13024
|
};
|
|
12506
|
-
var countriesCommand =
|
|
13025
|
+
var countriesCommand = defineCommand112({
|
|
12507
13026
|
meta: {
|
|
12508
13027
|
name: "countries",
|
|
12509
13028
|
description: "List all supported country codes for --location flag."
|
|
@@ -12514,7 +13033,7 @@ var countriesCommand = defineCommand110({
|
|
|
12514
13033
|
});
|
|
12515
13034
|
|
|
12516
13035
|
// src/commands/research/intent.ts
|
|
12517
|
-
import { defineCommand as
|
|
13036
|
+
import { defineCommand as defineCommand113 } from "citty";
|
|
12518
13037
|
registerSchema({
|
|
12519
13038
|
command: "research.intent",
|
|
12520
13039
|
description: "Classify Google Search intent for keywords. Determines if someone searching is looking to buy, research, or navigate. IMPORTANT: If --language is omitted, defaults to English (en). The response includes a query_context object showing which language was used.",
|
|
@@ -12537,7 +13056,7 @@ var FIELDS6 = {
|
|
|
12537
13056
|
intent: "Primary Google Search intent: informational, navigational, commercial, transactional",
|
|
12538
13057
|
probability: "Confidence score 0.0-1.0"
|
|
12539
13058
|
};
|
|
12540
|
-
var intentCommand =
|
|
13059
|
+
var intentCommand = defineCommand113({
|
|
12541
13060
|
meta: {
|
|
12542
13061
|
name: "intent",
|
|
12543
13062
|
description: `Classify Google Search intent for keywords. Returns intent type and confidence.
|
|
@@ -12585,7 +13104,7 @@ Examples:
|
|
|
12585
13104
|
});
|
|
12586
13105
|
|
|
12587
13106
|
// src/commands/research/keyword-gap.ts
|
|
12588
|
-
import { defineCommand as
|
|
13107
|
+
import { defineCommand as defineCommand114 } from "citty";
|
|
12589
13108
|
registerSchema({
|
|
12590
13109
|
command: "research.keyword-gap",
|
|
12591
13110
|
description: "Find keywords a competitor ranks for (organic or paid) that you don't. Discovers expansion opportunities. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en). The response includes a query_context object showing which location/language were used.",
|
|
@@ -12614,7 +13133,7 @@ var FIELDS7 = {
|
|
|
12614
13133
|
cpc: "Cost per click USD",
|
|
12615
13134
|
their_position: "Competitor's ranking position"
|
|
12616
13135
|
};
|
|
12617
|
-
var keywordGapCommand =
|
|
13136
|
+
var keywordGapCommand = defineCommand114({
|
|
12618
13137
|
meta: {
|
|
12619
13138
|
name: "keyword-gap",
|
|
12620
13139
|
description: `Find keywords a competitor has that you don't. Supports pagination via --offset.
|
|
@@ -12688,7 +13207,7 @@ Examples:
|
|
|
12688
13207
|
});
|
|
12689
13208
|
|
|
12690
13209
|
// src/commands/research/keywords-for-site.ts
|
|
12691
|
-
import { defineCommand as
|
|
13210
|
+
import { defineCommand as defineCommand115 } from "citty";
|
|
12692
13211
|
registerSchema({
|
|
12693
13212
|
command: "research.keywords-for-site",
|
|
12694
13213
|
description: "Get keywords a competitor targets in Google. Use --type paid to see only paid keywords, --type organic for organic only. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en). The response includes a query_context object showing which location/language were used.",
|
|
@@ -12721,7 +13240,7 @@ var FIELDS8 = {
|
|
|
12721
13240
|
competition: "LOW, MEDIUM, or HIGH",
|
|
12722
13241
|
competition_index: "Competition score 0-100"
|
|
12723
13242
|
};
|
|
12724
|
-
var keywordsForSiteCommand =
|
|
13243
|
+
var keywordsForSiteCommand = defineCommand115({
|
|
12725
13244
|
meta: {
|
|
12726
13245
|
name: "keywords-for-site",
|
|
12727
13246
|
description: `Get keywords a competitor targets in Google. Use --type to filter paid/organic.
|
|
@@ -12774,7 +13293,7 @@ Examples:
|
|
|
12774
13293
|
});
|
|
12775
13294
|
|
|
12776
13295
|
// src/commands/research/languages.ts
|
|
12777
|
-
import { defineCommand as
|
|
13296
|
+
import { defineCommand as defineCommand116 } from "citty";
|
|
12778
13297
|
registerSchema({
|
|
12779
13298
|
command: "research.languages",
|
|
12780
13299
|
description: "List all supported language codes for --language flag in research commands.",
|
|
@@ -12804,7 +13323,7 @@ var FIELDS9 = {
|
|
|
12804
13323
|
code: "Language code to pass as --language",
|
|
12805
13324
|
name: "Language name (also accepted by --language)"
|
|
12806
13325
|
};
|
|
12807
|
-
var languagesCommand2 =
|
|
13326
|
+
var languagesCommand2 = defineCommand116({
|
|
12808
13327
|
meta: {
|
|
12809
13328
|
name: "languages",
|
|
12810
13329
|
description: "List all supported language codes for --language flag."
|
|
@@ -12815,7 +13334,7 @@ var languagesCommand2 = defineCommand114({
|
|
|
12815
13334
|
});
|
|
12816
13335
|
|
|
12817
13336
|
// src/commands/research/lighthouse.ts
|
|
12818
|
-
import { defineCommand as
|
|
13337
|
+
import { defineCommand as defineCommand117 } from "citty";
|
|
12819
13338
|
registerSchema({
|
|
12820
13339
|
command: "research.lighthouse",
|
|
12821
13340
|
description: "Landing page performance audit. Returns metrics that affect Google Ads Quality Score and CPC.",
|
|
@@ -12834,7 +13353,7 @@ var FIELDS10 = {
|
|
|
12834
13353
|
speed_index_ms: "Speed Index in ms (good: < 3400)",
|
|
12835
13354
|
interactive_ms: "Time to Interactive in ms (good: < 3800)"
|
|
12836
13355
|
};
|
|
12837
|
-
var lighthouseCommand =
|
|
13356
|
+
var lighthouseCommand = defineCommand117({
|
|
12838
13357
|
meta: {
|
|
12839
13358
|
name: "lighthouse",
|
|
12840
13359
|
description: `Landing page performance audit. Metrics affecting Google Ads Quality Score.
|
|
@@ -12872,7 +13391,7 @@ Examples:
|
|
|
12872
13391
|
});
|
|
12873
13392
|
|
|
12874
13393
|
// src/commands/research/relevant-pages.ts
|
|
12875
|
-
import { defineCommand as
|
|
13394
|
+
import { defineCommand as defineCommand118 } from "citty";
|
|
12876
13395
|
registerSchema({
|
|
12877
13396
|
command: "research.relevant-pages",
|
|
12878
13397
|
description: "Get the top pages of a competitor domain with organic traffic and ranking data. Shows which pages drive the most traffic. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en).",
|
|
@@ -12898,7 +13417,7 @@ var FIELDS11 = {
|
|
|
12898
13417
|
keywords: "Total organic keywords the page ranks for",
|
|
12899
13418
|
top_10: "Keywords in positions 1-10"
|
|
12900
13419
|
};
|
|
12901
|
-
var relevantPagesCommand =
|
|
13420
|
+
var relevantPagesCommand = defineCommand118({
|
|
12902
13421
|
meta: {
|
|
12903
13422
|
name: "relevant-pages",
|
|
12904
13423
|
description: `Get the top pages of a competitor domain with traffic data.
|
|
@@ -12944,7 +13463,7 @@ Examples:
|
|
|
12944
13463
|
});
|
|
12945
13464
|
|
|
12946
13465
|
// src/commands/research/web.ts
|
|
12947
|
-
import { defineCommand as
|
|
13466
|
+
import { defineCommand as defineCommand119 } from "citty";
|
|
12948
13467
|
registerSchema({
|
|
12949
13468
|
command: "research.web",
|
|
12950
13469
|
description: "Search the web with AI to answer marketing questions \u2014 competitors, ICP, pricing, pain points, market trends. Three depth levels: medium (quick, default), high (thorough), xhigh (exhaustive deep research).",
|
|
@@ -12995,7 +13514,7 @@ async function runDeepResearch(question) {
|
|
|
12995
13514
|
}
|
|
12996
13515
|
throw new Error("Deep research timed out");
|
|
12997
13516
|
}
|
|
12998
|
-
var webCommand =
|
|
13517
|
+
var webCommand = defineCommand119({
|
|
12999
13518
|
meta: {
|
|
13000
13519
|
name: "web",
|
|
13001
13520
|
description: `Search the web with AI to answer any open-ended marketing question. Uses live internet data via Google Search.
|
|
@@ -13055,7 +13574,7 @@ Examples:
|
|
|
13055
13574
|
});
|
|
13056
13575
|
|
|
13057
13576
|
// src/commands/research/index.ts
|
|
13058
|
-
var researchCommand =
|
|
13577
|
+
var researchCommand = defineCommand120({
|
|
13059
13578
|
meta: {
|
|
13060
13579
|
name: "research",
|
|
13061
13580
|
description: `Competitive intelligence and AI-powered research commands.
|
|
@@ -13095,10 +13614,10 @@ Examples:
|
|
|
13095
13614
|
});
|
|
13096
13615
|
|
|
13097
13616
|
// src/commands/scheduled-actions/index.ts
|
|
13098
|
-
import { defineCommand as
|
|
13617
|
+
import { defineCommand as defineCommand127 } from "citty";
|
|
13099
13618
|
|
|
13100
13619
|
// src/commands/scheduled-actions/create.ts
|
|
13101
|
-
import { defineCommand as
|
|
13620
|
+
import { defineCommand as defineCommand121 } from "citty";
|
|
13102
13621
|
|
|
13103
13622
|
// src/commands/scheduled-actions/shared.ts
|
|
13104
13623
|
var TEMP_SCHEDULED_ACTION_PREFIX = "temp_sched_";
|
|
@@ -13203,7 +13722,7 @@ registerSchema({
|
|
|
13203
13722
|
prompt: { type: "string", description: "Additional prompt instructions for the spawned agent", required: false }
|
|
13204
13723
|
}
|
|
13205
13724
|
});
|
|
13206
|
-
var createCommand2 =
|
|
13725
|
+
var createCommand2 = defineCommand121({
|
|
13207
13726
|
meta: {
|
|
13208
13727
|
name: "create",
|
|
13209
13728
|
description: 'Stage a scheduled action. Example: baker scheduled-actions create --name "Weekly report" --description "..." --cron "0 9 * * MON"'
|
|
@@ -13251,7 +13770,7 @@ var createCommand2 = defineCommand119({
|
|
|
13251
13770
|
});
|
|
13252
13771
|
|
|
13253
13772
|
// src/commands/scheduled-actions/delete.ts
|
|
13254
|
-
import { defineCommand as
|
|
13773
|
+
import { defineCommand as defineCommand122 } from "citty";
|
|
13255
13774
|
registerSchema({
|
|
13256
13775
|
command: "scheduled-actions.delete",
|
|
13257
13776
|
description: "Stage deletion of a published scheduled action or cancellation of a temp_sched_* draft creation.",
|
|
@@ -13259,7 +13778,7 @@ registerSchema({
|
|
|
13259
13778
|
id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
|
|
13260
13779
|
}
|
|
13261
13780
|
});
|
|
13262
|
-
var deleteCommand2 =
|
|
13781
|
+
var deleteCommand2 = defineCommand122({
|
|
13263
13782
|
meta: {
|
|
13264
13783
|
name: "delete",
|
|
13265
13784
|
description: "Stage scheduled action deletion. Example: baker scheduled-actions delete <id-or-temp_sched_id>"
|
|
@@ -13288,7 +13807,7 @@ var deleteCommand2 = defineCommand120({
|
|
|
13288
13807
|
});
|
|
13289
13808
|
|
|
13290
13809
|
// src/commands/scheduled-actions/get.ts
|
|
13291
|
-
import { defineCommand as
|
|
13810
|
+
import { defineCommand as defineCommand123 } from "citty";
|
|
13292
13811
|
registerSchema({
|
|
13293
13812
|
command: "scheduled-actions.get",
|
|
13294
13813
|
description: "Get a published scheduled action or a temp_sched_* draft-created scheduled action.",
|
|
@@ -13296,7 +13815,7 @@ registerSchema({
|
|
|
13296
13815
|
id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
|
|
13297
13816
|
}
|
|
13298
13817
|
});
|
|
13299
|
-
var getCommand3 =
|
|
13818
|
+
var getCommand3 = defineCommand123({
|
|
13300
13819
|
meta: {
|
|
13301
13820
|
name: "get",
|
|
13302
13821
|
description: "Get a scheduled action. Example: baker scheduled-actions get <id-or-temp_sched_id>"
|
|
@@ -13333,13 +13852,13 @@ var getCommand3 = defineCommand121({
|
|
|
13333
13852
|
});
|
|
13334
13853
|
|
|
13335
13854
|
// src/commands/scheduled-actions/list.ts
|
|
13336
|
-
import { defineCommand as
|
|
13855
|
+
import { defineCommand as defineCommand124 } from "citty";
|
|
13337
13856
|
registerSchema({
|
|
13338
13857
|
command: "scheduled-actions.list",
|
|
13339
13858
|
description: "List published scheduled actions. Includes draft state when BAKER_CHAT_ID is set.",
|
|
13340
13859
|
args: {}
|
|
13341
13860
|
});
|
|
13342
|
-
var listCommand2 =
|
|
13861
|
+
var listCommand2 = defineCommand124({
|
|
13343
13862
|
meta: {
|
|
13344
13863
|
name: "list",
|
|
13345
13864
|
description: "List scheduled actions. Includes staged draft ops when BAKER_CHAT_ID is set."
|
|
@@ -13360,7 +13879,7 @@ var listCommand2 = defineCommand122({
|
|
|
13360
13879
|
});
|
|
13361
13880
|
|
|
13362
13881
|
// src/commands/scheduled-actions/trigger.ts
|
|
13363
|
-
import { defineCommand as
|
|
13882
|
+
import { defineCommand as defineCommand125 } from "citty";
|
|
13364
13883
|
registerSchema({
|
|
13365
13884
|
command: "scheduled-actions.trigger",
|
|
13366
13885
|
description: "Immediately trigger a published scheduled action. Does not require BAKER_CHAT_ID and rejects temp_sched_* IDs.",
|
|
@@ -13368,7 +13887,7 @@ registerSchema({
|
|
|
13368
13887
|
id: { type: "string", description: "Published scheduled action ID", required: true }
|
|
13369
13888
|
}
|
|
13370
13889
|
});
|
|
13371
|
-
var triggerCommand =
|
|
13890
|
+
var triggerCommand = defineCommand125({
|
|
13372
13891
|
meta: {
|
|
13373
13892
|
name: "trigger",
|
|
13374
13893
|
description: "Immediately trigger a published scheduled action. Example: baker scheduled-actions trigger <id>"
|
|
@@ -13405,7 +13924,7 @@ var triggerCommand = defineCommand123({
|
|
|
13405
13924
|
});
|
|
13406
13925
|
|
|
13407
13926
|
// src/commands/scheduled-actions/update.ts
|
|
13408
|
-
import { defineCommand as
|
|
13927
|
+
import { defineCommand as defineCommand126 } from "citty";
|
|
13409
13928
|
registerSchema({
|
|
13410
13929
|
command: "scheduled-actions.update",
|
|
13411
13930
|
description: "Stage an update to a published scheduled action or temp_sched_* draft-created scheduled action.",
|
|
@@ -13430,7 +13949,7 @@ registerSchema({
|
|
|
13430
13949
|
prompt: { type: "string", description: "Replacement additional spawned-agent instructions", required: false }
|
|
13431
13950
|
}
|
|
13432
13951
|
});
|
|
13433
|
-
var updateCommand2 =
|
|
13952
|
+
var updateCommand2 = defineCommand126({
|
|
13434
13953
|
meta: {
|
|
13435
13954
|
name: "update",
|
|
13436
13955
|
description: "Stage a scheduled action update. Example: baker scheduled-actions update <id> --enabled false"
|
|
@@ -13500,7 +14019,7 @@ var updateCommand2 = defineCommand124({
|
|
|
13500
14019
|
});
|
|
13501
14020
|
|
|
13502
14021
|
// src/commands/scheduled-actions/index.ts
|
|
13503
|
-
var scheduledActionsCommand =
|
|
14022
|
+
var scheduledActionsCommand = defineCommand127({
|
|
13504
14023
|
meta: {
|
|
13505
14024
|
name: "scheduled-actions",
|
|
13506
14025
|
description: `Manage Scheduled Actions. Subcommands: list, get, create, update, delete, trigger.
|
|
@@ -13526,8 +14045,8 @@ Examples:
|
|
|
13526
14045
|
});
|
|
13527
14046
|
|
|
13528
14047
|
// src/commands/schema.ts
|
|
13529
|
-
import { defineCommand as
|
|
13530
|
-
var schemaCommand =
|
|
14048
|
+
import { defineCommand as defineCommand128 } from "citty";
|
|
14049
|
+
var schemaCommand = defineCommand128({
|
|
13531
14050
|
meta: {
|
|
13532
14051
|
name: "schema",
|
|
13533
14052
|
description: "Inspect command argument schemas (for AI agent introspection). Lists all commands if no argument given. Example: baker schema images.search"
|
|
@@ -13563,10 +14082,10 @@ var schemaCommand = defineCommand126({
|
|
|
13563
14082
|
});
|
|
13564
14083
|
|
|
13565
14084
|
// src/commands/testimonials/index.ts
|
|
13566
|
-
import { defineCommand as
|
|
14085
|
+
import { defineCommand as defineCommand132 } from "citty";
|
|
13567
14086
|
|
|
13568
14087
|
// src/commands/testimonials/get.ts
|
|
13569
|
-
import { defineCommand as
|
|
14088
|
+
import { defineCommand as defineCommand129 } from "citty";
|
|
13570
14089
|
registerSchema({
|
|
13571
14090
|
command: "testimonials.get",
|
|
13572
14091
|
description: "Get a single testimonial by ID",
|
|
@@ -13574,7 +14093,7 @@ registerSchema({
|
|
|
13574
14093
|
id: { type: "string", description: "Testimonial ID", required: true }
|
|
13575
14094
|
}
|
|
13576
14095
|
});
|
|
13577
|
-
var getCommand4 =
|
|
14096
|
+
var getCommand4 = defineCommand129({
|
|
13578
14097
|
meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
|
|
13579
14098
|
args: {
|
|
13580
14099
|
id: { type: "positional", description: "Testimonial ID", required: false },
|
|
@@ -13611,7 +14130,7 @@ var getCommand4 = defineCommand127({
|
|
|
13611
14130
|
});
|
|
13612
14131
|
|
|
13613
14132
|
// src/commands/testimonials/list.ts
|
|
13614
|
-
import { defineCommand as
|
|
14133
|
+
import { defineCommand as defineCommand130 } from "citty";
|
|
13615
14134
|
registerSchema({
|
|
13616
14135
|
command: "testimonials.list",
|
|
13617
14136
|
description: "List testimonials with optional filters.",
|
|
@@ -13641,7 +14160,7 @@ registerSchema({
|
|
|
13641
14160
|
limit: { type: "number", description: "Max results (default 50)", required: false, default: 50 }
|
|
13642
14161
|
}
|
|
13643
14162
|
});
|
|
13644
|
-
var listCommand3 =
|
|
14163
|
+
var listCommand3 = defineCommand130({
|
|
13645
14164
|
meta: {
|
|
13646
14165
|
name: "list",
|
|
13647
14166
|
description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
|
|
@@ -13690,7 +14209,7 @@ var listCommand3 = defineCommand128({
|
|
|
13690
14209
|
});
|
|
13691
14210
|
|
|
13692
14211
|
// src/commands/testimonials/search.ts
|
|
13693
|
-
import { defineCommand as
|
|
14212
|
+
import { defineCommand as defineCommand131 } from "citty";
|
|
13694
14213
|
registerSchema({
|
|
13695
14214
|
command: "testimonials.search",
|
|
13696
14215
|
description: "Search testimonials by text query. Uses hybrid BM25 + vector + reranking.",
|
|
@@ -13721,7 +14240,7 @@ registerSchema({
|
|
|
13721
14240
|
tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
|
|
13722
14241
|
}
|
|
13723
14242
|
});
|
|
13724
|
-
var searchCommand2 =
|
|
14243
|
+
var searchCommand2 = defineCommand131({
|
|
13725
14244
|
meta: {
|
|
13726
14245
|
name: "search",
|
|
13727
14246
|
description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
|
|
@@ -13792,7 +14311,7 @@ var searchCommand2 = defineCommand129({
|
|
|
13792
14311
|
});
|
|
13793
14312
|
|
|
13794
14313
|
// src/commands/testimonials/index.ts
|
|
13795
|
-
var testimonialsCommand =
|
|
14314
|
+
var testimonialsCommand = defineCommand132({
|
|
13796
14315
|
meta: {
|
|
13797
14316
|
name: "testimonials",
|
|
13798
14317
|
description: `Find and browse testimonials in Baker. Subcommands: search, get, list.
|
|
@@ -13811,10 +14330,10 @@ Examples:
|
|
|
13811
14330
|
});
|
|
13812
14331
|
|
|
13813
14332
|
// src/commands/videos/index.ts
|
|
13814
|
-
import { defineCommand as
|
|
14333
|
+
import { defineCommand as defineCommand137 } from "citty";
|
|
13815
14334
|
|
|
13816
14335
|
// src/commands/videos/delete.ts
|
|
13817
|
-
import { defineCommand as
|
|
14336
|
+
import { defineCommand as defineCommand133 } from "citty";
|
|
13818
14337
|
registerSchema({
|
|
13819
14338
|
command: "videos.delete",
|
|
13820
14339
|
description: "Delete a video by ID",
|
|
@@ -13828,7 +14347,7 @@ registerSchema({
|
|
|
13828
14347
|
}
|
|
13829
14348
|
}
|
|
13830
14349
|
});
|
|
13831
|
-
var deleteCommand3 =
|
|
14350
|
+
var deleteCommand3 = defineCommand133({
|
|
13832
14351
|
meta: {
|
|
13833
14352
|
name: "delete",
|
|
13834
14353
|
description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
|
|
@@ -13869,7 +14388,7 @@ var deleteCommand3 = defineCommand131({
|
|
|
13869
14388
|
});
|
|
13870
14389
|
|
|
13871
14390
|
// src/commands/videos/get.ts
|
|
13872
|
-
import { defineCommand as
|
|
14391
|
+
import { defineCommand as defineCommand134 } from "citty";
|
|
13873
14392
|
registerSchema({
|
|
13874
14393
|
command: "videos.get",
|
|
13875
14394
|
description: "Get a single video by ID",
|
|
@@ -13877,7 +14396,7 @@ registerSchema({
|
|
|
13877
14396
|
id: { type: "string", description: "Video ID", required: true }
|
|
13878
14397
|
}
|
|
13879
14398
|
});
|
|
13880
|
-
var getCommand5 =
|
|
14399
|
+
var getCommand5 = defineCommand134({
|
|
13881
14400
|
meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
|
|
13882
14401
|
args: {
|
|
13883
14402
|
id: { type: "positional", description: "Video ID", required: false },
|
|
@@ -13914,7 +14433,7 @@ var getCommand5 = defineCommand132({
|
|
|
13914
14433
|
});
|
|
13915
14434
|
|
|
13916
14435
|
// src/commands/videos/search.ts
|
|
13917
|
-
import { defineCommand as
|
|
14436
|
+
import { defineCommand as defineCommand135 } from "citty";
|
|
13918
14437
|
registerSchema({
|
|
13919
14438
|
command: "videos.search",
|
|
13920
14439
|
description: "Search videos by text query. Only returns ready videos.",
|
|
@@ -13924,7 +14443,7 @@ registerSchema({
|
|
|
13924
14443
|
tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
|
|
13925
14444
|
}
|
|
13926
14445
|
});
|
|
13927
|
-
var searchCommand3 =
|
|
14446
|
+
var searchCommand3 = defineCommand135({
|
|
13928
14447
|
meta: {
|
|
13929
14448
|
name: "search",
|
|
13930
14449
|
description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
|
|
@@ -13971,9 +14490,9 @@ var searchCommand3 = defineCommand133({
|
|
|
13971
14490
|
});
|
|
13972
14491
|
|
|
13973
14492
|
// src/commands/videos/upload.ts
|
|
13974
|
-
import { readFile as
|
|
14493
|
+
import { readFile as readFile9, stat as stat3 } from "fs/promises";
|
|
13975
14494
|
import { extname as extname3 } from "path";
|
|
13976
|
-
import { defineCommand as
|
|
14495
|
+
import { defineCommand as defineCommand136 } from "citty";
|
|
13977
14496
|
var MIME_MAP2 = {
|
|
13978
14497
|
".mp4": "video/mp4",
|
|
13979
14498
|
".mov": "video/quicktime",
|
|
@@ -14007,7 +14526,7 @@ function detectContentType2(filePath) {
|
|
|
14007
14526
|
}
|
|
14008
14527
|
return mime;
|
|
14009
14528
|
}
|
|
14010
|
-
var uploadCommand2 =
|
|
14529
|
+
var uploadCommand2 = defineCommand136({
|
|
14011
14530
|
meta: {
|
|
14012
14531
|
name: "upload",
|
|
14013
14532
|
description: "Upload a video file to Baker via Mux direct upload. Auto-detects content type. Example: baker videos upload ./demo.mp4"
|
|
@@ -14036,7 +14555,7 @@ var uploadCommand2 = defineCommand134({
|
|
|
14036
14555
|
return;
|
|
14037
14556
|
}
|
|
14038
14557
|
const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
|
|
14039
|
-
const fileBuffer = await
|
|
14558
|
+
const fileBuffer = await readFile9(filePath);
|
|
14040
14559
|
const uploadResponse = await fetch(uploadUrl, {
|
|
14041
14560
|
method: "PUT",
|
|
14042
14561
|
headers: { "Content-Type": contentType },
|
|
@@ -14061,7 +14580,7 @@ var uploadCommand2 = defineCommand134({
|
|
|
14061
14580
|
});
|
|
14062
14581
|
|
|
14063
14582
|
// src/commands/videos/index.ts
|
|
14064
|
-
var videosCommand =
|
|
14583
|
+
var videosCommand = defineCommand137({
|
|
14065
14584
|
meta: {
|
|
14066
14585
|
name: "videos",
|
|
14067
14586
|
description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete.
|
|
@@ -14098,7 +14617,7 @@ function getCliVersion() {
|
|
|
14098
14617
|
}
|
|
14099
14618
|
|
|
14100
14619
|
// src/cli.ts
|
|
14101
|
-
var main =
|
|
14620
|
+
var main = defineCommand138({
|
|
14102
14621
|
meta: {
|
|
14103
14622
|
name: "baker",
|
|
14104
14623
|
version: getCliVersion(),
|