@koda-sl/baker-cli 0.116.0-dev.3bcc79f9c → 0.116.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
@@ -1080,7 +1080,7 @@ Notes:
1080
1080
  - All write commands take `--file <json>` payloads; explicit flags override file keys. `baker schema ads.linkedin.campaigns.create` for exact args.
1081
1081
  - Money flags (`--bid`, `--daily-budget`, `--total-budget`) require `--currency`.
1082
1082
  - Creative media comes from the Baker library (`--image-id`/`--video-id` from `baker images`/`baker videos` — uploaded to LinkedIn at publish) or as LinkedIn URNs (`--image-urn`/`--video-urn`). Formats: `image|video|text|spotlight|follower|document|carousel|conversation|tla|jobs`; complex formats take `--file` with the full content object; conversation ads take `--file` with the message flow (`{message: {subject, body, senderName?, buttons[]}}` — buttons `NESTED` (with `nestedMessage`) or `LANDING_PAGE` (with `landingPageUrl`), ≤25 messages, bodies ≤500 chars, labels ≤25). Limits: headline ≤70, text-ad 25/75, intro soft-truncates at 600 chars. TLA sponsors an existing post via `--post-urn`.
1083
- - Lead forms are file-first (`lead-forms create --file form.json`): name, headline (≤60), privacyPolicyUrl, questions[] (≤12; playbook: ≤4 for completion).
1083
+ - Lead forms are file-first (`lead-forms create --file form.json`). Required: name, headline (≤60), privacyPolicyUrl, questions[] (≤12; playbook: ≤4 for completion). Each question is a predefined profile field (`{ name, predefinedField: "EMAIL" }` — Contact/Work/Company/Education/Demographic library) or a custom question (`{ name, questionType: "SINGLE_LINE_TEXT" | "MULTIPLE_CHOICE", options?: [...] }`; ≤3 custom, MULTIPLE_CHOICE needs 2–30 options). Also supported: `locale {country,language}`, `formImageId`/`formImageUrn` (banner), `privacyPolicyText`, `consents[]` (≤5 disclosure checkboxes `{text, required}`), `hiddenFields[]` (≤20 `{name, value}` tracking fields), `legalDisclaimer`, `thankYou {message, cta, landingUrl | appointmentUrl}` (Calendly/Chili Piper booking link). The staged preview emits non-blocking best-practice warnings when a form has no qualifying question, no confirmation message/action, or no consent checkbox.
1084
1084
 
1085
1085
  #### `audit` — playbook diagnostic
1086
1086
 
@@ -2599,17 +2599,6 @@ baker canvas run my-canvas.json
2599
2599
  # single node — that old serial workaround is obsolete.
2600
2600
  baker canvas run my-canvas.json --parallel 8
2601
2601
 
2602
- # 2c. Runs persist across sandboxes/sessions by default: node results sync to a
2603
- # company-scoped remote cache (small JSON pointers; bytes stay in R2), so a FRESH
2604
- # sandbox re-runs an already-computed canvas at zero credits — assets rehydrate
2605
- # from R2, sha-verified. Every run also posts a durable history record (per-node
2606
- # outputs, credits, cached/fresh) that powers the dashboard's Creatives
2607
- # generations timeline. Opt out with --remote-cache off (env
2608
- # BAKER_CANVAS_REMOTE_CACHE=off) and --no-record. With --remote-cache off,
2609
- # assets are not uploaded, so a recorded run keeps its stats but has no
2610
- # browsable outputs — pass --no-record too if you want nothing persisted.
2611
- baker canvas run my-canvas.json --remote-cache off --no-record
2612
-
2613
2602
  # 3. Inspect a finished run (per-node timing, file list, optional video thumbs)
2614
2603
  baker canvas inspect <run_id>
2615
2604
 
@@ -3913,7 +3902,6 @@ baker canvas run ./reference-ad.video.canvas.json
3913
3902
  | Flag | Default | Effect |
3914
3903
  |---|---|---|
3915
3904
  | `--out <path>` | `<video-dir>/<name>.video.canvas.json` | Where to write the canvas (composition is copied alongside). |
3916
- | `--slug <slug>` | — | Creative slug (lowercase kebab): writes the canvas to `src/creatives/<slug>/<slug>.canvas.json` — the repo convention that attaches every run to the creative's dashboard generation history. `--out` wins over `--slug`. |
3917
3905
  | `--frames <mode>` | `generate` | `generate` emits ONE recast keyframe per scene (the original frame is dropped so the dropped `el_*` assets drive identity); `reuse` wires the real extracted first+last frames straight into the clips (faithful, cheaper, no recast). |
3918
3906
  | `--ambient` | off | Give silent **b-roll** scenes native diegetic ambient (Seedance `generate_audio`), mixed deep under the music bed. Talking scenes already carry voice; check levels don't muddy the mix before keeping it. |
3919
3907
  | `--max-scenes <n>` | all source scenes | **Cost lever that reduces fidelity** — caps the deconstruct, MERGING away every scene beyond the cap (fewer cuts, lost beats). Prints a warning when set; omit it to reproduce every scene. |
@@ -3955,7 +3943,6 @@ baker canvas run ./static-ad.canvas.json
3955
3943
  |---|---|---|
3956
3944
  | `--context <text>` | — | Known provenance (advertiser, category, market) to ground the describe. |
3957
3945
  | `--out <path>` | `<image-dir>/static-ad.canvas.json` (cwd when `<image>` is a URL) | Where to write the canvas (`prompt.json` is written alongside). |
3958
- | `--slug <slug>` | — | Creative slug (lowercase kebab): writes the canvas to `src/creatives/<slug>/<slug>.canvas.json` — the repo convention that attaches every run to the creative's dashboard generation history. `--out` wins over `--slug`. |
3959
3946
  | `--describe-model <id>` | registry default (`~google/gemini-pro-latest`) | Override the `image_describe` model. |
3960
3947
  | `--select-model <id>` | registry default (`~google/gemini-flash-latest`) | Override the element-selection `text_generate` model. |
3961
3948
  | `--layout-model <id>` | registry default (`~google/gemini-flash-latest`) | Override the global-layout `text_generate` model. |
@@ -4427,26 +4414,6 @@ import {
4427
4414
  } from "@koda-sl/baker-cli/engine";
4428
4415
  ```
4429
4416
 
4430
- ## Creatives
4431
-
4432
- Publish an approved canvas render as a first-class Baker creative. The image uploads to the Baker image library (tagged `creative`), a creative record is created/updated, and the command prints the creative reference JSON the dashboard renders in chat.
4433
-
4434
- ```bash
4435
- baker creatives publish ./canvas/<run_id>/<final>.png --title "Spring Offer 4x5" \
4436
- --slug spring-offer-4x5 --run-id r_01JXYZ... \
4437
- --source-reference-url "https://www.facebook.com/ads/library/?id=..."
4438
- ```
4439
-
4440
- | Flag | Effect |
4441
- |---|---|
4442
- | `--title <text>` | Required. Human title for the creative. |
4443
- | `--slug <slug>` | Creative slug (`src/creatives/<slug>/`) — attaches the image to that creative's row, marks it `published`. |
4444
- | `--run-id <r_…>` | Pins the approved generation from the creative's run history as the published one. |
4445
- | `--source-reference-url <url>` | Original reference ad URL, recorded on the creative. |
4446
- | `--context <text>` | Optional describe context for the uploaded image asset. |
4447
-
4448
- Without `--slug` the command behaves as before (one creative record per published image). With `--slug` it upserts the repo-convention row — the same one the dashboard's Creatives tab and the `src/creatives/{slug}/` folder describe — so publish, repo sync, and run history all land on a single record regardless of order.
4449
-
4450
4417
  ## Help & Discovery
4451
4418
 
4452
4419
  Every command supports `--help` for usage info:
@@ -4482,6 +4449,7 @@ This CLI is designed for AI agent consumption. Key patterns:
4482
4449
  - **0.104.0**: `baker actions tags ...` commands now type their `/api/actions/tags...` request/response payloads from the shared `@baker/api` contract package instead of hand-written local interfaces. No command, flag, or output-shape changes.
4483
4450
  - **0.105.0**: `baker images ...`, `baker videos ...`, and `baker testimonials ...` commands now type their `/api/{images,videos,testimonials}/...` request/response payloads from the shared `@baker/api` contract package instead of hand-written local interfaces. No command, flag, or output-shape changes.
4484
4451
  - **0.106.0**: `baker ads linkedin` gains staged write commands — `campaign-groups`/`campaigns`/`creatives` create|update|pause|resume|(archive|)duplicate, `audiences create|upload`, `conversions create|update`, `lead-forms create|update`, plus `draft [remove|clear]` for review/undo. Ops validate at stage time, apply on chat publish, and run simulated (`urn:li:simulated:*`) unless LinkedIn writes are enabled for the company.
4452
+ - **0.116.0**: `lead-forms create` models the full Campaign Manager form — `locale`, form banner image (`formImageId`/`formImageUrn`), predefined profile-field questions (validated enum) vs custom questions (`SINGLE_LINE_TEXT`/`MULTIPLE_CHOICE` with `options`, ≤3 custom), `privacyPolicyText`, disclosure `consents[]` (≤5), tracking `hiddenFields[]` (≤20), and `thankYou` confirmation CTA + landing/appointment link. Staged preview surfaces best-practice warnings (no qualifying question, no confirmation, no consent). No breaking flag changes.
4485
4453
 
4486
4454
  ## Publishing
4487
4455
 
@@ -24,9 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  mod
25
25
  ));
26
26
 
27
- // ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/index.js
27
+ // ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js
28
28
  var require_safe_stable_stringify = __commonJS({
29
- "../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/index.js"(exports, module) {
29
+ "../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js"(exports, module) {
30
30
  "use strict";
31
31
  var { hasOwnProperty } = Object.prototype;
32
32
  var stringify = configure2();
@@ -159,9 +159,9 @@ var require_safe_stable_stringify = __commonJS({
159
159
  }
160
160
  if (value) {
161
161
  return (value2) => {
162
- let message2 = `Object can not safely be stringified. Received type ${typeof value2}`;
163
- if (typeof value2 !== "function") message2 += ` (${value2.toString()})`;
164
- throw new Error(message2);
162
+ let message = `Object can not safely be stringified. Received type ${typeof value2}`;
163
+ if (typeof value2 !== "function") message += ` (${value2.toString()})`;
164
+ throw new Error(message);
165
165
  };
166
166
  }
167
167
  }
@@ -673,9 +673,6 @@ var HttpClient = class {
673
673
  async postJson(path16, body, signal) {
674
674
  return await this.requestJson("POST", path16, body, signal);
675
675
  }
676
- async putJson(path16, body, signal) {
677
- return await this.requestJson("PUT", path16, body, signal);
678
- }
679
676
  async getJson(path16, signal) {
680
677
  return await this.requestJson("GET", path16, void 0, signal);
681
678
  }
@@ -703,8 +700,8 @@ var HttpClient = class {
703
700
  try {
704
701
  const res = await this.fetchFn(url, {
705
702
  method,
706
- headers: method === "GET" ? { Authorization: `Bearer ${this.apiKey}` } : { "Content-Type": "application/json", Authorization: `Bearer ${this.apiKey}` },
707
- body: method === "GET" ? void 0 : JSON.stringify(body),
703
+ headers: method === "POST" ? { "Content-Type": "application/json", Authorization: `Bearer ${this.apiKey}` } : { Authorization: `Bearer ${this.apiKey}` },
704
+ body: method === "POST" ? JSON.stringify(body) : void 0,
708
705
  signal: controller.signal
709
706
  });
710
707
  if (res.ok) return { kind: "value", value: await res.json() };
@@ -741,33 +738,33 @@ async function parseErrorBody(res) {
741
738
  const errObj = body.error ?? {};
742
739
  return classifyHttpError(res.status, errObj, errObj.message ?? `HTTP ${res.status}`);
743
740
  }
744
- function classifyHttpError(status, errObj, message2) {
741
+ function classifyHttpError(status, errObj, message) {
745
742
  if (errObj.code === CONTENT_POLICY_CODE) {
746
- return { kind: "content_policy", status, provider: errObj.provider, message: message2 };
743
+ return { kind: "content_policy", status, provider: errObj.provider, message };
747
744
  }
748
745
  if (status === 401 || status === 403) {
749
- return { kind: "unauthorized", status, message: message2 };
746
+ return { kind: "unauthorized", status, message };
750
747
  }
751
748
  if (status === 400 || status === 422) {
752
- return { kind: "validation", status, message: message2, details: errObj.details };
749
+ return { kind: "validation", status, message, details: errObj.details };
753
750
  }
754
751
  if (status === 502 || status === 504) {
755
752
  if (errObj.code === "provider_timeout" || status === 504) {
756
- return { kind: "timeout", provider: errObj.provider, message: message2 };
753
+ return { kind: "timeout", provider: errObj.provider, message };
757
754
  }
758
755
  return {
759
756
  kind: "provider",
760
757
  status,
761
758
  provider: errObj.provider,
762
759
  code: errObj.code ?? "provider_error",
763
- message: message2,
760
+ message,
764
761
  retryable: errObj.retryable ?? true
765
762
  };
766
763
  }
767
764
  if (status >= 500 || status === 429) {
768
- return { kind: "server", status, message: message2 };
765
+ return { kind: "server", status, message };
769
766
  }
770
- return { kind: "validation", status, message: message2, details: errObj.details };
767
+ return { kind: "validation", status, message, details: errObj.details };
771
768
  }
772
769
  function backoffMs(attempt) {
773
770
  return 1e3 * 2 ** attempt;
@@ -838,27 +835,6 @@ var BackendClient = class {
838
835
  signal
839
836
  );
840
837
  }
841
- /** Remote cache lookup. A miss (404) — or an old backend without the route — returns null. */
842
- async getCacheEntry(cacheKey, signal) {
843
- try {
844
- const res = await this.http.getJson(`/api/canvas/cache/${encodeURIComponent(cacheKey)}`, signal);
845
- return res.entry;
846
- } catch (e) {
847
- if (e instanceof BackendHttpError && "status" in e.detail && e.detail.status === 404) return null;
848
- throw e;
849
- }
850
- }
851
- async putCacheEntry(entry, signal) {
852
- await this.http.putJson(
853
- `/api/canvas/cache/${encodeURIComponent(entry.cacheKey)}`,
854
- entry,
855
- signal
856
- );
857
- }
858
- /** Durable run-history record — POST /api/canvas/runs (idempotent server-side on runId). */
859
- async recordRun(payload, signal) {
860
- await this.http.postJson("/api/canvas/runs", payload, signal);
861
- }
862
838
  getArtifact(kind, name, version, signal) {
863
839
  const path16 = version ? `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}` : `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}`;
864
840
  return this.http.getJson(path16, signal);
@@ -882,17 +858,14 @@ function requireCredentialsFromEnv(env = process.env) {
882
858
  }
883
859
  return c;
884
860
  }
885
- function remoteCacheEnabledFromEnv(env = process.env) {
886
- return env.BAKER_CANVAS_REMOTE_CACHE !== "off";
887
- }
888
861
 
889
862
  // src/engine/engine/errors.ts
890
863
  function isBlocking(issue) {
891
864
  return issue.severity !== "warning";
892
865
  }
893
866
  var CanvasError = class extends Error {
894
- constructor(message2) {
895
- super(message2);
867
+ constructor(message) {
868
+ super(message);
896
869
  this.name = "CanvasError";
897
870
  }
898
871
  };
@@ -949,7 +922,7 @@ function describeCause(c) {
949
922
  }
950
923
  }
951
924
 
952
- // ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/esm/wrapper.js
925
+ // ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
953
926
  var import__ = __toESM(require_safe_stable_stringify(), 1);
954
927
  var configure = import__.default.configure;
955
928
  var wrapper_default = import__.default;
@@ -1610,158 +1583,6 @@ function encodeRandom() {
1610
1583
  return out;
1611
1584
  }
1612
1585
 
1613
- // src/engine/storage/remote-cache-store.ts
1614
- var CANVAS_ASSETS_URL_SEGMENT = "/canvas-assets/";
1615
- function isPersistedAssetRef(ref) {
1616
- return typeof ref.url === "string" && ref.url.includes(`${CANVAS_ASSETS_URL_SEGMENT}${ref.sha256}`);
1617
- }
1618
- function isAssetRefLike(value) {
1619
- return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.sha256 === "string" && typeof value.mime === "string";
1620
- }
1621
- function collectAssetRefLikes(value, out = []) {
1622
- if (Array.isArray(value)) {
1623
- for (const item of value) collectAssetRefLikes(item, out);
1624
- return out;
1625
- }
1626
- if (typeof value !== "object" || value === null) return out;
1627
- if (isAssetRefLike(value)) {
1628
- out.push(value);
1629
- }
1630
- for (const item of Object.values(value)) collectAssetRefLikes(item, out);
1631
- return out;
1632
- }
1633
- function entryFullyPersisted(entry) {
1634
- return collectAssetRefLikes(entry.outputs).every((ref) => isPersistedAssetRef(ref));
1635
- }
1636
- function stripLocalFields(entry) {
1637
- const clone = JSON.parse(JSON.stringify(entry));
1638
- for (const ref of collectAssetRefLikes(clone.outputs)) {
1639
- delete ref.path;
1640
- delete ref.bytes;
1641
- }
1642
- return clone;
1643
- }
1644
- var RemoteCacheStore = class {
1645
- client;
1646
- log;
1647
- constructor(client, log) {
1648
- this.client = client;
1649
- this.log = log ?? (() => void 0);
1650
- }
1651
- async get(cacheKey) {
1652
- return await this.client.getCacheEntry(cacheKey);
1653
- }
1654
- async put(entry) {
1655
- if (!entryFullyPersisted(entry)) {
1656
- this.log(`[cache ] ${entry.cacheKey.slice(0, 12)}\u2026 has local-only assets, kept local`);
1657
- return;
1658
- }
1659
- const stripped = stripLocalFields(entry);
1660
- if (stripped.refs.length > MAX_REMOTE_REFS) {
1661
- stripped.refs = stripped.refs.slice(0, MAX_REMOTE_REFS);
1662
- }
1663
- await this.client.putCacheEntry(stripped);
1664
- }
1665
- };
1666
- var MAX_REMOTE_REFS = 512;
1667
- var LayeredCacheStore = class {
1668
- rootDir;
1669
- local;
1670
- remote;
1671
- assets;
1672
- log;
1673
- constructor(opts) {
1674
- this.local = opts.local;
1675
- this.remote = opts.remote;
1676
- this.assets = opts.assets;
1677
- this.rootDir = opts.local.rootDir;
1678
- this.log = opts.log ?? (() => void 0);
1679
- }
1680
- async get(cacheKey) {
1681
- const localHit = await this.local.get(cacheKey);
1682
- if (localHit) return localHit;
1683
- let remoteEntry;
1684
- try {
1685
- remoteEntry = await this.remote.get(cacheKey);
1686
- } catch (e) {
1687
- this.log(`[cache ] remote lookup failed (${message(e)}) \u2014 treating as miss`);
1688
- return null;
1689
- }
1690
- if (!remoteEntry) return null;
1691
- let rehydrated;
1692
- try {
1693
- rehydrated = await this.rehydrate(remoteEntry);
1694
- } catch (e) {
1695
- this.log(`[cache ] ${cacheKey.slice(0, 12)}\u2026 rehydration failed (${message(e)}) \u2014 treating as miss`);
1696
- return null;
1697
- }
1698
- await this.local.put(rehydrated);
1699
- return rehydrated;
1700
- }
1701
- async put(entry) {
1702
- await this.local.put(entry);
1703
- try {
1704
- await this.remote.put(entry);
1705
- } catch (e) {
1706
- this.log(`[cache ] remote write failed (${message(e)}) \u2014 entry kept local`);
1707
- }
1708
- }
1709
- /**
1710
- * Download every referenced asset into the local content-addressed store
1711
- * (sha-verified) and stamp fresh local paths. Any ref that cannot be
1712
- * rehydrated fails the WHOLE entry — a partially-hydrated cache hit would
1713
- * crash materialization later with a far less actionable error.
1714
- */
1715
- async rehydrate(entry) {
1716
- const clone = JSON.parse(JSON.stringify(entry));
1717
- for (const ref of collectAssetRefLikes(clone.outputs)) {
1718
- if (!isPersistedAssetRef(ref)) {
1719
- throw new Error(`ref ${ref.sha256.slice(0, 12)}\u2026 has no persisted url`);
1720
- }
1721
- const ingested = await this.assets.ingestRemote({
1722
- kind: typeof ref.kind === "string" ? ref.kind : "json",
1723
- url: ref.url,
1724
- sha256: ref.sha256,
1725
- mime: ref.mime,
1726
- metadata: ref.metadata ?? void 0
1727
- });
1728
- ref.path = ingested.path;
1729
- }
1730
- return clone;
1731
- }
1732
- };
1733
- function message(e) {
1734
- return e instanceof Error ? e.message : String(e);
1735
- }
1736
-
1737
- // src/engine/nodes/remote/upload.ts
1738
- async function presignAndPut(args) {
1739
- const { putUrl, publicUrl } = await args.ctx.client.presignAssetUpload(args.sha256, args.mime, args.ctx.signal);
1740
- const putRes = await fetch(putUrl, {
1741
- method: "PUT",
1742
- body: new Uint8Array(args.bytes),
1743
- headers: { "Content-Type": args.mime },
1744
- signal: args.ctx.signal
1745
- });
1746
- if (!putRes.ok) {
1747
- throw new Error(`upload: presigned PUT failed ${putRes.status} ${putRes.statusText}`);
1748
- }
1749
- return publicUrl;
1750
- }
1751
- async function ensureUploaded(ref, ctx) {
1752
- if (ref.url) return ref;
1753
- const bytes = await ctx.assets.readBytes(ref.sha256, ref.mime);
1754
- const url = await presignAndPut({ bytes, sha256: ref.sha256, mime: ref.mime, ctx });
1755
- return { ...ref, url };
1756
- }
1757
- async function persistOutputAssetUrls(outputs, ctx) {
1758
- for (const ref of collectAssetRefLikes(outputs)) {
1759
- if (isPersistedAssetRef(ref)) continue;
1760
- const bytes = await ctx.assets.readBytes(ref.sha256, ref.mime);
1761
- ref.url = await presignAndPut({ bytes, sha256: ref.sha256, mime: ref.mime, ctx });
1762
- }
1763
- }
1764
-
1765
1586
  // src/engine/schema/canvas.ts
1766
1587
  import { z } from "zod";
1767
1588
  var REF_PREFIX = "$ref:";
@@ -2978,7 +2799,6 @@ var Engine = class {
2978
2799
  cache;
2979
2800
  outputsDir;
2980
2801
  log;
2981
- persistAssets;
2982
2802
  constructor(opts) {
2983
2803
  this.registry = opts.registry;
2984
2804
  this.client = opts.client;
@@ -2986,7 +2806,6 @@ var Engine = class {
2986
2806
  this.cache = opts.cache;
2987
2807
  this.outputsDir = opts.outputsDir;
2988
2808
  this.log = opts.log ?? (() => void 0);
2989
- this.persistAssets = opts.persistAssets ?? false;
2990
2809
  }
2991
2810
  validate(canvas) {
2992
2811
  return validateCanvas(canvas, this.registry);
@@ -3033,7 +2852,7 @@ var Engine = class {
3033
2852
  `[done ] ${stats.cached_nodes}/${stats.total_nodes} cached, ${stats.total_credits} credits, ${stats.duration_ms}ms`
3034
2853
  );
3035
2854
  this.log(`outputs in: ${writer.runDir}`);
3036
- return { run_id: runId, output, outputs_by_node: outputs, stats, outputs_dir: writer.runDir, node_runs: nodeRuns };
2855
+ return { run_id: runId, output, outputs_by_node: outputs, stats, outputs_dir: writer.runDir };
3037
2856
  }
3038
2857
  async runLayers(canvas, outputs, runId, writer, opts, counters, nodeRuns) {
3039
2858
  const layers = topologicalLayers(this.pruneToOutput(canvas, buildGraph(canvas)));
@@ -3130,14 +2949,6 @@ var Engine = class {
3130
2949
  const credits = def.cost ? def.cost({ params: parsedParams }).credits : 0;
3131
2950
  const outputsObj = result;
3132
2951
  outputs[node.id] = outputsObj;
3133
- if (this.persistAssets) {
3134
- try {
3135
- await persistOutputAssetUrls(outputsObj, ctx);
3136
- } catch (e) {
3137
- const msg = e instanceof Error ? e.message : String(e);
3138
- this.log(`[warn ] ${node.id}: asset persistence failed (${msg}) \u2014 outputs stay local-only`);
3139
- }
3140
- }
3141
2952
  if (policy === "read_write") {
3142
2953
  await this.cache.put({
3143
2954
  cacheKey: prepared.cacheKey,
@@ -3456,6 +3267,27 @@ var FontRef = BaseAssetRef.extend({
3456
3267
  });
3457
3268
  var AssetRef = z4.discriminatedUnion("kind", [ImageRef, VideoRef, AudioRef, JsonRef, TextRef, FontRef]);
3458
3269
 
3270
+ // src/engine/nodes/remote/upload.ts
3271
+ async function presignAndPut(args) {
3272
+ const { putUrl, publicUrl } = await args.ctx.client.presignAssetUpload(args.sha256, args.mime, args.ctx.signal);
3273
+ const putRes = await fetch(putUrl, {
3274
+ method: "PUT",
3275
+ body: new Uint8Array(args.bytes),
3276
+ headers: { "Content-Type": args.mime },
3277
+ signal: args.ctx.signal
3278
+ });
3279
+ if (!putRes.ok) {
3280
+ throw new Error(`upload: presigned PUT failed ${putRes.status} ${putRes.statusText}`);
3281
+ }
3282
+ return publicUrl;
3283
+ }
3284
+ async function ensureUploaded(ref, ctx) {
3285
+ if (ref.url) return ref;
3286
+ const bytes = await ctx.assets.readBytes(ref.sha256, ref.mime);
3287
+ const url = await presignAndPut({ bytes, sha256: ref.sha256, mime: ref.mime, ctx });
3288
+ return { ...ref, url };
3289
+ }
3290
+
3459
3291
  // src/engine/nodes/remote/delegate.ts
3460
3292
  function delegated(spec) {
3461
3293
  return {
@@ -3850,10 +3682,10 @@ function inferKindFromMime(mime) {
3850
3682
  if (mime.startsWith("font/")) return "font";
3851
3683
  return null;
3852
3684
  }
3853
- function localExecError(ctx, message2) {
3685
+ function localExecError(ctx, message) {
3854
3686
  return new NodeExecutionError(ctx.nodeId, ctx.nodeType, {
3855
3687
  kind: "local",
3856
- cause: new Error(`ingest: ${message2}`)
3688
+ cause: new Error(`ingest: ${message}`)
3857
3689
  });
3858
3690
  }
3859
3691
  async function execLocalFile(params, ctx) {
@@ -4520,7 +4352,7 @@ async function refToUrl(ref) {
4520
4352
  return `data:${ref.mime};base64,${bytes.toString("base64")}`;
4521
4353
  }
4522
4354
  var ASSET_KINDS = /* @__PURE__ */ new Set(["image", "video", "audio", "json", "text", "font"]);
4523
- function isAssetRefLike2(value) {
4355
+ function isAssetRefLike(value) {
4524
4356
  if (!value || typeof value !== "object") return false;
4525
4357
  const v = value;
4526
4358
  return typeof v.kind === "string" && ASSET_KINDS.has(v.kind) && typeof v.mime === "string" && typeof v.sha256 === "string" && (typeof v.url === "string" || typeof v.path === "string");
@@ -4892,8 +4724,8 @@ var NEVER_BLOCK = [
4892
4724
  /text[_-]?occluded/i
4893
4725
  ];
4894
4726
  var UNAVAILABLE = /unknown command|command not found|not found|Did you mean|Unknown argument|ENOENT/i;
4895
- function isAdvisory(code, message2) {
4896
- const hay = `${code} ${message2}`;
4727
+ function isAdvisory(code, message) {
4728
+ const hay = `${code} ${message}`;
4897
4729
  return NEVER_BLOCK.some((re) => re.test(hay));
4898
4730
  }
4899
4731
  function parseCheckJson(raw) {
@@ -4921,10 +4753,10 @@ function classifyLint(json) {
4921
4753
  for (const f of findings) {
4922
4754
  const rec = f;
4923
4755
  const code = String(rec?.code ?? "");
4924
- const message2 = String(rec?.message ?? "");
4756
+ const message = String(rec?.message ?? "");
4925
4757
  const severity = String(rec?.severity ?? "info");
4926
- const blocking = severity === "error" && !isAdvisory(code, message2);
4927
- out.push({ source: "lint", code, message: message2, severity: blocking ? "blocking" : "warning" });
4758
+ const blocking = severity === "error" && !isAdvisory(code, message);
4759
+ out.push({ source: "lint", code, message, severity: blocking ? "blocking" : "warning" });
4928
4760
  }
4929
4761
  return out;
4930
4762
  }
@@ -4936,9 +4768,9 @@ function classifyInspect(json) {
4936
4768
  for (const iss of issues) {
4937
4769
  const rec = iss;
4938
4770
  const code = String(rec?.code ?? rec?.type ?? "overflow");
4939
- const message2 = String(rec?.message ?? rec?.detail ?? JSON.stringify(iss));
4771
+ const message = String(rec?.message ?? rec?.detail ?? JSON.stringify(iss));
4940
4772
  const severity = rec?.severity ? String(rec.severity) : obj?.ok === false ? "error" : "warning";
4941
- out.push({ source: "inspect", code, message: message2, severity: severity === "error" ? "blocking" : "warning" });
4773
+ out.push({ source: "inspect", code, message, severity: severity === "error" ? "blocking" : "warning" });
4942
4774
  }
4943
4775
  return out;
4944
4776
  }
@@ -5383,7 +5215,7 @@ async function buildSubstitutionValues(compositionParams, meta, duration) {
5383
5215
  }
5384
5216
  function coerceImageParam(value) {
5385
5217
  if (typeof value === "string") return Promise.resolve(value);
5386
- if (isAssetRefLike2(value)) return refToUrl(value);
5218
+ if (isAssetRefLike(value)) return refToUrl(value);
5387
5219
  throw new Error("hyperframe_render: image param must be a URL string or AssetRef");
5388
5220
  }
5389
5221
  async function substituteCompositionFiles(tmp, values) {
@@ -5613,7 +5445,7 @@ async function buildSubstitutionValues2(compositionParams, meta) {
5613
5445
  }
5614
5446
  function coerceImageParam2(value) {
5615
5447
  if (typeof value === "string") return Promise.resolve(value);
5616
- if (isAssetRefLike2(value)) return refToUrl(value);
5448
+ if (isAssetRefLike(value)) return refToUrl(value);
5617
5449
  throw new Error("hyperframe_snapshot: image param must be a URL string or AssetRef");
5618
5450
  }
5619
5451
  async function substituteCompositionFiles2(tmp, values) {
@@ -6639,29 +6471,17 @@ function createEngineFromEnv(opts = {}) {
6639
6471
  const cacheDir = opts.cacheDir ?? path15.join(cwd, "canvas", ".cache");
6640
6472
  const outputsDir = opts.outputsDir ?? path15.join(cwd, "canvas");
6641
6473
  const creds = requireCredentialsFromEnv();
6642
- const client = new BackendClient({ baseUrl: creds.url, apiKey: creds.apiKey });
6643
- const assets = new LocalAssetStore(path15.join(cacheDir, "assets"));
6644
- const localCache = new LocalCacheStore(path15.join(cacheDir, "index"));
6645
- const remoteCacheEnabled = opts.remoteCache ?? remoteCacheEnabledFromEnv();
6646
- const cache = remoteCacheEnabled ? new LayeredCacheStore({
6647
- local: localCache,
6648
- remote: new RemoteCacheStore(client, opts.log),
6649
- assets,
6650
- log: opts.log
6651
- }) : localCache;
6652
6474
  return new Engine({
6653
6475
  registry: defaultRegistry(),
6654
- client,
6655
- assets,
6656
- cache,
6476
+ client: new BackendClient({ baseUrl: creds.url, apiKey: creds.apiKey }),
6477
+ assets: new LocalAssetStore(path15.join(cacheDir, "assets")),
6478
+ cache: new LocalCacheStore(path15.join(cacheDir, "index")),
6657
6479
  outputsDir,
6658
- log: opts.log,
6659
- persistAssets: remoteCacheEnabled
6480
+ log: opts.log
6660
6481
  });
6661
6482
  }
6662
6483
 
6663
6484
  export {
6664
- requireCredentialsFromEnv,
6665
6485
  LayerExecutionError,
6666
6486
  describeFailureReason,
6667
6487
  SEEDANCE_DURATIONS,
@@ -6669,10 +6489,6 @@ export {
6669
6489
  IMAGE_GENERATE_MODELS,
6670
6490
  MODEL_REGISTRY,
6671
6491
  resolveConcurrency,
6672
- ulid,
6673
- isPersistedAssetRef,
6674
- collectAssetRefLikes,
6675
- sha256Hex,
6676
6492
  BackendClient2 as BackendClient,
6677
6493
  Engine2 as Engine,
6678
6494
  LocalAssetStore2 as LocalAssetStore,
@@ -6683,4 +6499,4 @@ export {
6683
6499
  defaultRegistry,
6684
6500
  createEngineFromEnv
6685
6501
  };
6686
- //# sourceMappingURL=chunk-OCMOQOIJ.js.map
6502
+ //# sourceMappingURL=chunk-ZWMMCLJ4.js.map