@koda-sl/baker-cli 0.290.2 → 0.291.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 +5 -1
- package/dist/cli.js +22 -10
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -669,7 +669,7 @@ Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remo
|
|
|
669
669
|
|
|
670
670
|
**A device criterion can be added, but never removed** — `campaign-criteria add` with a device criterion works, but Google then rejects `campaign-criteria remove` on it forever, including on a criterion you added yourself (and Baker refuses the removal before publish). Adjust it instead with `campaign-criteria update customers/<cid>/campaignCriteria/<campaignId>~<criterionId> --bid-modifier <n>`: `1` for no adjustment, `1.25` to bid 25% more, `0.9` to bid 10% less, `0` to stop serving on that device. Locations, languages and ad schedules remove normally.
|
|
671
671
|
|
|
672
|
-
**Images on a Search campaign** — `assets create` with an image publishes it into the advertiser's Google Ads asset library, but Google refuses the asset link that puts an image extension on a Search campaign (`AD_IMAGE` and every marketing-image field type, at campaign, ad group and customer level alike), so `assets attach`
|
|
672
|
+
**Images on a Search campaign** — `assets create` with an image publishes it into the advertiser's Google Ads asset library, but Google refuses the asset link that puts an image extension on a Search campaign (`AD_IMAGE` and every marketing-image field type, at campaign, ad group and customer level alike), so `assets attach` **skips** it at stage time and names what to do instead — the op is never stored, the rest of a batch stages normally, and the skipped link is what records which images belong on which campaign. Staging an image, or asking to link one the account already holds, therefore also stages a **Task** — "Add N images to <campaign> in Google Ads" — which applies with the rest of the chat, so the one step Google keeps inside its own interface is tracked rather than mentioned. Attach the image to a Performance Max asset group (`asset-groups attach`) and the Task withdraws itself. The one image a Search campaign does take through a link is the advertiser's logo: `BUSINESS_LOGO`, at campaign or customer level, on a verified account.
|
|
673
673
|
|
|
674
674
|
**Primary vs secondary conversion actions** — `conversions update <id> --primary` makes an action a primary action (automated bidding optimizes toward it); `--no-primary` (or `--primary=false`) demotes it to secondary (reported only) — the spaced form `--primary false` is rejected, since a boolean flag written with a space is set to true and the word dropped. `conversions create` stages a new action as **secondary** unless its `--file` payload sets `"primaryForGoal": true`, so a new action never silently joins the bidding target of a Maximize Conversions or Target CPA campaign. The staged card shows an "Action optimization" row with the before/after role.
|
|
675
675
|
|
|
@@ -5870,6 +5870,8 @@ baker landing critique spring-offer summer-offer claude # score three landings
|
|
|
5870
5870
|
|
|
5871
5871
|
- **Positioning integrity is scored too, and it is the one copy rule that blocks on a single sentence.** `competitor-concession` fires when the page ranks somebody else above the client, or says the client does not lead at what the page sells ("su constructor es mejor que el nuestro", "we're not the best"). Every other prose rule waits for a cluster because one AI tell proves nothing; this one is the whole defect on its own — the page is paid traffic, and it is arguing the visitor should leave. Two shapes that read identically are exempt by design: a concession about **price**, in either direction ("we are not the cheapest / the most expensive, and here is why" is premium positioning), and a superlative flipped by a negation ("ningún equipo trabaja más rápido que el nuestro"), which is the strongest claim *for* the client. Its warn-tier sibling `competitor-named-in-comparison` fires when a name from `src/content/competitors/` appears in a comparative sentence outside a `<table>` — a comparison table is a section the landing skill teaches, but naming a rival anywhere else on paid traffic is the client's call, not the build's. Spanish, Portuguese and English.
|
|
5872
5872
|
|
|
5873
|
+
- **Comments are not markup, and no rule reads one.** Astro expression comments (`{/* … */}`) and HTML comments (`<!-- … -->`) are blanked before detection, offsets preserved so `file:line` stays exact. A shared scaffold component explaining its `preconnect` in prose that spelled `<img>` twice was read as two broken images — block-tier — so every landing rendering a video failed the publish gate on a file the client cannot edit. Comment prose is also where a component's reasoning lives, em-dashes and buzzwords included, so reading it inflated the count-threshold tells too.
|
|
5874
|
+
|
|
5873
5875
|
- **Copy held in component frontmatter is read like markup copy.** Astro landings keep FAQ pairs, testimonial quotes, feature cards and pricing rows as a `const items = [{ q, a }]` array above the `---` fence and render them in a `.map()`, so a critic that blanked the whole block was blind to a large share of every page's actual words. Prose string literals in that block now feed every copy rule; imports, class strings, URLs and config do not, and the bar is deliberately high in that direction — a Tailwind class list in front of the copy rules is worse than one missed tell.
|
|
5874
5876
|
|
|
5875
5877
|
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.
|
|
@@ -6015,6 +6017,8 @@ Pushing to `main` with changes in `packages/cli/` triggers the GitHub Actions wo
|
|
|
6015
6017
|
|
|
6016
6018
|
- **0.271.0**: **`--format demandGenVideo` — the Demand Gen ad most Demand Gen spend actually runs through can be edited.** A `DEMAND_GEN_VIDEO_RESPONSIVE_AD` keeps its copy in `demandGenVideoResponsiveAd`, which has a `longHeadlines` list no other ad format has and takes its business name as a text asset rather than a string. Baker modelled only the multi-asset Demand Gen ad and labelled all four Demand Gen types as one format, so every served video ad was uneditable: the strict content schema refused `longHeadlines` and named a field instead of a missing format, and the only route left was the Google Ads interface. On the account that surfaced it, Demand Gen was 76% of the spend. New flags on `ads create|update`: `--long-headlines` (up to 5 × 90 chars, this format only), `--video-assets`, `--logo-image-assets`, `--call-to-action-assets`; a create with no video is refused with the `assets create` call to make first, and an omitted media list on an update leaves that media on the ad. Two more gates ship with it. **A content flag a format does not read is now refused instead of dropped** — `--long-headlines` on `--format demandGen` staged the copy without them and reported success, the same silent-drop the backend's strict schemas exist to stop. And **the format is checked against the live `ad_group_ad.ad.type` when it stages**: a mismatch is refused with the right format named, and the two Demand Gen types Baker writes no format for (`DEMAND_GEN_CAROUSEL_AD`, `DEMAND_GEN_PRODUCT_AD`) are refused by name, so "Baker cannot edit this ad" arrives at stage time rather than as a publish failure a Session too late.
|
|
6017
6019
|
|
|
6020
|
+
- **0.291.0**: **the image handover Task no longer depends on having uploaded the images.** It was built only from staged `asset.create` image ops, so a Session that audited a live account — found the brand campaign carrying 22 image extensions and the generic one carrying none, and reached for `assets attach` on assets that were *already in the library* — got Google's refusal and nothing else: no upload to hang a Task on, and a CLI hint telling the agent not to raise one by hand because "Baker stages it itself". The one step only the user can take reached them as a sentence in a chat that scrolls away. An image `assets attach` is now **skipped rather than refused**: it still never reaches Google (re-verified live with `validate_only` on v23 *and* v25 against 3865083115, against a control op that validates clean in the same request — `AD_IMAGE` → `UNSUPPORTED_FIELD_TYPE` at customer and campaign level, `MARKETING_IMAGE` → `FIELD_TYPE_INCOMPATIBLE_WITH_CAMPAIGN_TYPE`, `BUSINESS_LOGO` → `CUSTOMER_NOT_VERIFIED`, which is what proves the field type is the objection; **we are not on Google's allowlist for it**), but the skip carries the image and campaign names, recorded on the draft so the next staged op cannot recompute the Task away, and a batch that also carries sitelinks keeps them instead of failing whole. The schema stopped refusing the field type for the same reason: a boundary refusal has no chat to record a handover against.
|
|
6021
|
+
|
|
6018
6022
|
### Testing a pre-release in sandboxes
|
|
6019
6023
|
|
|
6020
6024
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -2373,7 +2373,6 @@ var IMAGE_ASSET_LINK_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
|
2373
2373
|
"PORTRAIT_MARKETING_IMAGE",
|
|
2374
2374
|
"AD_IMAGE"
|
|
2375
2375
|
]);
|
|
2376
|
-
var IMAGE_ASSET_LINK_MESSAGE = "Google refuses the LINK, not the upload \u2014 so do not stop at this refusal, finish the handover. `assets create` with an image DOES publish: the image lands in the advertiser's Google Ads asset library and is theirs to use. Only this link is refused (MARKETING_IMAGE is a Performance Max asset-group slot \u2014 use `asset-groups attach`; AD_IMAGE comes back UNSUPPORTED_FIELD_TYPE at every level, on every API version). So: stage the image assets anyway, drop this link, and tell the user the images are already in their Google Ads account and only need adding to the campaign under Assets \u2014 name them. Baker stages the Task for that step itself, alongside the image, so it is already in this Session's changes \u2014 do not stage a second one with `baker actions`. Never tell them to upload the images; they are uploaded. The one image a Search campaign does take through a link is the advertiser's logo \u2014 BUSINESS_LOGO at campaign or customer level, on a verified account.";
|
|
2377
2376
|
function isImageAssetLinkFieldType(fieldType) {
|
|
2378
2377
|
return IMAGE_ASSET_LINK_FIELD_TYPES.has(fieldType);
|
|
2379
2378
|
}
|
|
@@ -3377,7 +3376,6 @@ var assetLinkAttachSchema = z5.object({
|
|
|
3377
3376
|
if (value.fieldType === "BUSINESS_LOGO" && value.level === "adGroup") {
|
|
3378
3377
|
ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["fieldType"], message: BUSINESS_LOGO_LEVEL_MESSAGE });
|
|
3379
3378
|
} else if (isImageAssetLinkFieldType(value.fieldType)) {
|
|
3380
|
-
ctx.addIssue({ code: z5.ZodIssueCode.custom, path: ["fieldType"], message: IMAGE_ASSET_LINK_MESSAGE });
|
|
3381
3379
|
} else if (!ASSET_FIELD_TYPES.includes(value.fieldType)) {
|
|
3382
3380
|
ctx.addIssue({
|
|
3383
3381
|
code: z5.ZodIssueCode.custom,
|
|
@@ -16575,8 +16573,8 @@ function imageAssetHints(payload) {
|
|
|
16575
16573
|
return [];
|
|
16576
16574
|
}
|
|
16577
16575
|
return [
|
|
16578
|
-
"This image publishes into the advertiser's Google Ads asset library \u2014 that half works. What Google refuses is the link that puts it on a Search campaign, so
|
|
16579
|
-
"Finish the handover rather than reporting a limitation: tell the user the images are already in their Google Ads account and only need adding to the campaign under Assets, and name the ones you staged. Baker stages the Task for that step itself the moment you stage the image, so it is already in this chat's changes \u2014 do not stage a second one with `baker actions`. Never tell them to upload images they already have. (A Performance Max asset group is different \u2014 images do link there, with `asset-groups attach`.)"
|
|
16576
|
+
"This image publishes into the advertiser's Google Ads asset library \u2014 that half works. What Google refuses is the link that puts it on a Search campaign, so the `assets attach` for it never stores: it is skipped with the reason, and staging it anyway is what tells Baker which campaign these images belong on.",
|
|
16577
|
+
"Finish the handover rather than reporting a limitation: tell the user the images are already in their Google Ads account and only need adding to the campaign under Assets, and name the ones you staged. Baker stages the Task for that step itself the moment you stage the image, so it is already in this chat's changes \u2014 do not stage a second one with `baker actions`. Stage the `assets attach` too if you know which campaign they belong on: it is skipped rather than stored, and it is what puts the campaign's name in that Task. Never tell them to upload images they already have. (A Performance Max asset group is different \u2014 images do link there, with `asset-groups attach`.)"
|
|
16580
16578
|
];
|
|
16581
16579
|
}
|
|
16582
16580
|
var assetsUpdateCommand = defineCommand31({
|
|
@@ -16619,7 +16617,7 @@ var assetsUpdateCommand = defineCommand31({
|
|
|
16619
16617
|
var assetsCommand = defineCommand31({
|
|
16620
16618
|
meta: {
|
|
16621
16619
|
name: "assets",
|
|
16622
|
-
description: "Stage asset create/update + asset-link attach/detach (via --file). For a high-performance Search campaign add \u22654 sitelinks, \u22653 callouts and \u22651 structured snippet per campaign. Image extensions are the one thing you cannot finish here \u2014 `create` uploads the image into the advertiser's account, but Google refuses the link that puts it on a Search campaign, so hand that one step over rather than telling them to upload anything. The image a Search campaign does take through a link is the advertiser's logo \u2014 BUSINESS_LOGO, at campaign or customer level, on a verified account."
|
|
16620
|
+
description: "Stage asset create/update + asset-link attach/detach (via --file). For a high-performance Search campaign add \u22654 sitelinks, \u22653 callouts and \u22651 structured snippet per campaign. Image extensions are the one thing you cannot finish here \u2014 `create` uploads the image into the advertiser's account, but Google refuses the link that puts it on a Search campaign, so hand that one step over rather than telling them to upload anything (stage the `attach` regardless: it is skipped, and it is what records the handover Task). The image a Search campaign does take through a link is the advertiser's logo \u2014 BUSINESS_LOGO, at campaign or customer level, on a verified account."
|
|
16623
16621
|
},
|
|
16624
16622
|
subCommands: {
|
|
16625
16623
|
create: fileCreateCommand(
|
|
@@ -16630,7 +16628,7 @@ var assetsCommand = defineCommand31({
|
|
|
16630
16628
|
),
|
|
16631
16629
|
update: assetsUpdateCommand,
|
|
16632
16630
|
attach: fileCreateCommand("attach", "google.assetLink.attach", "Attach an asset to a campaign/adGroup/customer", [
|
|
16633
|
-
"Image extensions
|
|
16631
|
+
"Image extensions cannot be linked \u2014 Google refuses AD_IMAGE and every marketing-image field type at every level, on every API version \u2014 but stage the link anyway: it is skipped rather than stored, the rest of a batch is unaffected, and it is what records the handover Task naming those images and that campaign. That is the ONLY record when the images are already in the account and you are uploading nothing. Then tell the user they are already in their Google Ads account and only need adding to the campaign under Assets; do not stage a second Task with `baker actions`. Two images that DO link: the advertiser's logo through this command (`BUSINESS_LOGO`, campaign or customer level, on a verified account), and Performance Max asset-group images, which are a different mechanism (`asset-groups attach`)."
|
|
16634
16632
|
]),
|
|
16635
16633
|
detach: statusCommand2("google.assetLink.detach", "asset link")
|
|
16636
16634
|
}
|
|
@@ -48982,6 +48980,20 @@ function blankAstroFrontmatter(text2) {
|
|
|
48982
48980
|
}
|
|
48983
48981
|
return kept.join("") + text2.slice(cut);
|
|
48984
48982
|
}
|
|
48983
|
+
function blankUnrenderedSource(text2) {
|
|
48984
|
+
return blankTemplateComments(blankAstroFrontmatter(text2));
|
|
48985
|
+
}
|
|
48986
|
+
function blankTemplateComments(text2) {
|
|
48987
|
+
const out = text2.split("");
|
|
48988
|
+
const pattern = /\{\s*\/\*[\s\S]*?\*\/\s*\}|<!--[\s\S]*?-->/g;
|
|
48989
|
+
for (const m of text2.matchAll(pattern)) {
|
|
48990
|
+
const start = m.index ?? 0;
|
|
48991
|
+
for (let i = start; i < start + m[0].length && i < out.length; i++) {
|
|
48992
|
+
if (out[i] !== "\n") out[i] = " ";
|
|
48993
|
+
}
|
|
48994
|
+
}
|
|
48995
|
+
return out.join("");
|
|
48996
|
+
}
|
|
48985
48997
|
function frontmatterLength(text2) {
|
|
48986
48998
|
if (!text2.startsWith("---")) return 0;
|
|
48987
48999
|
const lines = text2.split("\n");
|
|
@@ -49355,7 +49367,7 @@ function distinctCompetitorNames(competitors) {
|
|
|
49355
49367
|
return [...byFold.values()];
|
|
49356
49368
|
}
|
|
49357
49369
|
function mentionsInSource(source, names) {
|
|
49358
|
-
const text2 =
|
|
49370
|
+
const text2 = blankUnrenderedSource(source.text);
|
|
49359
49371
|
const visible = blankHtmlKeepingOffsets(text2);
|
|
49360
49372
|
const tables = tableRanges(text2);
|
|
49361
49373
|
const out = [];
|
|
@@ -49395,7 +49407,7 @@ function escapeRegExp(value) {
|
|
|
49395
49407
|
}
|
|
49396
49408
|
function detectSource(source) {
|
|
49397
49409
|
const file = source.path;
|
|
49398
|
-
const text2 =
|
|
49410
|
+
const text2 = blankUnrenderedSource(source.text);
|
|
49399
49411
|
const findings = [];
|
|
49400
49412
|
const lines = text2.split("\n");
|
|
49401
49413
|
for (const matcher of LINE_MATCHERS) {
|
|
@@ -49413,7 +49425,7 @@ function detectSource(source) {
|
|
|
49413
49425
|
}
|
|
49414
49426
|
function detectPage(sources) {
|
|
49415
49427
|
if (sources.length === 0) return [];
|
|
49416
|
-
const combined = sources.map((s) =>
|
|
49428
|
+
const combined = sources.map((s) => blankUnrenderedSource(s.text)).join("\n");
|
|
49417
49429
|
const pageFile = sources.find((s) => s.path === "index.astro" || s.path.endsWith("/index.astro"))?.path ?? sources[0]?.path ?? "index.astro";
|
|
49418
49430
|
const findings = [];
|
|
49419
49431
|
for (const analyzer of ANALYZERS) {
|
|
@@ -49493,7 +49505,7 @@ function describeCounts(findings) {
|
|
|
49493
49505
|
// src/commands/landing/snapshot.ts
|
|
49494
49506
|
import { mkdir as mkdir9, rename as rename2, writeFile as writeFile13 } from "fs/promises";
|
|
49495
49507
|
import path31 from "path";
|
|
49496
|
-
var CRITIC_VERSION = "
|
|
49508
|
+
var CRITIC_VERSION = "4";
|
|
49497
49509
|
function critiqueCacheDir(projectRoot) {
|
|
49498
49510
|
return path31.join(projectRoot, ".cache", "landing-critique");
|
|
49499
49511
|
}
|