@koda-sl/baker-cli 0.165.0 → 0.166.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 +3 -1
- package/dist/cli.js +23 -11
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -296,7 +296,7 @@ Read it before interpreting any null. It does not fire on the `--out` path (rows
|
|
|
296
296
|
| Preset | Description | Default date range |
|
|
297
297
|
|------------------------|------------------------------------------|--------------------|
|
|
298
298
|
| `campaign-performance` | Campaign metrics overview | LAST_30_DAYS |
|
|
299
|
-
| `keyword-analysis` | Keyword performance
|
|
299
|
+
| `keyword-analysis` | Keyword performance per ad group | LAST_30_DAYS |
|
|
300
300
|
| `positive-keywords` | Positive (targeting) keywords only | ALL_TIME |
|
|
301
301
|
| `negative-keywords` | Negative (blocking) keywords only | ALL_TIME |
|
|
302
302
|
| `negative-keyword-lists` | Shared negative lists + their terms | ALL_TIME |
|
|
@@ -582,6 +582,8 @@ Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remo
|
|
|
582
582
|
|
|
583
583
|
**Batch keyword adds** — `keywords add`, `negative-keywords add`, and `keyword-lists add` take a whole batch in one command: comma-separate `--text` entries and/or pass `--file <list.txt>` (one keyword per line). A `:EXACT`/`:PHRASE`/`:BROAD` suffix per entry overrides the `--match-type` default. Batches stage all-or-nothing as one request (limit 500); each keyword still lands as its own draft op, so it stays individually removable/amendable.
|
|
584
584
|
|
|
585
|
+
**Pausing or removing a keyword returns a per-ad-group hint** — `keywords update --status PAUSED|REMOVED` and `keywords remove` come back with hints telling you to base the decision on `keyword_view` rows carrying `ad_group.id` + `ad_group_criterion.criterion_id`, never on campaign totals. A keyword's criterion id is derived from its text + match type, so every copy of the same keyword across ad groups shares one id and any aggregate blends the copy holding the history with the copies that never served. The `keyword-analysis` and `positive-keywords` presets select both ids so one query identifies each copy. A bid or URL edit gets no hint — only a pause/removal can strand history.
|
|
586
|
+
|
|
585
587
|
---
|
|
586
588
|
|
|
587
589
|
### Caching
|
package/dist/cli.js
CHANGED
|
@@ -4436,6 +4436,7 @@ var FIELD_DESCRIPTIONS = {
|
|
|
4436
4436
|
"ad_group_ad.ad.responsive_search_ad.headlines": "Ad headline variations (array)",
|
|
4437
4437
|
"ad_group_ad.ad.responsive_search_ad.descriptions": "Ad description variations (array)",
|
|
4438
4438
|
// Keywords
|
|
4439
|
+
"ad_group_criterion.criterion_id": "Keyword criterion id \u2014 derived from text + match type, so every copy of the same keyword across ad groups shares it. Pair with ad_group.id to identify one copy (writes take customers/<cid>/adGroupCriteria/<adGroupId>~<criterionId>)",
|
|
4439
4440
|
"ad_group_criterion.keyword.text": "The keyword text",
|
|
4440
4441
|
"ad_group_criterion.keyword.match_type": "EXACT, PHRASE, or BROAD",
|
|
4441
4442
|
"ad_group_criterion.status": "ENABLED, PAUSED, or REMOVED",
|
|
@@ -7083,8 +7084,8 @@ async function stageGoogleOps(rawOps, hints) {
|
|
|
7083
7084
|
async function stageUpdate(kind, customerId, target, payload, hints) {
|
|
7084
7085
|
await stageGoogleOp({ kind, customerId, target, payload }, hints);
|
|
7085
7086
|
}
|
|
7086
|
-
async function stageTarget(kind, customerId, target) {
|
|
7087
|
-
await stageGoogleOp({ kind, customerId, target });
|
|
7087
|
+
async function stageTarget(kind, customerId, target, hints) {
|
|
7088
|
+
await stageGoogleOp({ kind, customerId, target }, hints);
|
|
7088
7089
|
}
|
|
7089
7090
|
async function draftAction(path28, body) {
|
|
7090
7091
|
try {
|
|
@@ -7894,7 +7895,7 @@ var PRESETS = [
|
|
|
7894
7895
|
{
|
|
7895
7896
|
name: "keyword-analysis",
|
|
7896
7897
|
description: "Keyword performance with match type and quality",
|
|
7897
|
-
gaqlTemplate: `SELECT campaign.id, campaign.name, campaign.status, ad_group.name, ad_group.status, ad_group_criterion.keyword.text, ad_group_criterion.keyword.match_type, ad_group_criterion.status, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions, metrics.ctr FROM keyword_view WHERE segments.date DURING {dateRange}{statusScope} ORDER BY metrics.impressions DESC LIMIT {limit}`,
|
|
7898
|
+
gaqlTemplate: `SELECT campaign.id, campaign.name, campaign.status, ad_group.id, ad_group.name, ad_group.status, ad_group_criterion.criterion_id, ad_group_criterion.keyword.text, ad_group_criterion.keyword.match_type, ad_group_criterion.status, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions, metrics.ctr FROM keyword_view WHERE segments.date DURING {dateRange}{statusScope} ORDER BY metrics.impressions DESC LIMIT {limit}`,
|
|
7898
7899
|
defaultDateRange: "LAST_30_DAYS",
|
|
7899
7900
|
defaultLimit: 200,
|
|
7900
7901
|
statusFields: ["campaign.status", "ad_group.status", "ad_group_criterion.status"]
|
|
@@ -7902,7 +7903,7 @@ var PRESETS = [
|
|
|
7902
7903
|
{
|
|
7903
7904
|
name: "positive-keywords",
|
|
7904
7905
|
description: "Positive (targeting) keywords only \u2014 excludes negatives",
|
|
7905
|
-
gaqlTemplate: `SELECT campaign.id, campaign.name, campaign.status, ad_group.name, ad_group.status, ad_group_criterion.keyword.text, ad_group_criterion.keyword.match_type, ad_group_criterion.status, ad_group_criterion.negative FROM ad_group_criterion WHERE ad_group_criterion.type = 'KEYWORD' AND ad_group_criterion.negative = FALSE{statusScope} ORDER BY campaign.name LIMIT {limit}`,
|
|
7906
|
+
gaqlTemplate: `SELECT campaign.id, campaign.name, campaign.status, ad_group.id, ad_group.name, ad_group.status, ad_group_criterion.criterion_id, ad_group_criterion.keyword.text, ad_group_criterion.keyword.match_type, ad_group_criterion.status, ad_group_criterion.negative FROM ad_group_criterion WHERE ad_group_criterion.type = 'KEYWORD' AND ad_group_criterion.negative = FALSE{statusScope} ORDER BY campaign.name LIMIT {limit}`,
|
|
7906
7907
|
defaultDateRange: "ALL_TIME",
|
|
7907
7908
|
defaultLimit: 500,
|
|
7908
7909
|
statusFields: ["campaign.status", "ad_group.status", "ad_group_criterion.status"]
|
|
@@ -8743,7 +8744,7 @@ var campaignsCommand = defineCommand30({
|
|
|
8743
8744
|
remove: statusCommand2("google.campaign.remove", "campaign")
|
|
8744
8745
|
}
|
|
8745
8746
|
});
|
|
8746
|
-
function statusCommand2(kind, entity) {
|
|
8747
|
+
function statusCommand2(kind, entity, hints) {
|
|
8747
8748
|
return defineCommand30({
|
|
8748
8749
|
meta: { name: kind.split(".")[2], description: `Stage a ${entity} ${kind.split(".")[2]}` },
|
|
8749
8750
|
args: {
|
|
@@ -8752,7 +8753,7 @@ function statusCommand2(kind, entity) {
|
|
|
8752
8753
|
},
|
|
8753
8754
|
run: async ({ args }) => {
|
|
8754
8755
|
const customerId = requireCustomerId(args);
|
|
8755
|
-
await stageTarget(kind, customerId, requireTarget(args, entity));
|
|
8756
|
+
await stageTarget(kind, customerId, requireTarget(args, entity), hints);
|
|
8756
8757
|
}
|
|
8757
8758
|
});
|
|
8758
8759
|
}
|
|
@@ -8807,6 +8808,14 @@ var adGroupsCommand = defineCommand30({
|
|
|
8807
8808
|
remove: statusCommand2("google.adGroup.remove", "ad group")
|
|
8808
8809
|
}
|
|
8809
8810
|
});
|
|
8811
|
+
var KEYWORD_KILL_HINTS = [
|
|
8812
|
+
"Keyword metrics are per ad group. Decide this from keyword_view rows carrying ad_group.id + ad_group_criterion.criterion_id (`--preset keyword-analysis` returns both) \u2014 never from campaign totals or metrics summed across ad groups.",
|
|
8813
|
+
"The same keyword text in several ad groups shares ONE criterion id, so an aggregate blends the copy that holds the history with the copies that never served. Compare the copies row by row and keep the one with the impressions/conversions; if the keyword is duplicated and only one copy performs, pause the others, not it."
|
|
8814
|
+
];
|
|
8815
|
+
function keywordKillHints(status) {
|
|
8816
|
+
const kills = typeof status === "string" && ["PAUSED", "REMOVED"].includes(status.toUpperCase());
|
|
8817
|
+
return kills ? KEYWORD_KILL_HINTS : [];
|
|
8818
|
+
}
|
|
8810
8819
|
var keywordWriteSubcommands = {
|
|
8811
8820
|
add: defineCommand30({
|
|
8812
8821
|
meta: { name: "add", description: "Add keyword(s) to an ad group \u2014 one --text or a whole batch" },
|
|
@@ -8841,13 +8850,16 @@ var keywordWriteSubcommands = {
|
|
|
8841
8850
|
args: { ...customerIdArg, "cpc-bid": { type: "string" }, status: { type: "string" } },
|
|
8842
8851
|
run: async ({ args }) => {
|
|
8843
8852
|
const customerId = requireCustomerId(args);
|
|
8844
|
-
await stageUpdate(
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8853
|
+
await stageUpdate(
|
|
8854
|
+
"google.keyword.update",
|
|
8855
|
+
customerId,
|
|
8856
|
+
requireTarget(args, "keyword"),
|
|
8857
|
+
{ cpcBidMicros: microsFlag(args["cpc-bid"], "--cpc-bid"), status: args.status },
|
|
8858
|
+
keywordKillHints(args.status)
|
|
8859
|
+
);
|
|
8848
8860
|
}
|
|
8849
8861
|
}),
|
|
8850
|
-
remove: statusCommand2("google.keyword.remove", "keyword")
|
|
8862
|
+
remove: statusCommand2("google.keyword.remove", "keyword", KEYWORD_KILL_HINTS)
|
|
8851
8863
|
};
|
|
8852
8864
|
var NEGATIVE_COVERAGE_HINTS = [
|
|
8853
8865
|
"Before publishing, confirm each term isn't already blocked: run `baker ads google query --preset negative-keyword-lists` (shared-list terms) and `--preset negative-keywords` (campaign/ad-group negatives).",
|