@koda-sl/baker-cli 0.404.0 → 0.406.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 +1 -2
- package/dist/{chunk-KE6GXLDE.js → chunk-YOF4XTUW.js} +55 -47
- package/dist/chunk-YOF4XTUW.js.map +1 -0
- package/dist/cli.js +90 -43
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +24 -9
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-KE6GXLDE.js.map +0 -1
package/README.md
CHANGED
|
@@ -5640,7 +5640,6 @@ A completed render the review calls ready (`verdict` `ready` or `ready_with_note
|
|
|
5640
5640
|
| `--cache-policy <policy>` | `read_write` | `read_write`, `bypass`, or `read_only`. |
|
|
5641
5641
|
| `--concurrency <n>` | `5` (or `BAKER_CANVAS_CONCURRENCY`) | Max nodes executing at once within a layer. |
|
|
5642
5642
|
| `--remote-cache <on\|off>` | `on` (or `BAKER_CANVAS_REMOTE_CACHE`) | Operator only. Company-scoped remote cache + durable asset persistence. |
|
|
5643
|
-
| `--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. |
|
|
5644
5643
|
| `--no-record` | records | Skip posting the durable run-history record (and its live progress). |
|
|
5645
5644
|
| `--until frames` | none | Stop before the clips with the frames settled; the run is recorded as `awaiting_approval` (not failed, not interrupted) and resumes with `--run-id`. |
|
|
5646
5645
|
| `--cache-dir <path>` | `<workspace root>/canvas/.cache` | Operator only. Where content-addressed bytes + cache index live. |
|
|
@@ -5775,7 +5774,7 @@ It then scaffolds a canvas that draws **only what `prompt.json` holds**; no elem
|
|
|
5775
5774
|
|
|
5776
5775
|
**Placement formats.** The canvas delivers the platform's full placement set, each format at its exact size, collected under `final` with the format as its label. The set is derived from `--platform` (`meta` → 4:5 + 9:16, `google` → 1:1 + 4:5 + 16:9, `linkedin` → 1:1, `tiktok` → 9:16, `youtube` → 16:9); override it with `--placements <preset>` or `--placements none` for a single format. The report's `aspect_ratio` is the ratio the plates really render at (the model's nearest to the lead format), and `format_plan` says how each format is made.
|
|
5777
5776
|
|
|
5778
|
-
**After a run** the CLI reads the finished ad back on what makes a static win, not a checklist of elements: every delivered image's pixel shape against its format, and one vision pass that reads each word against the copy `prompt.json` holds and checks one dominant idea, clipping, artifacts, the brand with the logo covered and the concept's mechanisms, with every format shown a second time at phone-feed thumbnail size (240px wide, same call): a lead message unreadable at that size is blocking. A missing logo, CTA or copy is never a finding; a logo that is drawn and broken is. stdout carries `verdict` (`ready` | `ready_with_notes` | `needs_fixes` | `not_reviewed`), `judge` (the per-plate scores) and hints that end with what the run made; the findings are stored on the run as its `review`. `needs_fixes` means the ad is not done even though every node completed; its hints name the cheapest fix (overlapping or unreadable copy is fixed with a smaller `size`, a `highlight` or `sticker` treatment or shorter words, which re-draw the formats from the cached plates). **Render budget:** a static ad gets
|
|
5777
|
+
**After a run** the CLI reads the finished ad back on what makes a static win, not a checklist of elements: every delivered image's pixel shape against its format, and one vision pass that reads each word against the copy `prompt.json` holds and checks one dominant idea, clipping, artifacts, the brand with the logo covered and the concept's mechanisms, with every format shown a second time at phone-feed thumbnail size (240px wide, same call): a lead message unreadable at that size is blocking. A missing logo, CTA or copy is never a finding; a logo that is drawn and broken is. stdout carries `verdict` (`ready` | `ready_with_notes` | `needs_fixes` | `not_reviewed`), `judge` (the per-plate scores) and hints that end with what the run made; the findings are stored on the run as its `review`. `needs_fixes` means the ad is not done even though every node completed; its hints name the cheapest fix (overlapping or unreadable copy is fixed with a smaller `size`, a `highlight` or `sticker` treatment or shorter words, which re-draw the formats from the cached plates). **Render budget:** a static ad gets, per creative in a chat before the person reacts, 3 renders that paint pictures and then 4 re-draws from those pictures; a render that paints nothing (every billed node from the cache) counts as a re-draw. With the pictures spent a re-draw runs only from the cache (the engine's `billed_from_cache_only`), and one that would paint exits 2 with `RENDER_BUDGET` before billing; with both spent every render exits 2 with `RENDER_BUDGET` without rendering, and `baker creatives feedback record` or `baker creatives select` on that creative opens a new round. Resuming an interrupted run does not count again, and a run that died only on an outage (timeout, network, a provider that says retry) gives its render back.
|
|
5779
5778
|
|
|
5780
5779
|
The logo is never a slot to fill: use the company's committed logo in `src/brand/logos/` (per `BRAND.md`), never `baker images logo <domain>`, whose Brandfetch CDN often serves an outdated mark.
|
|
5781
5780
|
|
|
@@ -3894,8 +3894,12 @@ function estimateNodeCredits2(def, params) {
|
|
|
3894
3894
|
if (!parsed.success) return 0;
|
|
3895
3895
|
return priceNode(def, parsed.data)?.credits ?? 0;
|
|
3896
3896
|
}
|
|
3897
|
-
function estimateCanvasCredits(canvas, registry
|
|
3898
|
-
return canvas.nodes.
|
|
3897
|
+
function estimateCanvasCredits(canvas, registry) {
|
|
3898
|
+
return canvas.nodes.map((n) => ({
|
|
3899
|
+
node_id: n.id,
|
|
3900
|
+
node_type: n.type,
|
|
3901
|
+
credits: estimateNodeCredits2(registry.get(n.type), n.params)
|
|
3902
|
+
}));
|
|
3899
3903
|
}
|
|
3900
3904
|
function sumCredits(perNode) {
|
|
3901
3905
|
return perNode.reduce((total, n) => total + n.credits, 0);
|
|
@@ -4270,16 +4274,6 @@ function dfsCycle(u, color, stack, reverseAdj) {
|
|
|
4270
4274
|
stack.pop();
|
|
4271
4275
|
return null;
|
|
4272
4276
|
}
|
|
4273
|
-
function nodesBeforeCheckpoint(graph, types, stopKinds) {
|
|
4274
|
-
const all = new Set(graph.keys());
|
|
4275
|
-
if (!stopKinds?.size) return all;
|
|
4276
|
-
const running = /* @__PURE__ */ new Set();
|
|
4277
|
-
for (const layer of topologicalLayers(graph)) {
|
|
4278
|
-
if (layer.some((id) => stopKinds.has(types.get(id) ?? ""))) return running;
|
|
4279
|
-
for (const id of layer) running.add(id);
|
|
4280
|
-
}
|
|
4281
|
-
return running;
|
|
4282
|
-
}
|
|
4283
4277
|
|
|
4284
4278
|
// src/engine/engine/validator.ts
|
|
4285
4279
|
import path5 from "path";
|
|
@@ -6153,12 +6147,6 @@ var Engine = class {
|
|
|
6153
6147
|
async run(input, opts = {}) {
|
|
6154
6148
|
const validation = await this.validateDeep(input);
|
|
6155
6149
|
if (!validation.ok) throw new ValidationError(validation.issues);
|
|
6156
|
-
if (!opts.stop_before_kinds?.size && opts.max_credits !== void 0 && validation.estimatedCredits > opts.max_credits) {
|
|
6157
|
-
throw new RunAbortedError(
|
|
6158
|
-
"cost_cap",
|
|
6159
|
-
`estimated ${validation.estimatedCredits} credits exceeds the ${opts.max_credits}-credit cap \u2014 nothing was billed; raise --max-credits or shrink the canvas`
|
|
6160
|
-
);
|
|
6161
|
-
}
|
|
6162
6150
|
const canvas = validation.canvas;
|
|
6163
6151
|
const runId = opts.run_id ?? `r_${ulid()}`;
|
|
6164
6152
|
const writer = new OutputWriter({ outputsDir: this.outputsDir, runId });
|
|
@@ -6172,20 +6160,6 @@ var Engine = class {
|
|
|
6172
6160
|
const counters = { cachedNodes: 0, totalCredits: 0 };
|
|
6173
6161
|
const nodeRuns = [];
|
|
6174
6162
|
const graph = this.pruneToOutput(canvas, buildDependencyGraph(canvas));
|
|
6175
|
-
if (opts.max_credits !== void 0 && opts.stop_before_kinds?.size) {
|
|
6176
|
-
const willRun = nodesBeforeCheckpoint(
|
|
6177
|
-
graph,
|
|
6178
|
-
new Map(canvas.nodes.map((n) => [n.id, n.type])),
|
|
6179
|
-
opts.stop_before_kinds
|
|
6180
|
-
);
|
|
6181
|
-
const estimate = sumCredits(estimateCanvasCredits(canvas, this.registry, willRun));
|
|
6182
|
-
if (estimate > opts.max_credits) {
|
|
6183
|
-
throw new RunAbortedError(
|
|
6184
|
-
"cost_cap",
|
|
6185
|
-
`estimated ${estimate} credits exceeds the ${opts.max_credits}-credit cap \u2014 nothing was billed; raise --max-credits or shrink the canvas`
|
|
6186
|
-
);
|
|
6187
|
-
}
|
|
6188
|
-
}
|
|
6189
6163
|
const needsBytes = computeNeedsLocalBytes(canvas, graph, this.registry);
|
|
6190
6164
|
this.emitProgress(opts, {
|
|
6191
6165
|
kind: "plan",
|
|
@@ -6223,20 +6197,7 @@ var Engine = class {
|
|
|
6223
6197
|
const layers = topologicalLayers(graph);
|
|
6224
6198
|
const limit = resolveConcurrency(opts.concurrency);
|
|
6225
6199
|
for (const layer of layers) {
|
|
6226
|
-
|
|
6227
|
-
if (opts.max_credits !== void 0 && counters.totalCredits > opts.max_credits) {
|
|
6228
|
-
throw new RunAbortedError(
|
|
6229
|
-
"cost_cap",
|
|
6230
|
-
`spent ${counters.totalCredits} credits, over the ${opts.max_credits}-credit cap \u2014 completed nodes are cached; raise --max-credits to continue where this stopped`
|
|
6231
|
-
);
|
|
6232
|
-
}
|
|
6233
|
-
const held = heldByCheckpoint(canvas, layer, opts.stop_before_kinds);
|
|
6234
|
-
if (held.length > 0) {
|
|
6235
|
-
throw new RunAbortedError(
|
|
6236
|
-
"checkpoint",
|
|
6237
|
-
`stopped before ${held.join(", ")} \u2014 everything up to here is done and cached, so continuing this run id pays for none of it again`
|
|
6238
|
-
);
|
|
6239
|
-
}
|
|
6200
|
+
await this.assertLayerMayRun(canvas, layer, outputs, runId, opts);
|
|
6240
6201
|
const settled = await mapWithConcurrency(layer, limit, (nodeId) => {
|
|
6241
6202
|
if (opts.signal?.aborted) {
|
|
6242
6203
|
return Promise.reject(new RunAbortedError("signal", "run aborted before node dispatch"));
|
|
@@ -6363,6 +6324,53 @@ var Engine = class {
|
|
|
6363
6324
|
this.logOutputWarnings(node.id, outputsObj);
|
|
6364
6325
|
return { cached: false, credits, durationMs: elapsed };
|
|
6365
6326
|
}
|
|
6327
|
+
/**
|
|
6328
|
+
* Every reason a layer must not dispatch, checked before any of its nodes starts, so a stop
|
|
6329
|
+
* never leaves a layer half billed: a signal, the cost cap, a checkpoint, and a billed node
|
|
6330
|
+
* the cache cannot serve when the run is cache-only.
|
|
6331
|
+
*/
|
|
6332
|
+
async assertLayerMayRun(canvas, layer, outputs, runId, opts) {
|
|
6333
|
+
if (opts.signal?.aborted) throw new RunAbortedError("signal", "run aborted before layer dispatch");
|
|
6334
|
+
const held = heldByCheckpoint(canvas, layer, opts.stop_before_kinds);
|
|
6335
|
+
if (held.length > 0) {
|
|
6336
|
+
throw new RunAbortedError(
|
|
6337
|
+
"checkpoint",
|
|
6338
|
+
`stopped before ${held.join(", ")} \u2014 everything up to here is done and cached, so continuing this run id pays for none of it again`
|
|
6339
|
+
);
|
|
6340
|
+
}
|
|
6341
|
+
if (opts.billed_from_cache_only) {
|
|
6342
|
+
const fresh = await this.billedMisses(canvas, layer, outputs, runId, opts);
|
|
6343
|
+
if (fresh.length > 0) {
|
|
6344
|
+
throw new RunAbortedError(
|
|
6345
|
+
"cache_only",
|
|
6346
|
+
`${fresh.join(", ")} would be rendered fresh and billed \u2014 nothing in this layer ran`
|
|
6347
|
+
);
|
|
6348
|
+
}
|
|
6349
|
+
}
|
|
6350
|
+
}
|
|
6351
|
+
/**
|
|
6352
|
+
* The billed nodes of a layer that would miss the cache, found before any of them runs:
|
|
6353
|
+
* each is prepared exactly as `executeOne` would (same inputs, params and cache key).
|
|
6354
|
+
*
|
|
6355
|
+
* `executeOne` prepares each node again when it runs, so a cache-only layer hydrates its
|
|
6356
|
+
* text slots and hashes its `cacheKeyExtras` twice. Deliberate: a re-draw's layers are
|
|
6357
|
+
* small, and threading this preparation into `executeOne` would couple the check to the
|
|
6358
|
+
* run path, where a stale preparation would bill the wrong key.
|
|
6359
|
+
*/
|
|
6360
|
+
async billedMisses(canvas, layer, outputs, runId, opts) {
|
|
6361
|
+
const misses = [];
|
|
6362
|
+
for (const nodeId of layer) {
|
|
6363
|
+
const node = canvas.nodes.find((n) => n.id === nodeId);
|
|
6364
|
+
const def = node ? this.registry.get(node.type) : void 0;
|
|
6365
|
+
if (!node || !def) continue;
|
|
6366
|
+
const token = resolveRegenerateToken(node, opts.regenerate, runId);
|
|
6367
|
+
const prepared = await prepareForExecution(node, outputs, def, canvas.cache_salt, token, this.assets);
|
|
6368
|
+
if (estimateNodeCredits2(def, prepared.renderedParams) <= 0) continue;
|
|
6369
|
+
const hit = opts.cache_policy === "bypass" ? null : await this.cache.get(prepared.cacheKey);
|
|
6370
|
+
if (!hit) misses.push(nodeId);
|
|
6371
|
+
}
|
|
6372
|
+
return misses;
|
|
6373
|
+
}
|
|
6366
6374
|
/** A cache hit replayed as this node's result: its outputs written, and free. */
|
|
6367
6375
|
async replayCached(node, cacheKey, outputs, writer) {
|
|
6368
6376
|
const cacheT0 = Date.now();
|
|
@@ -11668,4 +11676,4 @@ export {
|
|
|
11668
11676
|
defaultRegistry,
|
|
11669
11677
|
createEngineFromEnv
|
|
11670
11678
|
};
|
|
11671
|
-
//# sourceMappingURL=chunk-
|
|
11679
|
+
//# sourceMappingURL=chunk-YOF4XTUW.js.map
|