@koda-sl/baker-cli 0.177.0 → 0.178.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 +1 -0
- package/dist/cli.js +19 -3
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5011,6 +5011,7 @@ This CLI is designed for AI agent consumption. Key patterns:
|
|
|
5011
5011
|
|
|
5012
5012
|
- **0.177.0**: `baker ga4` can change what a property measures, not only report on it. Five staged write families — `key-event` (create|update|delete), `custom-dimension` and `custom-metric` (create|update|archive), `custom-event` (create|update|delete), `data-retention set` — plus `baker ga4 config` to read the property's current configuration and `baker ga4 draft` (list|show|amend|remove|clear) to review the staged set. `custom-event` is GA4's "Create event" rule: build a new event out of one the site already sends (a `form_submit` on `/quote` becomes `quote_request`), then mark it a key event so Google Ads imports it — the loop that previously ended with "do this by hand in Analytics". Staging follows Tag Manager: nothing is sent during the chat, the user reviews the whole set in the dashboard's **Analytics** tab, and it applies when they complete the chat. Unlike Tag Manager there is no version left over — an applied change is in effect at once, and there is no undo, so removals ask for confirmation first. Everything Google would reject is refused at stage time against a fresh read of the property rather than discovered at publish: a duplicate `eventName` or `parameterName` (including against another change staged on the same chat), an immutable field in an update, a currency metric with no restricted type, a retention window past 14 months on a standard property, a target the property does not have. Marking an event Google fires by itself (`page_view`, `scroll`, …) as a key event is a **warning**, not a refusal — it is legal, it just inflates conversions and poisons Smart Bidding. The one thing that is *not* a duplicate is the replacement of something the same chat has already staged the removal of: ops apply in stage order, so `custom-dimension archive 12` followed by a create on the same `parameterName` works, which is the only way to change a field GA4 marks immutable. Every `create` also takes a JSON array and stages the list in one request, because each stage costs a full read of the property. One chat stages changes for one property. **The OAuth scope changed:** Baker now requests `analytics.edit`, so every Google Analytics connection made before this is read-only until it is reconnected; the write commands refuse up front with that as the fix, and `baker ga4 config` reports `canWrite`. Reporting is unaffected. Not covered, and reported as such by `baker capabilities`: property create/rename/merge, data streams, the Google Ads link, and audiences.
|
|
5013
5013
|
- **0.169.0**: `baker capabilities` — one call, before any platform work, that answers what Baker **cannot** do. Everything else (`baker schema`, the family docs, `--help`) describes what it can, so the only way to learn a capability was absent was to try it, usually after telling the user it would happen. Per surface (`google-ads`, `meta-ads`, `linkedin-ads`, `x-ads`, `tag-manager`, `ga4`, `gsc`) it reports: what is connected and which accounts/properties/containers were picked; whether writes are **live** or **simulated** for this company, and that Tag Manager has neither mode — completing the chat changes the real container; and a `limits` list separating *not built yet* from *the platform forbids it* from *we hold no permission for it*, because each leads to something different to tell the user. Naming one surface (`baker capabilities google-ads`) also returns `ops`: every change with its required and optional fields and the exact values each accepts — so "is there a field to demote a conversion action, and how is it spelled?" is one call instead of four guesses. Almost everything is derived rather than written down: the accounts and permissions come from the connection, the write mode from the company's gate, the writable entities and field contracts from the same schemas the backend parses with, and read-only vs read-write from the OAuth scopes the app actually requests (which is why "GA4 is read-only" is a fact, not a sentence). The handful of hand-written entries each carry a machine-checkable claim about the code behind them, and a coverage test fails when one stops holding. A rejected staged write on Google, Meta or LinkedIn now points at this command instead of leaving the agent to guess another field name.
|
|
5014
|
+
- **0.178.0**: `baker ads google keywords add --final-url` is now described and nudged as the exception it is. A keyword-level final URL overrides the ad's landing page for every click on that keyword, and the flag applies to every keyword in the call — so one URL pasted across a batch restates the ad's own destination on each row while making the change review read like a per-keyword routing decision. The flag's description says to leave it unset unless the destination is keyword-specific, and staging with it returns a hint: on a batch, that the shared page belongs on the ad (`ads create --final-url`) and the keywords should be re-staged without it; on a single keyword, to keep it only where that keyword's destination differs. The review surface changed to match — a staged keyword's final URL is now shown in full on its row in the change card (it used to be visible only on a keyword *update*), so a URL that adds nothing is visible to the user rather than buried.
|
|
5014
5015
|
|
|
5015
5016
|
## Publishing
|
|
5016
5017
|
|
package/dist/cli.js
CHANGED
|
@@ -2892,6 +2892,7 @@ var historyCategorySchema = z9.enum([
|
|
|
2892
2892
|
"creative",
|
|
2893
2893
|
"report",
|
|
2894
2894
|
"domain",
|
|
2895
|
+
"hosting",
|
|
2895
2896
|
"integration",
|
|
2896
2897
|
"tag_manager"
|
|
2897
2898
|
]);
|
|
@@ -9962,6 +9963,16 @@ function keywordKillHints(status) {
|
|
|
9962
9963
|
const kills = typeof status === "string" && ["PAUSED", "REMOVED"].includes(status.toUpperCase());
|
|
9963
9964
|
return kills ? KEYWORD_KILL_HINTS : [];
|
|
9964
9965
|
}
|
|
9966
|
+
function keywordFinalUrlHints(finalUrl, count) {
|
|
9967
|
+
if (typeof finalUrl !== "string" || finalUrl.length === 0) {
|
|
9968
|
+
return [];
|
|
9969
|
+
}
|
|
9970
|
+
return count > 1 ? [
|
|
9971
|
+
`--final-url applied the same landing page to all ${count} keywords. A keyword-level URL only earns its place when that keyword must land somewhere the ad doesn't \u2014 one URL shared by every keyword belongs on the ad instead. Re-stage without --final-url unless the destinations genuinely differ per keyword, and set the ad's final URL to this page.`
|
|
9972
|
+
] : [
|
|
9973
|
+
"Keep --final-url only if this keyword's destination differs from the ad's final URL \u2014 otherwise drop it and let the keyword inherit the ad's landing page."
|
|
9974
|
+
];
|
|
9975
|
+
}
|
|
9965
9976
|
var keywordWriteSubcommands = {
|
|
9966
9977
|
add: defineCommand30({
|
|
9967
9978
|
meta: { name: "add", description: "Add keyword(s) to an ad group \u2014 one --text or a whole batch" },
|
|
@@ -9975,19 +9986,24 @@ var keywordWriteSubcommands = {
|
|
|
9975
9986
|
file: { type: "string", description: "Text file with one keyword[:MATCH_TYPE] per line" },
|
|
9976
9987
|
"match-type": { type: "string", description: "Default match type: EXACT | PHRASE | BROAD" },
|
|
9977
9988
|
"cpc-bid": { type: "string", description: "Keyword CPC bid in major units (applies to all)" },
|
|
9978
|
-
"final-url": {
|
|
9989
|
+
"final-url": {
|
|
9990
|
+
type: "string",
|
|
9991
|
+
description: "Rarely needed: send THIS keyword to a page the ad doesn't go to. Applies to every keyword in the call, so leave it unset unless the destination is keyword-specific \u2014 otherwise set the ad's final URL"
|
|
9992
|
+
}
|
|
9979
9993
|
},
|
|
9980
9994
|
run: async ({ args }) => {
|
|
9981
9995
|
const customerId = requireCustomerId(args);
|
|
9982
9996
|
const adGroup = requireStringFlag(args["ad-group-ref"], "--ad-group-ref");
|
|
9983
9997
|
const cpcBidMicros = microsFlag(args["cpc-bid"], "--cpc-bid");
|
|
9984
9998
|
const finalUrls = args["final-url"] ? [args["final-url"]] : void 0;
|
|
9999
|
+
const entries = keywordEntries(args);
|
|
9985
10000
|
await stageGoogleOps(
|
|
9986
|
-
|
|
10001
|
+
entries.map((entry) => ({
|
|
9987
10002
|
kind: "google.keyword.add",
|
|
9988
10003
|
customerId,
|
|
9989
10004
|
payload: { adGroup, text: entry.text, matchType: entry.matchType, cpcBidMicros, finalUrls }
|
|
9990
|
-
}))
|
|
10005
|
+
})),
|
|
10006
|
+
keywordFinalUrlHints(args["final-url"], entries.length)
|
|
9991
10007
|
);
|
|
9992
10008
|
}
|
|
9993
10009
|
}),
|