@koda-sl/baker-cli 0.99.1-dev.5b1957cc → 0.100.0-dev.63a14d2d

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
@@ -516,14 +516,140 @@ ${description}`;
516
516
  return SCHEDULE_SIGNALS.some((re) => re.test(haystack));
517
517
  }
518
518
 
519
+ // src/commands/actions/skillCatalog.ts
520
+ import { existsSync, readdirSync, readFileSync } from "fs";
521
+ import { dirname, join } from "path";
522
+ var DESCRIPTION_MAX = 600;
523
+ var SKILLS_SUBPATH = join(".claude", "skills");
524
+ var EXCLUDED_SKILLS = /* @__PURE__ */ new Set(["actions"]);
525
+ function stripQuotes(value) {
526
+ const trimmed = value.trim();
527
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
528
+ return trimmed.slice(1, -1).replace(/\\"/g, '"').replace(/\\n/g, "\n");
529
+ }
530
+ if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
531
+ return trimmed.slice(1, -1).replace(/''/g, "'");
532
+ }
533
+ return trimmed;
534
+ }
535
+ function extractFrontmatterLines(md) {
536
+ if (!md.startsWith("---")) return null;
537
+ const end = md.indexOf("\n---", 3);
538
+ if (end === -1) return null;
539
+ return md.slice(md.indexOf("\n", 3) + 1, end).split("\n");
540
+ }
541
+ var BLOCK_SCALAR = /^([|>])[+-]?$/;
542
+ function collectBlockLines(lines, start) {
543
+ const collected = [];
544
+ for (let i = start; i < lines.length; i++) {
545
+ const line = lines[i] ?? "";
546
+ if (line.trim() === "") {
547
+ collected.push("");
548
+ } else if (/^\s/.test(line)) {
549
+ collected.push(line.trim());
550
+ } else {
551
+ break;
552
+ }
553
+ }
554
+ while (collected.length > 0 && collected.at(-1) === "") collected.pop();
555
+ return collected;
556
+ }
557
+ function foldLines(collected) {
558
+ const paragraphs = [];
559
+ let buffer = [];
560
+ for (const line of collected) {
561
+ if (line === "") {
562
+ if (buffer.length > 0) paragraphs.push(buffer.join(" "));
563
+ buffer = [];
564
+ } else {
565
+ buffer.push(line);
566
+ }
567
+ }
568
+ if (buffer.length > 0) paragraphs.push(buffer.join(" "));
569
+ return paragraphs.join("\n");
570
+ }
571
+ function readBlockScalar(lines, start, style) {
572
+ const collected = collectBlockLines(lines, start);
573
+ return style === "|" ? collected.join("\n") : foldLines(collected);
574
+ }
575
+ function readField(lines, field) {
576
+ for (let i = 0; i < lines.length; i++) {
577
+ const match = (lines[i] ?? "").match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
578
+ if (!match || match[1] !== field) continue;
579
+ const inline = (match[2] ?? "").trim();
580
+ const scalar = inline.match(BLOCK_SCALAR);
581
+ if (scalar) {
582
+ return readBlockScalar(lines, i + 1, scalar[1]);
583
+ }
584
+ return stripQuotes(inline);
585
+ }
586
+ return null;
587
+ }
588
+ function parseSkillFrontmatter(md) {
589
+ const lines = extractFrontmatterLines(md);
590
+ if (!lines) return null;
591
+ const name = readField(lines, "name");
592
+ const description = readField(lines, "description");
593
+ if (!name || !description) return null;
594
+ const trimmed = description.length > DESCRIPTION_MAX ? `${description.slice(0, DESCRIPTION_MAX)}\u2026` : description;
595
+ return { name, description: trimmed };
596
+ }
597
+ function findSkillsDir(startDir) {
598
+ let dir = startDir;
599
+ for (; ; ) {
600
+ const candidate = join(dir, SKILLS_SUBPATH);
601
+ if (existsSync(candidate)) return candidate;
602
+ const parent = dirname(dir);
603
+ if (parent === dir) return null;
604
+ dir = parent;
605
+ }
606
+ }
607
+ function readSkillCatalog(startDir) {
608
+ const skillsDir = findSkillsDir(startDir);
609
+ if (!skillsDir) return [];
610
+ const entries = [];
611
+ for (const dirent of readdirSync(skillsDir, { withFileTypes: true })) {
612
+ if (!dirent.isDirectory() || EXCLUDED_SKILLS.has(dirent.name)) continue;
613
+ const skillFile = join(skillsDir, dirent.name, "SKILL.md");
614
+ if (!existsSync(skillFile)) continue;
615
+ try {
616
+ const parsed = parseSkillFrontmatter(readFileSync(skillFile, "utf8"));
617
+ if (parsed) entries.push(parsed);
618
+ } catch {
619
+ }
620
+ }
621
+ return entries.sort((a, b) => a.name.localeCompare(b.name));
622
+ }
623
+
519
624
  // src/commands/actions/claim.ts
520
625
  registerSchema({
521
626
  command: "actions.claim",
522
- description: "Claim an action for the current chat (live \u2014 visible to other chats immediately). Returns action details with tags and description to help decide which skills to load.",
627
+ description: "Claim an action for the current chat (live \u2014 visible to other chats immediately). Returns action details plus a fast-model recommendation of which skills to load for the work (`recommendedSkills`).",
523
628
  args: {
524
629
  id: { type: "string", description: "Action ID", required: true }
525
630
  }
526
631
  });
632
+ async function recommendSkills(actionId) {
633
+ const skills = readSkillCatalog(process.cwd());
634
+ if (skills.length === 0) return [];
635
+ const response = await apiPost(
636
+ "/api/actions/recommend-skills",
637
+ { actionId, skills }
638
+ );
639
+ return response.data?.recommendations ?? [];
640
+ }
641
+ function buildHints(recommendations) {
642
+ if (recommendations.length === 0) {
643
+ return [
644
+ "Review the action name, description, and tags above \u2014 then load any skills that would help you complete this work."
645
+ ];
646
+ }
647
+ return [
648
+ "Recommended skills for this action (load the ones you'll use):",
649
+ ...recommendations.map((r) => ` /${r.name} \u2014 ${r.reason}`),
650
+ "Suggestions from the action's name/description/tags \u2014 load others if the work needs them."
651
+ ];
652
+ }
527
653
  var claimCommand = defineCommand({
528
654
  meta: {
529
655
  name: "claim",
@@ -545,10 +671,13 @@ var claimCommand = defineCommand({
545
671
  actionId: id,
546
672
  chatId
547
673
  });
548
- const hints = [
549
- "Review the action name, description, and tags above \u2014 then load any skills that would help you complete this work."
550
- ];
551
- writeJson({ ...response, hints });
674
+ let recommendedSkills = [];
675
+ try {
676
+ recommendedSkills = await recommendSkills(id);
677
+ } catch {
678
+ }
679
+ const data = response.data ? { ...response.data, recommendedSkills } : response.data;
680
+ writeJson({ ok: response.ok, data, hints: buildHints(recommendedSkills) });
552
681
  } catch (err) {
553
682
  failApi(err);
554
683
  }
@@ -1396,12 +1525,12 @@ import { defineCommand as defineCommand19 } from "citty";
1396
1525
 
1397
1526
  // src/commands/ads/cache.ts
1398
1527
  import { createHash } from "crypto";
1399
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
1528
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
1400
1529
  import { homedir } from "os";
1401
- import { join } from "path";
1402
- var CACHE_DIR = join(homedir(), ".baker", "cache", "ads");
1530
+ import { join as join2 } from "path";
1531
+ var CACHE_DIR = join2(homedir(), ".baker", "cache", "ads");
1403
1532
  function ensureDir(dir) {
1404
- if (!existsSync(dir)) {
1533
+ if (!existsSync2(dir)) {
1405
1534
  mkdirSync(dir, { recursive: true });
1406
1535
  }
1407
1536
  }
@@ -1409,17 +1538,17 @@ function hashKey(key) {
1409
1538
  return createHash("sha256").update(key).digest("hex").slice(0, 16);
1410
1539
  }
1411
1540
  function cachePath(category, key) {
1412
- const dir = join(CACHE_DIR, category);
1541
+ const dir = join2(CACHE_DIR, category);
1413
1542
  ensureDir(dir);
1414
- return join(dir, `${hashKey(key)}.json`);
1543
+ return join2(dir, `${hashKey(key)}.json`);
1415
1544
  }
1416
1545
  function cacheGet(category, key) {
1417
1546
  const path11 = cachePath(category, key);
1418
- if (!existsSync(path11)) {
1547
+ if (!existsSync2(path11)) {
1419
1548
  return null;
1420
1549
  }
1421
1550
  try {
1422
- const raw = readFileSync(path11, "utf-8");
1551
+ const raw = readFileSync2(path11, "utf-8");
1423
1552
  const entry = JSON.parse(raw);
1424
1553
  if (entry.expiresAt < Date.now()) {
1425
1554
  rmSync(path11, { force: true });
@@ -3166,7 +3295,7 @@ var library = defineCommand27({
3166
3295
  });
3167
3296
 
3168
3297
  // src/commands/ads/google/query.ts
3169
- import { appendFileSync, existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
3298
+ import { appendFileSync, existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
3170
3299
  import { resolve } from "path";
3171
3300
  import { defineCommand as defineCommand28 } from "citty";
3172
3301
 
@@ -3443,7 +3572,7 @@ function extractFields(rows) {
3443
3572
  function writeRowsToFile(filePath, rows, fields, append) {
3444
3573
  const ext = filePath.split(".").pop()?.toLowerCase();
3445
3574
  if (ext === "csv") {
3446
- if (!append || !existsSync2(filePath)) {
3575
+ if (!append || !existsSync3(filePath)) {
3447
3576
  writeFileSync2(filePath, `${toCsvRow(fields)}
3448
3577
  `, "utf-8");
3449
3578
  }
@@ -3452,15 +3581,15 @@ function writeRowsToFile(filePath, rows, fields, append) {
3452
3581
  `, "utf-8");
3453
3582
  } else if (ext === "jsonl") {
3454
3583
  const lines = rows.map((row) => JSON.stringify(row));
3455
- if (append && existsSync2(filePath)) {
3584
+ if (append && existsSync3(filePath)) {
3456
3585
  appendFileSync(filePath, `${lines.join("\n")}
3457
3586
  `, "utf-8");
3458
3587
  } else {
3459
3588
  writeFileSync2(filePath, `${lines.join("\n")}
3460
3589
  `, "utf-8");
3461
3590
  }
3462
- } else if (append && existsSync2(filePath)) {
3463
- const existing = JSON.parse(readFileSync2(filePath, "utf-8"));
3591
+ } else if (append && existsSync3(filePath)) {
3592
+ const existing = JSON.parse(readFileSync3(filePath, "utf-8"));
3464
3593
  writeFileSync2(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
3465
3594
  } else {
3466
3595
  writeFileSync2(filePath, JSON.stringify(rows, null, 2), "utf-8");
@@ -4596,7 +4725,7 @@ Examples \u2014 common AI questions:
4596
4725
  });
4597
4726
 
4598
4727
  // src/commands/ads/linkedin/audience-size.ts
4599
- import { readFileSync as readFileSync3 } from "fs";
4728
+ import { readFileSync as readFileSync4 } from "fs";
4600
4729
  import { defineCommand as defineCommand33 } from "citty";
4601
4730
  function loadTargeting(args) {
4602
4731
  const inline = args.targeting;
@@ -4610,7 +4739,7 @@ function loadTargeting(args) {
4610
4739
  const file = args["targeting-file"];
4611
4740
  if (file) {
4612
4741
  try {
4613
- return JSON.parse(readFileSync3(file, "utf-8"));
4742
+ return JSON.parse(readFileSync4(file, "utf-8"));
4614
4743
  } catch (e) {
4615
4744
  handleLinkedinError(
4616
4745
  new Error(`Failed to read --targeting-file: ${e instanceof Error ? e.message : "I/O error"}`)
@@ -4789,7 +4918,7 @@ Examples:
4789
4918
  });
4790
4919
 
4791
4920
  // src/commands/ads/linkedin/bid-pricing.ts
4792
- import { readFileSync as readFileSync4 } from "fs";
4921
+ import { readFileSync as readFileSync5 } from "fs";
4793
4922
  import { defineCommand as defineCommand35 } from "citty";
4794
4923
  function loadTargeting2(args) {
4795
4924
  const inline = args.targeting;
@@ -4803,7 +4932,7 @@ function loadTargeting2(args) {
4803
4932
  const file = args["targeting-file"];
4804
4933
  if (file) {
4805
4934
  try {
4806
- return JSON.parse(readFileSync4(file, "utf-8"));
4935
+ return JSON.parse(readFileSync5(file, "utf-8"));
4807
4936
  } catch (e) {
4808
4937
  handleLinkedinError(
4809
4938
  new Error(`Failed to read --targeting-file: ${e instanceof Error ? e.message : "I/O error"}`)
@@ -5367,7 +5496,7 @@ Subcommands:
5367
5496
  });
5368
5497
 
5369
5498
  // src/commands/ads/linkedin/forecast.ts
5370
- import { readFileSync as readFileSync5 } from "fs";
5499
+ import { readFileSync as readFileSync6 } from "fs";
5371
5500
  import { defineCommand as defineCommand43 } from "citty";
5372
5501
  function loadTargeting3(args) {
5373
5502
  const inline = args.targeting;
@@ -5381,7 +5510,7 @@ function loadTargeting3(args) {
5381
5510
  const file = args["targeting-file"];
5382
5511
  if (file) {
5383
5512
  try {
5384
- return JSON.parse(readFileSync5(file, "utf-8"));
5513
+ return JSON.parse(readFileSync6(file, "utf-8"));
5385
5514
  } catch (e) {
5386
5515
  handleLinkedinError(
5387
5516
  new Error(`Failed to read --targeting-file: ${e instanceof Error ? e.message : "I/O error"}`)
@@ -8849,7 +8978,7 @@ import { defineCommand as defineCommand83 } from "citty";
8849
8978
  import { execFile as execFile2 } from "child_process";
8850
8979
  import { mkdtemp, readdir as readdir2, readFile as readFile4, rm as rm2 } from "fs/promises";
8851
8980
  import { tmpdir } from "os";
8852
- import { join as join2 } from "path";
8981
+ import { join as join3 } from "path";
8853
8982
  import { promisify as promisify2 } from "util";
8854
8983
  var execFileAsync2 = promisify2(execFile2);
8855
8984
  var PYSCENEDETECT_THRESHOLD = 18;
@@ -8892,7 +9021,7 @@ function parsePySceneDetectCsvCuts(csv) {
8892
9021
  return [...new Set(cuts)].sort((a, b) => a - b);
8893
9022
  }
8894
9023
  async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs) {
8895
- const outDir = await mkdtemp(join2(tmpdir(), "baker-scenedetect-"));
9024
+ const outDir = await mkdtemp(join3(tmpdir(), "baker-scenedetect-"));
8896
9025
  try {
8897
9026
  await execFileAsync2(
8898
9027
  "scenedetect",
@@ -8913,7 +9042,7 @@ async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs)
8913
9042
  );
8914
9043
  const csvName = (await readdir2(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
8915
9044
  if (!csvName) return [];
8916
- return parsePySceneDetectCsvCuts(await readFile4(join2(outDir, csvName), "utf-8"));
9045
+ return parsePySceneDetectCsvCuts(await readFile4(join3(outDir, csvName), "utf-8"));
8917
9046
  } finally {
8918
9047
  await rm2(outDir, { recursive: true, force: true });
8919
9048
  }
@@ -11012,15 +11141,15 @@ function xfadeSpineArgs(clips) {
11012
11141
  let cur = "c0";
11013
11142
  let accLen = clipInputLen(clips[0]);
11014
11143
  for (let k = 0; k < n - 1; k++) {
11015
- const join4 = clips[k].out;
11144
+ const join5 = clips[k].out;
11016
11145
  const next = `c${k + 1}`;
11017
11146
  const out = k === n - 2 ? "v" : `j${k + 1}`;
11018
- if (join4) {
11019
- const offset = Math.max(0, accLen - join4.dur);
11147
+ if (join5) {
11148
+ const offset = Math.max(0, accLen - join5.dur);
11020
11149
  filt.push(
11021
- `[${cur}][${next}]xfade=transition=${join4.xfade}:duration=${join4.dur.toFixed(3)}:offset=${offset.toFixed(3)}[${out}]`
11150
+ `[${cur}][${next}]xfade=transition=${join5.xfade}:duration=${join5.dur.toFixed(3)}:offset=${offset.toFixed(3)}[${out}]`
11022
11151
  );
11023
- accLen = accLen - join4.dur + clipInputLen(clips[k + 1]);
11152
+ accLen = accLen - join5.dur + clipInputLen(clips[k + 1]);
11024
11153
  } else {
11025
11154
  filt.push(`[${cur}][${next}]concat=n=2:v=1[${out}]`);
11026
11155
  accLen += clipInputLen(clips[k + 1]);
@@ -11457,9 +11586,9 @@ function videoReport(input, elementsInput) {
11457
11586
  }
11458
11587
 
11459
11588
  // src/commands/canvas/composition-path.ts
11460
- import { existsSync as existsSync3 } from "fs";
11589
+ import { existsSync as existsSync4 } from "fs";
11461
11590
  import path6 from "path";
11462
- function resolveShippedCanvasDir(name, startDir, exists = existsSync3, maxDepth = 8) {
11591
+ function resolveShippedCanvasDir(name, startDir, exists = existsSync4, maxDepth = 8) {
11463
11592
  const rel = path6.join("canvas", name);
11464
11593
  let dir = startDir;
11465
11594
  for (let i = 0; i < maxDepth; i++) {
@@ -11969,7 +12098,6 @@ Subcommands:
11969
12098
  import { defineCommand as defineCommand88 } from "citty";
11970
12099
 
11971
12100
  // src/commands/creatives/publish.ts
11972
- import { extname as extname2 } from "path";
11973
12101
  import { defineCommand as defineCommand87 } from "citty";
11974
12102
 
11975
12103
  // src/commands/images/api.ts
@@ -12016,6 +12144,9 @@ async function uploadLocalImage(args, deps = defaultImageApiDeps) {
12016
12144
  function getImage(deps, imageId) {
12017
12145
  return deps.get("/api/images/get", { id: imageId });
12018
12146
  }
12147
+ function publishImageAsCreative(deps, args) {
12148
+ return deps.post("/api/creatives/publish", args);
12149
+ }
12019
12150
  async function waitForReadyImage(deps, imageId, opts = {}) {
12020
12151
  const timeoutMs = opts.timeoutMs ?? imageProcessingTimeoutMs;
12021
12152
  const pollIntervalMs = opts.pollIntervalMs ?? imageReadyPollIntervalMs;
@@ -12036,222 +12167,93 @@ async function waitForReadyImage(deps, imageId, opts = {}) {
12036
12167
  }
12037
12168
 
12038
12169
  // src/commands/creatives/publish.ts
12039
- var creativeTag = "creative";
12040
- var winningAdsTag = "winning-ads";
12041
- var creativeTags = [creativeTag, winningAdsTag];
12042
- var creativeImageContentTypes = ["image/png", "image/jpeg", "image/webp"];
12043
- var creativeVideoContentTypesByExtension = {
12044
- ".mp4": "video/mp4",
12045
- ".mov": "video/quicktime",
12046
- ".webm": "video/webm"
12047
- };
12048
- var videoProcessingTimeoutMs = 5 * 60 * 1e3;
12049
- var videoReadyPollIntervalMs = 5e3;
12050
- var defaultCreativePublishDeps = {
12051
- ...defaultImageApiDeps,
12052
- fetch
12053
- };
12170
+ var creativeContentTypes = ["image/png", "image/jpeg", "image/webp"];
12054
12171
  registerSchema({
12055
12172
  command: "creatives.publish",
12056
- description: "Publish a final creative image or video, apply creative and winning-ads tags, and return an asset reference.",
12173
+ description: "Publish a final static creative image to Baker Creatives and return a creative reference.",
12057
12174
  args: {
12058
- file: { type: "string", description: "Local PNG/JPG/WebP/MP4/MOV/WebM creative path", required: true },
12175
+ file: { type: "string", description: "Local PNG/JPG/WebP creative image path", required: true },
12059
12176
  title: { type: "string", description: "Human title for the creative output", required: true },
12060
- body: { type: "string", description: "Reference traceability body for the creative output", required: true }
12177
+ context: { type: "string", description: "Optional describe context for the image asset", required: false },
12178
+ sourceReferenceUrl: {
12179
+ type: "string",
12180
+ description: "Optional URL of the original reference ad",
12181
+ required: false
12182
+ }
12061
12183
  }
12062
12184
  });
12063
- function uniqueTags(tags) {
12064
- return [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))];
12065
- }
12066
- function requiredMetadata(args) {
12067
- const title = args.title.trim();
12068
- const body = args.body.trim();
12069
- if (!title) {
12070
- throw new ApiError("VALIDATION_ERROR", "--title is required");
12071
- }
12072
- if (!body) {
12073
- throw new ApiError("VALIDATION_ERROR", "--body is required");
12074
- }
12075
- return { title, body };
12076
- }
12077
12185
  function detectCreativeContentType(filePath) {
12078
- const ext = extname2(filePath).toLowerCase();
12079
- const videoContentType = creativeVideoContentTypesByExtension[ext];
12080
- if (videoContentType) {
12081
- return videoContentType;
12082
- }
12083
12186
  return detectImageContentType(filePath, {
12084
- allowedContentTypes: creativeImageContentTypes,
12085
- unsupportedMessage: "Unsupported creative extension. Use PNG, JPG, WebP, MP4, MOV, or WebM."
12187
+ allowedContentTypes: creativeContentTypes,
12188
+ unsupportedMessage: "Unsupported creative image extension. Use PNG, JPG, or WebP."
12086
12189
  });
12087
12190
  }
12088
- function isVideoContentType(contentType) {
12089
- return contentType.startsWith("video/");
12090
- }
12091
- function imageToCreativeReference(image, title) {
12092
- if (!image.imageUrl) {
12093
- throw new ApiError("IMAGE_PROCESSING_ERROR", "Published image is missing imageUrl");
12191
+ function parseOptionalUrl(value) {
12192
+ if (value === void 0 || value.trim() === "") {
12193
+ return void 0;
12094
12194
  }
12095
- const tags = uniqueTags([...image.tags ?? [], ...creativeTags]);
12096
- return {
12097
- type: "image",
12098
- slug: image._id,
12099
- title,
12100
- body: image.description,
12101
- tags,
12102
- imageUrl: image.imageUrl,
12103
- thumbnailUrl: image.thumbnailUrl ?? image.imageUrl,
12104
- storageKey: image.storageKey,
12105
- width: image.width,
12106
- height: image.height,
12107
- aspectRatio: image.aspectRatio,
12108
- source: image.source
12109
- };
12110
- }
12111
- function videoToCreativeReference(video, title) {
12112
- const tags = uniqueTags([...video.tags, ...creativeTags]);
12113
- return {
12114
- type: "video",
12115
- slug: video._id,
12116
- title,
12117
- body: video.description,
12118
- tags,
12119
- thumbnailUrl: video.thumbnailUrl,
12120
- muxPlaybackId: video.muxPlaybackId,
12121
- playbackUrl: video.muxPlaybackId ? `https://stream.mux.com/${video.muxPlaybackId}.m3u8` : void 0,
12122
- duration: video.duration,
12123
- width: video.width,
12124
- height: video.height,
12125
- aspectRatio: video.aspectRatio,
12126
- source: video.source
12127
- };
12128
- }
12129
- async function updateImageMetadata(deps, args) {
12130
- await deps.post("/api/images/update-description", {
12131
- imageId: args.imageId,
12132
- name: args.title,
12133
- description: args.body,
12134
- tags: args.tags
12135
- });
12136
- }
12137
- function createVideoUpload(deps) {
12138
- return deps.post("/api/videos/upload", {});
12139
- }
12140
- function getVideo(deps, videoId) {
12141
- return deps.get("/api/videos/get", { id: videoId });
12142
- }
12143
- async function updateVideoMetadata(deps, args) {
12144
- await deps.post("/api/videos/update-description", {
12145
- videoId: args.videoId,
12146
- name: args.title,
12147
- description: args.body,
12148
- tags: args.tags
12149
- });
12150
- }
12151
- async function waitForReadyVideo(deps, videoId, opts = {}) {
12152
- const timeoutMs = opts.timeoutMs ?? videoProcessingTimeoutMs;
12153
- const pollIntervalMs = opts.pollIntervalMs ?? videoReadyPollIntervalMs;
12154
- const deadline = Date.now() + timeoutMs;
12155
- let lastStatus = "unknown";
12156
- while (Date.now() <= deadline) {
12157
- const video = await getVideo(deps, videoId);
12158
- lastStatus = video.status ?? "unknown";
12159
- if (video.status === "ready") {
12160
- return video;
12161
- }
12162
- if (video.status === "error") {
12163
- throw new ApiError(
12164
- "INTERNAL_ERROR",
12165
- `Video processing failed for videoId ${videoId}: ${video.errorMessage ?? "unknown error"}`
12166
- );
12167
- }
12168
- await deps.sleep(pollIntervalMs);
12195
+ try {
12196
+ return new URL(value).toString();
12197
+ } catch {
12198
+ throw new ApiError("VALIDATION_ERROR", "--source-reference-url must be a valid URL");
12169
12199
  }
12170
- throw new ApiError("TIMEOUT", `Video was not ready before timeout; videoId: ${videoId}; last status: ${lastStatus}`);
12171
12200
  }
12172
- async function uploadLocalVideo(args, deps) {
12173
- const { uploadUrl, videoId } = await createVideoUpload(deps);
12174
- const fileBuffer = await deps.readFile(args.file);
12175
- const uploadResponse = await deps.fetch(uploadUrl, {
12176
- method: "PUT",
12177
- headers: { "Content-Type": args.contentType },
12178
- body: fileBuffer
12179
- });
12180
- if (!uploadResponse.ok) {
12181
- throw new ApiError(
12182
- "INTERNAL_ERROR",
12183
- `Mux upload failed: HTTP ${uploadResponse.status} ${uploadResponse.statusText}`
12184
- );
12201
+ async function publishCreative(args, deps = defaultImageApiDeps) {
12202
+ const title = args.title.trim();
12203
+ if (!title) {
12204
+ throw new ApiError("VALIDATION_ERROR", "--title is required");
12185
12205
  }
12186
- return { videoId };
12187
- }
12188
- async function publishCreativeImage(args, deps) {
12206
+ const sourceReferenceUrl = parseOptionalUrl(args.sourceReferenceUrl);
12207
+ const contentType = detectCreativeContentType(args.file);
12189
12208
  const upload = await uploadLocalImage(
12190
12209
  {
12191
12210
  file: args.file,
12192
- contentType: args.contentType,
12211
+ contentType,
12193
12212
  source: "ai_generated",
12194
- descriptionContext: args.body
12213
+ descriptionContext: args.context ?? `Static ad creative: ${title}`
12195
12214
  },
12196
12215
  deps
12197
12216
  );
12198
- const readyImage = await waitForReadyImage(deps, upload.imageId, { timeoutMs: imageProcessingTimeoutMs });
12199
- const tags = uniqueTags([...readyImage.tags ?? [], ...creativeTags]);
12200
- await updateImageMetadata(deps, { imageId: upload.imageId, title: args.title, body: args.body, tags });
12201
- const taggedImage = await getImage(deps, upload.imageId);
12202
- return {
12217
+ await waitForReadyImage(deps, upload.imageId, { timeoutMs: imageProcessingTimeoutMs });
12218
+ return publishImageAsCreative(deps, {
12203
12219
  imageId: upload.imageId,
12204
- reference: imageToCreativeReference({ ...readyImage, ...taggedImage }, args.title)
12205
- };
12206
- }
12207
- async function publishCreativeVideo(args, deps) {
12208
- const { videoId } = await uploadLocalVideo({ file: args.file, contentType: args.contentType }, deps);
12209
- const readyVideo = await waitForReadyVideo(deps, videoId);
12210
- const tags = uniqueTags([...readyVideo.tags, ...creativeTags]);
12211
- await updateVideoMetadata(deps, { videoId, title: args.title, body: args.body, tags });
12212
- const taggedVideo = await getVideo(deps, videoId);
12213
- return { videoId, reference: videoToCreativeReference(taggedVideo, args.title) };
12214
- }
12215
- function publishCreative(args, deps = defaultCreativePublishDeps) {
12216
- try {
12217
- const metadata = requiredMetadata(args);
12218
- const contentType = detectCreativeContentType(args.file);
12219
- if (isVideoContentType(contentType)) {
12220
- return publishCreativeVideo({ file: args.file, ...metadata, contentType }, deps);
12221
- }
12222
- return publishCreativeImage({ file: args.file, ...metadata, contentType }, deps);
12223
- } catch (error) {
12224
- return Promise.reject(error);
12225
- }
12220
+ title,
12221
+ sourceReferenceUrl
12222
+ });
12226
12223
  }
12227
12224
  var publishCommand = defineCommand87({
12228
12225
  meta: {
12229
12226
  name: "publish",
12230
- description: "Publish a final creative image or video, tag it as creative and winning-ads, and print the asset reference JSON."
12227
+ description: "Publish a final static creative image to Baker Creatives and print the creative reference JSON."
12231
12228
  },
12232
12229
  args: {
12233
- file: { type: "positional", description: "Local PNG/JPG/WebP/MP4/MOV/WebM creative path", required: false },
12230
+ file: { type: "positional", description: "Local PNG/JPG/WebP creative image path", required: false },
12234
12231
  title: { type: "string", description: "Human title for the creative output", required: false },
12235
- body: { type: "string", description: "Reference traceability body for the creative output", required: false }
12232
+ context: { type: "string", description: "Optional describe context for the image asset", required: false },
12233
+ sourceReferenceUrl: {
12234
+ type: "string",
12235
+ description: "Optional URL of the original reference ad",
12236
+ required: false
12237
+ }
12236
12238
  },
12237
12239
  run: async ({ args }) => {
12238
12240
  try {
12239
12241
  const file = args.file;
12240
12242
  const title = args.title;
12241
- const body = args.body;
12242
12243
  if (!file) {
12243
- writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "File path is required" } });
12244
+ writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "Image path is required" } });
12244
12245
  process.exit(1);
12245
12246
  }
12246
12247
  if (!title) {
12247
12248
  writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "--title is required" } });
12248
12249
  process.exit(1);
12249
12250
  }
12250
- if (!body) {
12251
- writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "--body is required" } });
12252
- process.exit(1);
12253
- }
12254
- const data = await publishCreative({ file, title, body });
12251
+ const data = await publishCreative({
12252
+ file,
12253
+ title,
12254
+ context: args.context,
12255
+ sourceReferenceUrl: args.sourceReferenceUrl
12256
+ });
12255
12257
  writeJson({ ok: true, data });
12256
12258
  } catch (err) {
12257
12259
  if (err instanceof ApiError) {
@@ -12268,13 +12270,12 @@ var publishCommand = defineCommand87({
12268
12270
  var creativesCommand3 = defineCommand88({
12269
12271
  meta: {
12270
12272
  name: "creatives",
12271
- description: `Publish ad creatives as first-class Baker outputs.
12273
+ description: `Publish static ad creatives as first-class Baker outputs.
12272
12274
 
12273
- Creative handoff:
12274
- baker creatives publish ./canvas/run/final.png --title "Winning Ads: Offer + Meta 4:5 + Angle" --body "Reference: Advertiser[ad_id]: https://example.com/ad.mp4
12275
- Angle adapted: Client-safe angle"
12275
+ Static creative handoff:
12276
+ baker creatives publish ./canvas/run/final.png --title "Spring Offer Static Ad"
12276
12277
 
12277
- Publishing routes images to Images and videos to the video library, applies creative + winning-ads tags, and returns an asset reference for chat previews.`
12278
+ Publishing uploads the image to the Company image library, applies the official creative tag, and returns an image reference for chat previews.`
12278
12279
  },
12279
12280
  subCommands: {
12280
12281
  publish: publishCommand
@@ -12456,7 +12457,7 @@ Examples:
12456
12457
  });
12457
12458
 
12458
12459
  // src/commands/ga4/query.ts
12459
- import { appendFileSync as appendFileSync2, existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
12460
+ import { appendFileSync as appendFileSync2, existsSync as existsSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
12460
12461
  import { resolve as resolve2 } from "path";
12461
12462
  import { defineCommand as defineCommand91 } from "citty";
12462
12463
 
@@ -12530,7 +12531,7 @@ function writeRowsToFile2(filePath, rows, append) {
12530
12531
  const fields = extractFields2(rows);
12531
12532
  const ext = filePath.split(".").pop()?.toLowerCase();
12532
12533
  if (ext === "csv") {
12533
- if (!append || !existsSync4(filePath)) {
12534
+ if (!append || !existsSync5(filePath)) {
12534
12535
  writeFileSync4(filePath, `${toCsvRow(fields)}
12535
12536
  `, "utf-8");
12536
12537
  }
@@ -12541,13 +12542,13 @@ function writeRowsToFile2(filePath, rows, append) {
12541
12542
  const lines = rows.map((row) => JSON.stringify(row));
12542
12543
  const content = `${lines.join("\n")}
12543
12544
  `;
12544
- if (append && existsSync4(filePath)) {
12545
+ if (append && existsSync5(filePath)) {
12545
12546
  appendFileSync2(filePath, content, "utf-8");
12546
12547
  } else {
12547
12548
  writeFileSync4(filePath, content, "utf-8");
12548
12549
  }
12549
- } else if (append && existsSync4(filePath)) {
12550
- const existing = JSON.parse(readFileSync6(filePath, "utf-8"));
12550
+ } else if (append && existsSync5(filePath)) {
12551
+ const existing = JSON.parse(readFileSync7(filePath, "utf-8"));
12551
12552
  writeFileSync4(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
12552
12553
  } else {
12553
12554
  writeFileSync4(filePath, JSON.stringify(rows, null, 2), "utf-8");
@@ -12687,7 +12688,7 @@ Examples:
12687
12688
  import { defineCommand as defineCommand96 } from "citty";
12688
12689
 
12689
12690
  // src/commands/gsc/query.ts
12690
- import { appendFileSync as appendFileSync3, existsSync as existsSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
12691
+ import { appendFileSync as appendFileSync3, existsSync as existsSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
12691
12692
  import { resolve as resolve3 } from "path";
12692
12693
  import { defineCommand as defineCommand93 } from "citty";
12693
12694
 
@@ -12816,7 +12817,7 @@ function writeRowsToFile3(filePath, rows, append) {
12816
12817
  const fields = extractFields3(rows);
12817
12818
  const ext = filePath.split(".").pop()?.toLowerCase();
12818
12819
  if (ext === "csv") {
12819
- if (!append || !existsSync5(filePath)) {
12820
+ if (!append || !existsSync6(filePath)) {
12820
12821
  writeFileSync5(filePath, `${toCsvRow(fields)}
12821
12822
  `, "utf-8");
12822
12823
  }
@@ -12826,13 +12827,13 @@ function writeRowsToFile3(filePath, rows, append) {
12826
12827
  } else if (ext === "jsonl") {
12827
12828
  const content = `${rows.map((row) => JSON.stringify(row)).join("\n")}
12828
12829
  `;
12829
- if (append && existsSync5(filePath)) {
12830
+ if (append && existsSync6(filePath)) {
12830
12831
  appendFileSync3(filePath, content, "utf-8");
12831
12832
  } else {
12832
12833
  writeFileSync5(filePath, content, "utf-8");
12833
12834
  }
12834
- } else if (append && existsSync5(filePath)) {
12835
- const existing = JSON.parse(readFileSync7(filePath, "utf-8"));
12835
+ } else if (append && existsSync6(filePath)) {
12836
+ const existing = JSON.parse(readFileSync8(filePath, "utf-8"));
12836
12837
  writeFileSync5(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
12837
12838
  } else {
12838
12839
  writeFileSync5(filePath, JSON.stringify(rows, null, 2), "utf-8");
@@ -13112,7 +13113,7 @@ function cropSprite(input, region) {
13112
13113
  // src/lib/image/io.ts
13113
13114
  import { randomBytes } from "crypto";
13114
13115
  import { glob as fsGlob, readFile as readFile10, rename, stat as stat2, writeFile as writeFile4 } from "fs/promises";
13115
- import { dirname, extname as extname3, join as join3, resolve as resolve4 } from "path";
13116
+ import { dirname as dirname2, extname as extname2, join as join4, resolve as resolve4 } from "path";
13116
13117
  var REMOTE_RE = /^https?:\/\//i;
13117
13118
  var GLOB_RE = /[*?[\]{}]/;
13118
13119
  function isRemoteUrl(value) {
@@ -13158,18 +13159,18 @@ async function isDirectory(path11) {
13158
13159
  }
13159
13160
  }
13160
13161
  async function resolveOutputPath(inputPath, outputArg, options) {
13161
- const base = options.newExtension ? inputPath.slice(0, -extname3(inputPath).length) + options.newExtension : inputPath;
13162
+ const base = options.newExtension ? inputPath.slice(0, -extname2(inputPath).length) + options.newExtension : inputPath;
13162
13163
  if (!outputArg) return base;
13163
13164
  if (options.multipleInputs || await isDirectory(outputArg)) {
13164
13165
  const filename = base.split("/").pop() ?? "out.png";
13165
- return join3(outputArg, filename);
13166
+ return join4(outputArg, filename);
13166
13167
  }
13167
13168
  return outputArg;
13168
13169
  }
13169
13170
  async function atomicWrite(targetPath, data) {
13170
13171
  const absolute = resolve4(targetPath);
13171
- const dir = dirname(absolute);
13172
- const tmp = join3(dir, `.baker-image-${randomBytes(8).toString("hex")}.tmp`);
13172
+ const dir = dirname2(absolute);
13173
+ const tmp = join4(dir, `.baker-image-${randomBytes(8).toString("hex")}.tmp`);
13173
13174
  await writeFile4(tmp, data);
13174
13175
  await rename(tmp, absolute);
13175
13176
  }
@@ -17471,7 +17472,7 @@ var tagsCommand4 = makeTagsCommand("videos", "video", "/api/videos/tags");
17471
17472
 
17472
17473
  // src/commands/videos/upload.ts
17473
17474
  import { readFile as readFile12, stat as stat3 } from "fs/promises";
17474
- import { extname as extname4 } from "path";
17475
+ import { extname as extname3 } from "path";
17475
17476
  import { defineCommand as defineCommand147 } from "citty";
17476
17477
  var MIME_MAP = {
17477
17478
  ".mp4": "video/mp4",
@@ -17499,7 +17500,7 @@ registerSchema({
17499
17500
  }
17500
17501
  });
17501
17502
  function detectContentType(filePath) {
17502
- const ext = extname4(filePath).toLowerCase();
17503
+ const ext = extname3(filePath).toLowerCase();
17503
17504
  const mime = MIME_MAP[ext];
17504
17505
  if (!mime) {
17505
17506
  throw new ApiError("VALIDATION_ERROR", `Cannot detect content type for extension "${ext}". Use --content-type.`);
@@ -17651,7 +17652,6 @@ var advertisersCommand2 = defineCommand149({
17651
17652
 
17652
17653
  // src/commands/winning-ads/search.ts
17653
17654
  import { defineCommand as defineCommand150 } from "citty";
17654
- import { z as z4 } from "zod";
17655
17655
  registerSchema({
17656
17656
  command: "winning-ads.search",
17657
17657
  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.",
@@ -17759,38 +17759,6 @@ function buildSearchBody(args) {
17759
17759
  }
17760
17760
  return body;
17761
17761
  }
17762
- function toOutputRecord(value) {
17763
- if (!value || typeof value !== "object" || Array.isArray(value)) {
17764
- return {};
17765
- }
17766
- return Object.fromEntries(Object.entries(value));
17767
- }
17768
- var winningAdsSearchResponseSchema = z4.object({
17769
- results: z4.array(z4.unknown()).optional(),
17770
- pool_size: z4.number().nullable().optional(),
17771
- match_confidence: z4.string().nullable().optional(),
17772
- ownAdvertiserExclusion: z4.object({
17773
- status: z4.enum(["applied", "unresolved", "skipped"]),
17774
- excludedIds: z4.array(z4.string())
17775
- }).nullable().optional()
17776
- });
17777
- function parseWinningAdsSearchResponse(data) {
17778
- const parsed = winningAdsSearchResponseSchema.safeParse(data);
17779
- if (!parsed.success) {
17780
- throw new ApiError("INTERNAL_ERROR", "Invalid winning ads search response");
17781
- }
17782
- return parsed.data;
17783
- }
17784
- function buildSearchOutputData(data, options) {
17785
- const rawResults = Array.isArray(data?.results) ? data.results : [];
17786
- const results = rawResults.map((r) => winningAdNormalizer(toOutputRecord(r), options.full));
17787
- return {
17788
- results,
17789
- pool_size: data?.pool_size ?? null,
17790
- match_confidence: data?.match_confidence ?? null,
17791
- ownAdvertiserExclusion: data?.ownAdvertiserExclusion ?? null
17792
- };
17793
- }
17794
17762
  var searchCommand4 = defineCommand150({
17795
17763
  meta: {
17796
17764
  name: "search",
@@ -17869,12 +17837,19 @@ var searchCommand4 = defineCommand150({
17869
17837
  });
17870
17838
  process.exit(1);
17871
17839
  }
17872
- const data = parseWinningAdsSearchResponse(await apiPost("/api/winning-ads/search", body));
17840
+ const data = await apiPost(
17841
+ "/api/winning-ads/search",
17842
+ body
17843
+ );
17873
17844
  const output = args.output || "json";
17874
17845
  const full = args.full;
17875
17846
  const rawResults = Array.isArray(data?.results) ? data.results : [];
17876
17847
  if (output === "json") {
17877
- writeJson({ ok: true, data: buildSearchOutputData(data, { full }) });
17848
+ const results = rawResults.map((r) => winningAdNormalizer(r, full));
17849
+ writeJson({
17850
+ ok: true,
17851
+ data: { results, pool_size: data?.pool_size ?? null, match_confidence: data?.match_confidence ?? null }
17852
+ });
17878
17853
  return;
17879
17854
  }
17880
17855
  writeOutput(
@@ -17920,7 +17895,7 @@ Examples:
17920
17895
  });
17921
17896
 
17922
17897
  // src/version.ts
17923
- import { readFileSync as readFileSync8 } from "fs";
17898
+ import { readFileSync as readFileSync9 } from "fs";
17924
17899
  function packageJsonUrl() {
17925
17900
  return new URL("../package.json", import.meta.url);
17926
17901
  }
@@ -17932,7 +17907,7 @@ function parsePackageVersion(raw) {
17932
17907
  throw new Error("Invalid CLI package.json: missing version");
17933
17908
  }
17934
17909
  function getCliVersion() {
17935
- return parsePackageVersion(readFileSync8(packageJsonUrl(), "utf8"));
17910
+ return parsePackageVersion(readFileSync9(packageJsonUrl(), "utf8"));
17936
17911
  }
17937
17912
 
17938
17913
  // src/cli.ts