@koda-sl/baker-cli 0.376.0 → 0.377.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5452,7 +5452,7 @@ Validate, then execute the graph. Blocks until done. Logs one line per node. Ret
5452
5452
 
5453
5453
  **Run history streams live.** The run posts its plan (every node + its dependency edges) the moment validation passes, then re-posts a progress snapshot as each node starts and settles — the dashboard's creative workflow graph shows nodes flipping pending → running → done in real time, with each node's outputs attached as they land. A failed run keeps its per-node trail (what completed, what died). All best-effort: an unreachable backend never changes the run's outcome.
5454
5454
 
5455
- **Failures don't abandon sibling work.** Nodes in a layer run under the concurrency cap and every one **settles** — a failed clip no longer kills its in-flight siblings, whose finished results still land in the content-addressed cache. One failure re-throws as-is; several are reported together (each failed node named). Re-running `baker canvas run` resumes from the cache and re-executes **only** the failed nodes and their descendants — never hand-orchestrate per-node renders. Long `video_generate` clips execute as **backend jobs** (the CLI polls; a CDN/proxy timeout can no longer kill a generation mid-flight).
5455
+ **Failures don't abandon sibling work.** Nodes in a layer run under the concurrency cap and every one **settles** — a failed clip no longer kills its in-flight siblings, whose finished results still land in the content-addressed cache. One failure re-throws as-is; several are reported together (each failed node named). Re-running `baker canvas run` resumes from the cache and re-executes **only** the failed nodes and their descendants — never hand-orchestrate per-node renders. **One render per canvas at a time:** a `run` of a canvas that another live process in the same workspace is still rendering (a backgrounded render, or a subagent's) is refused with `CONFLICT` instead of sharing its run, `--run-id` included — wait for it and use its result. Long `video_generate` clips execute as **backend jobs** (the CLI polls; a CDN/proxy timeout can no longer kill a generation mid-flight).
5456
5456
 
5457
5457
  #### `baker canvas scaffold-video <video> [flags]`
5458
5458
 
package/dist/cli.js CHANGED
@@ -41621,6 +41621,7 @@ async function clearCreativeDirty(slug, base) {
41621
41621
  }
41622
41622
 
41623
41623
  // src/commands/canvas/run-resume.ts
41624
+ import { readFileSync as readFileSync14 } from "fs";
41624
41625
  import { mkdir as mkdir4, readFile as readFile9, rm as rm4, writeFile as writeFile6 } from "fs/promises";
41625
41626
  import path13 from "path";
41626
41627
  function markerKey(canvasPath) {
@@ -41642,10 +41643,13 @@ function classifyRemoteRun(run, now) {
41642
41643
  return age > REMOTE_ADOPT_STALE_MS ? "adopt" : "concurrent";
41643
41644
  }
41644
41645
  async function resolveRunId(opts) {
41646
+ const existing = await readMarker(opts.outputsDir, opts.canvasPath);
41647
+ if (existing?.pid !== void 0 && existing.pid !== process.pid && (opts.isRendering ?? isRenderProcess)(existing.pid)) {
41648
+ return { runId: existing.runId, resumed: false, rendering: { runId: existing.runId, pid: existing.pid } };
41649
+ }
41645
41650
  if (opts.explicitRunId) return { runId: opts.explicitRunId, resumed: false };
41646
41651
  if (!opts.fresh) {
41647
- const existing = await readMarkerRunId(opts.outputsDir, opts.canvasPath);
41648
- if (existing) return { runId: existing, resumed: true, source: "local" };
41652
+ if (existing) return { runId: existing.runId, resumed: true, source: "local" };
41649
41653
  if (opts.remote) {
41650
41654
  try {
41651
41655
  const active = await opts.remote.getActiveRun(opts.remote.creativeSlug, opts.remote.canvasSha);
@@ -41665,22 +41669,31 @@ async function resolveRunId(opts) {
41665
41669
  }
41666
41670
  return { runId: `r_${ulid()}`, resumed: false };
41667
41671
  }
41668
- async function readMarkerRunId(outputsDir, canvasPath) {
41672
+ async function readMarker(outputsDir, canvasPath) {
41669
41673
  for (const key of [markerKey(canvasPath), legacyMarkerKey(canvasPath)]) {
41670
41674
  try {
41671
41675
  const raw = await readFile9(markerFile(outputsDir, key), "utf8");
41672
41676
  const parsed = JSON.parse(raw);
41673
- if (typeof parsed.runId === "string" && parsed.runId.length > 0) return parsed.runId;
41677
+ if (typeof parsed.runId === "string" && parsed.runId.length > 0) {
41678
+ return { runId: parsed.runId, ...Number.isInteger(parsed.pid) ? { pid: parsed.pid } : {} };
41679
+ }
41674
41680
  } catch {
41675
41681
  }
41676
41682
  }
41677
41683
  return null;
41678
41684
  }
41685
+ function isRenderProcess(pid) {
41686
+ try {
41687
+ return readFileSync14(`/proc/${pid}/cmdline`, "utf8").includes("canvas");
41688
+ } catch {
41689
+ return false;
41690
+ }
41691
+ }
41679
41692
  async function markRunInFlight(outputsDir, canvasPath, runId) {
41680
41693
  try {
41681
41694
  const file = markerFile(outputsDir, markerKey(canvasPath));
41682
41695
  await mkdir4(path13.dirname(file), { recursive: true });
41683
- await writeFile6(file, JSON.stringify({ runId, canvasPath: path13.resolve(canvasPath), startedAt: Date.now() }));
41696
+ await writeFile6(file, JSON.stringify({ runId, pid: process.pid, canvasPath: path13.resolve(canvasPath), startedAt: Date.now() }));
41684
41697
  } catch {
41685
41698
  }
41686
41699
  }
@@ -42636,13 +42649,31 @@ ${describeRewrites(healed.rewrites)}
42636
42649
  const creativeSlug = creativeSlugFromCanvasPath(filePath) ?? void 0;
42637
42650
  const client = opts.record === false ? null : buildBackendClient();
42638
42651
  const outputsDir = opts.outputsDir ? path16.resolve(opts.outputsDir) : canvasRoot;
42639
- const { runId, resumed, source, concurrentRunId } = await resolveRunId({
42652
+ const { runId, resumed, source, concurrentRunId, rendering } = await resolveRunId({
42640
42653
  explicitRunId: opts.runId,
42641
42654
  fresh: opts.fresh === true,
42642
42655
  outputsDir,
42643
42656
  canvasPath: filePath,
42644
42657
  remote: client && creativeSlug ? { creativeSlug, canvasSha, getActiveRun: (slug, sha) => client.getActiveRun(slug, sha) } : void 0
42645
42658
  });
42659
+ if (rendering) {
42660
+ process.stderr.write(
42661
+ `${JSON.stringify(
42662
+ {
42663
+ ok: false,
42664
+ error: {
42665
+ code: "CONFLICT",
42666
+ message: `This canvas is already rendering in another process (run ${rendering.runId}, pid ${rendering.pid}).`,
42667
+ fix: "Wait for that render to finish and use its result instead of starting a second one: two renders of one canvas share a run and overwrite each other. If you started it in the background, read its output when it completes."
42668
+ }
42669
+ },
42670
+ null,
42671
+ 2
42672
+ )}
42673
+ `
42674
+ );
42675
+ process.exit(2);
42676
+ }
42646
42677
  if (resumed) {
42647
42678
  process.stdout.write(
42648
42679
  `[resume] continuing interrupted run ${runId}${source === "remote" ? " (adopted from run history)" : ""} \u2014 in-flight jobs re-attach, cached nodes skip
@@ -49102,11 +49133,11 @@ var addSideEffectCommand = defineCommand125({
49102
49133
  });
49103
49134
 
49104
49135
  // src/commands/flows/map.ts
49105
- import { readFileSync as readFileSync15 } from "fs";
49136
+ import { readFileSync as readFileSync16 } from "fs";
49106
49137
  import { defineCommand as defineCommand126 } from "citty";
49107
49138
 
49108
49139
  // src/commands/flows/value-expression.ts
49109
- import { existsSync as existsSync6, readFileSync as readFileSync14 } from "fs";
49140
+ import { existsSync as existsSync6, readFileSync as readFileSync15 } from "fs";
49110
49141
  import { join as join4 } from "path";
49111
49142
  var PREFIXES2 = ["field", "tracking", "global", "code"];
49112
49143
  function splitParts(raw) {
@@ -49193,7 +49224,7 @@ function trackingFieldIds() {
49193
49224
  const path48 = join4(flowsDir(), "..", "tracking.ts");
49194
49225
  if (!existsSync6(path48)) return null;
49195
49226
  try {
49196
- const source = readFileSync14(path48, "utf-8");
49227
+ const source = readFileSync15(path48, "utf-8");
49197
49228
  const block2 = source.match(/TRACKING_FIELD_IDS\s*=\s*\[([\s\S]*?)\]\s*as const/)?.[1];
49198
49229
  if (!block2) return null;
49199
49230
  const ids = [...block2.matchAll(/"(tracking\.[a-z0-9_]+)"/g)].map((match) => match[1]);
@@ -49770,7 +49801,7 @@ function specsFromFile(parsed) {
49770
49801
  function readSpecFile(path48) {
49771
49802
  let raw;
49772
49803
  try {
49773
- raw = path48 === "-" ? readFileSync15(0, "utf-8") : readFileSync15(path48, "utf-8");
49804
+ raw = path48 === "-" ? readFileSync16(0, "utf-8") : readFileSync16(path48, "utf-8");
49774
49805
  } catch (error) {
49775
49806
  refuse(
49776
49807
  `Could not read ${path48 === "-" ? "the mapping from stdin" : `"${path48}"`}: ${error instanceof Error ? error.message : String(error)}`
@@ -50469,7 +50500,7 @@ Full guide: __tooling__/docs/tools/baker/flows.md`
50469
50500
  });
50470
50501
 
50471
50502
  // src/commands/funnel/index.ts
50472
- import { readFileSync as readFileSync16 } from "fs";
50503
+ import { readFileSync as readFileSync17 } from "fs";
50473
50504
  import { defineCommand as defineCommand130 } from "citty";
50474
50505
 
50475
50506
  // src/commands/funnel/formMoments.ts
@@ -51265,7 +51296,7 @@ function workspaceForms() {
51265
51296
  const forms = [];
51266
51297
  for (const slug of listFlowSlugs()) {
51267
51298
  try {
51268
- forms.push({ slug, tree: JSON.parse(readFileSync16(flowDataPath(slug), "utf-8")) });
51299
+ forms.push({ slug, tree: JSON.parse(readFileSync17(flowDataPath(slug), "utf-8")) });
51269
51300
  } catch {
51270
51301
  }
51271
51302
  }
@@ -51400,7 +51431,7 @@ Examples:
51400
51431
  import { defineCommand as defineCommand132 } from "citty";
51401
51432
 
51402
51433
  // src/commands/ga4/shared.ts
51403
- import { readFileSync as readFileSync17 } from "fs";
51434
+ import { readFileSync as readFileSync18 } from "fs";
51404
51435
  function failValidation3(message) {
51405
51436
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
51406
51437
  process.exit(1);
@@ -51416,7 +51447,7 @@ function requireTarget5(args5, entity) {
51416
51447
  function readJsonSource(args5, flag) {
51417
51448
  const inline = args5[flag];
51418
51449
  const file = args5.file;
51419
- const raw = typeof file === "string" && file.length > 0 ? readFileSync17(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
51450
+ const raw = typeof file === "string" && file.length > 0 ? readFileSync18(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
51420
51451
  if (raw === void 0) {
51421
51452
  failValidation3(`pass --${flag} with inline JSON or --file with a path to a JSON file`);
51422
51453
  }
@@ -51767,7 +51798,7 @@ Examples:
51767
51798
  });
51768
51799
 
51769
51800
  // src/commands/ga4/query.ts
51770
- import { appendFileSync as appendFileSync2, existsSync as existsSync7, readFileSync as readFileSync18, writeFileSync as writeFileSync4 } from "fs";
51801
+ import { appendFileSync as appendFileSync2, existsSync as existsSync7, readFileSync as readFileSync19, writeFileSync as writeFileSync4 } from "fs";
51771
51802
  import { resolve as resolve2 } from "path";
51772
51803
  import { defineCommand as defineCommand135 } from "citty";
51773
51804
 
@@ -51858,7 +51889,7 @@ function writeRowsToFile2(filePath, rows, append) {
51858
51889
  writeFileSync4(filePath, content, "utf-8");
51859
51890
  }
51860
51891
  } else if (append && existsSync7(filePath)) {
51861
- const existing = JSON.parse(readFileSync18(filePath, "utf-8"));
51892
+ const existing = JSON.parse(readFileSync19(filePath, "utf-8"));
51862
51893
  writeFileSync4(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
51863
51894
  } else {
51864
51895
  writeFileSync4(filePath, JSON.stringify(rows, null, 2), "utf-8");
@@ -52710,7 +52741,7 @@ Full guide: __tooling__/docs/tools/baker/ga4.md`
52710
52741
  import { defineCommand as defineCommand141 } from "citty";
52711
52742
 
52712
52743
  // src/commands/gsc/query.ts
52713
- import { appendFileSync as appendFileSync3, existsSync as existsSync8, readFileSync as readFileSync19, writeFileSync as writeFileSync5 } from "fs";
52744
+ import { appendFileSync as appendFileSync3, existsSync as existsSync8, readFileSync as readFileSync20, writeFileSync as writeFileSync5 } from "fs";
52714
52745
  import { resolve as resolve3 } from "path";
52715
52746
  import { defineCommand as defineCommand138 } from "citty";
52716
52747
 
@@ -52858,7 +52889,7 @@ function writeRowsToFile3(filePath, rows, append) {
52858
52889
  writeFileSync5(filePath, content, "utf-8");
52859
52890
  }
52860
52891
  } else if (append && existsSync8(filePath)) {
52861
- const existing = JSON.parse(readFileSync19(filePath, "utf-8"));
52892
+ const existing = JSON.parse(readFileSync20(filePath, "utf-8"));
52862
52893
  writeFileSync5(filePath, JSON.stringify([...existing, ...rows], null, 2), "utf-8");
52863
52894
  } else {
52864
52895
  writeFileSync5(filePath, JSON.stringify(rows, null, 2), "utf-8");
@@ -66583,7 +66614,7 @@ import { defineCommand as defineCommand221 } from "citty";
66583
66614
  import { defineCommand as defineCommand218 } from "citty";
66584
66615
 
66585
66616
  // src/commands/tag-manager/shared.ts
66586
- import { readFileSync as readFileSync20 } from "fs";
66617
+ import { readFileSync as readFileSync21 } from "fs";
66587
66618
  function failValidation5(message) {
66588
66619
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
66589
66620
  process.exit(1);
@@ -66599,7 +66630,7 @@ function requireTarget6(args5, entity) {
66599
66630
  function loadJsonArg2(args5, flag = "json") {
66600
66631
  const inline = args5[flag];
66601
66632
  const file = args5.file;
66602
- const raw = typeof file === "string" && file.length > 0 ? readFileSync20(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
66633
+ const raw = typeof file === "string" && file.length > 0 ? readFileSync21(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
66603
66634
  if (raw === void 0) {
66604
66635
  failValidation5(`pass --${flag} with inline JSON or --file with a path to a JSON file`);
66605
66636
  }
@@ -69747,7 +69778,7 @@ Full guide: __tooling__/docs/tools/baker/winning-ads.md`
69747
69778
  });
69748
69779
 
69749
69780
  // src/version.ts
69750
- import { readFileSync as readFileSync21 } from "fs";
69781
+ import { readFileSync as readFileSync22 } from "fs";
69751
69782
  function packageJsonUrl() {
69752
69783
  return new URL("../package.json", import.meta.url);
69753
69784
  }
@@ -69759,7 +69790,7 @@ function parsePackageVersion(raw) {
69759
69790
  throw new Error("Invalid CLI package.json: missing version");
69760
69791
  }
69761
69792
  function getCliVersion() {
69762
- return parsePackageVersion(readFileSync21(packageJsonUrl(), "utf8"));
69793
+ return parsePackageVersion(readFileSync22(packageJsonUrl(), "utf8"));
69763
69794
  }
69764
69795
 
69765
69796
  // src/cli.ts