@koda-sl/baker-cli 0.408.0 → 0.410.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.
Files changed (26) hide show
  1. package/README.md +14 -23
  2. package/dist/{chunk-N72QYDJY.js → chunk-ISCXDKWB.js} +25 -8
  3. package/dist/{chunk-N72QYDJY.js.map → chunk-ISCXDKWB.js.map} +1 -1
  4. package/dist/{chunk-5X5LUMVV.js → chunk-ISJOVAJP.js} +2 -2
  5. package/dist/{chunk-KMCVSEGU.js → chunk-KTMFHBN6.js} +3 -3
  6. package/dist/chunk-KTMFHBN6.js.map +1 -0
  7. package/dist/{chunk-WQE4SRLY.js → chunk-S5BY45H7.js} +3 -3
  8. package/dist/{chunk-X37GPJ54.js → chunk-VKWHVMMD.js} +3 -3
  9. package/dist/{chunk-3ZN2DUBU.js → chunk-ZIOW64BL.js} +4 -4
  10. package/dist/cli.js +750 -685
  11. package/dist/cli.js.map +1 -1
  12. package/dist/{client-BPKOHQWA.js → client-AY6TPFQ4.js} +4 -4
  13. package/dist/engine/index.js +3 -3
  14. package/dist/{env-B7V77K6K.js → env-CWEGTJF3.js} +2 -2
  15. package/dist/{output-5M2QPMKZ.js → output-EPF3HHJ4.js} +5 -5
  16. package/dist/{shared-OEQKSDWO.js → shared-53G34I3U.js} +6 -6
  17. package/package.json +1 -1
  18. package/dist/chunk-KMCVSEGU.js.map +0 -1
  19. /package/dist/{chunk-5X5LUMVV.js.map → chunk-ISJOVAJP.js.map} +0 -0
  20. /package/dist/{chunk-WQE4SRLY.js.map → chunk-S5BY45H7.js.map} +0 -0
  21. /package/dist/{chunk-X37GPJ54.js.map → chunk-VKWHVMMD.js.map} +0 -0
  22. /package/dist/{chunk-3ZN2DUBU.js.map → chunk-ZIOW64BL.js.map} +0 -0
  23. /package/dist/{client-BPKOHQWA.js.map → client-AY6TPFQ4.js.map} +0 -0
  24. /package/dist/{env-B7V77K6K.js.map → env-CWEGTJF3.js.map} +0 -0
  25. /package/dist/{output-5M2QPMKZ.js.map → output-EPF3HHJ4.js.map} +0 -0
  26. /package/dist/{shared-OEQKSDWO.js.map → shared-53G34I3U.js.map} +0 -0
package/README.md CHANGED
@@ -3237,11 +3237,11 @@ Search the **ad-dna** corpus of scored "winning" competitor ads for reference cr
3237
3237
 
3238
3238
  ### `baker winning-ads search <query>`
3239
3239
 
3240
- Semantic search (dense recall + BM25 + rerank) → `POST /api/ad-library/winners/search`. The CLI projects each result to a **lean, decision-focused** shape so the agent's context stays small — default fields: `advertiser`, `advertiser_id`, `platform`, `format`, `relevance`, `winner_score`, `summary` (what the ad is about), `media_url`, and `visual` (what the viewer sees) + `container` (a static's layout pattern) + `mechanism` (why it works) when known; plus top-level `pool_size`, `match_confidence` (`high|medium|low`), and `below_floor_count` (matches dropped under the relevance floor). `--full` adds DNA detail (`angle`, `target_persona`, `hook_archetype`, `awareness_stage`, `industry`) + longevity (`days_active`, `reach`, `active`, `winner_category`, `media_kind`). `--output json` (default) returns the lean objects; `--output md` prints a table.
3240
+ Semantic search (dense recall + BM25 + rerank) → `POST /api/ad-library/winners/search`. The CLI projects each result to a **lean, decision-focused** shape so the agent's context stays small — default fields: `advertiser`, `advertiser_id`, `platform`, `format`, `relevance`, `winner_score`, `summary` (what the ad is about), `media_url`, and `visual` (what the viewer sees) + `container` (a static's layout pattern) + `mechanism` (why it works) when known; plus top-level `pool_size`, `match_confidence` (`high|medium|low`), and `below_floor_count` (matches dropped under the relevance floor). The proof signals ride along (`winner_category`, `days_active`, `active`); `--full` adds DNA detail (`angle`, `target_persona`, `hook_archetype`, `awareness_stage`, `industry`) + `reach` and `media_kind`. By default the search draws from the brands the company follows first (`--scope followed-first`) and tops up from the shared library; every result says `source: followed|library`. `--output json` (default) returns the lean objects; `--output md` prints a table.
3241
3241
 
3242
3242
  > `media_url` is the creative itself: for `static` it's the image, for `video` it's the video file. ad-dna stores **no separate poster** for videos, so a video result has only the video URL.
3243
3243
 
3244
- > Results are already de-junked and winner-first: the corpus auto-drops measured failures (duds, thrash) and ranks `final = relevance^0.25 × winner_score^0.75`. So `--winner-category winner` is usually unnecessary — it's a stricter filter that also removes `untested` (unproven-but-maybe-fresh) ads. Rely on the default + `--min-relevance`; only add `--winner-category` to force-exclude unproven creatives. (With a query, restricting to a single `--advertiser-id` turns the failure-exclusion and the per-advertiser cap off: you then see that brand's duds too. A search with no query is a browse, which always drops failures and `untested` ads. A company that follows one brand keeps both with `--scope followed`/`followed-first`.)
3244
+ > Results are already de-junked and winner-first: the corpus auto-drops measured failures (duds, thrash) and ranks `final = relevance^0.5 × winner_score^0.5`, so a direct match beats a stronger-but-tangential ad. So `--winner-category winner` is usually unnecessary — it's a stricter filter that also removes `untested` (unproven-but-maybe-fresh) ads. Rely on the default + `--min-relevance`; only add `--winner-category` to force-exclude unproven creatives. (With a query, restricting to a single `--advertiser-id` turns the failure-exclusion and the per-advertiser cap off: you then see that brand's duds too. A search with no query is a browse, which always drops failures and `untested` ads. A company that follows one brand keeps both with `--scope followed`/`followed-first`.)
3245
3245
 
3246
3246
  ```bash
3247
3247
  # Only LinkedIn video ads:
@@ -3260,6 +3260,8 @@ baker winning-ads search --format-pattern comparison_table --platform meta --out
3260
3260
  |---|---|
3261
3261
  | `query` (positional) | Free-text natural-language query |
3262
3262
  | `--ref-ad-id <id>` | Find ads similar to this ad (instead of free text) |
3263
+ | `--scope followed-first\|followed\|all` | Which brands to draw from (default `followed-first`: the company's followed brands, topped up from the shared library; the whole library when it follows nobody) |
3264
+ | `--sort winner\|longest_running\|newest` | Order of a no-query browse (default `winner`); ignored when a query is given |
3263
3265
  | `--limit <n>` | Max results 1–100 (**default 10** — shortlist size) |
3264
3266
  | `--max-per-advertiser <n>` | Cap results per advertiser 0–50 (default 3; `0` = no cap) |
3265
3267
  | `--min-relevance <0-1>` | Relevance floor; trims weak matches |
@@ -3287,7 +3289,7 @@ Read the **winner-weighted rollup** of the ad-dna corpus → `GET /api/ad-librar
3287
3289
  mechanism / static-format patterns that actually win, ranked by `winner_score` among the ads of the segment you
3288
3290
  pass (`--platform`, `--awareness`, `--industry`; an unset one means any), keyed on the closed seeds/enums the
3289
3291
  extractor emits (no clustering). Ground a creative in a **proven** pattern for the exact segment instead of
3290
- guessing. The rollup is recomputed daily. (Distinct from `winning-ads patterns`, which contrasts two ad cohorts.)
3292
+ guessing. The rollup is recomputed daily.
3291
3293
 
3292
3294
  ```bash
3293
3295
  # Top winning hook seeds for problem-aware SaaS on Meta:
@@ -3390,24 +3392,6 @@ Stop following a brand by advertiser id → `POST /api/ad-library/unfollow`. Ret
3390
3392
  baker winning-ads unfollow adv_123
3391
3393
  ```
3392
3394
 
3393
- ### `baker winning-ads brief`
3394
-
3395
- Generate a creative brief grounded in strategically-similar winners → `POST /api/ad-library/brief`. Optionally describe the target creative with `--dna` (a JSON object), steer with `--notes`, and cap references with `--k`. Returns `brief_markdown` + `reference_ad_ids`.
3396
-
3397
- ```bash
3398
- baker winning-ads brief --dna '{"angle":"cost savings","awareness_stage":"solution_aware"}' --notes "B2B, LinkedIn video" --k 8
3399
- ```
3400
-
3401
- ### `baker winning-ads patterns --winners <adIds> --duds <adIds>`
3402
-
3403
- Mine what separates two cohorts of ads → `POST /api/ad-library/patterns`. Pass a comma-list of winning ad ids (`--winners`, cohort A) and weaker/dud ad ids (`--duds`, cohort B), plus optional `--top-n`. Returns each discriminating DNA field with the cohort it `favors` (`winners`/`duds`), a `score`, and the top values on each side.
3404
-
3405
- ```bash
3406
- baker winning-ads patterns --winners a_1,a_2,a_3 --duds a_9,a_8 --top-n 10 --output md
3407
- ```
3408
-
3409
- ---
3410
-
3411
3395
  ### Scheduled agents (`baker scheduled-agents`)
3412
3396
 
3413
3397
  A Scheduled agent is a **trigger** and a **prompt**. When the trigger fires, a Session opens with that prompt and does the job — there is no Task in between. `create`, `update`, and `delete` stage draft ops on `BAKER_CHAT_ID` and apply when the chat is published. `unstage` takes back what this chat staged on one item before Publish (`--index <n>` for one change only, by its index in `list` → `draft.ops`). `list` and `get` include draft state when `BAKER_CHAT_ID` is set; `get` on a published item also returns `runsAs` — who each run acts as and which tools it loads. `run` fires a published item immediately and does not require a chat id. Publishing every run unreviewed is switched on by the user on the item, never through the CLI: `--auto-publish` is refused with an `ask_user` fix.
@@ -5737,7 +5721,7 @@ The two scaffold passes are billed (the full `video_deconstruct` is the heavy on
5737
5721
 
5738
5722
  **New-creative limit:** a chat makes at most 10 new creatives. The eleventh `scaffold-static-ad`, `scaffold-ad` or `scaffold-video` with a new `--slug` exits 2 with `CREATIVE_LIMIT` before any billed pass. Re-scaffolding a creative already made in the chat, or one whose `_definition.md` existed before, does not count.
5739
5723
 
5740
- `<image>` is a **local path or an `http(s)` URL** (e.g. a presigned S3 link from ad-dna). With `--slug` the source is localized to durable, content-addressed storage before anything bills, so the emitted canvas stays runnable in any sandbox; without a slug a URL is passed straight into the `original` ingest node (a presigned one expires). For the URL case, `--out` defaults to the current directory — pass `--out` to scaffold two URL sources into distinct files, or the second run overwrites the first.
5724
+ `<image>` is a **local path or an `http(s)` URL** (e.g. a presigned S3 link from ad-dna), and it is **optional when `--source-ad-id` is given**: the library's picture is used. With `--slug` the source is localized to durable, content-addressed storage before anything bills, so the emitted canvas stays runnable in any sandbox; without a slug a URL is passed straight into the `original` ingest node (a presigned one expires). For the URL case, `--out` defaults to the current directory — pass `--out` to scaffold two URL sources into distinct files, or the second run overwrites the first.
5741
5725
 
5742
5726
  Turn a source/inspiration image into a **runnable, self-validated static-ad canvas** — the static counterpart of `scaffold-video`. It runs **billed Gemini passes** up front:
5743
5727
 
@@ -5759,7 +5743,11 @@ The canvas is validated before it is written. stdout returns `{ ok, canvas_path,
5759
5743
 
5760
5744
  The render prompt is **model-aware**: for **gpt-image** the exclusions (no invented badges/watermark) are hoisted to the very *end* of the prompt where the model reads them as pure exclusions rather than composition. Run `baker canvas critique` on the scaffolded canvas for an advisory read before you spend.
5761
5745
 
5746
+ **A reference from the winners library goes in by id.** `--source-ad-id <ad_id> --source-platform meta|linkedin|tiktok [--source-advertiser-id <id>]` (the ids beside the ad in `baker winning-ads search`) fetches the library's structured read of that ad, the same one `baker winning-ads content <ad_id> --full` returns, and writes it into `prompt.json` as a `reference` block: `{ ad_id, platform, advertiser, advertiser_id, format_pattern, layout: { composition, focal_subject, aspect_ratio, background, palette_mood, colors, production, aesthetic, cta_in_image, markers }, message: { mechanism, awareness, sophistication, angle, hook_archetype, pain_points, desires, value_props, objections_handled, proof, risk_reversal }, offer: { offer_type, pricing_model, entry_price, no_credit_card, guarantee, guarantee_days, urgency, bonus }, text_blocks: [{ text, role, position }], copy: { primary_text, headline, cta } }` (filled fields only). The describe pass still reads the pixels but is told that read as grounding, so the two cannot disagree on the container or the mechanism; when it names no `winning_mechanisms`, the library's container + mechanism seeds the list. The source ids (`sourceAdId`, `sourcePlatform`, `sourceAdvertiserId`, and `sourceAdvertiser` from the library when `--advertiser` is absent) are written to `_definition.md` and synced to the creative at scaffold time. A library read that fails warns, records the ids anyway and scaffolds from the picture alone (`hints`: `REFERENCE NOT READ`); with no picture either, the command exits 2 (`source_unavailable`). `baker canvas sync-definition` re-reads the three ids from `_definition.md` on every sync.
5747
+
5762
5748
  ```bash
5749
+ baker canvas scaffold-static-ad --source-ad-id adg_123 --source-platform meta --slug spring-offer --platform meta
5750
+ # → the library's picture and read; writes src/creatives/spring-offer/{spring-offer.canvas.json, prompt.json, _definition.md}
5763
5751
  baker canvas scaffold-static-ad ./reference-ad.png --context "competitor ad for <brand>, <category>, <market>"
5764
5752
  # → writes ./static-ad.canvas.json + ./prompt.json
5765
5753
  # edit prompt.json into your ad + replace each [TODO] ingest path with a real file, then:
@@ -5769,7 +5757,10 @@ baker canvas run ./static-ad.canvas.json
5769
5757
 
5770
5758
  | Flag | Default | Effect |
5771
5759
  |---|---|---|
5772
- | `--context <text>` | — | Known provenance (advertiser, category, market) to ground the describe. |
5760
+ | `--source-ad-id <ad_id>` | — | The winners-library ad this creative adapts. Fetches its structured read into `prompt.json` `reference`, grounds the describe pass with it, and records the source on the creative. Makes `<image>` optional. |
5761
+ | `--source-platform <platform>` | `meta` | Platform the library ad is on: `meta`\|`linkedin`\|`tiktok`. Another value exits 2 (`invalid_source_platform`). |
5762
+ | `--source-advertiser-id <id>` | the library's | The reference's library advertiser id, recorded as `sourceAdvertiserId`. |
5763
+ | `--context <text>` | — | Known provenance (advertiser, category, market) to ground the describe. Appended to the library's read when `--source-ad-id` is given. |
5773
5764
  | `--out <path>` | `<image-dir>/static-ad.canvas.json` (cwd when `<image>` is a URL) | Where to write the canvas (`prompt.json` is written alongside). |
5774
5765
  | `--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`. With a slug, the reference image is localized to durable storage and normalized to a model-safe format (SVG/AVIF/HEIC/… → PNG), so the canvas ingests a stable URL instead of an expiring one. |
5775
5766
  | `--describe-model <id>` | registry default (`~google/gemini-pro-latest`) | Override the `image_describe` model. |
@@ -951,7 +951,26 @@ function bakerUses(value) {
951
951
  const uses = asRecord(value);
952
952
  const companies = typeof uses.companies === "number" ? uses.companies : 0;
953
953
  if (companies === 0) return {};
954
- return { baker_uses: companies, ...uses.ours === true ? { already_adapted: true } : {} };
954
+ const count = (n) => typeof n === "number" && n > 0 ? n : void 0;
955
+ const approved = count(uses.approved);
956
+ const rejected = count(uses.rejected);
957
+ return {
958
+ baker_uses: companies,
959
+ ...uses.ours === true ? { already_adapted: true } : {},
960
+ ...approved !== void 0 ? { approved } : {},
961
+ ...rejected !== void 0 ? { rejected } : {}
962
+ };
963
+ }
964
+ function dnaMove(dna) {
965
+ const text = (value) => typeof value === "string" && value ? value : void 0;
966
+ const visual = text(dna.visual);
967
+ const container = text(dna.container);
968
+ const mechanism = text(dna.mechanism);
969
+ return {
970
+ ...visual ? { visual } : {},
971
+ ...container ? { container } : {},
972
+ ...mechanism ? { mechanism } : {}
973
+ };
955
974
  }
956
975
  function compactWinningAd(record) {
957
976
  const dna = asRecord(record.dna);
@@ -961,13 +980,14 @@ function compactWinningAd(record) {
961
980
  format: String(record.format ?? ""),
962
981
  relevance: round2(record.relevance),
963
982
  winner_score: round2(record.winner_score),
983
+ winner_category: typeof record.winner_category === "string" ? record.winner_category : null,
984
+ days_active: typeof record.days_active === "number" ? record.days_active : null,
985
+ active: typeof record.active === "boolean" ? record.active : null,
964
986
  summary: String(dna.creative_concept ?? ""),
965
987
  advertiser_id: String(record.advertiser_id ?? ""),
966
988
  ad_id: String(record.ad_id ?? ""),
967
989
  media_url: String(record.media_url ?? ""),
968
- ...typeof dna.visual === "string" && dna.visual ? { visual: dna.visual } : {},
969
- ...typeof dna.container === "string" && dna.container ? { container: dna.container } : {},
970
- ...typeof dna.mechanism === "string" && dna.mechanism ? { mechanism: dna.mechanism } : {},
990
+ ...dnaMove(dna),
971
991
  ...bakerUses(record.baker_uses),
972
992
  ...record.source === "followed" || record.source === "library" ? { source: record.source } : {}
973
993
  };
@@ -977,11 +997,8 @@ function fullWinningAd(record) {
977
997
  const dna = asRecord(record.dna);
978
998
  return {
979
999
  ...compact,
980
- winner_category: String(record.winner_category ?? ""),
981
1000
  media_kind: record.media_kind ?? null,
982
- days_active: record.days_active ?? null,
983
1001
  reach: record.reach ?? null,
984
- active: typeof record.active === "boolean" ? record.active : null,
985
1002
  angle: dna.angle ?? null,
986
1003
  awareness_stage: dna.awareness_stage ?? null,
987
1004
  target_persona: dna.target_persona ?? null,
@@ -1339,4 +1356,4 @@ export {
1339
1356
  captureVimeoCookie,
1340
1357
  childEnvWith
1341
1358
  };
1342
- //# sourceMappingURL=chunk-N72QYDJY.js.map
1359
+ //# sourceMappingURL=chunk-ISCXDKWB.js.map