@koda-sl/baker-cli 0.124.0-dev.8e4328629 → 0.124.0-dev.efe965ce2

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
@@ -2132,6 +2132,8 @@ Auto-ingests with prefetched bytes (no double-fetch). The screenshot bytes thems
2132
2132
 
2133
2133
  ScreenshotOne caches captures for 30 days, so re-shotting the same URL within that window returns the cached capture.
2134
2134
 
2135
+ If the target page is unreachable or returns a non-2xx status (e.g. a 404 path or a login wall), the command fails with an actionable `VALIDATION_ERROR` naming the page and the status it returned, plus a `fix` hint — verify the URL and retry, or continue without the screenshot. It no longer surfaces a generic "Internal server error".
2136
+
2135
2137
  **Flags:**
2136
2138
 
2137
2139
  | Flag | Description |
@@ -2502,6 +2504,46 @@ Rules:
2502
2504
 
2503
2505
  ---
2504
2506
 
2507
+ ### Marketing Tags (`baker tags`)
2508
+
2509
+ Read the client's marketing/analytics tags (Meta pixel, GA4, Google Ads, GTM, Clarity, Hotjar, PostHog, …) — production tags overlaid with the changes staged in this chat.
2510
+
2511
+ **The CLI is read-only.** Every tag change (create, edit, delete) goes through the `request_tag_input` tool (`baker_ui` MCP server): the agent proposes one or more changes — each becomes a tab in one blocking approval form — pre-filling the non-secret fields it knows; the user reviews, edits, fills secret fields, and approves or skips each tab. Approved changes stage on the chat and apply when the chat is published; discarding the chat drops them.
2512
+
2513
+ `BAKER_CHAT_ID` must be set.
2514
+
2515
+ ```bash
2516
+ baker tags list # effective view: production + staged, full readable config + secret status
2517
+ baker tags draft # review the staged changes awaiting publish
2518
+ ```
2519
+
2520
+ Notes:
2521
+
2522
+ - **`list` prints each tag's full readable config.** Every non-secret field is shown in full under the tag — the Clarity `projectId`, GA4 `measurementId`, the entire custom `code` snippet, etc. — so the agent reuses a stored value to pre-fill a follow-up change instead of asking the user for something already installed. Secrets are never among these fields. (`--json` emits the same data as the raw envelope.)
2523
+ - **Secrets never travel through this CLI or the chat.** Secret fields (`accessToken`, `apiSecret`, `authorizationToken`, `apiKey`, `conversionToken`, `oauthProviderId`) are entered only in the dashboard's secure tag form and flow straight into the staged draft; tool responses only ever name which secret fields are set/pending.
2524
+ - Staged creates get a server-generated `tag_temp_*` ref (returned in the tool result and printed by `list`). Use it (or a real tag id) as flow side-effect `tagIds` — the published tag keeps resolving under the temp ref.
2525
+ - Proposing a delete on a `tag_temp_*` ref drops the staged create instead.
2526
+ - Single-instance types (`code`, `posthog`, `datafast`) reject a second instance against the chat's effective view.
2527
+ - Conflicts at publish (tag deleted in the dashboard, config invalid) skip the op with a recorded reason — they never block the publish.
2528
+
2529
+ ### Forms / Flows (`baker flows`)
2530
+
2531
+ Read this workspace's Forms (flows) and the configuration status of each one's **confidential fields** — side-effect connection secrets, OAuth connections, and third-party field definitions (HubSpot, Calendly, HighLevel, SavvyCal). Reads the local `_data.json` files; no secret values are ever decrypted or printed.
2532
+
2533
+ **The CLI is read-only.** Author a Form's structure (nodes, conditions, side effects, field mappings) by editing its `_data.json` with the flow-builder skill. Configure the confidential fields it reports as `[missing]` / `[needs connection]` / `not selected` through the `request_flow_input` tool (`baker_ui` MCP server): the agent authors the surrounding structure, references it by `nodeId`/`sideEffectId`, and the user enters secrets, connects/picks OAuth accounts, and picks third-party forms in the dashboard. Those values are written (encrypted, where a secret) into the Form on the chat's branch and go live when the chat is published.
2534
+
2535
+ ```bash
2536
+ baker flows list # every Form + how many confidential fields still need setup
2537
+ baker flows show contact # one Form's confidential fields and their status
2538
+ baker flows show contact --full # include the full flow tree (secret values redacted)
2539
+ ```
2540
+
2541
+ Notes:
2542
+
2543
+ - **Secrets never travel through this CLI or the chat.** `show` reports only whether each field is configured (`[set]`/`[missing]`, `[connected]`/`[needs connection]`, selected/not selected) — never a value. Configured secrets appear only as `[configured]` ciphertext markers.
2544
+ - Confidential side-effect types: `httpWebhook`, `zapier`, `crmble` (typed credentials); `pipedrive`, `googleSpreadsheet`, `hubspotForm`, `goHighlevelContact` (OAuth connection + resource); `email`. Widget node types needing a resource pick: `calendly`, `savvycal`, `hubspot`, `hubspotMeeting`, `highlevel`, `highlevelForm`.
2545
+ - `request_flow_input` changes stage on the chat's branch and apply when the chat is published; discarding the chat drops them.
2546
+
2505
2547
  ### Action Items (`baker actions`)
2506
2548
 
2507
2549
  Manage action items for the current chat. Most write operations stage on the chat's draft and apply atomically when the chat is published. Claim/release run live so other chats can see who's working on what.
@@ -3945,6 +3987,7 @@ Validate, then execute the graph. Blocks until done. Logs one line per node. Ret
3945
3987
  | `--cache-policy <policy>` | `read_write` | `read_write`, `bypass`, or `read_only`. |
3946
3988
  | `--concurrency <n>` | `5` (or `BAKER_CANVAS_CONCURRENCY`) | Max nodes executing at once within a layer. |
3947
3989
  | `--remote-cache <on\|off>` | `on` (or `BAKER_CANVAS_REMOTE_CACHE`) | Company-scoped remote cache + durable asset persistence. |
3990
+ | `--max-credits <n>` | uncapped (or `BAKER_CANVAS_MAX_CREDITS`) | Credit ceiling: aborts before billing when the estimate exceeds it, and at layer boundaries once actual spend does. Completed nodes stay cached, so retrying with a higher cap loses nothing. |
3948
3991
  | `--no-record` | records | Skip posting the durable run-history record (and its live progress). |
3949
3992
 
3950
3993
  **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.
@@ -4069,7 +4112,25 @@ baker canvas run ./static-ad.canvas.json
4069
4112
 
4070
4113
  Scaffolding runs (and bills) the two vision passes; **running** the result generates a billed image. `baker canvas validate` does not check that the `[TODO]` paths exist — supply the real files before `run`.
4071
4114
 
4072
- **Resuming an interrupted run.** A long `baker canvas run` (multi-clip video) that is killed mid-render — session end, sandbox pause — leaves a marker under the outputs dir. The next `baker canvas run` of the same canvas automatically **resumes** that run: it reuses the run id so still-running billed jobs re-attach instead of being abandoned and re-billed, and completed nodes come from the cache. A clean completion (or a handled failure) clears the marker, so a normal re-run starts a fresh generation. Force a new run with `--fresh`, or pin a specific run with `--run-id <id>`. Independent same-layer nodes (e.g. video clips) fan out in parallel up to `--parallel`/`--concurrency` (default 8; env `BAKER_CANVAS_CONCURRENCY`).
4115
+ **Resuming an interrupted run.** A long `baker canvas run` (multi-clip video) that is killed mid-render — session end, sandbox pause — leaves a marker under the outputs dir. The next `baker canvas run` of the same canvas automatically **resumes** that run: it reuses the run id so still-running billed jobs re-attach instead of being abandoned and re-billed, and completed nodes come from the cache. Resume also works from a **different workspace or a fresh sandbox**: when no local marker survives, the run history is consulted and an interrupted (or stale) run of the exact same canvas is adopted automatically. Ctrl-C / SIGTERM aborts gracefully — no new nodes dispatch, a resumable snapshot is flushed, and the marker survives. A clean completion (or a handled failure) clears the marker, so a normal re-run starts a fresh generation. Force a new run with `--fresh`, or pin a specific run with `--run-id <id>` (also the escape hatch to adopt a run that is reported as concurrently live). Independent same-layer nodes (e.g. video clips) fan out in parallel up to `--parallel`/`--concurrency` (default 8; env `BAKER_CANVAS_CONCURRENCY`).
4116
+
4117
+ #### `baker canvas rerun <slug> [flags]`
4118
+
4119
+ Re-run a creative's latest recorded canvas **from run history** — no local files needed. Every `canvas run` of a creative uploads a portable snapshot (the canvas JSON plus the local files it ingests: prompt blueprints, composition dirs, reference images) to durable storage and records it on the run. `rerun` restores those files into `src/creatives/<slug>/` (sha-verified; files already matching are left untouched) and then executes the normal run flow — so an interrupted run **resumes** (in-flight jobs re-attach) and a completed one re-renders from the cache at zero credits.
4120
+
4121
+ | Flag | Default | Meaning |
4122
+ | --- | --- | --- |
4123
+ | `--force-remote` | off | Overwrite local files whose content differs from the snapshot (otherwise a conflict aborts with the differing paths). |
4124
+ | `--fresh` | off | Start a new run id instead of resuming an interrupted one. |
4125
+ | `--regenerate <ids>` | — | Same as `canvas run --regenerate`. |
4126
+ | `--concurrency <n>` | — | Same as `canvas run --concurrency`. |
4127
+
4128
+ ```bash
4129
+ baker canvas rerun spring-offer-4x5
4130
+ baker canvas rerun spring-offer-4x5 --force-remote --regenerate gen_4x5
4131
+ ```
4132
+
4133
+ Use it when a creative was built in another conversation (or its sandbox is gone) and you need to continue or re-render it here. Files the snapshot could not include (missing at run time, or oversized) are listed as warnings — supply those locally only if the run actually needs to regenerate the nodes that read them.
4073
4134
 
4074
4135
  #### `baker canvas inspect <run_id> [--thumbnails]`
4075
4136
 
@@ -812,10 +812,10 @@ var BackendClient = class {
812
812
  await sleep(pollInterval(attempt));
813
813
  }
814
814
  }
815
- presignAssetUpload(sha256, mime, signal) {
815
+ presignAssetUpload(sha256, mime, signal, purpose) {
816
816
  return this.http.postJson(
817
817
  "/api/canvas/assets/presign",
818
- { sha256, mime },
818
+ { sha256, mime, purpose },
819
819
  signal
820
820
  );
821
821
  }
@@ -840,6 +840,39 @@ var BackendClient = class {
840
840
  async recordRun(payload, signal) {
841
841
  await this.http.postJson("/api/canvas/runs", payload, signal);
842
842
  }
843
+ /**
844
+ * Resumable-run lookup — GET /api/canvas/runs/active. Returns the newest
845
+ * still-running/interrupted run for the creative (pinned to the exact canvas
846
+ * sha), so a fresh sandbox can adopt its run id and re-attach billed
847
+ * in-flight jobs. Null on no active run — or an older backend without the
848
+ * route (both 404).
849
+ */
850
+ async getActiveRun(creativeSlug, canvasSha, signal) {
851
+ const params = new URLSearchParams({ slug: creativeSlug });
852
+ if (canvasSha) params.set("sha", canvasSha);
853
+ try {
854
+ const res = await this.http.getJson(`/api/canvas/runs/active?${params}`, signal);
855
+ return res.run;
856
+ } catch (e) {
857
+ if (e instanceof BackendHttpError && "status" in e.detail && e.detail.status === 404) return null;
858
+ throw e;
859
+ }
860
+ }
861
+ /**
862
+ * Portable-rerun lookup — GET /api/canvas/runs/latest. The newest run for
863
+ * the creative that recorded a canvas snapshot manifest; null when none
864
+ * exists (or the backend predates the route — both 404).
865
+ */
866
+ async getLatestSnapshotRun(creativeSlug, signal) {
867
+ const params = new URLSearchParams({ slug: creativeSlug });
868
+ try {
869
+ const res = await this.http.getJson(`/api/canvas/runs/latest?${params}`, signal);
870
+ return res.run;
871
+ } catch (e) {
872
+ if (e instanceof BackendHttpError && "status" in e.detail && e.detail.status === 404) return null;
873
+ throw e;
874
+ }
875
+ }
843
876
  /**
844
877
  * Chat-scoped blueprint sync — POST /api/creatives/definition. Lets the
845
878
  * dashboard draw a scaffolded creative's workflow graph BEFORE the first run.
@@ -907,6 +940,14 @@ var NodeExecutionError = class extends CanvasError {
907
940
  this.cause = cause;
908
941
  }
909
942
  };
943
+ var RunAbortedError = class extends CanvasError {
944
+ reason;
945
+ constructor(reason, message2) {
946
+ super(message2);
947
+ this.name = "RunAbortedError";
948
+ this.reason = reason;
949
+ }
950
+ };
910
951
  var LayerExecutionError = class extends CanvasError {
911
952
  failures;
912
953
  constructor(failures) {
@@ -1732,18 +1773,43 @@ function message(e) {
1732
1773
  }
1733
1774
 
1734
1775
  // src/engine/nodes/remote/upload.ts
1776
+ var PUT_MAX_ATTEMPTS = 4;
1777
+ var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1735
1778
  async function presignAndPut(args) {
1736
- const { putUrl, publicUrl } = await args.ctx.client.presignAssetUpload(args.sha256, args.mime, args.ctx.signal);
1737
- const putRes = await fetch(putUrl, {
1738
- method: "PUT",
1739
- body: new Uint8Array(args.bytes),
1740
- headers: { "Content-Type": args.mime },
1741
- signal: args.ctx.signal
1742
- });
1743
- if (!putRes.ok) {
1744
- throw new Error(`upload: presigned PUT failed ${putRes.status} ${putRes.statusText}`);
1779
+ let lastFailure = null;
1780
+ for (let attempt = 0; attempt < PUT_MAX_ATTEMPTS; attempt++) {
1781
+ if (args.ctx.signal?.aborted) break;
1782
+ if (attempt > 0) await sleep2(500 * 2 ** (attempt - 1) * (1 + Math.random() * 0.25));
1783
+ const result = await attemptPresignedPut(args);
1784
+ if (result.ok) return result.url;
1785
+ lastFailure = result.failure;
1786
+ if (!result.retryable) break;
1787
+ }
1788
+ throw lastFailure ?? new Error("upload: aborted before the PUT could start");
1789
+ }
1790
+ async function attemptPresignedPut(args) {
1791
+ try {
1792
+ const { putUrl, publicUrl } = await args.ctx.client.presignAssetUpload(args.sha256, args.mime, args.ctx.signal);
1793
+ const putRes = await fetch(putUrl, {
1794
+ method: "PUT",
1795
+ body: new Uint8Array(args.bytes),
1796
+ headers: { "Content-Type": args.mime },
1797
+ signal: args.ctx.signal
1798
+ });
1799
+ if (putRes.ok) return { ok: true, url: publicUrl };
1800
+ return {
1801
+ ok: false,
1802
+ failure: new Error(`upload: presigned PUT failed ${putRes.status} ${putRes.statusText}`),
1803
+ // Only transient statuses warrant a replay — a 400/403 fails identically every attempt.
1804
+ retryable: putRes.status >= 500 || putRes.status === 429 || putRes.status === 408
1805
+ };
1806
+ } catch (e) {
1807
+ return {
1808
+ ok: false,
1809
+ failure: e instanceof Error ? e : new Error(String(e)),
1810
+ retryable: args.ctx.signal?.aborted !== true
1811
+ };
1745
1812
  }
1746
- return publicUrl;
1747
1813
  }
1748
1814
  async function ensureUploaded(ref, ctx) {
1749
1815
  if (ref.url) return ref;
@@ -3065,6 +3131,12 @@ var Engine = class {
3065
3131
  async run(input, opts = {}) {
3066
3132
  const validation = await this.validateDeep(input);
3067
3133
  if (!validation.ok) throw new ValidationError(validation.issues);
3134
+ if (opts.max_credits !== void 0 && validation.estimatedCredits > opts.max_credits) {
3135
+ throw new RunAbortedError(
3136
+ "cost_cap",
3137
+ `estimated ${validation.estimatedCredits} credits exceeds the ${opts.max_credits}-credit cap \u2014 nothing was billed; raise --max-credits or shrink the canvas`
3138
+ );
3139
+ }
3068
3140
  const canvas = validation.canvas;
3069
3141
  const runId = opts.run_id ?? `r_${ulid()}`;
3070
3142
  const writer = new OutputWriter({ outputsDir: this.outputsDir, runId });
@@ -3115,7 +3187,17 @@ var Engine = class {
3115
3187
  const layers = topologicalLayers(graph);
3116
3188
  const limit = resolveConcurrency(opts.concurrency);
3117
3189
  for (const layer of layers) {
3190
+ if (opts.signal?.aborted) throw new RunAbortedError("signal", "run aborted before layer dispatch");
3191
+ if (opts.max_credits !== void 0 && counters.totalCredits > opts.max_credits) {
3192
+ throw new RunAbortedError(
3193
+ "cost_cap",
3194
+ `spent ${counters.totalCredits} credits, over the ${opts.max_credits}-credit cap \u2014 completed nodes are cached; raise --max-credits to continue where this stopped`
3195
+ );
3196
+ }
3118
3197
  const settled = await mapWithConcurrency(layer, limit, (nodeId) => {
3198
+ if (opts.signal?.aborted) {
3199
+ return Promise.reject(new RunAbortedError("signal", "run aborted before node dispatch"));
3200
+ }
3119
3201
  this.emitProgress(opts, { kind: "node_start", node_id: nodeId });
3120
3202
  return this.executeOne(canvas, nodeId, outputs, runId, writer, opts, needsBytes.has(nodeId)).then((r) => {
3121
3203
  if (r.cached) counters.cachedNodes++;
@@ -3138,10 +3220,12 @@ var Engine = class {
3138
3220
  settled.forEach((result, i) => {
3139
3221
  const nodeId = layer[i];
3140
3222
  if (result.status === "rejected" && nodeId) {
3223
+ if (result.reason instanceof RunAbortedError) return;
3141
3224
  failures.push({ nodeId, reason: result.reason });
3142
3225
  this.emitProgress(opts, { kind: "node_failed", node_id: nodeId });
3143
3226
  }
3144
3227
  });
3228
+ if (opts.signal?.aborted) throw new RunAbortedError("signal", "run aborted by signal");
3145
3229
  if (failures.length === 1 && failures[0]) throw failures[0].reason;
3146
3230
  if (failures.length > 1) throw new LayerExecutionError(failures);
3147
3231
  }
@@ -6929,7 +7013,9 @@ function createEngineFromEnv(opts = {}) {
6929
7013
  }
6930
7014
 
6931
7015
  export {
7016
+ BackendClient,
6932
7017
  requireCredentialsFromEnv,
7018
+ RunAbortedError,
6933
7019
  LayerExecutionError,
6934
7020
  describeFailureReason,
6935
7021
  SEEDANCE_DURATIONS,
@@ -6945,7 +7031,7 @@ export {
6945
7031
  sha256Hex,
6946
7032
  elementMentionKeywords,
6947
7033
  toModelSafeImage,
6948
- BackendClient2 as BackendClient,
7034
+ BackendClient2,
6949
7035
  Engine2 as Engine,
6950
7036
  LocalAssetStore2 as LocalAssetStore,
6951
7037
  LocalCacheStore2 as LocalCacheStore,
@@ -6955,4 +7041,4 @@ export {
6955
7041
  defaultRegistry,
6956
7042
  createEngineFromEnv
6957
7043
  };
6958
- //# sourceMappingURL=chunk-Q3K5TXC6.js.map
7044
+ //# sourceMappingURL=chunk-TKL2CJ6G.js.map