@koda-sl/baker-cli 0.151.0-dev.4932063cd → 0.152.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
@@ -300,11 +300,22 @@ When a query fails, the error includes a `fix` object with the exact corrected c
300
300
 
301
301
  ### `baker ads google changes`
302
302
 
303
- Get recent change logs for an account.
303
+ Read the change history of an account.
304
+
305
+ Google keeps account history in two layers, and neither reaches past 90 days:
306
+
307
+ | Scope | Reach | What you get |
308
+ |-------------|---------|---------------------------------------------------------------------|
309
+ | `detail` | 30 days | What changed, old → new value, and who changed it |
310
+ | `summary` | 90 days | Which resources were added, changed or removed — no values, no user |
311
+
312
+ Beyond 90 days there is no API. Infer the change from a monthly spend trend per campaign
313
+ instead, or ask the user to check the change history in the Google Ads web UI.
304
314
 
305
315
  ```bash
306
316
  baker ads google changes --customer-id 1234567890
307
317
  baker ads google changes --customer-id 1234567890 --days 14 --resource-type CAMPAIGN
318
+ baker ads google changes --customer-id 1234567890 --days 60 --scope summary
308
319
  ```
309
320
 
310
321
  **Flags:**
@@ -312,8 +323,9 @@ baker ads google changes --customer-id 1234567890 --days 14 --resource-type CAMP
312
323
  | Flag | Description |
313
324
  |-------------------|---------------------------------------------------------------|
314
325
  | `--customer-id` | Google Ads customer ID. Falls back to `BAKER_GOOGLE_ADS_CUSTOMER_ID` env var. |
315
- | `--days` | Lookback days (default 7, max 90) |
316
- | `--resource-type` | Filter: CAMPAIGN, AD_GROUP, AD_GROUP_AD, AD_GROUP_CRITERION |
326
+ | `--scope` | `detail` (default, 30 days) or `summary` (90 days) |
327
+ | `--days` | Lookback days, counted inclusive of today (default 7). Max 30 with `detail`, 90 with `summary` |
328
+ | `--resource-type` | Filter: CAMPAIGN, CAMPAIGN_CRITERION, AD_GROUP, AD_GROUP_AD, AD_GROUP_CRITERION |
317
329
  | `--limit` | Max results (default 50) |
318
330
  | `--output` | Format: `json` \| `csv` \| `jsonl` \| `md` |
319
331
 
@@ -846,6 +858,7 @@ audit --format md # deliverable-ready markdown table
846
858
 
847
859
  campaign-groups create|update|pause|resume|duplicate # staged writes (see below)
848
860
  campaigns create|update|pause|resume|archive|duplicate
861
+ campaigns url-params <ad-set-id> # UTMs for every ad in the ad set
849
862
  creatives create|update|pause|resume|duplicate
850
863
  audiences create|upload
851
864
  conversions create|update
@@ -868,6 +881,12 @@ baker ads linkedin campaigns create --name "ABM Tier-1" --group li_temp_x1 \
868
881
  baker ads linkedin creatives create --campaign li_temp_x2 --format image \
869
882
  --image-id <bakerImageId> --headline "Book a demo" --landing-url https://example.com/demo --cta REQUEST_DEMO
870
883
 
884
+ # UTMs belong on the ad set — LinkedIn appends them to every ad in it, live ones included
885
+ baker ads linkedin campaigns url-params 123456 \
886
+ --param "utm_source=linkedin&utm_medium=paid-social" \
887
+ --dynamic utm_campaign=CAMPAIGN_NAME --dynamic utm_content=CREATIVE_ID
888
+ baker ads linkedin campaigns url-params 123456 --clear # remove them all
889
+
871
890
  # Fix the classic audit findings
872
891
  baker ads linkedin campaigns update 123456 --audience-expansion off --lan off
873
892
  baker ads linkedin campaigns update 123456 --bid 7.50 --daily-budget 100 --currency USD
@@ -3396,7 +3415,6 @@ Generate video. Async with polling. Two curated models.
3396
3415
  | `first_frame` | image | no | Starting keyframe (image-to-video) |
3397
3416
  | `last_frame` | image | no | Ending keyframe |
3398
3417
  | `reference` | image | no | Alternative to `first_frame` when only one image is given |
3399
- | `references` | image[] | no | Reference-to-video: several images used as visual guidance (this person, this product) instead of an exact opening frame. **Mutually exclusive with `first_frame`** — when both are wired the frame wins and these are ignored, so pick one mode. |
3400
3418
 
3401
3419
  Accepted ref-image MIMEs vary by model — see per-model sections below.
3402
3420
 
@@ -4658,8 +4676,29 @@ baker landing critique spring-offer --full # also print advisory-tier findings
4658
4676
  - **BRAND.md is the allowlist.** A font, hue, or ground that the client's `src/brand/BRAND.md` (mirrored into `src/styles/global.css` `@theme`, hex or oklch) commits is a decision, not a tell — matched values downgrade to advisory `brandDrift` so the critic never fights a real brand.
4659
4677
  - **Records a freshness snapshot** to `.cache/landing-critique/<slug>.json` (the source hash it scored). The publish quality gate reads it to enforce that every changed landing was critiqued and ships no block-tier tell. Re-run after edits so the snapshot stays fresh.
4660
4678
 
4679
+ - **Agent washing is a copy tell.** A page that claims autonomy ("fully autonomous", "while you sleep", "no human intervention") and never says anywhere what the buyer approves, overrides, or audits scores an `agent-washing` warn. One signal about oversight of the agent's actions — approval, review, override, undo/rollback, audit log, escalation — anywhere on the page clears it; the rule is page-scope precisely because the hero can defer the trust story to a section below it. Generic privacy boilerplate ("GDPR", "encrypted", "your data") deliberately does **not** clear it, since it appears in most footers and says nothing about who is in charge of the agent.
4680
+
4661
4681
  Output is the standard envelope `{ ok, data, hints }` with `data = { advisory, slug, overall, counts, dimensions, findings }`. `dimensions` scores seven design families (typography, color, borders_depth, motion, spacing, copy, integrity) 0–1 (higher is better); `counts` is the block/warn/advisory tally.
4662
4682
 
4683
+ ## Brand Fonts (`baker brand fonts`)
4684
+
4685
+ Verifies that a brand's typefaces really load, and self-hosts them. A font that isn't actually available never announces itself — the browser substitutes its nearest fallback, so the page renders fine and is quietly off-brand. Reading a family name off a live site tells you what the CSS *asks for*, never what loads.
4686
+
4687
+ ```bash
4688
+ baker brand fonts check # are the brand's fonts real?
4689
+ baker brand fonts fetch "DM Sans" # self-host, weights taken from global.css
4690
+ baker brand fonts fetch "Manrope" --weights 500,700 # self-host specific weights
4691
+ baker brand fonts fetch "Inter" --subsets latin,latin-ext # add accented-glyph coverage
4692
+ ```
4693
+
4694
+ - **`check`** asks Google Fonts what it *actually* serves for every family and weight `src/styles/global.css` requests, and reports `requested` / `served` / `missing` plus the available unicode subsets. It exits 2 with `FONT_NOT_SERVED` when the brand asks for something Google won't serve, and distinguishes an unknown family name from a family that exists at other weights — the fix differs.
4695
+ - **Failures carry the numbers, so nothing needs a second call.** `check`'s error keeps the full per-family breakdown at `error.fix.families` (healthy families included, so a multi-family brand shows what not to touch), and both commands probe the standard weights to report the ones that *do* exist at `error.fix.served` — the remediation is "retry with one of these" rather than "run `check`", which would be the command that just failed.
4696
+ - **`fetch`** downloads the family into `src/brand/fonts/` and prints the `@font-face` block to paste above `@theme`. Defaults to the `latin` subset; `--subsets latin,latin-ext` adds accented-glyph coverage for Spanish and Portuguese copy. One file per weight *and* subset, since Google ships them separately and `unicode-range` is carried through verbatim.
4697
+ - **It does not edit `global.css` for you.** Paste the block, then drop that family from the Google `@import` — otherwise the page self-hosts *and* still calls Google. Record the weights in `src/brand/BRAND.md` in the same change.
4698
+ - **Licensing:** Google Fonts are open-licensed and safe to self-host. A client's own commercial typeface is not — confirm before re-hosting one.
4699
+
4700
+ Complements the offline `validate-brand-fonts` check that runs in the scaffold's `verify`: that one compares what `global.css` declares against what provides it, with no network. Only the network can answer whether Google really serves a given family and weight.
4701
+
4663
4702
  ## Help & Discovery
4664
4703
 
4665
4704
  Every command supports `--help` for usage info:
@@ -4698,6 +4737,8 @@ This CLI is designed for AI agent consumption. Key patterns:
4698
4737
  - **0.116.0**: `lead-forms create` models the full Campaign Manager form — `locale`, form banner image (`formImageId`/`formImageUrn`), predefined profile-field questions (validated enum) vs custom questions (`SINGLE_LINE_TEXT`/`MULTIPLE_CHOICE` with `options`, ≤3 custom), `privacyPolicyText`, disclosure `consents[]` (≤5), tracking `hiddenFields[]` (≤20), and `thankYou` confirmation CTA + landing/appointment link. Staged preview surfaces best-practice warnings (no qualifying question, no confirmation, no consent). No breaking flag changes.
4699
4738
  - **0.121.0**: `lead-forms create` drops the `privacyPolicyText` field — LinkedIn's versioned lead-form API has no privacy-policy-text slot, so it was silently discarded on publish. Use `legalDisclaimer` (shown under the form) or `consents[]` (disclosure checkboxes) instead. (Companion backend fix: staged lead-form questions were serialized in a shape LinkedIn dropped — they now publish correctly, and the staged preview lists each question.)
4700
4739
  - **0.119.0**: `draft amend`/`draft show` land on both `baker ads google` and `baker ads linkedin` — a generic JSON-merge-patch to update any staged op in place plus a full-payload receipt, replacing remove+recreate as the correction path. Google gains `assets update` and `asset-groups create|update` (Performance Max asset groups are now their own entity — `ads create --format performanceMaxAssetGroup` never worked and is gone); `ads create --format video` moves from a bare YouTube id to `--video-assets` refs (**breaking flag change** — stage the video as an asset first); `--format demandGen` gains `--image-assets`/`--square-image-assets`/`--logo-image-assets` and flag-building for headlines/descriptions. LinkedIn's `draft list` now renders a readable Campaign group ▸ Campaign ▸ Creative tree by default (`--json` for raw), `creatives update` gains `--campaign` (re-parent while staged), and `campaigns update` passes create-only fields (`--group`/`--type`/`--locale`/`--associated-entity`) through when amending a `li_temp_*` staged create instead of always stripping them.
4740
+ - **0.150.0**: `baker landing critique` gains an `agent-washing` rule in the `copy` family — an autonomy claim ("fully autonomous", "while you sleep", "no human intervention") with no signal anywhere on the page about who oversees the agent (approval, review, override, undo/rollback, audit log, escalation) scores a warn. Page-scope, so a hero may defer the trust story to a later section; generic privacy boilerplate does not clear it. `CRITIC_VERSION` bumps to `2`.
4741
+ - **0.152.0**: `baker ads linkedin campaigns url-params <ad-set-id>` stages an ad set's URL tracking parameters (LinkedIn's `adTrackingParameters`) — `--param key=value` for fixed values (repeatable or `&`-joined), `--dynamic key=PLACEHOLDER` for values LinkedIn fills in per ad (`CAMPAIGN_NAME`, `CREATIVE_ID`, …), `--clear` to remove them. LinkedIn appends these to the landing URL of every ad in the ad set, including ads already running, so this replaces stamping the same UTM onto each ad's `--landing-url` — which missed later ads and double-appended keys the ad set already set. Account-level parameters remain UI-only (LinkedIn exposes no API for them).
4701
4742
 
4702
4743
  ## Publishing
4703
4744
 
@@ -7,9 +7,9 @@ import {
7
7
  __toESM
8
8
  } from "./chunk-RK67WL4O.js";
9
9
 
10
- // ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/index.js
10
+ // ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js
11
11
  var require_safe_stable_stringify = __commonJS({
12
- "../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/index.js"(exports, module) {
12
+ "../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js"(exports, module) {
13
13
  "use strict";
14
14
  var { hasOwnProperty } = Object.prototype;
15
15
  var stringify = configure2();
@@ -1070,7 +1070,7 @@ function resolveAdaptFormats(params) {
1070
1070
  return params.formats ?? [];
1071
1071
  }
1072
1072
 
1073
- // ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/esm/wrapper.js
1073
+ // ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
1074
1074
  var import__ = __toESM(require_safe_stable_stringify(), 1);
1075
1075
  var configure = import__.default.configure;
1076
1076
  var wrapper_default = import__.default;
@@ -1119,7 +1119,6 @@ var OPENROUTER_IMAGE_SIZE = ["1K", "2K", "4K"];
1119
1119
  var OPENROUTER_IMAGE_SIZE_EXTENDED = ["0.5K", ...OPENROUTER_IMAGE_SIZE];
1120
1120
  var OPENROUTER_IMAGE_QUALITY = ["auto", "low", "medium", "high"];
1121
1121
  var SEEDANCE_DURATIONS = [4, 5, 6, 8, 10, 12, 15];
1122
- var KLING_DURATIONS = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
1123
1122
  var ELEVENLABS_OUTPUT_FORMATS = [
1124
1123
  "mp3_22050_32",
1125
1124
  "mp3_44100_32",
@@ -1413,7 +1412,7 @@ var MODEL_REGISTRY = {
1413
1412
  prompt: { kind: "string" },
1414
1413
  negative_prompt: { kind: "string" },
1415
1414
  aspect_ratio: { kind: "string", enum: ["16:9", "9:16"] },
1416
- resolution: { kind: "string", enum: ["720p", "1080p", "4K"] },
1415
+ resolution: { kind: "string", enum: ["720p", "1080p"] },
1417
1416
  duration: { kind: "number", enum: [4, 6, 8] },
1418
1417
  seed: { kind: "number" },
1419
1418
  generate_audio: { kind: "boolean" },
@@ -1425,14 +1424,9 @@ var MODEL_REGISTRY = {
1425
1424
  "kwaivgi/kling-v3.0-pro": {
1426
1425
  // Motion-transfer / dynamic multi-shot beats. Reachable through the default
1427
1426
  // OpenRouter gateway (generic video body — no google-vertex block), so it
1428
- // needs no direct-provider exception. `cfg_scale` trades prompt adherence
1429
- // vs motion freedom; higher = closer to prompt.
1430
- //
1431
- // Params below are the ones OpenRouter actually advertises for this model
1432
- // (GET /api/v1/videos/models). Three were wrong before and each was a free
1433
- // 400 or a silently unsupported knob: it renders 720p ONLY (1080p was
1434
- // offered and rejected), it takes ANY duration from 3-15s (we allowed just
1435
- // 5 and 10), and it does not support `seed` at all.
1427
+ // needs no direct-provider exception. Cost is usage-based (known from the
1428
+ // provider response), so it has no pre-flight cost estimate. `cfg_scale`
1429
+ // trades prompt adherence vs motion freedom; higher = closer to prompt.
1436
1430
  label: "Kling 3.0",
1437
1431
  inputs: [],
1438
1432
  optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
@@ -1442,9 +1436,10 @@ var MODEL_REGISTRY = {
1442
1436
  // over-length prompt fails validate (free) not the billed call.
1443
1437
  prompt: { kind: "string", maxLength: 2500 },
1444
1438
  negative_prompt: { kind: "string" },
1445
- aspect_ratio: { kind: "string", enum: ["16:9", "9:16", "1:1"] },
1446
- resolution: { kind: "string", enum: ["720p"] },
1447
- duration: { kind: "number", enum: KLING_DURATIONS },
1439
+ aspect_ratio: { kind: "string", enum: ["1:1", "16:9", "9:16"] },
1440
+ resolution: { kind: "string", enum: ["720p", "1080p"] },
1441
+ duration: { kind: "number", enum: [5, 10] },
1442
+ seed: { kind: "number" },
1448
1443
  generate_audio: { kind: "boolean" },
1449
1444
  cfg_scale: { kind: "number", min: 0, max: 1 }
1450
1445
  }
@@ -1460,7 +1455,7 @@ var MODEL_REGISTRY = {
1460
1455
  prompt: { kind: "string" },
1461
1456
  negative_prompt: { kind: "string" },
1462
1457
  aspect_ratio: { kind: "string", enum: ["16:9", "9:16"] },
1463
- resolution: { kind: "string", enum: ["720p", "1080p", "4K"] },
1458
+ resolution: { kind: "string", enum: ["720p", "1080p"] },
1464
1459
  duration: { kind: "number", enum: [4, 6, 8] },
1465
1460
  seed: { kind: "number" },
1466
1461
  generate_audio: { kind: "boolean" },
@@ -1782,58 +1777,6 @@ function validateValue(key, value, schema, model) {
1782
1777
  }
1783
1778
  }
1784
1779
 
1785
- // ../canvas-contract/src/videoCost.ts
1786
- var CREDITS_PER_USD = 100;
1787
- var SEEDANCE_USD_PER_SECOND = {
1788
- "480p": 0.18,
1789
- "720p": 0.5,
1790
- "1080p": 0.62,
1791
- "4k": 0.62
1792
- };
1793
- var DEFAULT_VIDEO_RESOLUTION = "720p";
1794
- var DEFAULT_VIDEO_DURATION_S = 5;
1795
- function isSeedanceModel(model) {
1796
- return model.startsWith("bytedance/seedance");
1797
- }
1798
- var FALLBACK_USD_PER_SECOND = 0.5;
1799
- function seedanceUsdPerSecond(resolution) {
1800
- return SEEDANCE_USD_PER_SECOND[resolution ?? DEFAULT_VIDEO_RESOLUTION] ?? FALLBACK_USD_PER_SECOND;
1801
- }
1802
- var USD_PER_SECOND = {
1803
- // Kling publishes one flat rate across every resolution and mode.
1804
- "kwaivgi/kling-v3.0-pro": { silent: 0.112, audio: 0.168 },
1805
- // Veo prices 4K far above the base tier — and 4K is a selectable resolution,
1806
- // so folding it into the base rate under-quotes the dearest clip we can make.
1807
- "google/veo-3.1": {
1808
- silent: 0.2,
1809
- audio: 0.4,
1810
- byResolution: { "4K": { silent: 0.4, audio: 0.6 } }
1811
- },
1812
- "google/veo-3.1-fast": {
1813
- silent: 0.1,
1814
- audio: 0.12,
1815
- byResolution: {
1816
- "720p": { silent: 0.08, audio: 0.1 },
1817
- "4K": { silent: 0.25, audio: 0.3 }
1818
- }
1819
- }
1820
- };
1821
- function estimateVideoCostUsd({ model, duration, resolution, generateAudio }) {
1822
- const seconds = duration ?? DEFAULT_VIDEO_DURATION_S;
1823
- if (isSeedanceModel(model)) return seedanceUsdPerSecond(resolution) * seconds;
1824
- const published = USD_PER_SECOND[model];
1825
- if (published) {
1826
- const rate = (resolution ? published.byResolution?.[resolution] : void 0) ?? published;
1827
- return (generateAudio ? rate.audio : rate.silent) * seconds;
1828
- }
1829
- const dearestKnownRate = Math.max(...Object.values(SEEDANCE_USD_PER_SECOND));
1830
- return dearestKnownRate * seconds;
1831
- }
1832
- function estimateVideoCredits(input) {
1833
- const credits = estimateVideoCostUsd(input) * CREDITS_PER_USD;
1834
- return Math.ceil(Number(credits.toFixed(6)));
1835
- }
1836
-
1837
1780
  // src/engine/lib/concurrency.ts
1838
1781
  var DEFAULT_CONCURRENCY = 8;
1839
1782
  function resolveConcurrency(...candidates) {
@@ -2630,7 +2573,7 @@ function looksLikeHttpUrl(value) {
2630
2573
  var VEO_PERSON_GENERATION = "allow_adult";
2631
2574
  var VEO_NEGATIVE_PROMPT = "subtitles, captions, on-screen text, watermark, logo, warped face, distorted hands, extra fingers, low quality";
2632
2575
  var VEO_DURATIONS = [4, 6, 8];
2633
- var KLING_DURATIONS2 = [5, 10];
2576
+ var KLING_DURATIONS = [5, 10];
2634
2577
  var KLING_NEGATIVE_PROMPT = "warped face, distorted hands, extra fingers, morphing, flicker, on-screen text, watermark, low quality";
2635
2578
  var KLING_CFG_SCALE = 0.7;
2636
2579
  var SPEAKS_PROSE = (line) => `The person speaks to camera; lip-sync follows the dialogue verbatim, with delivery and emotion carried in the wording itself (no bracketed cues). Dialogue: "${line}"`;
@@ -2676,7 +2619,7 @@ var KLING_PROFILE = {
2676
2619
  // Kling takes a negative_prompt PARAM instead (paramDefaults).
2677
2620
  keyframeInstruction: "Preserve the composition and colors of the first frame; animate the motion described.",
2678
2621
  wordBudget: 200,
2679
- durationSet: KLING_DURATIONS2,
2622
+ durationSet: KLING_DURATIONS,
2680
2623
  paramDefaults: { negative_prompt: KLING_NEGATIVE_PROMPT, cfg_scale: KLING_CFG_SCALE }
2681
2624
  };
2682
2625
  function clipProfileFor(modelId) {
@@ -7510,33 +7453,12 @@ var videoGenerateNode = delegated({
7510
7453
  inputs: z28.object({
7511
7454
  first_frame: ImageRef.optional(),
7512
7455
  last_frame: ImageRef.optional(),
7513
- reference: ImageRef.optional(),
7514
- /**
7515
- * Reference-to-video: several images the model uses as visual guidance
7516
- * (this person, this product) instead of an exact opening frame. Mutually
7517
- * exclusive with `first_frame` — OpenRouter treats a request carrying both
7518
- * as image-to-video and ignores these, so wire one or the other.
7519
- */
7520
- references: z28.array(ImageRef).optional()
7456
+ reference: ImageRef.optional()
7521
7457
  }).loose(),
7522
7458
  params: VideoGenerateParams,
7523
7459
  outputs: z28.object({ video: VideoRef }).strict(),
7524
7460
  outputKinds: { video: "video" },
7525
- // Priced from the shared contract, so `validate`'s quote and the charge the
7526
- // backend applies come from ONE table. A flat number here under-quoted a
7527
- // 1080p 8s clip by ~10x, and the agent reads this out to the user as "this
7528
- // will cost N" before spending it.
7529
- cost: ({ params }) => ({
7530
- credits: estimateVideoCredits({
7531
- model: params.model,
7532
- duration: params.duration,
7533
- resolution: params.resolution,
7534
- // Audio bills at a separate, higher rate on every model that offers it
7535
- // (Veo 3.1 doubles), so omitting it quotes half the real cost.
7536
- generateAudio: params.generate_audio
7537
- }),
7538
- seconds_estimate: 120
7539
- })
7461
+ cost: () => ({ credits: 50, seconds_estimate: 120 })
7540
7462
  });
7541
7463
 
7542
7464
  // src/engine/nodes/remote/videoBackgroundRemove.ts
@@ -8019,4 +7941,4 @@ export {
8019
7941
  defaultRegistry,
8020
7942
  createEngineFromEnv
8021
7943
  };
8022
- //# sourceMappingURL=chunk-S43JQWI2.js.map
7944
+ //# sourceMappingURL=chunk-OO5BDH3J.js.map