@koda-sl/baker-cli 0.146.1 → 0.148.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 +10 -4
- package/dist/{chunk-TAGQCU36.js → chunk-2F5SCTNS.js} +63 -2
- package/dist/chunk-2F5SCTNS.js.map +1 -0
- package/dist/cli.js +43 -17
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-TAGQCU36.js.map +0 -1
package/README.md
CHANGED
|
@@ -492,6 +492,8 @@ Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remo
|
|
|
492
492
|
|
|
493
493
|
**Amending a staged op** — `draft amend <ref> (--file patch.json | --patch '<json>')` merges a JSON patch into an already-staged op's payload (objects deep-merge, `null` deletes a key, arrays/scalars replace) and re-validates it in full. Always prefer this over removing and re-staging — it preserves the op's ref (so dependents don't break) and its position in the draft. `draft show <ref>` prints the full staged payload (including warnings/annotations) so you can verify a change looks right before publish.
|
|
494
494
|
|
|
495
|
+
**Already-satisfied writes aren't staged** — an update or pause/resume whose every field already holds the requested value on the live account is dropped instead of staged: the envelope returns `"staged": false, "noop": true` with a `reason` ("this ad is already Paused on Google Ads"), plus a hint. This is a success, not an error — there is nothing to publish. Only a positively-read live snapshot can trigger it; a failed or empty read always stages. Batch responses list the dropped ops under `skipped`, and `count` reflects what actually entered the draft.
|
|
496
|
+
|
|
495
497
|
**Batch keyword adds** — `keywords add`, `negative-keywords add`, and `keyword-lists add` take a whole batch in one command: comma-separate `--text` entries and/or pass `--file <list.txt>` (one keyword per line). A `:EXACT`/`:PHRASE`/`:BROAD` suffix per entry overrides the `--match-type` default. Batches stage all-or-nothing as one request (limit 500); each keyword still lands as its own draft op, so it stays individually removable/amendable.
|
|
496
498
|
|
|
497
499
|
---
|
|
@@ -2647,10 +2649,14 @@ baker canvas validate my-canvas.json
|
|
|
2647
2649
|
# Blocking issues exit non-zero. Advisory `warnings[]` (exit 0) flag likely-wrong-but-
|
|
2648
2650
|
# legal config to review before billing — a frame that describes a recurring element
|
|
2649
2651
|
# (a pet/product) but doesn't wire its reference (VIDEO_REFERENCE_MISSING), or a scene
|
|
2650
|
-
# span that exceeds the assigned model's max clip duration (VIDEO_SPAN_EXCEEDS_MODEL)
|
|
2652
|
+
# span that exceeds the assigned model's max clip duration (VIDEO_SPAN_EXCEEDS_MODEL),
|
|
2653
|
+
# or the same ad rendered once per aspect ratio instead of one hero + a terminal
|
|
2654
|
+
# image_aspect_adapt (IMAGE_PARALLEL_FORMAT_RENDERS — separate renders drift in type,
|
|
2655
|
+
# crop, and color, so the sizes stop matching).
|
|
2651
2656
|
|
|
2652
2657
|
# 2. Run (executes nodes, writes files to ./canvas/<run_id>/)
|
|
2653
2658
|
baker canvas run my-canvas.json
|
|
2659
|
+
# Advisory warnings print as `[warn] <CODE>: …` before anything bills.
|
|
2654
2660
|
# Only nodes reachable from the declared output execute (orphaned nodes are skipped,
|
|
2655
2661
|
# not billed). Add --keep-runs N to prune old r_* run dirs, keeping the N newest.
|
|
2656
2662
|
|
|
@@ -2681,7 +2687,7 @@ baker canvas run my-canvas.json --remote-cache off --no-record
|
|
|
2681
2687
|
# restructure the canvas (repointing output / deleting nodes) to trick the cache:
|
|
2682
2688
|
# • One-shot flag — forces the named nodes + everything downstream fresh this
|
|
2683
2689
|
# run, leaving every other node cached (unknown ids fail loudly before billing):
|
|
2684
|
-
baker canvas run my-canvas.json --regenerate
|
|
2690
|
+
baker canvas run my-canvas.json --regenerate gen,adapt
|
|
2685
2691
|
# • Persistent — add/bump a node's `regenerate` field in the canvas JSON
|
|
2686
2692
|
# (e.g. "regenerate": 2) and re-run; the fresh render is reproducible in any
|
|
2687
2693
|
# later session. Bump it again (3, 4, …) for each additional draw.
|
|
@@ -4152,8 +4158,8 @@ Re-run a creative's latest recorded canvas **from run history** — no local fil
|
|
|
4152
4158
|
| `--concurrency <n>` | — | Same as `canvas run --concurrency`. |
|
|
4153
4159
|
|
|
4154
4160
|
```bash
|
|
4155
|
-
baker canvas rerun spring-offer
|
|
4156
|
-
baker canvas rerun spring-offer
|
|
4161
|
+
baker canvas rerun spring-offer
|
|
4162
|
+
baker canvas rerun spring-offer --force-remote --regenerate gen
|
|
4157
4163
|
```
|
|
4158
4164
|
|
|
4159
4165
|
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.
|
|
@@ -2762,7 +2762,8 @@ var STAGE_CODES = {
|
|
|
2762
2762
|
REGION_DROPPED: "VIDEO_REGION_DROPPED",
|
|
2763
2763
|
OVERLAY_OUT_OF_BOUNDS: "VIDEO_OVERLAY_OUT_OF_BOUNDS",
|
|
2764
2764
|
ORPHAN_NODE: "ORPHAN_NODE",
|
|
2765
|
-
INGEST_PATH_URL_HEALED: "INGEST_PATH_URL_HEALED"
|
|
2765
|
+
INGEST_PATH_URL_HEALED: "INGEST_PATH_URL_HEALED",
|
|
2766
|
+
PARALLEL_FORMAT_RENDERS: "IMAGE_PARALLEL_FORMAT_RENDERS"
|
|
2766
2767
|
};
|
|
2767
2768
|
var SPAN_MODEL_SLACK_S = 0.25;
|
|
2768
2769
|
var VIDEO_TIME_SLACK_S = 0.75;
|
|
@@ -2828,6 +2829,7 @@ function validateCanvas(input, registry) {
|
|
|
2828
2829
|
const estimatedCredits = estimateCredits(ctx);
|
|
2829
2830
|
checkOutputRef(ctx);
|
|
2830
2831
|
checkOrphanNodes(ctx);
|
|
2832
|
+
checkParallelFormatRenders(ctx);
|
|
2831
2833
|
checkVideoInvariants(ctx);
|
|
2832
2834
|
const hasBlocking = issues.some(isBlocking);
|
|
2833
2835
|
if (hasBlocking) return { ok: false, issues };
|
|
@@ -3719,6 +3721,65 @@ function checkAspectConsistency(ctx) {
|
|
|
3719
3721
|
message: `video_generate nodes disagree on aspect_ratio (${[...ratios].sort().join(", ")}) \u2014 the spine's segments would render at different shapes and the composite silently crops. Pin every clip to the same aspect_ratio`
|
|
3720
3722
|
});
|
|
3721
3723
|
}
|
|
3724
|
+
var SAME_CREATIVE_PROMPT_SIMILARITY = 0.6;
|
|
3725
|
+
function promptWordSet(text) {
|
|
3726
|
+
return new Set(
|
|
3727
|
+
text.toLowerCase().split(/[^a-z0-9']+/).filter((w) => w.length >= 3)
|
|
3728
|
+
);
|
|
3729
|
+
}
|
|
3730
|
+
function jaccard(a, b) {
|
|
3731
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
3732
|
+
let shared = 0;
|
|
3733
|
+
for (const w of a) if (b.has(w)) shared++;
|
|
3734
|
+
return shared / (a.size + b.size - shared);
|
|
3735
|
+
}
|
|
3736
|
+
function parallelFormatRenderGroups(nodes) {
|
|
3737
|
+
const parent = nodes.map((_, i) => i);
|
|
3738
|
+
const find = (i) => {
|
|
3739
|
+
let root = i;
|
|
3740
|
+
while (parent[root] !== root) root = parent[root];
|
|
3741
|
+
return root;
|
|
3742
|
+
};
|
|
3743
|
+
const words = nodes.map((n) => promptWordSet(n.prompt));
|
|
3744
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
3745
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
3746
|
+
const a = nodes[i];
|
|
3747
|
+
const b = nodes[j];
|
|
3748
|
+
if (a.ratio === b.ratio) continue;
|
|
3749
|
+
if (jaccard(words[i], words[j]) < SAME_CREATIVE_PROMPT_SIMILARITY) continue;
|
|
3750
|
+
parent[find(i)] = find(j);
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3754
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
3755
|
+
const root = find(i);
|
|
3756
|
+
const group = groups.get(root) ?? [];
|
|
3757
|
+
group.push(nodes[i]);
|
|
3758
|
+
groups.set(root, group);
|
|
3759
|
+
}
|
|
3760
|
+
return [...groups.values()].filter((g) => g.length > 1);
|
|
3761
|
+
}
|
|
3762
|
+
function checkParallelFormatRenders(ctx) {
|
|
3763
|
+
const candidates = [];
|
|
3764
|
+
for (const n of ctx.canvas.nodes) {
|
|
3765
|
+
if (n.type !== "image_generate") continue;
|
|
3766
|
+
const params = n.params;
|
|
3767
|
+
const prompt = params?.prompt;
|
|
3768
|
+
if (typeof prompt !== "string" || prompt.length === 0) continue;
|
|
3769
|
+
candidates.push({ id: n.id, prompt, ratio: String(params?.aspect_ratio ?? "(unset)") });
|
|
3770
|
+
}
|
|
3771
|
+
if (candidates.length < 2) return;
|
|
3772
|
+
for (const group of parallelFormatRenderGroups(candidates)) {
|
|
3773
|
+
const ids = group.map((g) => g.id);
|
|
3774
|
+
const ratios = [...new Set(group.map((g) => g.ratio))].sort();
|
|
3775
|
+
ctx.issues.push({
|
|
3776
|
+
path: "nodes",
|
|
3777
|
+
code: STAGE_CODES.PARALLEL_FORMAT_RENDERS,
|
|
3778
|
+
severity: "warning",
|
|
3779
|
+
message: `${ids.join(", ")} render the same creative once per aspect ratio (${ratios.join(", ")}) \u2014 each is an independent generation, so the formats come back with different type, crop, and color and stop reading as one ad. Keep ONE hero render and add a terminal image_aspect_adapt node fed from it (params.formats or params.platform) so every ratio is re-framed from the same image`
|
|
3780
|
+
});
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3722
3783
|
function checkOrphanNodes(ctx) {
|
|
3723
3784
|
const out = ctx.canvas.output;
|
|
3724
3785
|
if (!out || !ctx.idToIndex.has(out.node)) return;
|
|
@@ -7837,4 +7898,4 @@ export {
|
|
|
7837
7898
|
defaultRegistry,
|
|
7838
7899
|
createEngineFromEnv
|
|
7839
7900
|
};
|
|
7840
|
-
//# sourceMappingURL=chunk-
|
|
7901
|
+
//# sourceMappingURL=chunk-2F5SCTNS.js.map
|