@koda-sl/baker-cli 0.145.0 → 0.147.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
@@ -486,7 +486,7 @@ baker ads google draft remove g_temp_ab12 # cascades to dependents
486
486
  baker ads google draft clear
487
487
  ```
488
488
 
489
- Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remove), `negative-keywords`, `keyword-lists`, `ads`, `assets` (create/update/attach/detach), `asset-groups` (create/update — Performance Max), `audiences`, `conversions`, `bidding-strategies`, `labels`, `campaign-criteria`, and `draft`. Amounts are in major currency units (converted to micros). Money/bids: `--amount`, `--cpc-bid`, `--target-cpa` take major units; `--target-roas` a ratio. Less-common ops accept a `--file <payload.json>` (flags override file keys). Updates target a resource name or bare id as the positional argument; a target that names an op staged earlier **amends it in place**.
489
+ Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remove), `negative-keywords`, `keyword-lists`, `ads`, `assets` (create/update/attach/detach), `asset-groups` (create/update — Performance Max), `audiences`, `conversions`, `bidding-strategies`, `labels`, `campaign-criteria`, and `draft`. Amounts are in major currency units (converted to micros). Money/bids: `--amount`, `--cpc-bid`, `--target-cpa`, `--max-cpc` take major units; `--target-roas` a ratio. `--max-cpc` sets the max CPC bid ceiling for `TARGET_IMPRESSION_SHARE` / `TARGET_SPEND` / `PERCENT_CPC` and must be paired with `--bidding-strategy`. Less-common ops accept a `--file <payload.json>` (flags override file keys). Updates target a resource name or bare id as the positional argument; a target that names an op staged earlier **amends it in place**.
490
490
 
491
491
  **Reviewing the draft** — `baker ads google draft list` renders everything you've staged as a grouped campaign ▸ ad group ▸ ad tree (with the simulated/live mode banner and non-blocking completeness advisories), the CLI counterpart to the dashboard's Google Ads tab. Pass `--json` for the raw envelope. Aim for a fully built campaign — 2–4 ad groups, ≥5 keywords each, 2–4 RSAs with 8–12 headlines, ≥4 sitelinks / ≥3 callouts / ≥1 structured snippet, and ≥1 shared negative list; the advisories flag what's still thin.
492
492
 
@@ -2647,10 +2647,14 @@ baker canvas validate my-canvas.json
2647
2647
  # Blocking issues exit non-zero. Advisory `warnings[]` (exit 0) flag likely-wrong-but-
2648
2648
  # legal config to review before billing — a frame that describes a recurring element
2649
2649
  # (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).
2650
+ # span that exceeds the assigned model's max clip duration (VIDEO_SPAN_EXCEEDS_MODEL),
2651
+ # or the same ad rendered once per aspect ratio instead of one hero + a terminal
2652
+ # image_aspect_adapt (IMAGE_PARALLEL_FORMAT_RENDERS — separate renders drift in type,
2653
+ # crop, and color, so the sizes stop matching).
2651
2654
 
2652
2655
  # 2. Run (executes nodes, writes files to ./canvas/<run_id>/)
2653
2656
  baker canvas run my-canvas.json
2657
+ # Advisory warnings print as `[warn] <CODE>: …` before anything bills.
2654
2658
  # Only nodes reachable from the declared output execute (orphaned nodes are skipped,
2655
2659
  # not billed). Add --keep-runs N to prune old r_* run dirs, keeping the N newest.
2656
2660
 
@@ -2681,7 +2685,7 @@ baker canvas run my-canvas.json --remote-cache off --no-record
2681
2685
  # restructure the canvas (repointing output / deleting nodes) to trick the cache:
2682
2686
  # • One-shot flag — forces the named nodes + everything downstream fresh this
2683
2687
  # run, leaving every other node cached (unknown ids fail loudly before billing):
2684
- baker canvas run my-canvas.json --regenerate gen_4x5,gen_9x16
2688
+ baker canvas run my-canvas.json --regenerate gen,adapt
2685
2689
  # • Persistent — add/bump a node's `regenerate` field in the canvas JSON
2686
2690
  # (e.g. "regenerate": 2) and re-run; the fresh render is reproducible in any
2687
2691
  # later session. Bump it again (3, 4, …) for each additional draw.
@@ -4152,8 +4156,8 @@ Re-run a creative's latest recorded canvas **from run history** — no local fil
4152
4156
  | `--concurrency <n>` | — | Same as `canvas run --concurrency`. |
4153
4157
 
4154
4158
  ```bash
4155
- baker canvas rerun spring-offer-4x5
4156
- baker canvas rerun spring-offer-4x5 --force-remote --regenerate gen_4x5
4159
+ baker canvas rerun spring-offer
4160
+ baker canvas rerun spring-offer --force-remote --regenerate gen
4157
4161
  ```
4158
4162
 
4159
4163
  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-TAGQCU36.js.map
7901
+ //# sourceMappingURL=chunk-2F5SCTNS.js.map