@koda-sl/baker-cli 0.98.0-dev.62be5b016 → 0.98.1-dev.662146ce

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/dist/cli.js CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- AssetRef,
4
3
  ELEVENLABS_MAX_MUSIC_LENGTH_MS,
5
4
  IMAGE_GENERATE_MODELS,
6
5
  MODEL_REGISTRY,
@@ -8,13 +7,12 @@ import {
8
7
  ValidationError,
9
8
  createEngineFromEnv,
10
9
  defaultRegistry,
11
- extForMime,
12
10
  generateCatalog,
13
11
  validateCanvasDeep
14
- } from "./chunk-IKMDQQ4M.js";
12
+ } from "./chunk-26K7V346.js";
15
13
 
16
14
  // src/cli.ts
17
- import { defineCommand as defineCommand149, runMain } from "citty";
15
+ import { defineCommand as defineCommand151, runMain } from "citty";
18
16
 
19
17
  // src/commands/actions/index.ts
20
18
  import { defineCommand as defineCommand17 } from "citty";
@@ -149,9 +147,9 @@ async function handleResponse(response) {
149
147
  throw new ApiError("INTERNAL_ERROR", "Failed to parse API response as JSON");
150
148
  }
151
149
  }
152
- async function apiGet(path8, params) {
150
+ async function apiGet(path11, params) {
153
151
  const env = getEnv();
154
- const url = new URL(path8, env.BAKER_API_URL);
152
+ const url = new URL(path11, env.BAKER_API_URL);
155
153
  if (params) {
156
154
  const clean = sanitizeParams(params);
157
155
  for (const [key, value] of Object.entries(clean)) {
@@ -176,12 +174,12 @@ async function apiGet(path8, params) {
176
174
  }
177
175
  return handleResponse(response);
178
176
  }
179
- async function apiPost(path8, body, opts) {
177
+ async function apiPost(path11, body, opts) {
180
178
  const env = getEnv();
181
179
  const timeoutMs = opts?.timeoutMs ?? 6e4;
182
180
  let response;
183
181
  try {
184
- response = await fetchWithRateLimitRetry(new URL(path8, env.BAKER_API_URL).toString(), {
182
+ response = await fetchWithRateLimitRetry(new URL(path11, env.BAKER_API_URL).toString(), {
185
183
  method: "POST",
186
184
  headers: {
187
185
  Authorization: `Bearer ${env.BAKER_API_KEY}`,
@@ -1329,31 +1327,31 @@ function cachePath(category, key) {
1329
1327
  return join(dir, `${hashKey(key)}.json`);
1330
1328
  }
1331
1329
  function cacheGet(category, key) {
1332
- const path8 = cachePath(category, key);
1333
- if (!existsSync(path8)) {
1330
+ const path11 = cachePath(category, key);
1331
+ if (!existsSync(path11)) {
1334
1332
  return null;
1335
1333
  }
1336
1334
  try {
1337
- const raw = readFileSync(path8, "utf-8");
1335
+ const raw = readFileSync(path11, "utf-8");
1338
1336
  const entry = JSON.parse(raw);
1339
1337
  if (entry.expiresAt < Date.now()) {
1340
- rmSync(path8, { force: true });
1338
+ rmSync(path11, { force: true });
1341
1339
  return null;
1342
1340
  }
1343
1341
  return entry;
1344
1342
  } catch {
1345
- rmSync(path8, { force: true });
1343
+ rmSync(path11, { force: true });
1346
1344
  return null;
1347
1345
  }
1348
1346
  }
1349
1347
  function cacheSet(category, key, data, ttlMs, fields) {
1350
- const path8 = cachePath(category, key);
1348
+ const path11 = cachePath(category, key);
1351
1349
  const entry = {
1352
1350
  expiresAt: Date.now() + ttlMs,
1353
1351
  data,
1354
1352
  fields
1355
1353
  };
1356
- writeFileSync(path8, JSON.stringify(entry), "utf-8");
1354
+ writeFileSync(path11, JSON.stringify(entry), "utf-8");
1357
1355
  }
1358
1356
  var HOUR = 60 * 60 * 1e3;
1359
1357
  var MINUTE = 60 * 1e3;
@@ -8063,232 +8061,14 @@ var catalogCommand = defineCommand78({
8063
8061
  }
8064
8062
  });
8065
8063
 
8066
- // src/commands/canvas/gallery.ts
8067
- import { readdir, readFile } from "fs/promises";
8068
- import path from "path";
8069
- import { defineCommand as defineCommand79 } from "citty";
8070
-
8071
- // src/engine/gallery/descriptor.ts
8072
- var KNOWN_RATIOS = [
8073
- ["9:16", 9 / 16],
8074
- ["4:5", 4 / 5],
8075
- ["1:1", 1],
8076
- ["1.91:1", 1.91],
8077
- ["16:9", 16 / 9],
8078
- ["4:1", 4]
8079
- ];
8080
- var RATIO_TOLERANCE = 0.06;
8081
- function aspectLabel(width, height) {
8082
- if (!width || !height) {
8083
- return "other";
8084
- }
8085
- const ratio = width / height;
8086
- let best = "other";
8087
- let bestErr = Number.POSITIVE_INFINITY;
8088
- for (const [label, value] of KNOWN_RATIOS) {
8089
- const err = Math.abs(ratio - value) / value;
8090
- if (err < bestErr) {
8091
- bestErr = err;
8092
- best = label;
8093
- }
8094
- }
8095
- return bestErr <= RATIO_TOLERANCE ? best : `${width}x${height}`;
8096
- }
8097
- function visualRef(value) {
8098
- const parsed = AssetRef.safeParse(value);
8099
- if (!parsed.success) {
8100
- return null;
8101
- }
8102
- if (parsed.data.kind !== "image" && parsed.data.kind !== "video") {
8103
- return null;
8104
- }
8105
- return parsed.data;
8106
- }
8107
- function deliverableFor(ref, stem, resolveLocal) {
8108
- const width = "width" in ref ? ref.width : void 0;
8109
- const height = "height" in ref ? ref.height : void 0;
8110
- return {
8111
- kind: ref.kind,
8112
- format: aspectLabel(width, height),
8113
- // Remote-node outputs already carry a public R2 url; local composites are
8114
- // resolved against the mounted run dir's public base.
8115
- url: ref.url ?? resolveLocal(`${stem}.${extForMime(ref.mime)}`),
8116
- width,
8117
- height,
8118
- label: stem
8119
- };
8120
- }
8121
- function deliverablesFromOutput(output, resolveLocal) {
8122
- if (Array.isArray(output)) {
8123
- const out = [];
8124
- output.forEach((entry, i) => {
8125
- const ref2 = visualRef(entry);
8126
- if (ref2) {
8127
- out.push(deliverableFor(ref2, `_final__${i}`, resolveLocal));
8128
- }
8129
- });
8130
- return out;
8131
- }
8132
- const ref = visualRef(output);
8133
- return ref ? [deliverableFor(ref, "_final", resolveLocal)] : [];
8134
- }
8135
- function buildGeneration(runId, manifest, resolveLocal) {
8136
- const m = manifest ?? {};
8137
- const credits = typeof m.stats?.total_credits === "number" ? m.stats.total_credits : 0;
8138
- return {
8139
- runId,
8140
- createdAt: typeof m.completed_at === "number" ? m.completed_at : 0,
8141
- credits,
8142
- deliverables: deliverablesFromOutput(m.output, resolveLocal)
8143
- };
8144
- }
8145
- function buildGalleryDescriptor(input) {
8146
- const generations = [...input.generations].sort((a, b) => b.createdAt - a.createdAt);
8147
- return {
8148
- slug: input.slug,
8149
- title: input.definition.title,
8150
- platform: input.definition.platform,
8151
- status: input.definition.status,
8152
- reference: input.definition.reference,
8153
- selectedRun: input.definition.selectedRun,
8154
- generations
8155
- };
8156
- }
8157
- function titleFromSlug(slug) {
8158
- return slug.split(/[-_/]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
8159
- }
8160
- function stripQuotes(raw) {
8161
- const trimmed = raw.trim();
8162
- if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
8163
- return trimmed.slice(1, -1);
8164
- }
8165
- return trimmed;
8166
- }
8167
- function parseInlineList(raw) {
8168
- return raw.slice(1, -1).split(",").map((item) => stripQuotes(item)).filter(Boolean);
8169
- }
8170
- function parseFrontmatter(markdown) {
8171
- const match = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---/);
8172
- const block = match?.[1];
8173
- if (!block) {
8174
- return {};
8175
- }
8176
- const out = {};
8177
- let listKey = null;
8178
- for (const line of block.split(/\r?\n/)) {
8179
- const item = line.match(/^\s+-\s+(.*)$/)?.[1];
8180
- if (listKey && item !== void 0) {
8181
- out[listKey].push(stripQuotes(item));
8182
- continue;
8183
- }
8184
- const kv = line.match(/^([A-Za-z0-9_]+):\s*(.*)$/);
8185
- if (!kv?.[1]) {
8186
- continue;
8187
- }
8188
- listKey = null;
8189
- const key = kv[1];
8190
- const value = (kv[2] ?? "").trim();
8191
- if (value === "") {
8192
- out[key] = [];
8193
- listKey = key;
8194
- } else if (value.startsWith("[") && value.endsWith("]")) {
8195
- out[key] = parseInlineList(value);
8196
- } else {
8197
- out[key] = stripQuotes(value);
8198
- }
8199
- }
8200
- return out;
8201
- }
8202
- function asString(value) {
8203
- if (typeof value === "string" && value.length > 0) {
8204
- return value;
8205
- }
8206
- return void 0;
8207
- }
8208
- function asList(value) {
8209
- if (Array.isArray(value)) {
8210
- return value;
8211
- }
8212
- return typeof value === "string" && value.length > 0 ? [value] : [];
8213
- }
8214
- function parseCreativeDefinition(markdown, slug) {
8215
- const fm = parseFrontmatter(markdown);
8216
- return {
8217
- title: asString(fm.title) ?? titleFromSlug(slug),
8218
- platform: asList(fm.platform),
8219
- formats: asList(fm.formats),
8220
- status: asString(fm.status) ?? "draft",
8221
- reference: asString(fm.reference),
8222
- selectedRun: asString(fm.selected_run)
8223
- };
8224
- }
8225
-
8226
- // src/commands/canvas/gallery.ts
8227
- async function readJson(file) {
8228
- try {
8229
- return JSON.parse(await readFile(file, "utf8"));
8230
- } catch {
8231
- return null;
8232
- }
8233
- }
8234
- async function listRunDirs(runsDir) {
8235
- try {
8236
- const entries = await readdir(runsDir, { withFileTypes: true });
8237
- return entries.filter((e) => e.isDirectory()).map((e) => e.name);
8238
- } catch {
8239
- return [];
8240
- }
8241
- }
8242
- var galleryCommand = defineCommand79({
8243
- meta: {
8244
- name: "gallery",
8245
- description: "Read a creative's _definition.md + every persisted run manifest and emit the gallery descriptor (JSON) the dashboard renders."
8246
- },
8247
- args: {
8248
- dir: { type: "positional", required: true, description: "Creative folder, e.g. src/creatives/<slug>" },
8249
- "workspace-dir": { type: "string", description: "R2-mounted workspace root (default ./.creatives-workspace)" },
8250
- "public-url": { type: "string", description: "R2 public base (default $R2_PUBLIC_URL)" },
8251
- "company-id": { type: "string", description: "Company id for the R2 prefix (default $BAKER_COMPANY_ID)" }
8252
- },
8253
- async run({ args }) {
8254
- const creativeDir = path.resolve(String(args.dir));
8255
- const slug = path.basename(creativeDir);
8256
- const workspaceDir = path.resolve(String(args["workspace-dir"] ?? ".creatives-workspace"));
8257
- const runsDir = path.join(workspaceDir, slug, "runs");
8258
- const runtimeEnv = process.env;
8259
- const publicUrl = (args["public-url"] ?? runtimeEnv.R2_PUBLIC_URL ?? "").replace(/\/+$/, "");
8260
- const companyId = String(args["company-id"] ?? runtimeEnv.BAKER_COMPANY_ID ?? "");
8261
- const definitionPath = path.join(creativeDir, "_definition.md");
8262
- let definitionMd = "";
8263
- try {
8264
- definitionMd = await readFile(definitionPath, "utf8");
8265
- } catch {
8266
- }
8267
- const definition = parseCreativeDefinition(definitionMd, slug);
8268
- const generations = [];
8269
- for (const runId of await listRunDirs(runsDir)) {
8270
- const manifest = await readJson(path.join(runsDir, runId, "manifest.json"));
8271
- if (!manifest) {
8272
- continue;
8273
- }
8274
- const runDir = path.join(runsDir, runId);
8275
- const resolveLocal = (filename) => publicUrl && companyId ? `${publicUrl}/creatives/${companyId}/${slug}/runs/${runId}/${filename}` : path.join(runDir, filename);
8276
- generations.push(buildGeneration(runId, manifest, resolveLocal));
8277
- }
8278
- const descriptor = buildGalleryDescriptor({ slug, definition, generations });
8279
- process.stdout.write(`${JSON.stringify({ ok: true, descriptor }, null, 2)}
8280
- `);
8281
- }
8282
- });
8283
-
8284
8064
  // src/commands/canvas/inspect.ts
8285
8065
  import { execFile } from "child_process";
8286
- import { readdir as readdir2, readFile as readFile2, stat } from "fs/promises";
8287
- import path2 from "path";
8066
+ import { readdir, readFile, stat } from "fs/promises";
8067
+ import path from "path";
8288
8068
  import { promisify } from "util";
8289
- import { defineCommand as defineCommand80 } from "citty";
8069
+ import { defineCommand as defineCommand79 } from "citty";
8290
8070
  var execFileAsync = promisify(execFile);
8291
- var inspectCommand = defineCommand80({
8071
+ var inspectCommand = defineCommand79({
8292
8072
  meta: {
8293
8073
  name: "inspect",
8294
8074
  description: "Dump a one-page summary of a canvas run: per-node duration + cache status, list of output files in the run dir, and optionally three thumbnail frames per video output. Pass either a run_id (resolved against --outputs-dir) or an absolute run directory."
@@ -8302,7 +8082,7 @@ var inspectCommand = defineCommand80({
8302
8082
  }
8303
8083
  },
8304
8084
  async run({ args }) {
8305
- const outputsDir = path2.resolve(String(args["outputs-dir"] ?? "canvas"));
8085
+ const outputsDir = path.resolve(String(args["outputs-dir"] ?? "canvas"));
8306
8086
  const runArg = String(args.run);
8307
8087
  const runDir = await resolveRunDir(runArg, outputsDir);
8308
8088
  const manifest = await loadManifest(runDir);
@@ -8314,7 +8094,7 @@ var inspectCommand = defineCommand80({
8314
8094
  }
8315
8095
  const summary = {
8316
8096
  ok: true,
8317
- run_id: manifest.run_id ?? path2.basename(runDir),
8097
+ run_id: manifest.run_id ?? path.basename(runDir),
8318
8098
  run_dir: runDir,
8319
8099
  stats: manifest.stats ?? null,
8320
8100
  output: manifest.output ?? null,
@@ -8327,20 +8107,20 @@ var inspectCommand = defineCommand80({
8327
8107
  }
8328
8108
  });
8329
8109
  async function resolveRunDir(run, outputsDir) {
8330
- if (path2.isAbsolute(run)) {
8110
+ if (path.isAbsolute(run)) {
8331
8111
  const s2 = await stat(run).catch(() => null);
8332
8112
  if (s2?.isDirectory()) return run;
8333
8113
  throw new Error(`inspect: ${run} is not a directory`);
8334
8114
  }
8335
- const candidate = path2.join(outputsDir, run);
8115
+ const candidate = path.join(outputsDir, run);
8336
8116
  const s = await stat(candidate).catch(() => null);
8337
8117
  if (s?.isDirectory()) return candidate;
8338
8118
  throw new Error(`inspect: no run directory at ${candidate}`);
8339
8119
  }
8340
8120
  async function loadManifest(runDir) {
8341
- const manifestPath = path2.join(runDir, "manifest.json");
8121
+ const manifestPath = path.join(runDir, "manifest.json");
8342
8122
  try {
8343
- const raw = await readFile2(manifestPath, "utf-8");
8123
+ const raw = await readFile(manifestPath, "utf-8");
8344
8124
  return JSON.parse(raw);
8345
8125
  } catch {
8346
8126
  return {};
@@ -8348,9 +8128,9 @@ async function loadManifest(runDir) {
8348
8128
  }
8349
8129
  async function listRunFiles(runDir) {
8350
8130
  const out = [];
8351
- const names = await readdir2(runDir);
8131
+ const names = await readdir(runDir);
8352
8132
  for (const name of names) {
8353
- const abs = path2.join(runDir, name);
8133
+ const abs = path.join(runDir, name);
8354
8134
  const s = await stat(abs).catch(() => null);
8355
8135
  if (!s?.isFile()) continue;
8356
8136
  out.push({ name, path: abs, size: s.size });
@@ -8395,9 +8175,9 @@ async function probeDuration(filePath) {
8395
8175
  }
8396
8176
 
8397
8177
  // src/commands/canvas/run.ts
8398
- import { readFile as readFile3 } from "fs/promises";
8399
- import path3 from "path";
8400
- import { defineCommand as defineCommand81 } from "citty";
8178
+ import { readFile as readFile2 } from "fs/promises";
8179
+ import path4 from "path";
8180
+ import { defineCommand as defineCommand80 } from "citty";
8401
8181
 
8402
8182
  // src/commands/canvas/placeholders.ts
8403
8183
  function unsuppliedPlaceholderAssets(canvas) {
@@ -8415,19 +8195,74 @@ function unsuppliedPlaceholderAssets(canvas) {
8415
8195
  return out;
8416
8196
  }
8417
8197
 
8198
+ // src/commands/canvas/resolve-paths.ts
8199
+ import path2 from "path";
8200
+ function resolveRelativeCanvasPaths(canvas, baseDir) {
8201
+ if (!canvas || typeof canvas !== "object") return canvas;
8202
+ const c = canvas;
8203
+ if (!Array.isArray(c.nodes)) return canvas;
8204
+ return { ...canvas, nodes: c.nodes.map((n) => resolveNode(n, baseDir)) };
8205
+ }
8206
+ function resolveNode(node, baseDir) {
8207
+ if (!node || typeof node !== "object") return node;
8208
+ const n = node;
8209
+ const params = n.params;
8210
+ if (!params || typeof params !== "object") return node;
8211
+ if (n.type === "ingest" && params.source === "path" && isResolvableRelative(params.path)) {
8212
+ return { ...node, params: { ...params, path: path2.resolve(baseDir, params.path) } };
8213
+ }
8214
+ if (n.type === "hyperframe_render" && isResolvableRelative(params.composition)) {
8215
+ return { ...node, params: { ...params, composition: path2.resolve(baseDir, params.composition) } };
8216
+ }
8217
+ return node;
8218
+ }
8219
+ function isResolvableRelative(value) {
8220
+ return typeof value === "string" && value.length > 0 && !value.includes("[TODO") && !path2.isAbsolute(value);
8221
+ }
8222
+
8223
+ // src/commands/canvas/run-retention.ts
8224
+ import { rm } from "fs/promises";
8225
+ import path3 from "path";
8226
+ function runDirsToPrune(entries, keep, currentRunId) {
8227
+ const runs = entries.filter((e) => /^r_[0-9A-Za-z]+$/.test(e) && e !== currentRunId).sort();
8228
+ if (keep <= 0) return runs;
8229
+ return runs.slice(0, Math.max(0, runs.length - keep));
8230
+ }
8231
+ async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
8232
+ const { readdir: readdir3 } = await import("fs/promises");
8233
+ let entries;
8234
+ try {
8235
+ entries = await readdir3(outputsDir);
8236
+ } catch {
8237
+ return;
8238
+ }
8239
+ const toPrune = runDirsToPrune(entries, keep, currentRunId);
8240
+ if (toPrune.length === 0) return;
8241
+ for (const dir of toPrune) {
8242
+ await rm(path3.join(outputsDir, dir), { recursive: true, force: true }).catch(
8243
+ (e) => log(`[prune ] could not remove ${dir}: ${e.message}`)
8244
+ );
8245
+ }
8246
+ log(`[prune ] removed ${toPrune.length} old run dir(s), kept the ${keep} newest`);
8247
+ }
8248
+
8418
8249
  // src/commands/canvas/run.ts
8419
- var runCommand = defineCommand81({
8250
+ var runCommand = defineCommand80({
8420
8251
  meta: { name: "run", description: "Validate and execute a canvas JSON file." },
8421
8252
  args: {
8422
8253
  file: { type: "positional", required: true, description: "Path to canvas JSON" },
8423
8254
  "cache-dir": { type: "string", description: "Cache root (default ./canvas/.cache)" },
8424
8255
  "outputs-dir": { type: "string", description: "Per-run outputs root (default ./canvas)" },
8425
8256
  "run-id": { type: "string", description: "Override run id" },
8426
- "cache-policy": { type: "string", description: "read_write | bypass | read_only" }
8257
+ "cache-policy": { type: "string", description: "read_write | bypass | read_only" },
8258
+ "keep-runs": {
8259
+ type: "string",
8260
+ description: "After the run, prune old r_* run dirs, keeping the N newest (off by default)"
8261
+ }
8427
8262
  },
8428
8263
  async run({ args }) {
8429
- const filePath = path3.resolve(String(args.file));
8430
- const raw = await readFile3(filePath, "utf8");
8264
+ const filePath = path4.resolve(String(args.file));
8265
+ const raw = await readFile2(filePath, "utf8");
8431
8266
  let parsed;
8432
8267
  try {
8433
8268
  parsed = JSON.parse(raw);
@@ -8437,6 +8272,7 @@ var runCommand = defineCommand81({
8437
8272
  `);
8438
8273
  process.exit(2);
8439
8274
  }
8275
+ parsed = resolveRelativeCanvasPaths(parsed, path4.dirname(filePath));
8440
8276
  const pending = unsuppliedPlaceholderAssets(parsed);
8441
8277
  if (pending.length > 0) {
8442
8278
  process.stderr.write(
@@ -8468,6 +8304,12 @@ var runCommand = defineCommand81({
8468
8304
  run_id: args["run-id"] ? String(args["run-id"]) : void 0,
8469
8305
  cache_policy: policy
8470
8306
  });
8307
+ const keepRuns = args["keep-runs"] !== void 0 ? Number(args["keep-runs"]) : void 0;
8308
+ if (keepRuns !== void 0 && Number.isFinite(keepRuns)) {
8309
+ const outputsDir = args["outputs-dir"] ? path4.resolve(String(args["outputs-dir"])) : path4.resolve("canvas");
8310
+ await pruneOldRuns(outputsDir, keepRuns, result.run_id, (line) => process.stdout.write(`${line}
8311
+ `));
8312
+ }
8471
8313
  process.stdout.write(
8472
8314
  `${JSON.stringify(
8473
8315
  {
@@ -8499,9 +8341,9 @@ var runCommand = defineCommand81({
8499
8341
  });
8500
8342
 
8501
8343
  // src/commands/canvas/scaffold-static-ad.ts
8502
- import { readFile as readFile4, writeFile } from "fs/promises";
8503
- import path4 from "path";
8504
- import { defineCommand as defineCommand82 } from "citty";
8344
+ import { readFile as readFile3, writeFile } from "fs/promises";
8345
+ import path5 from "path";
8346
+ import { defineCommand as defineCommand81 } from "citty";
8505
8347
 
8506
8348
  // src/engine/scaffold/staticAd.ts
8507
8349
  import { z as z2 } from "zod";
@@ -8714,7 +8556,7 @@ var SELECT_SYSTEM = 'You identify the MAIN, identity-critical visual elements of
8714
8556
  var SELECT_PROMPT = 'AD BLUEPRINT (from image_describe):\n{{blueprint}}\n\nFrom this blueprint, list ONLY the elements that are prominent, important, and identity-bearing \u2014 the ones a reproduction must ground in a real asset:\n- the brand logo/wordmark (from brands_logos with function_in_image = advertiser_brand) -> type "logo"\n- trust/rating/certification/app-store/review badges (brands_logos with function_in_image = trust_badge | review_platform | certification_or_seal | app_store_badge | payment_method) -> type "badge"\n- a showcased/hero product or package (a foreground entry in subjects that the ad is selling) -> type "product"\n- a foreground person whose identity matters (from people) -> type "person"\n- a foreground animal/character with a specific expression (from subjects) -> type "animal"\n\nDROP background extras, decorative props, generic scenery, and anything small or incidental. Keep at most ~6. If there are none, return an empty list.\n\nFor each kept element return: { "type": one of logo|product|person|animal|badge, "label": a short UPPER_SNAKE_CASE name (e.g. LOGO, PRODUCT, HERO_DOG, TRUSTPILOT), "description": a concrete reusable description to source/shoot the real asset (include the exact expression for a living subject), "expression": the facial expression for a living subject or null, "reason": why it is identity-critical, "locator": the blueprint entry this element came from as { "collection": one of "subjects" | "people" | "brands_logos", "index": its 0-based position in that array } (people -> people; logos/badges -> brands_logos; products/animals/objects -> subjects). Output ONLY the JSON object.';
8715
8557
  async function loadAssetText(ref, label) {
8716
8558
  const r = ref;
8717
- if (typeof r?.path === "string") return readFile4(r.path, "utf8");
8559
+ if (typeof r?.path === "string") return readFile3(r.path, "utf8");
8718
8560
  if (typeof r?.url === "string") {
8719
8561
  const res = await fetch(r.url);
8720
8562
  if (!res.ok) throw new Error(`failed to fetch ${label} (${res.status})`);
@@ -8820,7 +8662,7 @@ async function runVisionPasses(canvas) {
8820
8662
  return fail("read_outputs", e instanceof Error ? e.message : String(e));
8821
8663
  }
8822
8664
  }
8823
- var scaffoldStaticAdCommand = defineCommand82({
8665
+ var scaffoldStaticAdCommand = defineCommand81({
8824
8666
  meta: {
8825
8667
  name: "scaffold-static-ad",
8826
8668
  description: "Turn a source/inspiration image into a runnable static-ad canvas. Runs billed passes \u2014 image_describe (the blueprint, baked to prompt.json as the editable 'prompt'), an AI selection of the image's MAIN identity elements, and a structured global-layout pass (the column/row grid with per-region bounds and text sizes) \u2014 then scaffolds a canvas that wires one [TODO] ingest slot per element (logo/product/subject/badge + brand font) into image_generate. Edit prompt.json and drop the real assets, then `baker canvas run` it."
@@ -8837,10 +8679,10 @@ var scaffoldStaticAdCommand = defineCommand82({
8837
8679
  "skip-font": { type: "boolean", description: "Skip the brand-font \u2192 type-specimen slot" }
8838
8680
  },
8839
8681
  async run({ args }) {
8840
- const imagePath = path4.resolve(String(args.file));
8841
- const outPath = args.out ? path4.resolve(String(args.out)) : path4.join(path4.dirname(imagePath), "static-ad.canvas.json");
8842
- const outDir = path4.dirname(outPath);
8843
- const blueprintPath = path4.join(outDir, "prompt.json");
8682
+ const imagePath = path5.resolve(String(args.file));
8683
+ const outPath = args.out ? path5.resolve(String(args.out)) : path5.join(path5.dirname(imagePath), "static-ad.canvas.json");
8684
+ const outDir = path5.dirname(outPath);
8685
+ const blueprintPath = path5.join(outDir, "prompt.json");
8844
8686
  const { describeModel, selectModel, layoutModel, genModel } = resolveModels(args);
8845
8687
  const describeCanvas = buildDescribeCanvas(
8846
8688
  imagePath,
@@ -8897,7 +8739,7 @@ var scaffoldStaticAdCommand = defineCommand82({
8897
8739
  run_estimated_credits: validation.estimatedCredits
8898
8740
  },
8899
8741
  checklist: {
8900
- edit_prompt: `Edit ${path4.basename(blueprintPath)} \u2014 it is the blueprint generated from your image; rewrite it into the ad you want (palette, copy, claims, subjects). It feeds the generator directly.`,
8742
+ edit_prompt: `Edit ${path5.basename(blueprintPath)} \u2014 it is the blueprint generated from your image; rewrite it into the ad you want (palette, copy, claims, subjects). It feeds the generator directly.`,
8901
8743
  assets_to_supply: report.elements,
8902
8744
  font_slot: report.includes_font ? "Drop a brand font at the [TODO] brandfont path, or delete the brandfont + type_ref nodes to skip it." : "skipped (--skip-font)",
8903
8745
  note: "Replace every [TODO] ingest path with a real file, then `baker canvas validate` and `baker canvas run`. Running generates a billed image \u2014 it is not free."
@@ -8913,12 +8755,12 @@ var scaffoldStaticAdCommand = defineCommand82({
8913
8755
 
8914
8756
  // src/commands/canvas/scaffold-video.ts
8915
8757
  import { cp, mkdir, readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
8916
- import path6 from "path";
8917
- import { defineCommand as defineCommand83 } from "citty";
8758
+ import path8 from "path";
8759
+ import { defineCommand as defineCommand82 } from "citty";
8918
8760
 
8919
8761
  // src/engine/nodes/local/lib/sceneDetect.ts
8920
8762
  import { execFile as execFile2 } from "child_process";
8921
- import { mkdtemp, readdir as readdir3, readFile as readFile5, rm } from "fs/promises";
8763
+ import { mkdtemp, readdir as readdir2, readFile as readFile4, rm as rm2 } from "fs/promises";
8922
8764
  import { tmpdir } from "os";
8923
8765
  import { join as join2 } from "path";
8924
8766
  import { promisify as promisify2 } from "util";
@@ -8982,11 +8824,11 @@ async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs)
8982
8824
  ],
8983
8825
  { encoding: "utf-8", maxBuffer: 32 * 1024 * 1024, timeout: timeoutMs }
8984
8826
  );
8985
- const csvName = (await readdir3(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
8827
+ const csvName = (await readdir2(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
8986
8828
  if (!csvName) return [];
8987
- return parsePySceneDetectCsvCuts(await readFile5(join2(outDir, csvName), "utf-8"));
8829
+ return parsePySceneDetectCsvCuts(await readFile4(join2(outDir, csvName), "utf-8"));
8988
8830
  } finally {
8989
- await rm(outDir, { recursive: true, force: true });
8831
+ await rm2(outDir, { recursive: true, force: true });
8990
8832
  }
8991
8833
  }
8992
8834
  async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
@@ -9242,6 +9084,13 @@ function stillHoldArgs(durationS, dims) {
9242
9084
  `scale=${dims.w}:${dims.h}:force_original_aspect_ratio=increase,crop=${dims.w}:${dims.h},setsar=1,format=yuv420p`,
9243
9085
  "-c:v",
9244
9086
  "libx264",
9087
+ // Near-visually-lossless re-encode. libx264 DEFAULTS (crf 23, preset medium)
9088
+ // roughly halve the source bitrate and add banding on smooth surfaces; the
9089
+ // spine concats these by stream-copy, so any loss here ships to the final cut.
9090
+ "-crf",
9091
+ "18",
9092
+ "-preset",
9093
+ "slow",
9245
9094
  "-pix_fmt",
9246
9095
  "yuv420p",
9247
9096
  "{{out.video}}"
@@ -9257,6 +9106,13 @@ function trimArgs(durationS, offsetS = 0) {
9257
9106
  "-an",
9258
9107
  "-c:v",
9259
9108
  "libx264",
9109
+ // Preserve the seedance source quality through the trim. libx264 DEFAULTS
9110
+ // (crf 23) halve the bitrate (measured 9.57→4.40 Mbps) and band on motion;
9111
+ // the spine stream-copies the result, so the loss is permanent without this.
9112
+ "-crf",
9113
+ "18",
9114
+ "-preset",
9115
+ "slow",
9260
9116
  "-pix_fmt",
9261
9117
  "yuv420p",
9262
9118
  "{{out.video}}"
@@ -9323,6 +9179,13 @@ var Scene = z3.object({
9323
9179
  // The scene's role in the ad's persuasion arc (DECON-supplied); drives the
9324
9180
  // script re-craft checklist. Inferred from position when absent.
9325
9181
  narrative_role: z3.string().optional(),
9182
+ // DECON-supplied on the HOOK scene: the engineered physical/emotional state that
9183
+ // makes the first frame stop the scroll (sweaty/breathless/urgent …). Injected
9184
+ // into the hook's start-frame description so the generator renders that state,
9185
+ // not a calm influencer (CCA-11).
9186
+ hook_mechanic: z3.object({ mechanic: z3.string().optional(), why_it_stops_scroll: z3.string().optional() }).loose().optional(),
9187
+ // DECON-supplied per-scene location (so a gym hook isn't flattened to "home").
9188
+ scene_setting: z3.string().optional(),
9326
9189
  // How this scene cuts to the next (DECON-supplied). A recognized non-cut type
9327
9190
  // (fade/whip/zoom/dissolve/swipe) is reproduced as an ffmpeg xfade at the
9328
9191
  // boundary; cut/match_cut/none/other stay hard cuts. The last scene's value is
@@ -9370,10 +9233,21 @@ var VideoBlueprint = z3.object({
9370
9233
  mode: z3.string().optional(),
9371
9234
  voice_description: z3.string().optional(),
9372
9235
  persona: z3.string().optional()
9373
- }).loose().optional()
9236
+ }).loose().optional(),
9237
+ // Visual palette — read only to colour a clean brand-card/CTA plate (the
9238
+ // first hex is the dominant brand colour); never to drive frame generation.
9239
+ style: z3.object({ palette: z3.array(z3.object({ hex: z3.string().optional() }).loose()).optional() }).loose().optional()
9374
9240
  }).loose().optional(),
9375
9241
  scenes: z3.array(Scene).min(1)
9376
9242
  }).loose();
9243
+ function injectHookPhysicality(blueprint) {
9244
+ for (const scene of blueprint.scenes) {
9245
+ const why = scene.hook_mechanic?.why_it_stops_scroll?.trim();
9246
+ const prompt = scene.start_frame_prompt?.trim();
9247
+ if (!why || !prompt || prompt.includes(why)) continue;
9248
+ scene.start_frame_prompt = `${prompt} The subject's physical state IS the scroll-stopper \u2014 render it explicitly, not a calm pose: ${why}.`;
9249
+ }
9250
+ }
9377
9251
  var AppearsItem = z3.union([z3.number(), z3.object({ scene: z3.number(), edge: z3.string().optional() }).loose()]);
9378
9252
  var RecurringElement = z3.object({
9379
9253
  // person | animal | product | logo | badge | other
@@ -9399,7 +9273,8 @@ function sanitizeId2(raw, fallback) {
9399
9273
  return /^[a-z]/.test(id) ? id : `${fallback}_${id}`.replace(/_+$/g, "") || fallback;
9400
9274
  }
9401
9275
  function labelFor2(el, used) {
9402
- const base = (el.label ?? el.type ?? "ELEMENT").toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "ELEMENT";
9276
+ const raw = el.type?.toLowerCase() === "logo" ? "BRAND_LOGO" : el.label ?? el.type ?? "ELEMENT";
9277
+ const base = raw.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "ELEMENT";
9403
9278
  let label = base;
9404
9279
  let n = 2;
9405
9280
  while (used.has(label)) label = `${base}_${n++}`;
@@ -9566,6 +9441,8 @@ function buildElementSheets(slots, nodes) {
9566
9441
  if (slot.sameAs) continue;
9567
9442
  if (slot.presence.size < 1) continue;
9568
9443
  const sheetId = `${slot.id}_sheet`;
9444
+ const slotType = slot.type.toLowerCase();
9445
+ const isCast = slotType === "person" || slotType === "animal";
9569
9446
  nodes.push({
9570
9447
  id: sheetId,
9571
9448
  type: "image_reference_sheet",
@@ -9578,7 +9455,14 @@ function buildElementSheets(slots, nodes) {
9578
9455
  // 4K: the sheet packs up to 8 cells (angles + tight face/detail close-ups), and
9579
9456
  // it's the ONE reference every frame grounds on — per-cell sharpness here
9580
9457
  // propagates to every clip, so it's worth the highest tier on this single asset.
9581
- image_size: "4K"
9458
+ image_size: "4K",
9459
+ // The sheet is the look that propagates to EVERY grounded frame, so a glossy
9460
+ // studio turnaround makes the whole UGC ad read as "produced" (the #1 AI tell).
9461
+ // Force a flat, real, front-camera look on the cast sheet so the actor stays
9462
+ // authentic, not an airbrushed influencer (CCA-02).
9463
+ ...isCast ? {
9464
+ style: "authentic UGC look: flat, even, natural front-camera lighting \u2014 no studio key/rim light, no seamless backdrop, no shallow depth of field; real skin texture and pores, no airbrushing or beauty retouch; true-to-life everyday styling"
9465
+ } : {}
9582
9466
  }
9583
9467
  });
9584
9468
  slot.ref = `$ref:${sheetId}.sheet`;
@@ -9686,8 +9570,7 @@ function buildFrameRef(edge, url, framePrompt, present, ctx, nodes) {
9686
9570
  const t = s.type.toLowerCase();
9687
9571
  return t === "person" || t === "animal";
9688
9572
  });
9689
- const castIdentityLocked = castSlots.every((s) => s.sheetBacked);
9690
- const useOriginalAnchor = Boolean(url) && (castSlots.length === 0 || castIdentityLocked);
9573
+ const useOriginalAnchor = Boolean(url) && castSlots.length === 0;
9691
9574
  const hasOriginal = useOriginalAnchor;
9692
9575
  const originalRef = useOriginalAnchor && url ? ingestFrameRef(url, edge, ctx, nodes) : void 0;
9693
9576
  const reference = [...present.map((s) => s.ref), ...originalRef ? [originalRef] : []];
@@ -9898,6 +9781,39 @@ function isUiOnlyComposite(regions) {
9898
9781
  const ui = regions.filter(regionIsUiSurface).length;
9899
9782
  return ui >= 1 && regions.length - ui <= 1;
9900
9783
  }
9784
+ function sceneIsFullScreenUi(scene, present) {
9785
+ if (scene.narrative_role?.trim() === "cta") return false;
9786
+ const hasCast = present.some((s) => {
9787
+ const t = s.type.toLowerCase();
9788
+ return t === "person" || t === "animal";
9789
+ });
9790
+ if (hasCast) return false;
9791
+ const hay = `${scene.summary ?? ""} ${scene.start_frame_prompt ?? ""} ${scene.end_frame_prompt ?? ""} ${scene.action_detail ?? ""}`;
9792
+ return UI_SURFACE_RE.test(hay);
9793
+ }
9794
+ function screenStillArgs(durationS, dims) {
9795
+ return [
9796
+ "-loop",
9797
+ "1",
9798
+ "-i",
9799
+ "{{in.frame}}",
9800
+ "-t",
9801
+ durationS.toFixed(3),
9802
+ "-r",
9803
+ "30",
9804
+ "-vf",
9805
+ `scale=${dims.w}:${dims.h}:force_original_aspect_ratio=decrease,pad=${dims.w}:${dims.h}:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1,format=yuv420p`,
9806
+ "-c:v",
9807
+ "libx264",
9808
+ "-crf",
9809
+ "18",
9810
+ "-preset",
9811
+ "slow",
9812
+ "-pix_fmt",
9813
+ "yuv420p",
9814
+ "{{out.video}}"
9815
+ ];
9816
+ }
9901
9817
  function layeredComposition(scene) {
9902
9818
  const comp = scene.composition;
9903
9819
  const layout = (comp?.layout ?? "").toLowerCase();
@@ -10068,6 +9984,77 @@ function emitFlashHold(i, scene, slots, ctx, lengths, out, ar, nodes, clips) {
10068
9984
  });
10069
9985
  clips.push({ ref: `$ref:s${i}_clip.video`, scene_s: lengths.dur, out });
10070
9986
  }
9987
+ function emitScreenScene(i, scene, lengths, out, ar, nodes, clips) {
9988
+ const label = commentSafe((scene.summary || scene.start_frame_prompt || "the app screen").slice(0, 120));
9989
+ const refId = `s${i}_screen_ref`;
9990
+ nodes.push({
9991
+ id: refId,
9992
+ type: "ingest",
9993
+ params: {
9994
+ source: "path",
9995
+ path: `[TODO: supply the REAL screen for "${label}" \u2014 NEVER AI-generate a UI. Capture a clean, text-free screenshot with \`baker images screenshot https://<brand-domain>/<path>\` (image-library skill); spoken/overlay text rides the overlay layer, not the screenshot]`,
9996
+ expect: "image"
9997
+ }
9998
+ });
9999
+ nodes.push({
10000
+ id: `s${i}_clip`,
10001
+ type: "ffmpeg",
10002
+ inputs: { frame: `$ref:${refId}.asset` },
10003
+ params: {
10004
+ args: screenStillArgs(lengths.trimTarget, canvasDims(ar)),
10005
+ outputs: { video: { kind: "video", ext: "mp4" } }
10006
+ }
10007
+ });
10008
+ clips.push({ ref: `$ref:s${i}_clip.video`, scene_s: lengths.dur, out });
10009
+ }
10010
+ var BRAND_CARD_RE = /\b(?:solid|plain|flat|brand|logo|wordmark|end[- ]?card|cta card|title card|colou?r background|background colou?r)\b/i;
10011
+ function sceneIsBrandCard(scene, present, isCta) {
10012
+ if (!isCta) return false;
10013
+ const hasCast = present.some((s) => {
10014
+ const t = s.type.toLowerCase();
10015
+ return t === "person" || t === "animal";
10016
+ });
10017
+ if (hasCast) return false;
10018
+ const hay = `${scene.summary ?? ""} ${scene.start_frame_prompt ?? ""} ${scene.end_frame_prompt ?? ""}`;
10019
+ return BRAND_CARD_RE.test(hay);
10020
+ }
10021
+ var HEX6_RE = /^#?[0-9a-fA-F]{6}$/;
10022
+ function brandPlateColor(blueprint) {
10023
+ const palette = blueprint.global?.style?.palette;
10024
+ const hex = palette?.map((p) => p?.hex).find((h) => typeof h === "string" && HEX6_RE.test(h));
10025
+ return hex ? `0x${hex.replace(/^#/, "").toUpperCase()}` : "0x000000";
10026
+ }
10027
+ function colorPlateArgs(durationS, dims, color) {
10028
+ return [
10029
+ "-f",
10030
+ "lavfi",
10031
+ "-i",
10032
+ `color=c=${color}:s=${dims.w}x${dims.h}:r=30`,
10033
+ "-t",
10034
+ durationS.toFixed(3),
10035
+ "-c:v",
10036
+ "libx264",
10037
+ "-crf",
10038
+ "18",
10039
+ "-preset",
10040
+ "slow",
10041
+ "-pix_fmt",
10042
+ "yuv420p",
10043
+ "{{out.video}}"
10044
+ ];
10045
+ }
10046
+ function emitBrandCardScene(i, lengths, out, ar, color, nodes, clips) {
10047
+ nodes.push({
10048
+ id: `s${i}_clip`,
10049
+ type: "ffmpeg",
10050
+ inputs: {},
10051
+ params: {
10052
+ args: colorPlateArgs(lengths.trimTarget, canvasDims(ar), color),
10053
+ outputs: { video: { kind: "video", ext: "mp4" } }
10054
+ }
10055
+ });
10056
+ clips.push({ ref: `$ref:s${i}_clip.video`, scene_s: lengths.dur, out });
10057
+ }
10071
10058
  function musicArcDigest(blueprint) {
10072
10059
  const roles = blueprint.scenes.map((s) => s.narrative_role).filter((r) => Boolean(r));
10073
10060
  const arc = roles.length > 0 ? roles.join(" \u2192 ") : "";
@@ -10187,7 +10174,7 @@ function makePresenterPresent(slots, canonical, opts = {}) {
10187
10174
  const solePerson = !opts.strict && personSlots.length === 1 ? personSlots[0].presence : null;
10188
10175
  return (speaker, sceneIndex) => {
10189
10176
  const presence = bySpeaker.get(speaker) ?? solePerson;
10190
- if (!presence) return opts.strict ? false : true;
10177
+ if (!presence) return !opts.strict;
10191
10178
  return presence.has(sceneIndex);
10192
10179
  };
10193
10180
  }
@@ -10552,6 +10539,15 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
10552
10539
  shootMode: mode,
10553
10540
  ingestCache: env.ingestCache
10554
10541
  };
10542
+ if (!env.reuse && sceneIsFullScreenUi(scene, present)) {
10543
+ emitScreenScene(i, scene, lengths, lengths.out, env.ar, nodes, out.clips);
10544
+ return void 0;
10545
+ }
10546
+ const isCta = scene.narrative_role?.trim() === "cta" || isLast;
10547
+ if (!env.reuse && sceneIsBrandCard(scene, present, isCta)) {
10548
+ emitBrandCardScene(i, lengths, lengths.out, env.ar, brandPlateColor(env.blueprint), nodes, out.clips);
10549
+ return void 0;
10550
+ }
10555
10551
  if (!ambientBroll && lengths.dur <= FLASH_HOLD_MAX_S) {
10556
10552
  emitFlashHold(i, scene, env.slots, ctx, lengths, lengths.out, env.ar, nodes, out.clips);
10557
10553
  return void 0;
@@ -10817,7 +10813,7 @@ function overlayElement(ov, at, dur) {
10817
10813
  const normAnim = normalizeAnim(ov.animation);
10818
10814
  const anim = normAnim ? ` data-anim="${normAnim}"` : "";
10819
10815
  const detail = ov.animation_detail ? ` data-anim-detail="${escapeHtml(ov.animation_detail)}"` : "";
10820
- return `<div class="ov ${positionClass(ov.position)}" data-start="${at}" data-dur="${dur}"${role}${anim}${detail}>${escapeHtml(ov.text.trim())}</div>`;
10816
+ return `<div class="ov clip ${positionClass(ov.position)}" data-start="${at}" data-dur="${dur}"${role}${anim}${detail}>${escapeHtml(ov.text.trim())}</div>`;
10821
10817
  }
10822
10818
  var RICH_OVERLAY_RE = /notif|tweet|\bx post\b|post\b|comment|message|chat|bubble|card|review|rating|stat|counter|toast|popup/;
10823
10819
  function sourceHint(fe) {
@@ -10847,7 +10843,7 @@ function floatingStub(fe, sceneStart) {
10847
10843
  const slug = (fe.kind ?? "element").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "element";
10848
10844
  return [
10849
10845
  `<!-- ${kind}: ${label} @ ${at}s for ${dur}s (${positionClass(fe.position)}). Source a real asset: ${hint} \u2014 drop it in this dir and uncomment:`,
10850
- `<img class="ov ${positionClass(fe.position)}" src="your-${slug}.png" data-start="${at}" data-dur="${dur}" alt="" /> -->`
10846
+ `<img class="ov clip ${positionClass(fe.position)}" src="your-${slug}.png" data-start="${at}" data-dur="${dur}" alt="" /> -->`
10851
10847
  ].join("\n");
10852
10848
  }
10853
10849
  function uiPipStub(scene) {
@@ -10867,7 +10863,7 @@ function uiPipStub(scene) {
10867
10863
  " \u2014 OR hand-build a brand-accurate HTML screen; then frame it in a phone mockup:",
10868
10864
  " npx hyperframes add phone-scroll (writes compositions/phone-scroll.html)",
10869
10865
  " drop the screenshot as screenshot.png in this dir and nest it as a PIP clip:",
10870
- ` <div data-composition-src="compositions/phone-scroll.html" data-start="${at}" data-duration="${dur}" data-track-index="2" data-width="1080" data-height="1920"></div> -->`
10866
+ ` <div class="clip" data-composition-src="compositions/phone-scroll.html" data-start="${at}" data-duration="${dur}" data-track-index="2" data-width="1080" data-height="1920"></div> -->`
10871
10867
  ].join("\n");
10872
10868
  }
10873
10869
  function buildOverlayHtml(input) {
@@ -10963,6 +10959,7 @@ function buildSpine(clips, nodes) {
10963
10959
  }
10964
10960
  function scaffoldVideoCanvas(input, elementsInput, opts) {
10965
10961
  const blueprint = VideoBlueprint.parse(input);
10962
+ injectHookPhysicality(blueprint);
10966
10963
  const elements = RecurringElements.parse(elementsInput);
10967
10964
  const nodes = [];
10968
10965
  nodes.push({
@@ -11374,18 +11371,44 @@ function videoReport(input, elementsInput) {
11374
11371
 
11375
11372
  // src/commands/canvas/composition-path.ts
11376
11373
  import { existsSync as existsSync3 } from "fs";
11377
- import path5 from "path";
11374
+ import path6 from "path";
11378
11375
  function resolveShippedCanvasDir(name, startDir, exists = existsSync3, maxDepth = 8) {
11379
- const rel = path5.join("canvas", name);
11376
+ const rel = path6.join("canvas", name);
11380
11377
  let dir = startDir;
11381
11378
  for (let i = 0; i < maxDepth; i++) {
11382
- const candidate = path5.join(dir, rel);
11383
- if (exists(path5.join(candidate, "meta.json"))) return candidate;
11384
- const parent = path5.dirname(dir);
11379
+ const candidate = path6.join(dir, rel);
11380
+ if (exists(path6.join(candidate, "meta.json"))) return candidate;
11381
+ const parent = path6.dirname(dir);
11385
11382
  if (parent === dir) break;
11386
11383
  dir = parent;
11387
11384
  }
11388
- return path5.resolve(startDir, "../../../", rel);
11385
+ return path6.resolve(startDir, "../../../", rel);
11386
+ }
11387
+
11388
+ // src/commands/canvas/gitignore.ts
11389
+ import { appendFile, readFile as readFile5 } from "fs/promises";
11390
+ import path7 from "path";
11391
+ function missingGitignoreEntries(existing, entries) {
11392
+ const present = new Set(
11393
+ existing.split("\n").map((l) => l.trim().replace(/\/+$/, "")).filter((l) => l.length > 0 && !l.startsWith("#"))
11394
+ );
11395
+ return entries.filter((e) => !present.has(e.trim().replace(/\/+$/, "")));
11396
+ }
11397
+ async function ensureGitignore(dir, entries) {
11398
+ const file = path7.join(dir, ".gitignore");
11399
+ let existing;
11400
+ try {
11401
+ existing = await readFile5(file, "utf8");
11402
+ } catch {
11403
+ return;
11404
+ }
11405
+ const missing = missingGitignoreEntries(existing, entries);
11406
+ if (missing.length === 0) return;
11407
+ const prefix = existing.endsWith("\n") || existing.length === 0 ? "" : "\n";
11408
+ await appendFile(file, `${prefix}
11409
+ # Baker canvas (engine cache + scaffold working files)
11410
+ ${missing.join("\n")}
11411
+ `);
11389
11412
  }
11390
11413
 
11391
11414
  // src/commands/canvas/scaffold-video.ts
@@ -11433,7 +11456,7 @@ async function loadTranscriptBestEffort(ref) {
11433
11456
  async function stageCaptions(outDir, transcript) {
11434
11457
  const text = transcript?.trim();
11435
11458
  if (!text || text === "[]") return {};
11436
- const compositionPath = path6.join(outDir, "tiktok-captions-composition");
11459
+ const compositionPath = path8.join(outDir, "tiktok-captions-composition");
11437
11460
  await cp(SHIPPED_CAPTIONS_DIR, compositionPath, { recursive: true });
11438
11461
  return { compositionPath };
11439
11462
  }
@@ -11565,7 +11588,7 @@ async function runAnalysisPasses(deconstructCanvas, selectModel) {
11565
11588
  return fail2("deconstruct", e instanceof Error ? e.message : String(e));
11566
11589
  }
11567
11590
  }
11568
- var scaffoldVideoCommand = defineCommand83({
11591
+ var scaffoldVideoCommand = defineCommand82({
11569
11592
  meta: {
11570
11593
  name: "scaffold-video",
11571
11594
  description: "Turn a reference video into a runnable reproduction canvas in one command. Runs billed passes \u2014 video_deconstruct (the full scene-by-scene blueprint + transcript, baked to prompt.json as the editable 'prompt') and an AI selection of the video's RECURRING identity elements (person/animal/product/logo) \u2014 then scaffolds a pipeline where every scene boundary is a static-ad-grade frame (the blueprint as target_blueprint, a reference legend, the real frame as anchor) and each recurring element gets ONE shared [TODO] ingest slot wired into every frame it appears in. The clips feed Seedance an ultra-detailed motion brief (action, camera, dialogue, transcript). Edit prompt.json, drop the real source images, then `baker canvas run`."
@@ -11595,11 +11618,11 @@ var scaffoldVideoCommand = defineCommand83({
11595
11618
  }
11596
11619
  },
11597
11620
  async run({ args }) {
11598
- const videoPath = path6.resolve(String(args.file));
11599
- const base = path6.basename(videoPath, path6.extname(videoPath));
11600
- const outPath = args.out ? path6.resolve(String(args.out)) : path6.join(path6.dirname(videoPath), `${base}.video.canvas.json`);
11601
- const outDir = path6.dirname(outPath);
11602
- const blueprintPath = path6.join(outDir, "prompt.json");
11621
+ const videoPath = path8.resolve(String(args.file));
11622
+ const base = path8.basename(videoPath, path8.extname(videoPath));
11623
+ const outPath = args.out ? path8.resolve(String(args.out)) : path8.join(path8.dirname(videoPath), `${base}.video.canvas.json`);
11624
+ const outDir = path8.dirname(outPath);
11625
+ const blueprintPath = path8.join(outDir, "prompt.json");
11603
11626
  const frames = args.frames === "reuse" ? "reuse" : "generate";
11604
11627
  const maxScenes = args["max-scenes"] ? Number(args["max-scenes"]) : void 0;
11605
11628
  if (Number.isFinite(maxScenes)) {
@@ -11622,9 +11645,9 @@ var scaffoldVideoCommand = defineCommand83({
11622
11645
  const annotated = annotateBlueprintWithElements(blueprint, elements);
11623
11646
  await writeFile2(blueprintPath, `${JSON.stringify(annotated, null, 2)}
11624
11647
  `, "utf8");
11625
- const compositionDest = path6.join(outDir, "video-overlay-composition");
11648
+ const compositionDest = path8.join(outDir, "video-overlay-composition");
11626
11649
  await cp(SHIPPED_COMPOSITION_DIR, compositionDest, { recursive: true });
11627
- const indexPath = path6.join(compositionDest, "index.html");
11650
+ const indexPath = path8.join(compositionDest, "index.html");
11628
11651
  const overlayHtml = buildOverlayHtml(blueprint);
11629
11652
  const indexHtml = await readFile6(indexPath, "utf8");
11630
11653
  const injected = indexHtml.replace("<!--OVERLAYS-->", () => overlayHtml);
@@ -11639,9 +11662,9 @@ var scaffoldVideoCommand = defineCommand83({
11639
11662
  const opts = {
11640
11663
  imageModel,
11641
11664
  videoModel,
11642
- overlayCompositionPath: compositionDest,
11643
- captionsCompositionPath: captions.compositionPath,
11644
- blueprintPath,
11665
+ overlayCompositionPath: path8.relative(outDir, compositionDest),
11666
+ captionsCompositionPath: captions.compositionPath ? path8.relative(outDir, captions.compositionPath) : void 0,
11667
+ blueprintPath: path8.relative(outDir, blueprintPath),
11645
11668
  frames,
11646
11669
  ambient: Boolean(args.ambient),
11647
11670
  ...args.resolution ? { resolution: String(args.resolution) } : {}
@@ -11654,7 +11677,7 @@ var scaffoldVideoCommand = defineCommand83({
11654
11677
  } catch (e) {
11655
11678
  return fail2("scaffold", e instanceof Error ? e.message : String(e));
11656
11679
  }
11657
- const validation = await validateCanvasDeep(canvas, defaultRegistry());
11680
+ const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(canvas, outDir), defaultRegistry());
11658
11681
  if (!validation.ok) {
11659
11682
  process.stderr.write(
11660
11683
  `${JSON.stringify({ ok: false, error: { code: "validation", issues: validation.issues } }, null, 2)}
@@ -11664,6 +11687,7 @@ var scaffoldVideoCommand = defineCommand83({
11664
11687
  }
11665
11688
  await writeFile2(outPath, `${JSON.stringify(canvas, null, 2)}
11666
11689
  `, "utf8");
11690
+ await ensureGitignore(process.cwd(), ["canvas/", ".context/"]);
11667
11691
  process.stdout.write(
11668
11692
  `${JSON.stringify(
11669
11693
  {
@@ -11681,7 +11705,7 @@ var scaffoldVideoCommand = defineCommand83({
11681
11705
  run_estimated_credits: validation.estimatedCredits
11682
11706
  },
11683
11707
  checklist: {
11684
- edit_prompt: `Edit ${path6.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.`,
11708
+ edit_prompt: `Edit ${path8.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.`,
11685
11709
  recurring_elements_to_supply: report.elements,
11686
11710
  voices_to_confirm: report.dialogue.map((d) => ({
11687
11711
  scene: d.scene,
@@ -11706,9 +11730,84 @@ var scaffoldVideoCommand = defineCommand83({
11706
11730
  }
11707
11731
  });
11708
11732
 
11733
+ // src/commands/canvas/set-prompt.ts
11734
+ import { readFile as readFile7, writeFile as writeFile3 } from "fs/promises";
11735
+ import path9 from "path";
11736
+ import { defineCommand as defineCommand83 } from "citty";
11737
+ function setNodePrompt(canvas, nodeId, text) {
11738
+ const nodes = canvas?.nodes;
11739
+ if (!Array.isArray(nodes)) throw new Error("canvas has no nodes array");
11740
+ const idx = nodes.findIndex((n) => n?.id === nodeId);
11741
+ if (idx < 0) {
11742
+ const ids = nodes.map((n) => n?.id).filter((id) => typeof id === "string");
11743
+ throw new Error(`node "${nodeId}" not found. Known nodes: ${ids.join(", ")}`);
11744
+ }
11745
+ const node = nodes[idx];
11746
+ const newNode = { ...node, params: { ...node.params ?? {}, prompt: text } };
11747
+ const newNodes = [...nodes];
11748
+ newNodes[idx] = newNode;
11749
+ return { ...canvas, nodes: newNodes };
11750
+ }
11751
+ var setPromptCommand = defineCommand83({
11752
+ meta: {
11753
+ name: "set-prompt",
11754
+ description: "Safely set a node's params.prompt (a frame description, motion prompt, etc.) without hand-editing the JSON. Prefer --text-file for multi-line/accented copy \u2014 it preserves UTF-8 exactly, unlike shell-quoted jq."
11755
+ },
11756
+ args: {
11757
+ file: { type: "positional", required: true, description: "Path to canvas JSON" },
11758
+ node: { type: "positional", required: true, description: "Node id to edit (e.g. s0_start)" },
11759
+ text: { type: "string", description: "New prompt text (inline)" },
11760
+ "text-file": { type: "string", description: "Read the new prompt from a UTF-8 file (preserves accents/newlines)" }
11761
+ },
11762
+ async run({ args }) {
11763
+ const filePath = path9.resolve(String(args.file));
11764
+ let canvas;
11765
+ try {
11766
+ canvas = JSON.parse(await readFile7(filePath, "utf8"));
11767
+ } catch (e) {
11768
+ process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "parse", message: String(e) } }, null, 2)}
11769
+ `);
11770
+ process.exit(2);
11771
+ }
11772
+ let text;
11773
+ if (args["text-file"]) text = await readFile7(path9.resolve(String(args["text-file"])), "utf8");
11774
+ else if (args.text !== void 0) text = String(args.text);
11775
+ else {
11776
+ process.stderr.write(
11777
+ `${JSON.stringify({ ok: false, error: { code: "no_text", message: "pass --text or --text-file" } }, null, 2)}
11778
+ `
11779
+ );
11780
+ process.exit(2);
11781
+ return;
11782
+ }
11783
+ let updated;
11784
+ try {
11785
+ updated = setNodePrompt(canvas, String(args.node), text);
11786
+ } catch (e) {
11787
+ process.stderr.write(
11788
+ `${JSON.stringify({ ok: false, error: { code: "node_not_found", message: String(e.message) } }, null, 2)}
11789
+ `
11790
+ );
11791
+ process.exit(2);
11792
+ return;
11793
+ }
11794
+ const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated, path9.dirname(filePath)), defaultRegistry());
11795
+ if (!validation.ok) {
11796
+ process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "validation", issues: validation.issues } }, null, 2)}
11797
+ `);
11798
+ process.exit(2);
11799
+ return;
11800
+ }
11801
+ await writeFile3(filePath, `${JSON.stringify(updated, null, 2)}
11802
+ `, "utf8");
11803
+ process.stdout.write(`${JSON.stringify({ ok: true, node: String(args.node), bytes: text.length }, null, 2)}
11804
+ `);
11805
+ }
11806
+ });
11807
+
11709
11808
  // src/commands/canvas/validate.ts
11710
- import { readFile as readFile7 } from "fs/promises";
11711
- import path7 from "path";
11809
+ import { readFile as readFile8 } from "fs/promises";
11810
+ import path10 from "path";
11712
11811
  import { defineCommand as defineCommand84 } from "citty";
11713
11812
  var validateCommand = defineCommand84({
11714
11813
  meta: {
@@ -11717,8 +11816,8 @@ var validateCommand = defineCommand84({
11717
11816
  },
11718
11817
  args: { file: { type: "positional", required: true, description: "Path to canvas JSON" } },
11719
11818
  async run({ args }) {
11720
- const filePath = path7.resolve(String(args.file));
11721
- const raw = await readFile7(filePath, "utf8");
11819
+ const filePath = path10.resolve(String(args.file));
11820
+ const raw = await readFile8(filePath, "utf8");
11722
11821
  let parsed;
11723
11822
  try {
11724
11823
  parsed = JSON.parse(raw);
@@ -11728,6 +11827,7 @@ var validateCommand = defineCommand84({
11728
11827
  `);
11729
11828
  process.exit(2);
11730
11829
  }
11830
+ parsed = resolveRelativeCanvasPaths(parsed, path10.dirname(filePath));
11731
11831
  const result = await validateCanvasDeep(parsed, defaultRegistry());
11732
11832
  if (!result.ok) {
11733
11833
  process.stderr.write(`${JSON.stringify({ ok: false, issues: result.issues }, null, 2)}
@@ -11764,7 +11864,6 @@ Subcommands:
11764
11864
  baker canvas run <file.json> \u2014 execute the canvas, write outputs to ./canvas/<run_id>/
11765
11865
  baker canvas catalog \u2014 print the agent-facing node + composition catalog (JSON Schema)
11766
11866
  baker canvas inspect <run_id> \u2014 one-page summary of a completed run
11767
- baker canvas gallery <dir> \u2014 read a creative folder's _definition.md + run manifests into the dashboard gallery descriptor (JSON)
11768
11867
  baker canvas scaffold-video <video> \u2014 turn a reference video into a runnable reproduction canvas (deconstruct + recurring-element detection)
11769
11868
  baker canvas scaffold-static-ad <image> \u2014 turn a source image into a runnable static-ad canvas (describe + element detection)`
11770
11869
  },
@@ -11773,17 +11872,200 @@ Subcommands:
11773
11872
  validate: validateCommand,
11774
11873
  catalog: catalogCommand,
11775
11874
  inspect: inspectCommand,
11776
- gallery: galleryCommand,
11777
11875
  "scaffold-video": scaffoldVideoCommand,
11778
- "scaffold-static-ad": scaffoldStaticAdCommand
11876
+ "scaffold-static-ad": scaffoldStaticAdCommand,
11877
+ "set-prompt": setPromptCommand
11878
+ }
11879
+ });
11880
+
11881
+ // src/commands/creatives/index.ts
11882
+ import { defineCommand as defineCommand87 } from "citty";
11883
+
11884
+ // src/commands/creatives/publish.ts
11885
+ import { defineCommand as defineCommand86 } from "citty";
11886
+
11887
+ // src/commands/images/api.ts
11888
+ import { readFile as readFile9 } from "fs/promises";
11889
+ import { extname } from "path";
11890
+ var imageProcessingTimeoutMs = 18e4;
11891
+ var imageReadyPollIntervalMs = 2e3;
11892
+ var mimeMap = {
11893
+ ".png": "image/png",
11894
+ ".jpg": "image/jpeg",
11895
+ ".jpeg": "image/jpeg",
11896
+ ".gif": "image/gif",
11897
+ ".webp": "image/webp",
11898
+ ".svg": "image/svg+xml",
11899
+ ".avif": "image/avif"
11900
+ };
11901
+ var defaultImageApiDeps = {
11902
+ readFile: readFile9,
11903
+ post: apiPost,
11904
+ get: apiGet,
11905
+ sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms))
11906
+ };
11907
+ function detectImageContentType(filePath, opts = {}) {
11908
+ const ext = extname(filePath).toLowerCase();
11909
+ const contentType = mimeMap[ext];
11910
+ if (!contentType || opts.allowedContentTypes && !opts.allowedContentTypes.includes(contentType)) {
11911
+ throw new ApiError(
11912
+ "VALIDATION_ERROR",
11913
+ opts.unsupportedMessage ?? `Cannot detect content type for extension "${ext}". Use --content-type.`
11914
+ );
11915
+ }
11916
+ return contentType;
11917
+ }
11918
+ async function uploadLocalImage(args, deps = defaultImageApiDeps) {
11919
+ const fileBuffer = await deps.readFile(args.file);
11920
+ const body = {
11921
+ base64: fileBuffer.toString("base64"),
11922
+ contentType: args.contentType
11923
+ };
11924
+ if (args.source) body.source = args.source;
11925
+ if (args.descriptionContext) body.descriptionContext = args.descriptionContext;
11926
+ return deps.post("/api/images/upload", body, { timeoutMs: imageProcessingTimeoutMs });
11927
+ }
11928
+ function getImage(deps, imageId) {
11929
+ return deps.get("/api/images/get", { id: imageId });
11930
+ }
11931
+ function updateImageTags(deps, args) {
11932
+ return deps.post("/api/images/tag", args);
11933
+ }
11934
+ async function waitForReadyImage(deps, imageId, opts = {}) {
11935
+ const timeoutMs = opts.timeoutMs ?? imageProcessingTimeoutMs;
11936
+ const pollIntervalMs = opts.pollIntervalMs ?? imageReadyPollIntervalMs;
11937
+ const deadline = Date.now() + timeoutMs;
11938
+ let lastStatus = "unknown";
11939
+ while (Date.now() <= deadline) {
11940
+ const image = await getImage(deps, imageId);
11941
+ lastStatus = image.status ?? "unknown";
11942
+ if (image.status === "ready") {
11943
+ return image;
11944
+ }
11945
+ if (image.status === "error") {
11946
+ throw new ApiError("IMAGE_PROCESSING_ERROR", "Image processing failed");
11947
+ }
11948
+ await deps.sleep(pollIntervalMs);
11949
+ }
11950
+ throw new ApiError("TIMEOUT", `Image was not ready before timeout; last status: ${lastStatus}`);
11951
+ }
11952
+
11953
+ // src/commands/creatives/publish.ts
11954
+ var creativeTag = "creative";
11955
+ var creativeContentTypes = ["image/png", "image/jpeg", "image/webp"];
11956
+ registerSchema({
11957
+ command: "creatives.publish",
11958
+ description: "Publish a final static creative image to Baker Images, apply the official creative tag, and return an image reference.",
11959
+ args: {
11960
+ file: { type: "string", description: "Local PNG/JPG/WebP creative image path", required: true },
11961
+ title: { type: "string", description: "Human title for the creative output", required: true },
11962
+ context: { type: "string", description: "Optional describe context for the image row", required: false }
11963
+ }
11964
+ });
11965
+ function detectCreativeContentType(filePath) {
11966
+ return detectImageContentType(filePath, {
11967
+ allowedContentTypes: creativeContentTypes,
11968
+ unsupportedMessage: "Unsupported creative image extension. Use PNG, JPG, or WebP."
11969
+ });
11970
+ }
11971
+ function imageToCreativeReference(image, title) {
11972
+ if (!image.imageUrl) {
11973
+ throw new ApiError("IMAGE_PROCESSING_ERROR", "Published image is missing imageUrl");
11974
+ }
11975
+ return {
11976
+ type: "image",
11977
+ slug: image._id,
11978
+ title,
11979
+ tags: image.tags?.includes(creativeTag) ? image.tags : [...image.tags ?? [], creativeTag],
11980
+ imageUrl: image.imageUrl,
11981
+ thumbnailUrl: image.thumbnailUrl ?? image.imageUrl,
11982
+ storageKey: image.storageKey,
11983
+ width: image.width,
11984
+ height: image.height,
11985
+ aspectRatio: image.aspectRatio,
11986
+ source: image.source
11987
+ };
11988
+ }
11989
+ async function publishCreative(args, deps = defaultImageApiDeps) {
11990
+ const title = args.title.trim();
11991
+ if (!title) {
11992
+ throw new ApiError("VALIDATION_ERROR", "--title is required");
11993
+ }
11994
+ const contentType = detectCreativeContentType(args.file);
11995
+ const upload = await uploadLocalImage(
11996
+ {
11997
+ file: args.file,
11998
+ contentType,
11999
+ source: "ai_generated",
12000
+ descriptionContext: args.context ?? `Static ad creative: ${title}`
12001
+ },
12002
+ deps
12003
+ );
12004
+ const readyImage = await waitForReadyImage(deps, upload.imageId, { timeoutMs: imageProcessingTimeoutMs });
12005
+ await updateImageTags(deps, {
12006
+ imageIds: [upload.imageId],
12007
+ addTags: [creativeTag],
12008
+ removeTags: []
12009
+ });
12010
+ const taggedImage = await getImage(deps, upload.imageId);
12011
+ return { imageId: upload.imageId, reference: imageToCreativeReference({ ...readyImage, ...taggedImage }, title) };
12012
+ }
12013
+ var publishCommand = defineCommand86({
12014
+ meta: {
12015
+ name: "publish",
12016
+ description: "Publish a final static creative image to Baker Images, deterministically tag it as creative, and print the image reference JSON."
12017
+ },
12018
+ args: {
12019
+ file: { type: "positional", description: "Local PNG/JPG/WebP creative image path", required: false },
12020
+ title: { type: "string", description: "Human title for the creative output", required: false },
12021
+ context: { type: "string", description: "Optional describe context for the image row", required: false }
12022
+ },
12023
+ run: async ({ args }) => {
12024
+ try {
12025
+ const file = args.file;
12026
+ const title = args.title;
12027
+ if (!file) {
12028
+ writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "Image path is required" } });
12029
+ process.exit(1);
12030
+ }
12031
+ if (!title) {
12032
+ writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "--title is required" } });
12033
+ process.exit(1);
12034
+ }
12035
+ const data = await publishCreative({ file, title, context: args.context });
12036
+ writeJson({ ok: true, data });
12037
+ } catch (err) {
12038
+ if (err instanceof ApiError) {
12039
+ writeJson({ ok: false, error: { code: err.code, message: err.message } });
12040
+ process.exit(1);
12041
+ }
12042
+ writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
12043
+ process.exit(1);
12044
+ }
12045
+ }
12046
+ });
12047
+
12048
+ // src/commands/creatives/index.ts
12049
+ var creativesCommand3 = defineCommand87({
12050
+ meta: {
12051
+ name: "creatives",
12052
+ description: `Publish static ad creatives as first-class Baker outputs.
12053
+
12054
+ Static creative handoff:
12055
+ baker creatives publish ./canvas/run/final.png --title "Spring Offer Static Ad"
12056
+
12057
+ Publishing uploads the image to the Company image library, applies the official creative tag, and returns an image reference for chat previews.`
12058
+ },
12059
+ subCommands: {
12060
+ publish: publishCommand
11779
12061
  }
11780
12062
  });
11781
12063
 
11782
12064
  // src/commands/ga4/index.ts
11783
- import { defineCommand as defineCommand89 } from "citty";
12065
+ import { defineCommand as defineCommand91 } from "citty";
11784
12066
 
11785
12067
  // src/commands/ga4/audit.ts
11786
- import { defineCommand as defineCommand86 } from "citty";
12068
+ import { defineCommand as defineCommand88 } from "citty";
11787
12069
 
11788
12070
  // src/commands/ga4/resolve.ts
11789
12071
  async function fetchProperties(useCache = true) {
@@ -11846,7 +12128,7 @@ registerSchema({
11846
12128
  "no-cache": { type: "boolean", description: "Skip cache, hit API directly", required: false }
11847
12129
  }
11848
12130
  });
11849
- var auditCommand2 = defineCommand86({
12131
+ var auditCommand2 = defineCommand88({
11850
12132
  meta: {
11851
12133
  name: "audit",
11852
12134
  description: `Run all GA4 admin health checks. Returns property config with playbook warnings.
@@ -11898,7 +12180,7 @@ Examples:
11898
12180
  });
11899
12181
 
11900
12182
  // src/commands/ga4/properties.ts
11901
- import { defineCommand as defineCommand87 } from "citty";
12183
+ import { defineCommand as defineCommand89 } from "citty";
11902
12184
  registerSchema({
11903
12185
  command: "ga4.properties",
11904
12186
  description: "List all accessible GA4 properties. Returns property IDs needed for query and audit commands. Run this first to find property IDs.",
@@ -11906,7 +12188,7 @@ registerSchema({
11906
12188
  "no-cache": { type: "boolean", description: "Skip cache, hit API directly", required: false }
11907
12189
  }
11908
12190
  });
11909
- var propertiesCommand = defineCommand87({
12191
+ var propertiesCommand = defineCommand89({
11910
12192
  meta: {
11911
12193
  name: "properties",
11912
12194
  description: `List accessible GA4 properties.
@@ -11956,7 +12238,7 @@ Examples:
11956
12238
  // src/commands/ga4/query.ts
11957
12239
  import { appendFileSync as appendFileSync2, existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
11958
12240
  import { resolve as resolve2 } from "path";
11959
- import { defineCommand as defineCommand88 } from "citty";
12241
+ import { defineCommand as defineCommand90 } from "citty";
11960
12242
 
11961
12243
  // src/commands/ga4/presets.ts
11962
12244
  var GA4_PRESETS = [
@@ -12088,7 +12370,7 @@ function handleError(err) {
12088
12370
  });
12089
12371
  process.exit(1);
12090
12372
  }
12091
- var queryCommand2 = defineCommand88({
12373
+ var queryCommand2 = defineCommand90({
12092
12374
  meta: {
12093
12375
  name: "query",
12094
12376
  description: `Run GA4 Data API reports. Preset-first with free-form escape hatch.
@@ -12159,7 +12441,7 @@ Free-form (escape hatch):
12159
12441
  });
12160
12442
 
12161
12443
  // src/commands/ga4/index.ts
12162
- var ga4Command = defineCommand89({
12444
+ var ga4Command = defineCommand91({
12163
12445
  meta: {
12164
12446
  name: "ga4",
12165
12447
  description: `Google Analytics 4 commands. Audit property config, run playbook-aligned reports.
@@ -12182,12 +12464,12 @@ Examples:
12182
12464
  });
12183
12465
 
12184
12466
  // src/commands/gsc/index.ts
12185
- import { defineCommand as defineCommand93 } from "citty";
12467
+ import { defineCommand as defineCommand95 } from "citty";
12186
12468
 
12187
12469
  // src/commands/gsc/query.ts
12188
12470
  import { appendFileSync as appendFileSync3, existsSync as existsSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
12189
12471
  import { resolve as resolve3 } from "path";
12190
- import { defineCommand as defineCommand90 } from "citty";
12472
+ import { defineCommand as defineCommand92 } from "citty";
12191
12473
 
12192
12474
  // src/commands/gsc/presets.ts
12193
12475
  var GSC_PRESETS = [
@@ -12375,7 +12657,7 @@ function handleError2(err) {
12375
12657
  });
12376
12658
  process.exit(1);
12377
12659
  }
12378
- var queryCommand3 = defineCommand90({
12660
+ var queryCommand3 = defineCommand92({
12379
12661
  meta: {
12380
12662
  name: "query",
12381
12663
  description: `Run GSC Search Analytics queries. Preset-first with free-form escape hatch.
@@ -12453,7 +12735,7 @@ Free-form (escape hatch):
12453
12735
  });
12454
12736
 
12455
12737
  // src/commands/gsc/sitemaps.ts
12456
- import { defineCommand as defineCommand91 } from "citty";
12738
+ import { defineCommand as defineCommand93 } from "citty";
12457
12739
  registerSchema({
12458
12740
  command: "gsc.sitemaps",
12459
12741
  description: "List sitemaps for a Search Console site. Check sitemap health and errors.",
@@ -12462,7 +12744,7 @@ registerSchema({
12462
12744
  "no-cache": { type: "boolean", description: "Skip cache, hit API directly", required: false }
12463
12745
  }
12464
12746
  });
12465
- var sitemapsCommand = defineCommand91({
12747
+ var sitemapsCommand = defineCommand93({
12466
12748
  meta: {
12467
12749
  name: "sitemaps",
12468
12750
  description: `List sitemaps for a site. Check health and errors.
@@ -12512,7 +12794,7 @@ Examples:
12512
12794
  });
12513
12795
 
12514
12796
  // src/commands/gsc/sites.ts
12515
- import { defineCommand as defineCommand92 } from "citty";
12797
+ import { defineCommand as defineCommand94 } from "citty";
12516
12798
  registerSchema({
12517
12799
  command: "gsc.sites",
12518
12800
  description: "List all verified Google Search Console sites. Returns site URLs needed for query and sitemaps commands.",
@@ -12520,7 +12802,7 @@ registerSchema({
12520
12802
  "no-cache": { type: "boolean", description: "Skip cache, hit API directly", required: false }
12521
12803
  }
12522
12804
  });
12523
- var sitesCommand = defineCommand92({
12805
+ var sitesCommand = defineCommand94({
12524
12806
  meta: {
12525
12807
  name: "sites",
12526
12808
  description: `List verified Search Console sites.
@@ -12568,7 +12850,7 @@ Examples:
12568
12850
  });
12569
12851
 
12570
12852
  // src/commands/gsc/index.ts
12571
- var gscCommand = defineCommand93({
12853
+ var gscCommand = defineCommand95({
12572
12854
  meta: {
12573
12855
  name: "gsc",
12574
12856
  description: `Google Search Console commands. PPC-SEO arbitrage, brand halo analysis, negative keyword discovery.
@@ -12591,10 +12873,10 @@ Examples:
12591
12873
  });
12592
12874
 
12593
12875
  // src/commands/images/index.ts
12594
- import { defineCommand as defineCommand117 } from "citty";
12876
+ import { defineCommand as defineCommand119 } from "citty";
12595
12877
 
12596
12878
  // src/commands/images/crop.ts
12597
- import { defineCommand as defineCommand94 } from "citty";
12879
+ import { defineCommand as defineCommand96 } from "citty";
12598
12880
 
12599
12881
  // src/lib/image/crop-sprite.ts
12600
12882
  import sharp from "sharp";
@@ -12609,8 +12891,8 @@ function cropSprite(input, region) {
12609
12891
 
12610
12892
  // src/lib/image/io.ts
12611
12893
  import { randomBytes } from "crypto";
12612
- import { glob as fsGlob, readFile as readFile8, rename, stat as stat2, writeFile as writeFile3 } from "fs/promises";
12613
- import { dirname, extname, join as join3, resolve as resolve4 } from "path";
12894
+ import { glob as fsGlob, readFile as readFile10, rename, stat as stat2, writeFile as writeFile4 } from "fs/promises";
12895
+ import { dirname, extname as extname2, join as join3, resolve as resolve4 } from "path";
12614
12896
  var REMOTE_RE = /^https?:\/\//i;
12615
12897
  var GLOB_RE = /[*?[\]{}]/;
12616
12898
  function isRemoteUrl(value) {
@@ -12645,18 +12927,18 @@ async function readImageBuffer(pathOrUrl) {
12645
12927
  }
12646
12928
  return Buffer.from(await response.arrayBuffer());
12647
12929
  }
12648
- return readFile8(pathOrUrl);
12930
+ return readFile10(pathOrUrl);
12649
12931
  }
12650
- async function isDirectory(path8) {
12932
+ async function isDirectory(path11) {
12651
12933
  try {
12652
- const s = await stat2(path8);
12934
+ const s = await stat2(path11);
12653
12935
  return s.isDirectory();
12654
12936
  } catch {
12655
12937
  return false;
12656
12938
  }
12657
12939
  }
12658
12940
  async function resolveOutputPath(inputPath, outputArg, options) {
12659
- const base = options.newExtension ? inputPath.slice(0, -extname(inputPath).length) + options.newExtension : inputPath;
12941
+ const base = options.newExtension ? inputPath.slice(0, -extname2(inputPath).length) + options.newExtension : inputPath;
12660
12942
  if (!outputArg) return base;
12661
12943
  if (options.multipleInputs || await isDirectory(outputArg)) {
12662
12944
  const filename = base.split("/").pop() ?? "out.png";
@@ -12668,7 +12950,7 @@ async function atomicWrite(targetPath, data) {
12668
12950
  const absolute = resolve4(targetPath);
12669
12951
  const dir = dirname(absolute);
12670
12952
  const tmp = join3(dir, `.baker-image-${randomBytes(8).toString("hex")}.tmp`);
12671
- await writeFile3(tmp, data);
12953
+ await writeFile4(tmp, data);
12672
12954
  await rename(tmp, absolute);
12673
12955
  }
12674
12956
 
@@ -12719,7 +13001,7 @@ function emitError2(err) {
12719
13001
  }
12720
13002
  process.exit(1);
12721
13003
  }
12722
- var cropCommand = defineCommand94({
13004
+ var cropCommand = defineCommand96({
12723
13005
  meta: {
12724
13006
  name: "crop",
12725
13007
  description: "Crop a rectangular region from an image.\n\nExample: baker images crop sprite.png --x 0 --y 0 --width 64 --height 64 --output icon.png"
@@ -12755,7 +13037,7 @@ var cropCommand = defineCommand94({
12755
13037
  });
12756
13038
 
12757
13039
  // src/commands/images/delete.ts
12758
- import { defineCommand as defineCommand95 } from "citty";
13040
+ import { defineCommand as defineCommand97 } from "citty";
12759
13041
  registerSchema({
12760
13042
  command: "images.delete",
12761
13043
  description: "Delete an image by ID",
@@ -12769,7 +13051,7 @@ registerSchema({
12769
13051
  }
12770
13052
  }
12771
13053
  });
12772
- var deleteCommand = defineCommand95({
13054
+ var deleteCommand = defineCommand97({
12773
13055
  meta: {
12774
13056
  name: "delete",
12775
13057
  description: "Delete an image by ID. Use --dry-run to preview. Example: baker images delete j571abc123 --dry-run"
@@ -12810,7 +13092,7 @@ var deleteCommand = defineCommand95({
12810
13092
  });
12811
13093
 
12812
13094
  // src/commands/images/dimensions.ts
12813
- import { defineCommand as defineCommand96 } from "citty";
13095
+ import { defineCommand as defineCommand98 } from "citty";
12814
13096
 
12815
13097
  // src/lib/image/dimensions.ts
12816
13098
  import { imageSize } from "image-size";
@@ -12833,7 +13115,7 @@ registerSchema({
12833
13115
  target: { type: "string", description: "Local file path or remote http(s) URL", required: true }
12834
13116
  }
12835
13117
  });
12836
- var dimensionsCommand = defineCommand96({
13118
+ var dimensionsCommand = defineCommand98({
12837
13119
  meta: {
12838
13120
  name: "dimensions",
12839
13121
  description: "Read image dimensions without decoding the full file.\n\nExample: baker images dimensions ./logo.png\nExample: baker images dimensions https://acme.com/hero.png"
@@ -12877,7 +13159,7 @@ var dimensionsCommand = defineCommand96({
12877
13159
  });
12878
13160
 
12879
13161
  // src/commands/images/extract.ts
12880
- import { defineCommand as defineCommand97 } from "citty";
13162
+ import { defineCommand as defineCommand99 } from "citty";
12881
13163
  registerSchema({
12882
13164
  command: "images.extract",
12883
13165
  description: "Extract images from a URL via Firecrawl (formats: images).",
@@ -12893,7 +13175,7 @@ registerSchema({
12893
13175
  }
12894
13176
  }
12895
13177
  });
12896
- var extractCommand = defineCommand97({
13178
+ var extractCommand = defineCommand99({
12897
13179
  meta: {
12898
13180
  name: "extract",
12899
13181
  description: "Pull every image from a single URL via Firecrawl. ~$0.001/scrape. Cap auto-ingest at 20.\n\nExample: baker images extract https://stripe.com --auto-ingest 5"
@@ -12931,7 +13213,7 @@ var extractCommand = defineCommand97({
12931
13213
  });
12932
13214
 
12933
13215
  // src/commands/images/find.ts
12934
- import { defineCommand as defineCommand98 } from "citty";
13216
+ import { defineCommand as defineCommand100 } from "citty";
12935
13217
  registerSchema({
12936
13218
  command: "images.find",
12937
13219
  description: "Fanout image search: library first, then opted-in external providers.",
@@ -12963,7 +13245,7 @@ registerSchema({
12963
13245
  }
12964
13246
  }
12965
13247
  });
12966
- var findCommand = defineCommand98({
13248
+ var findCommand = defineCommand100({
12967
13249
  meta: {
12968
13250
  name: "find",
12969
13251
  description: "Library-first fanout image search. Opt in to providers with --sources. `--fallback` short-circuits to externals only when library is thin. With --auto-ingest, ingested external hits return Baker-owned URLs.\n\nExample: baker images find 'office' --sources library,magnific --limit 20"
@@ -13009,8 +13291,8 @@ var findCommand = defineCommand98({
13009
13291
  });
13010
13292
 
13011
13293
  // src/commands/images/generate.ts
13012
- import { readFile as readFile9 } from "fs/promises";
13013
- import { defineCommand as defineCommand99 } from "citty";
13294
+ import { readFile as readFile11 } from "fs/promises";
13295
+ import { defineCommand as defineCommand101 } from "citty";
13014
13296
  import sharp2 from "sharp";
13015
13297
  var GENERATE_TIMEOUT_MS = 18e4;
13016
13298
  var REFERENCE_MAX_EDGE = 1536;
@@ -13092,7 +13374,7 @@ async function resolveReferences(spec) {
13092
13374
  }
13093
13375
  let raw;
13094
13376
  try {
13095
- raw = await readFile9(entry);
13377
+ raw = await readFile11(entry);
13096
13378
  } catch {
13097
13379
  throw new ApiError("VALIDATION_ERROR", `Reference file not found: ${entry}`);
13098
13380
  }
@@ -13106,7 +13388,7 @@ async function resolveReferences(spec) {
13106
13388
  }
13107
13389
  return out;
13108
13390
  }
13109
- var generateCommand = defineCommand99({
13391
+ var generateCommand = defineCommand101({
13110
13392
  meta: {
13111
13393
  name: "generate",
13112
13394
  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]]'"
@@ -13158,7 +13440,7 @@ var generateCommand = defineCommand99({
13158
13440
  });
13159
13441
 
13160
13442
  // src/commands/images/get.ts
13161
- import { defineCommand as defineCommand100 } from "citty";
13443
+ import { defineCommand as defineCommand102 } from "citty";
13162
13444
  registerSchema({
13163
13445
  command: "images.get",
13164
13446
  description: "Get a single image by ID",
@@ -13166,7 +13448,7 @@ registerSchema({
13166
13448
  id: { type: "string", description: "Image ID", required: true }
13167
13449
  }
13168
13450
  });
13169
- var getCommand2 = defineCommand100({
13451
+ var getCommand2 = defineCommand102({
13170
13452
  meta: { name: "get", description: "Get a single image by ID. Example: baker images get j571abc123" },
13171
13453
  args: {
13172
13454
  id: { type: "positional", description: "Image ID", required: false },
@@ -13202,7 +13484,7 @@ var getCommand2 = defineCommand100({
13202
13484
  });
13203
13485
 
13204
13486
  // src/commands/images/gif.ts
13205
- import { defineCommand as defineCommand101 } from "citty";
13487
+ import { defineCommand as defineCommand103 } from "citty";
13206
13488
  registerSchema({
13207
13489
  command: "images.gif",
13208
13490
  description: "Search Giphy for GIFs / reaction memes (paid social creative).",
@@ -13234,7 +13516,7 @@ registerSchema({
13234
13516
  }
13235
13517
  }
13236
13518
  });
13237
- var gifCommand = defineCommand101({
13519
+ var gifCommand = defineCommand103({
13238
13520
  meta: {
13239
13521
  name: "gif",
13240
13522
  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"
@@ -13281,7 +13563,7 @@ var gifCommand = defineCommand101({
13281
13563
  });
13282
13564
 
13283
13565
  // src/commands/images/google.ts
13284
- import { defineCommand as defineCommand102 } from "citty";
13566
+ import { defineCommand as defineCommand104 } from "citty";
13285
13567
  registerSchema({
13286
13568
  command: "images.google",
13287
13569
  description: "Google Images search via the official Custom Search JSON API. Unverified source \u2014 inspect before placing.",
@@ -13317,7 +13599,7 @@ registerSchema({
13317
13599
  }
13318
13600
  }
13319
13601
  });
13320
- var googleCommand2 = defineCommand102({
13602
+ var googleCommand2 = defineCommand104({
13321
13603
  meta: {
13322
13604
  name: "google",
13323
13605
  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"
@@ -13365,7 +13647,7 @@ var googleCommand2 = defineCommand102({
13365
13647
  });
13366
13648
 
13367
13649
  // src/commands/images/icon.ts
13368
- import { defineCommand as defineCommand103 } from "citty";
13650
+ import { defineCommand as defineCommand105 } from "citty";
13369
13651
  registerSchema({
13370
13652
  command: "images.icon",
13371
13653
  description: "Icon lookup via Iconify (200+ icon sets, free CDN).",
@@ -13391,7 +13673,7 @@ registerSchema({
13391
13673
  }
13392
13674
  }
13393
13675
  });
13394
- var iconCommand = defineCommand103({
13676
+ var iconCommand = defineCommand105({
13395
13677
  meta: {
13396
13678
  name: "icon",
13397
13679
  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'"
@@ -13431,7 +13713,7 @@ var iconCommand = defineCommand103({
13431
13713
  });
13432
13714
 
13433
13715
  // src/commands/images/ingest.ts
13434
- import { defineCommand as defineCommand104 } from "citty";
13716
+ import { defineCommand as defineCommand106 } from "citty";
13435
13717
  registerSchema({
13436
13718
  command: "images.ingest",
13437
13719
  description: "Ingest a remote image URL into the library (full describe + embed).",
@@ -13443,7 +13725,7 @@ registerSchema({
13443
13725
  context: { type: "string", description: "Description context hint", required: false }
13444
13726
  }
13445
13727
  });
13446
- var ingestCommand = defineCommand104({
13728
+ var ingestCommand = defineCommand106({
13447
13729
  meta: {
13448
13730
  name: "ingest",
13449
13731
  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"
@@ -13485,7 +13767,7 @@ var ingestCommand = defineCommand104({
13485
13767
  });
13486
13768
 
13487
13769
  // src/commands/images/library.ts
13488
- import { defineCommand as defineCommand105 } from "citty";
13770
+ import { defineCommand as defineCommand107 } from "citty";
13489
13771
  registerSchema({
13490
13772
  command: "images.library",
13491
13773
  description: "Search the company image library. Returns only ready images.",
@@ -13511,7 +13793,7 @@ registerSchema({
13511
13793
  }
13512
13794
  }
13513
13795
  });
13514
- var libraryCommand = defineCommand105({
13796
+ var libraryCommand = defineCommand107({
13515
13797
  meta: {
13516
13798
  name: "library",
13517
13799
  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"
@@ -13568,7 +13850,7 @@ var libraryCommand = defineCommand105({
13568
13850
  });
13569
13851
 
13570
13852
  // src/commands/images/logo.ts
13571
- import { defineCommand as defineCommand106 } from "citty";
13853
+ import { defineCommand as defineCommand108 } from "citty";
13572
13854
  registerSchema({
13573
13855
  command: "images.logo",
13574
13856
  description: "Brand logo lookup via Brandfetch CDN (fallback/404). Auto-ingests by default.",
@@ -13593,7 +13875,7 @@ registerSchema({
13593
13875
  }
13594
13876
  }
13595
13877
  });
13596
- var logoCommand = defineCommand106({
13878
+ var logoCommand = defineCommand108({
13597
13879
  meta: {
13598
13880
  name: "logo",
13599
13881
  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"
@@ -13631,7 +13913,7 @@ var logoCommand = defineCommand106({
13631
13913
  });
13632
13914
 
13633
13915
  // src/commands/images/normalize.ts
13634
- import { defineCommand as defineCommand107 } from "citty";
13916
+ import { defineCommand as defineCommand109 } from "citty";
13635
13917
 
13636
13918
  // src/lib/image/color-changer.ts
13637
13919
  import quantize from "quantize";
@@ -14363,7 +14645,7 @@ function coerceRawArgs(args) {
14363
14645
  "dry-run": bool(args["dry-run"])
14364
14646
  };
14365
14647
  }
14366
- var normalizeCommand = defineCommand107({
14648
+ var normalizeCommand = defineCommand109({
14367
14649
  meta: {
14368
14650
  name: "normalize",
14369
14651
  description: `Normalize logos / images: declarative recolor + bg removal + trim + resize. Operates on local files; writes in-place by default.
@@ -14418,7 +14700,7 @@ Examples:
14418
14700
  });
14419
14701
 
14420
14702
  // src/commands/images/pinterest.ts
14421
- import { defineCommand as defineCommand108 } from "citty";
14703
+ import { defineCommand as defineCommand110 } from "citty";
14422
14704
  registerSchema({
14423
14705
  command: "images.pinterest",
14424
14706
  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.",
@@ -14438,7 +14720,7 @@ registerSchema({
14438
14720
  }
14439
14721
  }
14440
14722
  });
14441
- var pinterestCommand = defineCommand108({
14723
+ var pinterestCommand = defineCommand110({
14442
14724
  meta: {
14443
14725
  name: "pinterest",
14444
14726
  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'"
@@ -14478,7 +14760,7 @@ var pinterestCommand = defineCommand108({
14478
14760
  });
14479
14761
 
14480
14762
  // src/commands/images/screenshot.ts
14481
- import { defineCommand as defineCommand109 } from "citty";
14763
+ import { defineCommand as defineCommand111 } from "citty";
14482
14764
  registerSchema({
14483
14765
  command: "images.screenshot",
14484
14766
  description: "Capture a website screenshot via ScreenshotOne. Auto-ingests on success.",
@@ -14494,7 +14776,7 @@ registerSchema({
14494
14776
  }
14495
14777
  }
14496
14778
  });
14497
- var screenshotCommand = defineCommand109({
14779
+ var screenshotCommand = defineCommand111({
14498
14780
  meta: {
14499
14781
  name: "screenshot",
14500
14782
  description: "Screenshot a URL via ScreenshotOne. $0.009/capture. Auto-ingests to library.\n\nExample: baker images screenshot https://stripe.com --full-page"
@@ -14544,7 +14826,7 @@ var screenshotCommand = defineCommand109({
14544
14826
  });
14545
14827
 
14546
14828
  // src/commands/images/search.ts
14547
- import { defineCommand as defineCommand110 } from "citty";
14829
+ import { defineCommand as defineCommand112 } from "citty";
14548
14830
  registerSchema({
14549
14831
  command: "images.search",
14550
14832
  description: "Search images by text query. Only returns ready images.",
@@ -14560,7 +14842,7 @@ registerSchema({
14560
14842
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
14561
14843
  }
14562
14844
  });
14563
- var searchCommand = defineCommand110({
14845
+ var searchCommand = defineCommand112({
14564
14846
  meta: {
14565
14847
  name: "search",
14566
14848
  description: "Semantic search images by text query. Uses hybrid BM25 + vector + reranking. Example: baker images search 'hero banner' --aspect-ratio 16:9 --tags logo"
@@ -14620,7 +14902,7 @@ var searchCommand = defineCommand110({
14620
14902
  });
14621
14903
 
14622
14904
  // src/commands/images/sticker.ts
14623
- import { defineCommand as defineCommand111 } from "citty";
14905
+ import { defineCommand as defineCommand113 } from "citty";
14624
14906
  registerSchema({
14625
14907
  command: "images.sticker",
14626
14908
  description: "Search Giphy stickers \u2014 transparent-background overlays for ad creative.",
@@ -14652,7 +14934,7 @@ registerSchema({
14652
14934
  }
14653
14935
  }
14654
14936
  });
14655
- var stickerCommand = defineCommand111({
14937
+ var stickerCommand = defineCommand113({
14656
14938
  meta: {
14657
14939
  name: "sticker",
14658
14940
  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"
@@ -14699,7 +14981,7 @@ var stickerCommand = defineCommand111({
14699
14981
  });
14700
14982
 
14701
14983
  // src/commands/images/stock.ts
14702
- import { defineCommand as defineCommand112 } from "citty";
14984
+ import { defineCommand as defineCommand114 } from "citty";
14703
14985
  registerSchema({
14704
14986
  command: "images.stock",
14705
14987
  description: "Stock photo, vector illustration, icon-set, and PSD search via Magnific (Freepik's developer API).",
@@ -14757,7 +15039,7 @@ registerSchema({
14757
15039
  }
14758
15040
  }
14759
15041
  });
14760
- var stockCommand = defineCommand112({
15042
+ var stockCommand = defineCommand114({
14761
15043
  meta: {
14762
15044
  name: "stock",
14763
15045
  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"
@@ -14813,7 +15095,7 @@ var stockCommand = defineCommand112({
14813
15095
  });
14814
15096
 
14815
15097
  // src/lib/tags-command.ts
14816
- import { defineCommand as defineCommand113 } from "citty";
15098
+ import { defineCommand as defineCommand115 } from "citty";
14817
15099
  function makeTagsCommand(command, label, endpoint) {
14818
15100
  registerSchema({
14819
15101
  command: `${command}.tags`,
@@ -14822,7 +15104,7 @@ function makeTagsCommand(command, label, endpoint) {
14822
15104
  output: { type: "string", description: "Output format: md|json", required: false, default: "md" }
14823
15105
  }
14824
15106
  });
14825
- return defineCommand113({
15107
+ return defineCommand115({
14826
15108
  meta: {
14827
15109
  name: "tags",
14828
15110
  description: `List the available ${label} tag names (defaults + company custom tags). Use before filtering with --tags. Example: baker ${command} tags`
@@ -14858,18 +15140,7 @@ function makeTagsCommand(command, label, endpoint) {
14858
15140
  var tagsCommand2 = makeTagsCommand("images", "image", "/api/images/tags");
14859
15141
 
14860
15142
  // src/commands/images/upload.ts
14861
- import { readFile as readFile10 } from "fs/promises";
14862
- import { extname as extname2 } from "path";
14863
- import { defineCommand as defineCommand114 } from "citty";
14864
- var MIME_MAP = {
14865
- ".png": "image/png",
14866
- ".jpg": "image/jpeg",
14867
- ".jpeg": "image/jpeg",
14868
- ".gif": "image/gif",
14869
- ".webp": "image/webp",
14870
- ".svg": "image/svg+xml",
14871
- ".avif": "image/avif"
14872
- };
15143
+ import { defineCommand as defineCommand116 } from "citty";
14873
15144
  registerSchema({
14874
15145
  command: "images.upload",
14875
15146
  description: "Upload an image to the library \u2014 local file path or remote http(s) URL.",
@@ -14907,15 +15178,7 @@ registerSchema({
14907
15178
  function isRemoteUrl2(value) {
14908
15179
  return /^https?:\/\//i.test(value);
14909
15180
  }
14910
- function detectContentType(filePath) {
14911
- const ext = extname2(filePath).toLowerCase();
14912
- const mime = MIME_MAP[ext];
14913
- if (!mime) {
14914
- throw new ApiError("VALIDATION_ERROR", `Cannot detect content type for extension "${ext}". Use --content-type.`);
14915
- }
14916
- return mime;
14917
- }
14918
- var uploadCommand = defineCommand114({
15181
+ var uploadCommand = defineCommand116({
14919
15182
  meta: {
14920
15183
  name: "upload",
14921
15184
  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'"
@@ -14983,7 +15246,7 @@ async function uploadRemote(target, args) {
14983
15246
  writeJson({ ok: true, data });
14984
15247
  }
14985
15248
  async function uploadLocal(target, args) {
14986
- const contentType = args["content-type"] || detectContentType(target);
15249
+ const contentType = args["content-type"] || detectImageContentType(target);
14987
15250
  if (args["dry-run"]) {
14988
15251
  writeJson({
14989
15252
  ok: true,
@@ -14998,17 +15261,17 @@ async function uploadLocal(target, args) {
14998
15261
  });
14999
15262
  return;
15000
15263
  }
15001
- const fileBuffer = await readFile10(target);
15002
- const base64 = fileBuffer.toString("base64");
15003
- const body = { base64, contentType };
15004
- if (args.source) body.source = args.source;
15005
- if (args.context) body.descriptionContext = args.context;
15006
- const data = await apiPost("/api/images/upload", body);
15264
+ const data = await uploadLocalImage({
15265
+ file: target,
15266
+ contentType,
15267
+ source: args.source,
15268
+ descriptionContext: args.context
15269
+ });
15007
15270
  writeJson({ ok: true, data });
15008
15271
  }
15009
15272
 
15010
15273
  // src/commands/images/upscale.ts
15011
- import { defineCommand as defineCommand115 } from "citty";
15274
+ import { defineCommand as defineCommand117 } from "citty";
15012
15275
  registerSchema({
15013
15276
  command: "images.upscale",
15014
15277
  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).",
@@ -15023,7 +15286,7 @@ registerSchema({
15023
15286
  }
15024
15287
  });
15025
15288
  var POLL_INTERVAL_MS3 = 1500;
15026
- var upscaleCommand = defineCommand115({
15289
+ var upscaleCommand = defineCommand117({
15027
15290
  meta: {
15028
15291
  name: "upscale",
15029
15292
  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"
@@ -15078,7 +15341,7 @@ var upscaleCommand = defineCommand115({
15078
15341
  });
15079
15342
 
15080
15343
  // src/commands/images/use.ts
15081
- import { defineCommand as defineCommand116 } from "citty";
15344
+ import { defineCommand as defineCommand118 } from "citty";
15082
15345
  registerSchema({
15083
15346
  command: "images.use",
15084
15347
  description: "Ingest a URL and wait for the library record to be ready.",
@@ -15094,7 +15357,7 @@ registerSchema({
15094
15357
  }
15095
15358
  });
15096
15359
  var POLL_INTERVAL_MS4 = 1500;
15097
- var useCommand = defineCommand116({
15360
+ var useCommand = defineCommand118({
15098
15361
  meta: {
15099
15362
  name: "use",
15100
15363
  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"
@@ -15140,7 +15403,7 @@ var useCommand = defineCommand116({
15140
15403
  });
15141
15404
 
15142
15405
  // src/commands/images/index.ts
15143
- var imagesCommand = defineCommand117({
15406
+ var imagesCommand = defineCommand119({
15144
15407
  meta: {
15145
15408
  name: "images",
15146
15409
  description: `Find, source, and normalize images. Subcommands route by provider so cost + license are explicit.
@@ -15210,10 +15473,10 @@ Paid transforms (run on the Convex backend, cost-tracked):
15210
15473
  });
15211
15474
 
15212
15475
  // src/commands/research/index.ts
15213
- import { defineCommand as defineCommand128 } from "citty";
15476
+ import { defineCommand as defineCommand130 } from "citty";
15214
15477
 
15215
15478
  // src/commands/research/advertisers.ts
15216
- import { defineCommand as defineCommand118 } from "citty";
15479
+ import { defineCommand as defineCommand120 } from "citty";
15217
15480
 
15218
15481
  // src/commands/research/output.ts
15219
15482
  var RESEARCH_DATA_NOTE = "Estimates based on third-party SERP data \u2014 not exact figures. Use for directional insights, not precise measurement.";
@@ -15326,7 +15589,7 @@ var FIELDS3 = {
15326
15589
  etv: "Estimated traffic value (USD)",
15327
15590
  visibility: "SERP visibility score (0-1)"
15328
15591
  };
15329
- var advertisersCommand = defineCommand118({
15592
+ var advertisersCommand = defineCommand120({
15330
15593
  meta: {
15331
15594
  name: "advertisers",
15332
15595
  description: `Find domains competing for a keyword in Google SERPs.
@@ -15373,7 +15636,7 @@ Examples:
15373
15636
  });
15374
15637
 
15375
15638
  // src/commands/research/autocomplete.ts
15376
- import { defineCommand as defineCommand119 } from "citty";
15639
+ import { defineCommand as defineCommand121 } from "citty";
15377
15640
  registerSchema({
15378
15641
  command: "research.autocomplete",
15379
15642
  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).",
@@ -15396,7 +15659,7 @@ registerSchema({
15396
15659
  var FIELDS4 = {
15397
15660
  suggestion: "Autocomplete suggestion from Google"
15398
15661
  };
15399
- var autocompleteCommand = defineCommand119({
15662
+ var autocompleteCommand = defineCommand121({
15400
15663
  meta: {
15401
15664
  name: "autocomplete",
15402
15665
  description: `Get Google Autocomplete suggestions for keyword expansion.
@@ -15442,7 +15705,7 @@ Examples:
15442
15705
  });
15443
15706
 
15444
15707
  // src/commands/research/countries.ts
15445
- import { defineCommand as defineCommand120 } from "citty";
15708
+ import { defineCommand as defineCommand122 } from "citty";
15446
15709
  registerSchema({
15447
15710
  command: "research.countries",
15448
15711
  description: "List all supported country codes for --location flag in research commands.",
@@ -15499,7 +15762,7 @@ var FIELDS5 = {
15499
15762
  code: "Country code to pass as --location",
15500
15763
  name: "Country name"
15501
15764
  };
15502
- var countriesCommand = defineCommand120({
15765
+ var countriesCommand = defineCommand122({
15503
15766
  meta: {
15504
15767
  name: "countries",
15505
15768
  description: "List all supported country codes for --location flag."
@@ -15510,7 +15773,7 @@ var countriesCommand = defineCommand120({
15510
15773
  });
15511
15774
 
15512
15775
  // src/commands/research/intent.ts
15513
- import { defineCommand as defineCommand121 } from "citty";
15776
+ import { defineCommand as defineCommand123 } from "citty";
15514
15777
  registerSchema({
15515
15778
  command: "research.intent",
15516
15779
  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.",
@@ -15533,7 +15796,7 @@ var FIELDS6 = {
15533
15796
  intent: "Primary Google Search intent: informational, navigational, commercial, transactional",
15534
15797
  probability: "Confidence score 0.0-1.0"
15535
15798
  };
15536
- var intentCommand = defineCommand121({
15799
+ var intentCommand = defineCommand123({
15537
15800
  meta: {
15538
15801
  name: "intent",
15539
15802
  description: `Classify Google Search intent for keywords. Returns intent type and confidence.
@@ -15581,7 +15844,7 @@ Examples:
15581
15844
  });
15582
15845
 
15583
15846
  // src/commands/research/keyword-gap.ts
15584
- import { defineCommand as defineCommand122 } from "citty";
15847
+ import { defineCommand as defineCommand124 } from "citty";
15585
15848
  registerSchema({
15586
15849
  command: "research.keyword-gap",
15587
15850
  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.",
@@ -15610,7 +15873,7 @@ var FIELDS7 = {
15610
15873
  cpc: "Cost per click USD",
15611
15874
  their_position: "Competitor's ranking position"
15612
15875
  };
15613
- var keywordGapCommand = defineCommand122({
15876
+ var keywordGapCommand = defineCommand124({
15614
15877
  meta: {
15615
15878
  name: "keyword-gap",
15616
15879
  description: `Find keywords a competitor has that you don't. Supports pagination via --offset.
@@ -15684,7 +15947,7 @@ Examples:
15684
15947
  });
15685
15948
 
15686
15949
  // src/commands/research/keywords-for-site.ts
15687
- import { defineCommand as defineCommand123 } from "citty";
15950
+ import { defineCommand as defineCommand125 } from "citty";
15688
15951
  registerSchema({
15689
15952
  command: "research.keywords-for-site",
15690
15953
  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.",
@@ -15717,7 +15980,7 @@ var FIELDS8 = {
15717
15980
  competition: "LOW, MEDIUM, or HIGH",
15718
15981
  competition_index: "Competition score 0-100"
15719
15982
  };
15720
- var keywordsForSiteCommand = defineCommand123({
15983
+ var keywordsForSiteCommand = defineCommand125({
15721
15984
  meta: {
15722
15985
  name: "keywords-for-site",
15723
15986
  description: `Get keywords a competitor targets in Google. Use --type to filter paid/organic.
@@ -15770,7 +16033,7 @@ Examples:
15770
16033
  });
15771
16034
 
15772
16035
  // src/commands/research/languages.ts
15773
- import { defineCommand as defineCommand124 } from "citty";
16036
+ import { defineCommand as defineCommand126 } from "citty";
15774
16037
  registerSchema({
15775
16038
  command: "research.languages",
15776
16039
  description: "List all supported language codes for --language flag in research commands.",
@@ -15800,7 +16063,7 @@ var FIELDS9 = {
15800
16063
  code: "Language code to pass as --language",
15801
16064
  name: "Language name (also accepted by --language)"
15802
16065
  };
15803
- var languagesCommand2 = defineCommand124({
16066
+ var languagesCommand2 = defineCommand126({
15804
16067
  meta: {
15805
16068
  name: "languages",
15806
16069
  description: "List all supported language codes for --language flag."
@@ -15811,7 +16074,7 @@ var languagesCommand2 = defineCommand124({
15811
16074
  });
15812
16075
 
15813
16076
  // src/commands/research/lighthouse.ts
15814
- import { defineCommand as defineCommand125 } from "citty";
16077
+ import { defineCommand as defineCommand127 } from "citty";
15815
16078
  registerSchema({
15816
16079
  command: "research.lighthouse",
15817
16080
  description: "Landing page performance audit. Returns metrics that affect Google Ads Quality Score and CPC.",
@@ -15830,7 +16093,7 @@ var FIELDS10 = {
15830
16093
  speed_index_ms: "Speed Index in ms (good: < 3400)",
15831
16094
  interactive_ms: "Time to Interactive in ms (good: < 3800)"
15832
16095
  };
15833
- var lighthouseCommand = defineCommand125({
16096
+ var lighthouseCommand = defineCommand127({
15834
16097
  meta: {
15835
16098
  name: "lighthouse",
15836
16099
  description: `Landing page performance audit. Metrics affecting Google Ads Quality Score.
@@ -15868,7 +16131,7 @@ Examples:
15868
16131
  });
15869
16132
 
15870
16133
  // src/commands/research/relevant-pages.ts
15871
- import { defineCommand as defineCommand126 } from "citty";
16134
+ import { defineCommand as defineCommand128 } from "citty";
15872
16135
  registerSchema({
15873
16136
  command: "research.relevant-pages",
15874
16137
  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).",
@@ -15894,7 +16157,7 @@ var FIELDS11 = {
15894
16157
  keywords: "Total organic keywords the page ranks for",
15895
16158
  top_10: "Keywords in positions 1-10"
15896
16159
  };
15897
- var relevantPagesCommand = defineCommand126({
16160
+ var relevantPagesCommand = defineCommand128({
15898
16161
  meta: {
15899
16162
  name: "relevant-pages",
15900
16163
  description: `Get the top pages of a competitor domain with traffic data.
@@ -15940,7 +16203,7 @@ Examples:
15940
16203
  });
15941
16204
 
15942
16205
  // src/commands/research/web.ts
15943
- import { defineCommand as defineCommand127 } from "citty";
16206
+ import { defineCommand as defineCommand129 } from "citty";
15944
16207
  registerSchema({
15945
16208
  command: "research.web",
15946
16209
  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).",
@@ -15991,7 +16254,7 @@ async function runDeepResearch(question) {
15991
16254
  }
15992
16255
  throw new Error("Deep research timed out");
15993
16256
  }
15994
- var webCommand = defineCommand127({
16257
+ var webCommand = defineCommand129({
15995
16258
  meta: {
15996
16259
  name: "web",
15997
16260
  description: `Search the web with AI to answer any open-ended marketing question. Uses live internet data via Google Search.
@@ -16051,7 +16314,7 @@ Examples:
16051
16314
  });
16052
16315
 
16053
16316
  // src/commands/research/index.ts
16054
- var researchCommand = defineCommand128({
16317
+ var researchCommand = defineCommand130({
16055
16318
  meta: {
16056
16319
  name: "research",
16057
16320
  description: `Competitive intelligence and AI-powered research commands.
@@ -16091,10 +16354,10 @@ Examples:
16091
16354
  });
16092
16355
 
16093
16356
  // src/commands/scheduled-actions/index.ts
16094
- import { defineCommand as defineCommand135 } from "citty";
16357
+ import { defineCommand as defineCommand137 } from "citty";
16095
16358
 
16096
16359
  // src/commands/scheduled-actions/create.ts
16097
- import { defineCommand as defineCommand129 } from "citty";
16360
+ import { defineCommand as defineCommand131 } from "citty";
16098
16361
 
16099
16362
  // src/commands/scheduled-actions/shared.ts
16100
16363
  var TEMP_SCHEDULED_ACTION_PREFIX = "temp_sched_";
@@ -16199,7 +16462,7 @@ registerSchema({
16199
16462
  prompt: { type: "string", description: "Additional prompt instructions for the spawned agent", required: false }
16200
16463
  }
16201
16464
  });
16202
- var createCommand2 = defineCommand129({
16465
+ var createCommand2 = defineCommand131({
16203
16466
  meta: {
16204
16467
  name: "create",
16205
16468
  description: 'Stage a scheduled action. Example: baker scheduled-actions create --name "Weekly report" --description "..." --cron "0 9 * * MON"'
@@ -16247,7 +16510,7 @@ var createCommand2 = defineCommand129({
16247
16510
  });
16248
16511
 
16249
16512
  // src/commands/scheduled-actions/delete.ts
16250
- import { defineCommand as defineCommand130 } from "citty";
16513
+ import { defineCommand as defineCommand132 } from "citty";
16251
16514
  registerSchema({
16252
16515
  command: "scheduled-actions.delete",
16253
16516
  description: "Stage deletion of a published scheduled action or cancellation of a temp_sched_* draft creation.",
@@ -16255,7 +16518,7 @@ registerSchema({
16255
16518
  id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
16256
16519
  }
16257
16520
  });
16258
- var deleteCommand2 = defineCommand130({
16521
+ var deleteCommand2 = defineCommand132({
16259
16522
  meta: {
16260
16523
  name: "delete",
16261
16524
  description: "Stage scheduled action deletion. Example: baker scheduled-actions delete <id-or-temp_sched_id>"
@@ -16284,7 +16547,7 @@ var deleteCommand2 = defineCommand130({
16284
16547
  });
16285
16548
 
16286
16549
  // src/commands/scheduled-actions/get.ts
16287
- import { defineCommand as defineCommand131 } from "citty";
16550
+ import { defineCommand as defineCommand133 } from "citty";
16288
16551
  registerSchema({
16289
16552
  command: "scheduled-actions.get",
16290
16553
  description: "Get a published scheduled action or a temp_sched_* draft-created scheduled action.",
@@ -16292,7 +16555,7 @@ registerSchema({
16292
16555
  id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
16293
16556
  }
16294
16557
  });
16295
- var getCommand3 = defineCommand131({
16558
+ var getCommand3 = defineCommand133({
16296
16559
  meta: {
16297
16560
  name: "get",
16298
16561
  description: "Get a scheduled action. Example: baker scheduled-actions get <id-or-temp_sched_id>"
@@ -16329,13 +16592,13 @@ var getCommand3 = defineCommand131({
16329
16592
  });
16330
16593
 
16331
16594
  // src/commands/scheduled-actions/list.ts
16332
- import { defineCommand as defineCommand132 } from "citty";
16595
+ import { defineCommand as defineCommand134 } from "citty";
16333
16596
  registerSchema({
16334
16597
  command: "scheduled-actions.list",
16335
16598
  description: "List published scheduled actions. Includes draft state when BAKER_CHAT_ID is set.",
16336
16599
  args: {}
16337
16600
  });
16338
- var listCommand3 = defineCommand132({
16601
+ var listCommand3 = defineCommand134({
16339
16602
  meta: {
16340
16603
  name: "list",
16341
16604
  description: "List scheduled actions. Includes staged draft ops when BAKER_CHAT_ID is set."
@@ -16356,7 +16619,7 @@ var listCommand3 = defineCommand132({
16356
16619
  });
16357
16620
 
16358
16621
  // src/commands/scheduled-actions/trigger.ts
16359
- import { defineCommand as defineCommand133 } from "citty";
16622
+ import { defineCommand as defineCommand135 } from "citty";
16360
16623
  registerSchema({
16361
16624
  command: "scheduled-actions.trigger",
16362
16625
  description: "Immediately trigger a published scheduled action. Does not require BAKER_CHAT_ID and rejects temp_sched_* IDs.",
@@ -16364,7 +16627,7 @@ registerSchema({
16364
16627
  id: { type: "string", description: "Published scheduled action ID", required: true }
16365
16628
  }
16366
16629
  });
16367
- var triggerCommand = defineCommand133({
16630
+ var triggerCommand = defineCommand135({
16368
16631
  meta: {
16369
16632
  name: "trigger",
16370
16633
  description: "Immediately trigger a published scheduled action. Example: baker scheduled-actions trigger <id>"
@@ -16401,7 +16664,7 @@ var triggerCommand = defineCommand133({
16401
16664
  });
16402
16665
 
16403
16666
  // src/commands/scheduled-actions/update.ts
16404
- import { defineCommand as defineCommand134 } from "citty";
16667
+ import { defineCommand as defineCommand136 } from "citty";
16405
16668
  registerSchema({
16406
16669
  command: "scheduled-actions.update",
16407
16670
  description: "Stage an update to a published scheduled action or temp_sched_* draft-created scheduled action.",
@@ -16426,7 +16689,7 @@ registerSchema({
16426
16689
  prompt: { type: "string", description: "Replacement additional spawned-agent instructions", required: false }
16427
16690
  }
16428
16691
  });
16429
- var updateCommand2 = defineCommand134({
16692
+ var updateCommand2 = defineCommand136({
16430
16693
  meta: {
16431
16694
  name: "update",
16432
16695
  description: "Stage a scheduled action update. Example: baker scheduled-actions update <id> --enabled false"
@@ -16496,7 +16759,7 @@ var updateCommand2 = defineCommand134({
16496
16759
  });
16497
16760
 
16498
16761
  // src/commands/scheduled-actions/index.ts
16499
- var scheduledActionsCommand = defineCommand135({
16762
+ var scheduledActionsCommand = defineCommand137({
16500
16763
  meta: {
16501
16764
  name: "scheduled-actions",
16502
16765
  description: `Manage Scheduled Actions. Subcommands: list, get, create, update, delete, trigger.
@@ -16522,8 +16785,8 @@ Examples:
16522
16785
  });
16523
16786
 
16524
16787
  // src/commands/schema.ts
16525
- import { defineCommand as defineCommand136 } from "citty";
16526
- var schemaCommand = defineCommand136({
16788
+ import { defineCommand as defineCommand138 } from "citty";
16789
+ var schemaCommand = defineCommand138({
16527
16790
  meta: {
16528
16791
  name: "schema",
16529
16792
  description: "Inspect command argument schemas (for AI agent introspection). Lists all commands if no argument given. Example: baker schema images.search"
@@ -16559,10 +16822,10 @@ var schemaCommand = defineCommand136({
16559
16822
  });
16560
16823
 
16561
16824
  // src/commands/testimonials/index.ts
16562
- import { defineCommand as defineCommand140 } from "citty";
16825
+ import { defineCommand as defineCommand142 } from "citty";
16563
16826
 
16564
16827
  // src/commands/testimonials/get.ts
16565
- import { defineCommand as defineCommand137 } from "citty";
16828
+ import { defineCommand as defineCommand139 } from "citty";
16566
16829
  registerSchema({
16567
16830
  command: "testimonials.get",
16568
16831
  description: "Get a single testimonial by ID",
@@ -16570,7 +16833,7 @@ registerSchema({
16570
16833
  id: { type: "string", description: "Testimonial ID", required: true }
16571
16834
  }
16572
16835
  });
16573
- var getCommand4 = defineCommand137({
16836
+ var getCommand4 = defineCommand139({
16574
16837
  meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
16575
16838
  args: {
16576
16839
  id: { type: "positional", description: "Testimonial ID", required: false },
@@ -16607,7 +16870,7 @@ var getCommand4 = defineCommand137({
16607
16870
  });
16608
16871
 
16609
16872
  // src/commands/testimonials/list.ts
16610
- import { defineCommand as defineCommand138 } from "citty";
16873
+ import { defineCommand as defineCommand140 } from "citty";
16611
16874
  registerSchema({
16612
16875
  command: "testimonials.list",
16613
16876
  description: "List testimonials with optional filters.",
@@ -16637,7 +16900,7 @@ registerSchema({
16637
16900
  limit: { type: "number", description: "Max results (default 50)", required: false, default: 50 }
16638
16901
  }
16639
16902
  });
16640
- var listCommand4 = defineCommand138({
16903
+ var listCommand4 = defineCommand140({
16641
16904
  meta: {
16642
16905
  name: "list",
16643
16906
  description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
@@ -16686,7 +16949,7 @@ var listCommand4 = defineCommand138({
16686
16949
  });
16687
16950
 
16688
16951
  // src/commands/testimonials/search.ts
16689
- import { defineCommand as defineCommand139 } from "citty";
16952
+ import { defineCommand as defineCommand141 } from "citty";
16690
16953
  registerSchema({
16691
16954
  command: "testimonials.search",
16692
16955
  description: "Search testimonials by text query. Uses hybrid BM25 + vector + reranking.",
@@ -16717,7 +16980,7 @@ registerSchema({
16717
16980
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
16718
16981
  }
16719
16982
  });
16720
- var searchCommand2 = defineCommand139({
16983
+ var searchCommand2 = defineCommand141({
16721
16984
  meta: {
16722
16985
  name: "search",
16723
16986
  description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
@@ -16791,7 +17054,7 @@ var searchCommand2 = defineCommand139({
16791
17054
  var tagsCommand3 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
16792
17055
 
16793
17056
  // src/commands/testimonials/index.ts
16794
- var testimonialsCommand = defineCommand140({
17057
+ var testimonialsCommand = defineCommand142({
16795
17058
  meta: {
16796
17059
  name: "testimonials",
16797
17060
  description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
@@ -16812,10 +17075,10 @@ Examples:
16812
17075
  });
16813
17076
 
16814
17077
  // src/commands/videos/index.ts
16815
- import { defineCommand as defineCommand145 } from "citty";
17078
+ import { defineCommand as defineCommand147 } from "citty";
16816
17079
 
16817
17080
  // src/commands/videos/delete.ts
16818
- import { defineCommand as defineCommand141 } from "citty";
17081
+ import { defineCommand as defineCommand143 } from "citty";
16819
17082
  registerSchema({
16820
17083
  command: "videos.delete",
16821
17084
  description: "Delete a video by ID",
@@ -16829,7 +17092,7 @@ registerSchema({
16829
17092
  }
16830
17093
  }
16831
17094
  });
16832
- var deleteCommand3 = defineCommand141({
17095
+ var deleteCommand3 = defineCommand143({
16833
17096
  meta: {
16834
17097
  name: "delete",
16835
17098
  description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
@@ -16870,7 +17133,7 @@ var deleteCommand3 = defineCommand141({
16870
17133
  });
16871
17134
 
16872
17135
  // src/commands/videos/get.ts
16873
- import { defineCommand as defineCommand142 } from "citty";
17136
+ import { defineCommand as defineCommand144 } from "citty";
16874
17137
  registerSchema({
16875
17138
  command: "videos.get",
16876
17139
  description: "Get a single video by ID",
@@ -16878,7 +17141,7 @@ registerSchema({
16878
17141
  id: { type: "string", description: "Video ID", required: true }
16879
17142
  }
16880
17143
  });
16881
- var getCommand5 = defineCommand142({
17144
+ var getCommand5 = defineCommand144({
16882
17145
  meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
16883
17146
  args: {
16884
17147
  id: { type: "positional", description: "Video ID", required: false },
@@ -16915,7 +17178,7 @@ var getCommand5 = defineCommand142({
16915
17178
  });
16916
17179
 
16917
17180
  // src/commands/videos/search.ts
16918
- import { defineCommand as defineCommand143 } from "citty";
17181
+ import { defineCommand as defineCommand145 } from "citty";
16919
17182
  registerSchema({
16920
17183
  command: "videos.search",
16921
17184
  description: "Search videos by text query. Only returns ready videos.",
@@ -16925,7 +17188,7 @@ registerSchema({
16925
17188
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
16926
17189
  }
16927
17190
  });
16928
- var searchCommand3 = defineCommand143({
17191
+ var searchCommand3 = defineCommand145({
16929
17192
  meta: {
16930
17193
  name: "search",
16931
17194
  description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
@@ -16975,10 +17238,10 @@ var searchCommand3 = defineCommand143({
16975
17238
  var tagsCommand4 = makeTagsCommand("videos", "video", "/api/videos/tags");
16976
17239
 
16977
17240
  // src/commands/videos/upload.ts
16978
- import { readFile as readFile11, stat as stat3 } from "fs/promises";
17241
+ import { readFile as readFile12, stat as stat3 } from "fs/promises";
16979
17242
  import { extname as extname3 } from "path";
16980
- import { defineCommand as defineCommand144 } from "citty";
16981
- var MIME_MAP2 = {
17243
+ import { defineCommand as defineCommand146 } from "citty";
17244
+ var MIME_MAP = {
16982
17245
  ".mp4": "video/mp4",
16983
17246
  ".mov": "video/quicktime",
16984
17247
  ".webm": "video/webm",
@@ -17003,15 +17266,15 @@ registerSchema({
17003
17266
  }
17004
17267
  }
17005
17268
  });
17006
- function detectContentType2(filePath) {
17269
+ function detectContentType(filePath) {
17007
17270
  const ext = extname3(filePath).toLowerCase();
17008
- const mime = MIME_MAP2[ext];
17271
+ const mime = MIME_MAP[ext];
17009
17272
  if (!mime) {
17010
17273
  throw new ApiError("VALIDATION_ERROR", `Cannot detect content type for extension "${ext}". Use --content-type.`);
17011
17274
  }
17012
17275
  return mime;
17013
17276
  }
17014
- var uploadCommand2 = defineCommand144({
17277
+ var uploadCommand2 = defineCommand146({
17015
17278
  meta: {
17016
17279
  name: "upload",
17017
17280
  description: "Upload a video file to Baker via Mux direct upload. Auto-detects content type. Example: baker videos upload ./demo.mp4"
@@ -17028,7 +17291,7 @@ var uploadCommand2 = defineCommand144({
17028
17291
  writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "File path is required" } });
17029
17292
  process.exit(1);
17030
17293
  }
17031
- const contentType = args["content-type"] || detectContentType2(filePath);
17294
+ const contentType = args["content-type"] || detectContentType(filePath);
17032
17295
  if (args["dry-run"]) {
17033
17296
  const fileStats = await stat3(filePath);
17034
17297
  writeJson({
@@ -17040,7 +17303,7 @@ var uploadCommand2 = defineCommand144({
17040
17303
  return;
17041
17304
  }
17042
17305
  const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
17043
- const fileBuffer = await readFile11(filePath);
17306
+ const fileBuffer = await readFile12(filePath);
17044
17307
  const uploadResponse = await fetch(uploadUrl, {
17045
17308
  method: "PUT",
17046
17309
  headers: { "Content-Type": contentType },
@@ -17065,7 +17328,7 @@ var uploadCommand2 = defineCommand144({
17065
17328
  });
17066
17329
 
17067
17330
  // src/commands/videos/index.ts
17068
- var videosCommand = defineCommand145({
17331
+ var videosCommand = defineCommand147({
17069
17332
  meta: {
17070
17333
  name: "videos",
17071
17334
  description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete, tags.
@@ -17088,10 +17351,10 @@ Examples:
17088
17351
  });
17089
17352
 
17090
17353
  // src/commands/winning-ads/index.ts
17091
- import { defineCommand as defineCommand148 } from "citty";
17354
+ import { defineCommand as defineCommand150 } from "citty";
17092
17355
 
17093
17356
  // src/commands/winning-ads/advertisers.ts
17094
- import { defineCommand as defineCommand146 } from "citty";
17357
+ import { defineCommand as defineCommand148 } from "citty";
17095
17358
  registerSchema({
17096
17359
  command: "winning-ads.advertisers",
17097
17360
  description: "Resolve a brand name to advertiser_id(s) in the ad-dna corpus \u2014 to find your OWN advertiser (to --exclude-advertiser) or a competitor (to --advertiser-id).",
@@ -17104,7 +17367,7 @@ registerSchema({
17104
17367
  function identity(record) {
17105
17368
  return record;
17106
17369
  }
17107
- var advertisersCommand2 = defineCommand146({
17370
+ var advertisersCommand2 = defineCommand148({
17108
17371
  meta: {
17109
17372
  name: "advertisers",
17110
17373
  description: 'Resolve a brand name to advertiser_id(s). Use it to find your own advertiser for --exclude-advertiser, or a competitor for --advertiser-id. Example: baker winning-ads advertisers "Deel" --output md'
@@ -17155,7 +17418,7 @@ var advertisersCommand2 = defineCommand146({
17155
17418
  });
17156
17419
 
17157
17420
  // src/commands/winning-ads/search.ts
17158
- import { defineCommand as defineCommand147 } from "citty";
17421
+ import { defineCommand as defineCommand149 } from "citty";
17159
17422
  registerSchema({
17160
17423
  command: "winning-ads.search",
17161
17424
  description: "Search the ad-dna corpus of scored winning ads. Returns a lean shortlist (advertiser, summary, scores, media_url) to pick a reference to reproduce.",
@@ -17263,7 +17526,7 @@ function buildSearchBody(args) {
17263
17526
  }
17264
17527
  return body;
17265
17528
  }
17266
- var searchCommand4 = defineCommand147({
17529
+ var searchCommand4 = defineCommand149({
17267
17530
  meta: {
17268
17531
  name: "search",
17269
17532
  description: "Search winning reference ads. Example: baker winning-ads search 'B2B SaaS before/after AI automation' --platform meta --format static --winner-category winner --exclude-advertiser adv_123 --output md"
@@ -17375,7 +17638,7 @@ var searchCommand4 = defineCommand147({
17375
17638
  });
17376
17639
 
17377
17640
  // src/commands/winning-ads/index.ts
17378
- var winningAdsCommand = defineCommand148({
17641
+ var winningAdsCommand = defineCommand150({
17379
17642
  meta: {
17380
17643
  name: "winning-ads",
17381
17644
  description: `Search the ad-dna corpus of scored "winning" ads for reference creatives to reproduce. Proxied through the Baker backend (BAKER_API_KEY) \u2014 no separate token needed.
@@ -17415,7 +17678,7 @@ function getCliVersion() {
17415
17678
  }
17416
17679
 
17417
17680
  // src/cli.ts
17418
- var main = defineCommand149({
17681
+ var main = defineCommand151({
17419
17682
  meta: {
17420
17683
  name: "baker",
17421
17684
  version: getCliVersion(),
@@ -17434,6 +17697,7 @@ Introspection: Run 'baker schema <command>' to inspect argument schemas.`
17434
17697
  ga4: ga4Command,
17435
17698
  gsc: gscCommand,
17436
17699
  research: researchCommand,
17700
+ creatives: creativesCommand3,
17437
17701
  images: imagesCommand,
17438
17702
  videos: videosCommand,
17439
17703
  testimonials: testimonialsCommand,