siluzan-tso-cli 1.1.62-beta.1 → 1.1.62-beta.3
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 -1
- package/dist/index.js +66 -18
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/analytics/account-analytics.md +4 -1
- package/dist/skill/references/core/playbooks.md +1 -1
- package/dist/skill/references/google-ads/google-ads-read.md +10 -2
- package/dist/skill/references/google-ads/google-ads-write.md +5 -5
- package/dist/skill/references/google-ads/google-ads.md +4 -1
- package/dist/skill/references/google-ads/rules/google-ads-account-audit-assets.md +2 -2
- package/dist/skill/references/google-ads/rules/google-ads-campaign-optimization.md +1 -1
- package/dist/skill/references/operations/hosted-automation-optimize-scale.md +7 -9
- package/dist/skill/references/operations/hosted-automation-self-control.md +5 -5
- package/dist/skill/references/report-templates/google-period-report-excel.md +1 -1
- package/dist/skill/report-templates/google-period-report-excel.md +1 -1
- package/dist/skill/scripts/install.ps1 +1 -1
- package/dist/skill/scripts/install.sh +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
|
|
|
51
51
|
siluzan-tso init --force # 强制覆盖已存在文件
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
> **注意**:当前为测试版(1.1.62-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.62-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -6529,9 +6529,13 @@ function buildOutlineHints(section) {
|
|
|
6529
6529
|
if (section === "campaigns") {
|
|
6530
6530
|
hints.push(
|
|
6531
6531
|
...GOOGLE_ANALYSIS_CAMPAIGNS_OUTLINE_BUDGET_HINTS,
|
|
6532
|
-
...GOOGLE_ANALYSIS_CAMPAIGNS_COMPETITIVE_METRICS_HINTS
|
|
6532
|
+
...GOOGLE_ANALYSIS_CAMPAIGNS_COMPETITIVE_METRICS_HINTS,
|
|
6533
|
+
...GOOGLE_ANALYSIS_CONVERSIONS_VALUE_HINTS
|
|
6533
6534
|
);
|
|
6534
6535
|
}
|
|
6536
|
+
if (section === "ads" || section === "keywords" || section === "search-terms") {
|
|
6537
|
+
hints.push(...GOOGLE_ANALYSIS_CONVERSIONS_VALUE_HINTS);
|
|
6538
|
+
}
|
|
6535
6539
|
if (section === "ads") {
|
|
6536
6540
|
hints.push(...GOOGLE_ANALYSIS_ADS_OUTLINE_BUDGET_HINTS);
|
|
6537
6541
|
}
|
|
@@ -6651,7 +6655,7 @@ async function writeGoogleAnalysisSnapshot(params) {
|
|
|
6651
6655
|
agentHint: OUTLINE_AGENT_HINT
|
|
6652
6656
|
};
|
|
6653
6657
|
}
|
|
6654
|
-
var LEGACY_MANIFEST_FILE, CLI_PACKAGE, SCHEMA_VERSION2, DEFAULT_FIELD_GUIDE, GOOGLE_ANALYSIS_CAMPAIGNS_OUTLINE_BUDGET_HINTS, GOOGLE_ANALYSIS_ADS_OUTLINE_BUDGET_HINTS, GOOGLE_ANALYSIS_CAMPAIGNS_COMPETITIVE_METRICS_HINTS, RATE_BEARING_SECTIONS, GOOGLE_ANALYSIS_RATE_NORMALIZED_OUTLINE_HINTS, GOOGLE_ANALYSIS_ENVELOPE_OUTLINE_HINTS, GOOGLE_ANALYSIS_OVERVIEW_ACTIVE_DAYS_HINTS, GOOGLE_ANALYSIS_ZH_FIELD_HINTS_BY_SECTION;
|
|
6658
|
+
var LEGACY_MANIFEST_FILE, CLI_PACKAGE, SCHEMA_VERSION2, DEFAULT_FIELD_GUIDE, GOOGLE_ANALYSIS_CAMPAIGNS_OUTLINE_BUDGET_HINTS, GOOGLE_ANALYSIS_ADS_OUTLINE_BUDGET_HINTS, GOOGLE_ANALYSIS_CONVERSIONS_VALUE_HINTS, GOOGLE_ANALYSIS_CAMPAIGNS_COMPETITIVE_METRICS_HINTS, RATE_BEARING_SECTIONS, GOOGLE_ANALYSIS_RATE_NORMALIZED_OUTLINE_HINTS, GOOGLE_ANALYSIS_ENVELOPE_OUTLINE_HINTS, GOOGLE_ANALYSIS_OVERVIEW_ACTIVE_DAYS_HINTS, GOOGLE_ANALYSIS_ZH_FIELD_HINTS_BY_SECTION;
|
|
6655
6659
|
var init_google_analysis = __esm({
|
|
6656
6660
|
"src/utils/snapshot/google-analysis.ts"() {
|
|
6657
6661
|
"use strict";
|
|
@@ -6674,6 +6678,9 @@ var init_google_analysis = __esm({
|
|
|
6674
6678
|
GOOGLE_ANALYSIS_ADS_OUTLINE_BUDGET_HINTS = [
|
|
6675
6679
|
"// \u91D1\u989D\uFF1A`ads[].spend` / `averageCpc` / `costPerConversion` \u843D\u76D8\u524D\u5DF2\u7EDF\u4E00\u4E3A**\u5143**\uFF08`costPerConversion` \u7531\u7F51\u5173\u5FAE\u5143 \xF71e6\uFF09\uFF1B**\u7981\u6B62**\u518D\u5BF9 `costPerConversion` \u505A \xF71_000_000\u3002"
|
|
6676
6680
|
];
|
|
6681
|
+
GOOGLE_ANALYSIS_CONVERSIONS_VALUE_HINTS = [
|
|
6682
|
+
"// \u8F6C\u5316\u4EF7\u503C\uFF1A`conversionsValue` \u662F\u8D26\u6237\u5E01\u79CD\u91D1\u989D\uFF08Google\u300C\u8F6C\u5316\u4EF7\u503C\u300D\uFF09\uFF0C\u4E0D\u662F `conversions`\uFF08\u6B21\u6570\uFF09\u3002\u62A5\u544A\u5199\u8F6C\u5316\u4EF7\u503C\u6216 ROAS \u65F6\u7528\u5B83\uFF1AROAS = conversionsValue / spend\uFF08spend \u4E3A 0 \u65F6\u8BE5\u683C\u5199\u300C\u2014\u300D\uFF09\u3002\u7981\u6B62\u7528\u6D88\u8017\u3001\u70B9\u51FB\u3001\u8F6C\u5316\u6B21\u6570\u81EA\u884C\u4F30\u7B97\u3002\u884C\u4E0A\u6CA1\u6709\u8BE5\u5B57\u6BB5\u65F6\u5199\u300C\u672A\u8FD4\u56DE\u300D\uFF0C\u7981\u6B62\u628A\u7CFB\u5217\u4EF7\u503C\u644A\u5230\u8BCD\u6216\u641C\u7D22\u5B57\u8BCD\u4E0A\u3002"
|
|
6683
|
+
];
|
|
6677
6684
|
GOOGLE_ANALYSIS_CAMPAIGNS_COMPETITIVE_METRICS_HINTS = [
|
|
6678
6685
|
"// \u7ADE\u4E89\u6307\u6807\uFF1A`campaigns[].competitiveMetrics` \u4E3A **0~1 \u5C0F\u6570**\uFF08\u5982 `0.0999` = 9.99%\uFF09\uFF0C\u542B Top/AbsoluteTop/Content/ClickShare \u7B49\uFF1B\u65E0\u6570\u636E\u65F6\u4E3A `null`\u3002",
|
|
6679
6686
|
"// \u884C\u9876 `searchImpressionShare` / `searchBudgetLostImpressionShare` / `searchRankLostImpressionShare` \u843D\u76D8\u524D\u5DF2\u5F52\u4E00\u4E3A **0~1 \u5C0F\u6570**\uFF08\u4E0E `competitiveMetrics` \u540C\u540D\u9879\u4E00\u81F4\uFF09\uFF1B\u5C55\u793A\u4E3A % \u65F6 \xD7100\u3002",
|
|
@@ -115352,7 +115359,8 @@ ${label2}\uFF08\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672
|
|
|
115352
115359
|
{ key: "impressions", header: "\u5C55\u793A" },
|
|
115353
115360
|
{ key: "clicks", header: "\u70B9\u51FB" },
|
|
115354
115361
|
{ key: "ctr", header: "\u70B9\u51FB\u7387" },
|
|
115355
|
-
{ key: "spend", header: "\u8D39\u7528" }
|
|
115362
|
+
{ key: "spend", header: "\u8D39\u7528" },
|
|
115363
|
+
{ key: "conversionsValue", header: "\u8F6C\u5316\u4EF7\u503C" }
|
|
115356
115364
|
];
|
|
115357
115365
|
const rows = items3.map((item) => {
|
|
115358
115366
|
const kwText = String(item["text"] ?? item["keywordText"] ?? "\u2014");
|
|
@@ -115362,6 +115370,8 @@ ${label2}\uFF08\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672
|
|
|
115362
115370
|
const matchType = String(item["matchTypeV2"] ?? "");
|
|
115363
115371
|
const ctr = item["ctr"] != null ? (Number(item["ctr"]) * 100).toFixed(2) + "%" : "\u2014";
|
|
115364
115372
|
const spend = item["spend"] != null ? Number(item["spend"]).toFixed(2) : "\u2014";
|
|
115373
|
+
const valueRaw = item["conversionsValue"];
|
|
115374
|
+
const conversionsValue = typeof valueRaw === "number" && Number.isFinite(valueRaw) ? valueRaw.toFixed(2) : "\u2014";
|
|
115365
115375
|
return {
|
|
115366
115376
|
keyword: kwText.slice(0, kwW),
|
|
115367
115377
|
campaign: campaign.slice(0, campW),
|
|
@@ -115371,7 +115381,8 @@ ${label2}\uFF08\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672
|
|
|
115371
115381
|
impressions: String(item["impressions"] ?? 0),
|
|
115372
115382
|
clicks: String(item["clicks"] ?? 0),
|
|
115373
115383
|
ctr,
|
|
115374
|
-
spend
|
|
115384
|
+
spend,
|
|
115385
|
+
conversionsValue
|
|
115375
115386
|
};
|
|
115376
115387
|
});
|
|
115377
115388
|
printCliTable(rows, cols);
|
|
@@ -115595,8 +115606,10 @@ function findKeywordEditMismatches(requested, returned, changed) {
|
|
|
115595
115606
|
);
|
|
115596
115607
|
}
|
|
115597
115608
|
if (changed.maxCpc !== void 0 && typeof returned["maxCPC"] === "number" && returned["maxCPC"] !== Number(requested["maxCPC"])) {
|
|
115609
|
+
const expectedCents = Number(requested["maxCPC"]);
|
|
115610
|
+
const actualCents = returned["maxCPC"];
|
|
115598
115611
|
mismatches.push(
|
|
115599
|
-
`maxCPC\
|
|
115612
|
+
`maxCPC\uFF1A\u671F\u671B ${expectedCents / 100} \u5143\uFF0C\u5217\u8868\u4ECD\u662F ${actualCents / 100} \u5143\u3002\u5173\u952E\u8BCD\u5217\u8868\u6309\u8D26\u6237\u548C\u65E5\u671F\u7F13\u5B58\u7EA6 60 \u79D2\uFF0C\u4E14\u6CA1\u6709 --fresh\uFF0C\u8FD9\u6B21\u56DE\u8BFB\u53EF\u80FD\u4ECD\u662F\u5199\u5165\u524D\u7684\u503C\u3002\u4E0D\u8981\u7ACB\u523B\u91CD\u8DD1 keyword-edit\uFF0C\u4E5F\u4E0D\u8981\u5220\u8BCD\u91CD\u5EFA\u3002\u7EA6 60 \u79D2\u540E\u7528 ad keywords --json-out \u770B\u8BE5 id \u7684 maxCPCYuan\uFF0C\u5DF2\u7ECF\u662F\u76EE\u6807\u503C\u5C31\u5F53\u6210\u529F\u3002`
|
|
115600
115613
|
);
|
|
115601
115614
|
}
|
|
115602
115615
|
if (changed.status !== void 0 && typeof returned["userStatusV2"] === "string" && str3(returned["userStatusV2"]).toLowerCase() !== changed.status.toLowerCase()) {
|
|
@@ -117339,10 +117352,11 @@ async function runAdCampaigns(opts) {
|
|
|
117339
117352
|
}
|
|
117340
117353
|
const rawItems = data.data ?? [];
|
|
117341
117354
|
const items3 = rawItems.map((item) => {
|
|
117342
|
-
const budgetYuan = toDisplayMoney(item.budget);
|
|
117343
117355
|
return {
|
|
117344
117356
|
...item,
|
|
117345
|
-
budget:
|
|
117357
|
+
budget: toDisplayMoney(item.budget),
|
|
117358
|
+
targetSpendBidCeilingYuan: toDisplayMoney(item["targetSpend_BidCeilingAmount"]),
|
|
117359
|
+
targetCpaAmountYuan: toDisplayMoney(item["targetCpa_BidingAmount"]),
|
|
117346
117360
|
statusDisplay: formatGoogleCampaignListStatus(item)
|
|
117347
117361
|
};
|
|
117348
117362
|
});
|
|
@@ -119283,12 +119297,15 @@ async function runAdSearchTerms(opts) {
|
|
|
119283
119297
|
{ key: "impressions", header: "\u5C55\u793A" },
|
|
119284
119298
|
{ key: "clicks", header: "\u70B9\u51FB" },
|
|
119285
119299
|
{ key: "ctr", header: "\u70B9\u51FB\u7387" },
|
|
119286
|
-
{ key: "cost", header: "\u8D39\u7528" }
|
|
119300
|
+
{ key: "cost", header: "\u8D39\u7528" },
|
|
119301
|
+
{ key: "conversionsValue", header: "\u8F6C\u5316\u4EF7\u503C" }
|
|
119287
119302
|
];
|
|
119288
119303
|
const rows = items3.map((item) => {
|
|
119289
119304
|
const term = String(item["searchTermText"] ?? "");
|
|
119290
119305
|
const ctr = item["ctr"] != null ? (Number(item["ctr"]) * 100).toFixed(2) + "%" : "\u2014";
|
|
119291
119306
|
const cost = item["cost"] != null ? Number(item["cost"]).toFixed(2) : "\u2014";
|
|
119307
|
+
const valueRaw = item["conversionsValue"];
|
|
119308
|
+
const conversionsValue = typeof valueRaw === "number" && Number.isFinite(valueRaw) ? valueRaw.toFixed(2) : "\u2014";
|
|
119292
119309
|
return {
|
|
119293
119310
|
term: term.slice(0, termW),
|
|
119294
119311
|
campaign: String(item["campaignName"] ?? "").slice(0, campW),
|
|
@@ -119297,7 +119314,8 @@ async function runAdSearchTerms(opts) {
|
|
|
119297
119314
|
impressions: String(item["impressions"] ?? 0),
|
|
119298
119315
|
clicks: String(item["clicks"] ?? 0),
|
|
119299
119316
|
ctr,
|
|
119300
|
-
cost
|
|
119317
|
+
cost,
|
|
119318
|
+
conversionsValue
|
|
119301
119319
|
};
|
|
119302
119320
|
});
|
|
119303
119321
|
printCliTable(rows, cols);
|
|
@@ -126557,9 +126575,7 @@ function register21(program2) {
|
|
|
126557
126575
|
verbose: opts.verbose
|
|
126558
126576
|
});
|
|
126559
126577
|
});
|
|
126560
|
-
adCmd.command("campaign-edit").description(
|
|
126561
|
-
"\u7F16\u8F91\u641C\u7D22\u5E7F\u544A\u7CFB\u5217\u3002\u540D\u79F0\u3001\u9884\u7B97\u3001\u51FA\u4EF7\u3001\u6295\u653E\u7F51\u7EDC\u90FD\u8D70\u5B57\u6BB5\u7EA7 PATCH\u3002\u81F3\u5C11\u4F20\u4E00\u4E2A\u4FEE\u6539\u9879"
|
|
126562
|
-
).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").option("--name <name>", "\u65B0\u540D\u79F0").option(
|
|
126578
|
+
adCmd.command("campaign-edit").description("\u7F16\u8F91\u641C\u7D22\u5E7F\u544A\u7CFB\u5217\u3002\u540D\u79F0\u3001\u9884\u7B97\u3001\u51FA\u4EF7\u3001\u6295\u653E\u7F51\u7EDC\u90FD\u8D70\u5B57\u6BB5\u7EA7 PATCH\u3002\u81F3\u5C11\u4F20\u4E00\u4E2A\u4FEE\u6539\u9879").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").option("--name <name>", "\u65B0\u540D\u79F0").option(
|
|
126563
126579
|
"--budget <amount>",
|
|
126564
126580
|
"\u65B0\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08\u5982 100 \u8868\u793A \xA5100\uFF1B\u652F\u6301\u5C0F\u6570\uFF1BCLI \u5185\u90E8\u81EA\u52A8 \xD7100 \u5199\u5165\u300C\u5206\u300D\u5B57\u6BB5\uFF09",
|
|
126565
126581
|
parseFloat
|
|
@@ -126693,7 +126709,7 @@ function register21(program2) {
|
|
|
126693
126709
|
'\u65B0\u5339\u914D\u7C7B\u578B\uFF1ABroad | Phrase | Exact\uFF08\u5199 matchTypeV2\uFF0C\u5E76\u9ED8\u8BA4\u540C\u6B65\u6539\u5199 keywordText \u4E3A\u8BCD\u5E72/"\u8BCD"/[\u8BCD] \u4EE5\u7B26\u5408\u7F51\u5173\u63A8\u65AD\uFF09'
|
|
126694
126710
|
).option(
|
|
126695
126711
|
"--max-cpc <n>",
|
|
126696
|
-
"\u6700\u9AD8\u6BCF\u6B21\u70B9\u51FB\u8D39\u7528
|
|
126712
|
+
"\u6700\u9AD8\u6BCF\u6B21\u70B9\u51FB\u8D39\u7528\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08\u5982 10 \u8868\u793A \xA510\uFF1BCLI \xD7100 \u5199\u5165\u30020 \u6E05\u7A7A\uFF0C\u56DE\u5230\u5E7F\u544A\u7EC4\u51FA\u4EF7\uFF0C\u4E0D\u662F\u5199\u6210 0 \u5143\uFF09"
|
|
126697
126713
|
).option("--final-url <url>", "\u5173\u952E\u8BCD\u7EA7\u6700\u7EC8\u5230\u8FBE\u7F51\u5740 finalURL").option("--status <Enabled|Paused>", "\u7528\u6237\u72B6\u6001\uFF08\u5199\u5165 userStatusV2\uFF0C\u4E0E Web \u5173\u952E\u8BCD\u5F00\u5173\u4E00\u81F4\uFF09").option("--start <date>", "\u5217\u8868\u67E5\u8BE2\u8D77\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u5217\u8868\u67E5\u8BE2\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
126698
126714
|
async (opts) => {
|
|
126699
126715
|
if (opts.matchType && !["Broad", "Phrase", "Exact"].includes(opts.matchType)) {
|
|
@@ -139129,7 +139145,7 @@ function matchConversionAction(actions, name2) {
|
|
|
139129
139145
|
return label2.includes(needle);
|
|
139130
139146
|
});
|
|
139131
139147
|
if (!hit) return null;
|
|
139132
|
-
const n = num2(hit.conversions ?? hit.
|
|
139148
|
+
const n = num2(hit.conversions ?? hit.allConversions);
|
|
139133
139149
|
return n;
|
|
139134
139150
|
}
|
|
139135
139151
|
function buildQuarterlyFacts(input) {
|
|
@@ -146667,10 +146683,8 @@ function playbookCodeFatal(cmd) {
|
|
|
146667
146683
|
};
|
|
146668
146684
|
const replacement = known[key];
|
|
146669
146685
|
if (replacement) {
|
|
146670
|
-
return fatal(
|
|
146671
|
-
|
|
146672
|
-
${replacement}`
|
|
146673
|
-
);
|
|
146686
|
+
return fatal(`\u274C \`${cmd}\` \u662F\u8DEF\u7531\u7F16\u53F7\uFF0C\u4E0D\u662F\u547D\u4EE4\u3002\u8BF7\u6539\u8DD1\uFF1A
|
|
146687
|
+
${replacement}`);
|
|
146674
146688
|
}
|
|
146675
146689
|
return fatal(
|
|
146676
146690
|
`\u274C \`${cmd}\` \u662F\u8DEF\u7531\u7F16\u53F7\uFF0C\u4E0D\u662F\u547D\u4EE4\u3002\u8D26\u6237\u5217\u8868\u7528 list-accounts\uFF0C\u591A\u6237\u4F59\u989D\u7528 balance-scan\uFF0C\u591A\u6237\u6D88\u8017/CPA \u7528 accounts-digest\u3002\u5176\u5B83\u7F16\u53F7\u5148 Read SKILL \u8DEF\u7531\u8868\u3002`
|
|
@@ -146703,6 +146717,36 @@ function loginCustomerFlagIssue(args) {
|
|
|
146703
146717
|
}
|
|
146704
146718
|
return null;
|
|
146705
146719
|
}
|
|
146720
|
+
var EDIT_WITHOUT_JSON_OUT = {
|
|
146721
|
+
"adgroup-edit": "ad groups -a <id> --fresh --json-out <dir>\uFF08\u8BFB maxCPCAmountYuan / targetCpaAmountYuan\uFF09",
|
|
146722
|
+
"keyword-edit": "ad keywords -a <id> --json-out <dir>\uFF08\u8BFB maxCPCYuan\u3002\u5217\u8868\u6CA1\u6709 --fresh\uFF0C\u7F13\u5B58\u7EA6 60 \u79D2\uFF09",
|
|
146723
|
+
"ad-edit": "ad list -a <id> --json-out <dir>"
|
|
146724
|
+
};
|
|
146725
|
+
function adEditJsonOutIssue(args) {
|
|
146726
|
+
if (args[0] !== "ad") return null;
|
|
146727
|
+
const sub = args[1];
|
|
146728
|
+
const verify = sub ? EDIT_WITHOUT_JSON_OUT[sub] : void 0;
|
|
146729
|
+
if (!verify) return null;
|
|
146730
|
+
const hasJsonOut = args.some((a) => a === "--json-out" || a.startsWith("--json-out="));
|
|
146731
|
+
if (!hasJsonOut) return null;
|
|
146732
|
+
return fatal(
|
|
146733
|
+
`\u274C \`${sub}\` \u6CA1\u6709 --json-out\u3002\u5E26\u4E0A\u5B83\u4F1A\u5728\u8BF7\u6C42\u53D1\u51FA\u524D\u5931\u8D25\uFF0C\u6539\u52A8\u6CA1\u6709\u63D0\u4EA4\u3002
|
|
146734
|
+
\u53BB\u6389 --json-out \u518D\u8DD1\u540C\u4E00\u6761\uFF08\u5199\u64CD\u4F5C\u4ECD\u8981 --commit\uFF09\u3002\u6838\u5BF9\uFF1Asiluzan-tso ${verify}`
|
|
146735
|
+
);
|
|
146736
|
+
}
|
|
146737
|
+
var LIST_WITHOUT_ID = /* @__PURE__ */ new Set(["campaigns", "groups", "keywords", "list"]);
|
|
146738
|
+
function adListIdFilterIssue(args) {
|
|
146739
|
+
if (args[0] !== "ad") return null;
|
|
146740
|
+
const sub = args[1];
|
|
146741
|
+
if (!sub || !LIST_WITHOUT_ID.has(sub)) return null;
|
|
146742
|
+
const hasId = args.some(
|
|
146743
|
+
(a) => a === "--id" || a.startsWith("--id=") || a === "--campaign-id" || a.startsWith("--campaign-id=")
|
|
146744
|
+
);
|
|
146745
|
+
if (!hasId) return null;
|
|
146746
|
+
return fatal(
|
|
146747
|
+
`\u274C \`ad ${sub}\` \u4E0D\u80FD\u6309 --id \u8FC7\u6EE4\u3002\u53BB\u6389 --id\uFF0C--json-out <dir> \u843D\u76D8\u540E\u5728 JSON \u91CC\u6309 id \u6311\u4E00\u6761\u3002`
|
|
146748
|
+
);
|
|
146749
|
+
}
|
|
146706
146750
|
function agentCompatFatalMessage(argv) {
|
|
146707
146751
|
const args = argv.slice(2);
|
|
146708
146752
|
if (args.includes("--period") || args.some((a) => a.startsWith("--period="))) {
|
|
@@ -146737,6 +146781,10 @@ function agentCompatFatalMessage(argv) {
|
|
|
146737
146781
|
"\u274C `ad \u2026` \u5B50\u547D\u4EE4\u6CA1\u6709 `-m/--media`\uFF0C\u8D26\u6237\u7528 `-a/--account <mediaCustomerId>`\u3002\n \u5355\u6237\uFF1Asiluzan-tso ad campaigns -a <id> --start <\u65E5> --end <\u65E5> --json-out ./snap\n \u5168\u6237\u5F53\u65E5\u8D85\u9884\u7B97/\u7A7A\u8017\u7194\u65AD\uFF1Asiluzan-tso guard budget-circuit|zero-conv -m Google --json-out ./snap"
|
|
146738
146782
|
);
|
|
146739
146783
|
}
|
|
146784
|
+
const editWithoutJsonOut = adEditJsonOutIssue(args);
|
|
146785
|
+
if (editWithoutJsonOut) return editWithoutJsonOut;
|
|
146786
|
+
const listIdFilter = adListIdFilterIssue(args);
|
|
146787
|
+
if (listIdFilter) return listIdFilter;
|
|
146740
146788
|
if (cmd === "list" && (args.length === 1 || args[1]?.startsWith("-"))) {
|
|
146741
146789
|
return fatal(
|
|
146742
146790
|
"\u274C \u6CA1\u6709\u9876\u5C42 `list` \u547D\u4EE4\u3002\u67E5\u8BE2\u5E7F\u544A\u8D26\u6237\u5217\u8868\u8BF7\u7528\uFF1A\n siluzan-tso list-accounts [-m Google] [-k <id\u6216\u5173\u952E\u5B57>] --json-out <dir>"
|
package/dist/skill/_meta.json
CHANGED
|
@@ -202,12 +202,15 @@ const data = d.record ?? d.items; // record 非空=汇总维度;否则=列表
|
|
|
202
202
|
| 消耗 | `spend` |
|
|
203
203
|
| 展示 | `impressions` |
|
|
204
204
|
| 点击 | `clicks` |
|
|
205
|
-
|
|
|
205
|
+
| 转化次数 | `conversions` |
|
|
206
|
+
| 转化价值 | `conversionsValue` |
|
|
206
207
|
| 点击率 | `ctr` |
|
|
207
208
|
| 转化率 | `conversionRate` |
|
|
208
209
|
| 平均点击成本 | `averageCpc` |
|
|
209
210
|
| 转化成本 | `costPerConversion` |
|
|
210
211
|
|
|
212
|
+
`conversionsValue` 是账户币种的 Google「转化价值」,和 `conversions`(次数)不是一回事。带 `startDate` / `endDate` 时,`keywords`、`search-terms`、`campaigns`、`ads`,以及 `ad keywords` / `ad search-terms` / `ad campaigns` / `ad groups` / `ad list` 的行上会有这个字段。报告要写转化价值或 ROAS 时用它:`ROAS = conversionsValue / spend`(`spend` 为 0 时该格写「—」)。禁止用消耗、点击、转化次数自己算。行上没有该字段(设备、每日趋势,或当次快照未返回)写「未返回」,禁止把系列价值摊到关键词或搜索字词上。账户级三格仍只汇总 `campaigns` 的 `items[]`。
|
|
213
|
+
|
|
211
214
|
### CampaignSectionData 关键字段
|
|
212
215
|
|
|
213
216
|
> `campaigns` 维度的系列行在 `items[]`(`schemaVersion 3` 起统一信封;本节下文的 `campaigns[]` 即指 `items[]`)。
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
- **步骤**:
|
|
82
82
|
1. **账户核验**:`list-accounts -m Google -k <mediaCustomerId> --json-out <dir>`;无记录则停止并告知用户。
|
|
83
83
|
2. 确认时间范围;区间 > 3 个月时分段(季度/月)。
|
|
84
|
-
3. **拉数**:用户已指定 Sheet/维度 → 只拉对应 `--sections`(见 `google-period-report-excel.md` 映射表);未指定 → 按 `google-period-report.md` 默认 9 个拉数维(含 `audience`、`negative-keywords`),并可并行追问可选追加。终稿要「转化价值 / 单转化价值 / ROAS」的上期时,再按 excel 规格拉等长上期 `--sections campaigns`
|
|
84
|
+
3. **拉数**:用户已指定 Sheet/维度 → 只拉对应 `--sections`(见 `google-period-report-excel.md` 映射表);未指定 → 按 `google-period-report.md` 默认 9 个拉数维(含 `audience`、`negative-keywords`),并可并行追问可选追加。终稿要「转化价值 / 单转化价值 / ROAS」的上期时,再按 excel 规格拉等长上期 `--sections campaigns` 后汇总,禁止把这三格留成「—」。关键词 / 搜索字词表用各自行的 `conversionsValue`(ROAS = 该值 / `spend`),不要用系列汇总值摊下去。
|
|
85
85
|
4. **门禁**:对每个 section Read `<section>-<accountId>_*.outline.txt` 后再写脚本。
|
|
86
86
|
5. 脚本读盘写 `google-period-report.json`(`meta` + `narrative`,`meta.lang` 跟本轮语言);用户要 **Excel** → Agent 脚本写 xlsx(**禁止**宿主第三方 xlsx Skill;**无** CLI excel 子命令;表头跟 `meta.lang`)。
|
|
87
87
|
6. 默认 HTML:`google-analysis render --data ./google-period-report.json --snapshot-dir ./snap-google --out ./google-period-report.html --lang <zh|en>`。须含账户概览、投放趋势、Top 关键词/系列/地区/受众分布、当前否词名单、优化建议。
|
|
@@ -46,14 +46,18 @@ siluzan-tso ad campaigns -a <accountId> [--start <YYYY-MM-DD>] [--end <YYYY-MM-D
|
|
|
46
46
|
|
|
47
47
|
落盘 JSON 中 **`budget` 为日预算**(主币种「元」,CLI 已 ÷100),另有 **`statusV2`**(`Enabled`/`Paused`/`Removed`)与 **`statusDisplay`**(`{statusV2}·日程文案`,如 `Enabled·有效`、`Enabled·投放期已结束`、`Removed·已删除`)。
|
|
48
48
|
|
|
49
|
+
同对象里 **`targetSpendBidCeilingYuan`**、**`targetCpaAmountYuan`** 也是元,与 `--bid-ceiling` / `--target-cpa` 同口径。`targetSpend_BidCeilingAmount`、`targetCpa_BidingAmount` 仍是分(`1` = 0.01 元),不要填回写参数。`targetRoas` 是倍数,不是金额。
|
|
50
|
+
|
|
51
|
+
本命令**没有** `--id`。要看一条,落盘后按 `id` 过滤。
|
|
52
|
+
|
|
49
53
|
> **消歧**:`statusDisplay` 含「投放期已结束」**不是**系列已删除——以 **`statusV2`** 为准;仅 `Removed` 才不可在其下新建组/广告。日程结束仍可 `ad-create`(是否投放另看预算与系列开关)。
|
|
50
54
|
|
|
51
55
|
命令名:列表是 **`ad campaigns`**(兼容别名 `campaign-list`),**没有** `google-ads` 命名空间。`--json-out` **必须带路径**(落盘,勿管道 stdout)。
|
|
52
56
|
|
|
53
|
-
带 `--start` / `--end` 时,同行的 **`spend` / `impressions` / `clicks` / `conversions`
|
|
57
|
+
带 `--start` / `--end` 时,同行的 **`spend` / `impressions` / `clicks` / `conversions` / `conversionsValue` 为该闭区间合计**(**不是**日消耗)。`conversionsValue` 是账户币种的转化价值,不是转化次数。报告写转化价值或 ROAS 时用它(ROAS = `conversionsValue` / `spend`)。`ad groups`、`ad list`、`ad keywords`、`ad search-terms` 在同一日期窗口下也返回该字段。
|
|
54
58
|
与日预算比较「是否超预算」时,**必须** `start=end=统计日`;多日窗口须先 ÷ 天数或改用 `balance-scan.dailySpend` / `overview.averageDailyCost` 再谈「日均」。
|
|
55
59
|
|
|
56
|
-
> **缓存**:网关侧此列表按 `账户 + 日期区间` 缓存 60 秒(Redis)。若刚 `campaign-create` / `campaign-edit` / `campaign-status` / `campaign-delete` 后立刻查询想看到最新结果,加 `--fresh` 跳过缓存(网关 `newest=true
|
|
60
|
+
> **缓存**:网关侧此列表按 `账户 + 日期区间` 缓存 60 秒(Redis)。若刚 `campaign-create` / `campaign-edit` / `campaign-status` / `campaign-delete` 后立刻查询想看到最新结果,加 `--fresh` 跳过缓存(网关 `newest=true`);日常巡检/报表不需要,留空更省配额。区间不要拉到跨很多年:可能 500,正文含 `authToken` 也不是登录失效,缩短到近 30 天再查,不要据此 `reauth`。
|
|
57
61
|
|
|
58
62
|
### 启停
|
|
59
63
|
|
|
@@ -185,6 +189,8 @@ siluzan-tso ad ad-delete -a <accountId> --id <adId>
|
|
|
185
189
|
siluzan-tso ad keywords -a <accountId> [--negative] [--start/--end <date>] [--json-out ./snap]
|
|
186
190
|
```
|
|
187
191
|
|
|
192
|
+
带日期时行上有 `conversionsValue`(账户币种的转化价值,不是 `conversions` 次数)。`--json-out` 里直接读这个字段;终端表「转化价值」列在字段缺失时为「—」。否定关键词名单没有这个指标。出价读 `maxCPCYuan`(元)。本列表按账户和日期缓存约 60 秒,**没有** `--fresh`,也**没有** `--id`。`keyword-edit` 回读报「列表仍是」时,等约 60 秒再看 `maxCPCYuan`,不要立刻重跑编辑。
|
|
193
|
+
|
|
188
194
|
### 添加关键词
|
|
189
195
|
|
|
190
196
|
```bash
|
|
@@ -263,6 +269,8 @@ siluzan-tso keyword geo-list [--country-code <US,CN,...>] [--name-contains <text
|
|
|
263
269
|
siluzan-tso ad search-terms -a <accountId> [--start/--end YYYY-MM-DD] [--json-out ./snap]
|
|
264
270
|
```
|
|
265
271
|
|
|
272
|
+
行上 `conversionsValue` 是该窗口的转化价值(账户币种)。ROAS = `conversionsValue` / `spend`(或同行费用字段)。不要用点击或转化次数换算。
|
|
273
|
+
|
|
266
274
|
---
|
|
267
275
|
|
|
268
276
|
## ad geo — 地理位置定向管理
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
## 写操作硬纪律(Agent 易踩)
|
|
24
24
|
|
|
25
25
|
- **必须** `--commit "…"`;漏了会直接失败。
|
|
26
|
-
- 列表命令:`ad campaigns` / `ad groups` / `ad list` / `ad keywords`(**没有** `google-ads`、没有必用 `campaign-list`;`campaign-list` 仅为 `campaigns`
|
|
27
|
-
- `--json-out <path>`:**路径必填**,JSON 落盘;禁止 `--json-out` 裸用或管道 stdout
|
|
26
|
+
- 列表命令:`ad campaigns` / `ad groups` / `ad list` / `ad keywords`(**没有** `google-ads`、没有必用 `campaign-list`;`campaign-list` 仅为 `campaigns` 别名)。这四条**没有** `--id`,单条在落盘 JSON 里按 `id` 过滤。
|
|
27
|
+
- `--json-out <path>`:**路径必填**,JSON 落盘;禁止 `--json-out` 裸用或管道 stdout。`adgroup-edit`、`keyword-edit`、`ad-edit` **没有**这个参数,加上会在请求发出前失败。核对用对应列表。
|
|
28
28
|
- 系列 `statusDisplay` 含「投放期已结束」≠ 已删除;以 JSON **`statusV2`** 为准(见 `google-ads-read.md`)。
|
|
29
29
|
- **勿创建 Display**:`campaign-create` / `ad-create` 仅面向 Search(RSA);PMax 走 `pmax-*`。
|
|
30
30
|
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
|
|
72
72
|
**PMax**:`ad pmax-create` **同步**返回 `campaignId`、`assetGroupId`、`budgetId`;无 `ad batch get/diff`。创建接口 400 才续跑:把比这次请求多出来的非空编号写回同一份 JSON,内容不改,再执行一次。首次创建不填这三个字段。已经 200、只是扩展或视频失败时不要再 `pmax-create`。详见 `pmax-api.md`。
|
|
73
73
|
|
|
74
|
-
**金额**:JSON 中 `budget`、`targetCpa_BidingAmount` 填主币种**元**;CLI 提交前 ×100(与 `ad campaigns` 列表 `budget`
|
|
74
|
+
**金额**:JSON 中 `budget`、`targetCpa_BidingAmount` 填主币种**元**;CLI 提交前 ×100(与 `ad campaigns` 列表 `budget` 口径一致)。`pmax-get` 返回的 `budget`、`targetCpa_BidingAmount` 仍是分(`500` = 5 元)。`pmax-edit --budget` / `--target-cpa` 填元,不要把 get 里的分填回去。`pmax-edit` 退出码 0 只表示请求被接受,改完用 `pmax-get` 核对。资产组 `statusV2` 为 `Removed` 时不要再 `pmax-asset-group-edit`。
|
|
75
75
|
|
|
76
76
|
**图片**:配置 `imagePaths`(相对 JSON 文件目录)或 `marketingImageBase64` / `squareMarketingImageBase64` / `logoImageBase64`。`pmax-validate` 会自动校验图片尺寸(最小值 / 推荐值 / 宽高比 ±2% / 文件大小 ≤5120 KB)。如需将任意图片转为合规素材,用 `ad pmax-image-convert`(`marketing` / `square` / `logo` 三种格式,sharp 处理,居中裁切)。
|
|
77
77
|
|
|
@@ -260,7 +260,7 @@ siluzan-tso ad campaign-edit -a <accountId> --id <campaignId> \
|
|
|
260
260
|
--bidding TARGET_CPA --target-cpa 80
|
|
261
261
|
```
|
|
262
262
|
|
|
263
|
-
相对运算:先 `ad campaigns --json-out ./snap` 读 `budget
|
|
263
|
+
相对运算:先 `ad campaigns --json-out ./snap` 读 `budget`、`targetSpendBidCeilingYuan`、`targetCpaAmountYuan`(都已是元)和 `biddingStrategyTypeV2`,再传入。不要读 `targetSpend_BidCeilingAmount` / `targetCpa_BidingAmount`(仍是分)。
|
|
264
264
|
|
|
265
265
|
`campaign-edit` 的名称、预算、出价、投放网络,以及 `campaign-status`,走字段级 PATCH(`writeScope=search-field-patch`)。请求只带本次传入的字段,没传的字段保持原样。金额仍是主币种元,CLI ×100 写入网关「分」。`--search-network` / `--content-network` 传 `true` 或 `false`,关闭传 `false`,不能传空。`budgetShared=true` 时本命令直接拒绝 `--budget`,不会改共享预算。PMax 不要用本命令。
|
|
266
266
|
|
|
@@ -354,7 +354,7 @@ siluzan-tso ad keyword-delete -a <accountId> --id <keywordId> --adgroup-id <adGr
|
|
|
354
354
|
|
|
355
355
|
## ad keyword-edit — 搜索关键词编辑
|
|
356
356
|
|
|
357
|
-
只改 `--final-url` / `--max-cpc` / `--status` 时走字段级 PATCH,原地更新,`id` 与历史数据保留。`--final-url` 传空会清空关键词网址,回到广告自己的网址。改 `--text` / `--match-type`(Google 侧词面与匹配类型不可变)仍走批量 PUT,会新建并删除旧词,`id` 变化时 CLI 会提示。改 `--final-url` / `--max-cpc` / `--status` 后 CLI
|
|
357
|
+
只改 `--final-url` / `--max-cpc` / `--status` 时走字段级 PATCH,原地更新,`id` 与历史数据保留。`--final-url` 传空会清空关键词网址,回到广告自己的网址。改 `--text` / `--match-type`(Google 侧词面与匹配类型不可变)仍走批量 PUT,会新建并删除旧词,`id` 变化时 CLI 会提示。改 `--final-url` / `--max-cpc` / `--status` 后 CLI 会重新拉列表回读。本命令没有 `--json-out`。关键词列表按账户和日期缓存约 60 秒,且没有 `--fresh`:回读报「列表仍是」时,先等约 60 秒再看 `maxCPCYuan`。已经是目标值就当成功,**不要**立刻重跑,**不要** `keyword-delete` 后重建。等过缓存仍是旧值,再告诉用户平台未生效。
|
|
358
358
|
|
|
359
359
|
```bash
|
|
360
360
|
siluzan-tso ad keyword-edit \
|
|
@@ -31,7 +31,10 @@ Meta / Facebook 线索广告走 **`meta-ad`(W13)**,**禁止**用本命令
|
|
|
31
31
|
|
|
32
32
|
- **广告类型**:仅 Search + PMax;**不支持 Display**(见上文「支持的广告类型」)。
|
|
33
33
|
- **Search 改字段**:`campaign-status`、`campaign-edit` 的名称/预算/出价/投放网络、`adgroup-status` / `adgroup-rename` / `adgroup-edit`、`keyword-status`、`keyword-edit` 的状态/出价/网址、`ad-status` / `ad-edit` 都是字段级 PATCH,只写本次参数。词面和匹配类型仍是批量 PUT。关闭搜索合作伙伴或展示网络传 `--search-network false` / `--content-network false`。PMax 用 `pmax-edit`,不要用 `campaign-edit`。
|
|
34
|
-
- **命令名**:系列列表是 `ad campaigns`(别名 `campaign-list`),**禁止** `ad-campaigns` / `campaign list`。`balance`/`stats` 须 `-m Google`。
|
|
34
|
+
- **命令名**:系列列表是 `ad campaigns`(别名 `campaign-list`),**禁止** `ad-campaigns` / `campaign list`。`balance`/`stats` 须 `-m Google`。`adgroup-edit` / `keyword-edit` / `ad-edit` 没有 `--json-out`。`ad campaigns|groups|keywords|list` 没有 `--id`。
|
|
35
|
+
- **列表金额**:`ad campaigns` 的 `budget`、`targetSpendBidCeilingYuan`、`targetCpaAmountYuan` 是元,与 `--budget` / `--bid-ceiling` / `--target-cpa` 同口径。同对象的 `targetSpend_BidCeilingAmount`、`targetCpa_BidingAmount` 仍是分(`1` = 0.01 元),禁止填回写参数。`ad groups` 只读 `maxCPCAmountYuan` / `targetCpaAmountYuan`。`pmax-get` 的 `budget` 仍是分(`500` = 5 元),`pmax-edit --budget` 填元。
|
|
36
|
+
- **关键词改价回读**:`keyword-edit` 报「列表仍是」时,列表缓存约 60 秒且没有 `--fresh`。不要立刻重跑,不要删词重建。等约 60 秒再看 `maxCPCYuan`。`--max-cpc 0` 是清空并回到广告组出价。广告组 `--target-cpa 0` 同样是清空;`--max-cpc` 必须大于 0。
|
|
37
|
+
- **日期 500**:列表默认近 30 天。区间过长返回 500 且正文含 `authToken` 时,不是登录失效,缩短区间再查,不要据此 `reauth`。
|
|
35
38
|
- **Google 客户 ID(CID)带连字符**:UI / 用户口述常为 `123-456-7890`,平台 `mediaCustomerId` 与 Google 网关路径需要 `1234567890`。`ad campaigns` / `ad groups` / `ad list` 等 `-a` 与 JSON 顶层 `account` **可带连字符**(CLI 自动去掉)。若错误为 `HTTP 403:123-456-7890`(回显带横杠),先换纯数字重试,**禁止**据此直接判断 OAuth 过期或 `reauth`。
|
|
36
39
|
- **禁止臆测授权过期**:`ad *` / geo / 拉数出现 403/500 时,**禁止**对用户说「授权可能过期」。须核验 ID 后执行 `account check-access -a <mediaCustomerId>`,仅当 `reauth_required`(或与 `invalidOAuthToken=true` 交叉确认)才走重授权;无激活字段时禁止谈套餐。见 [`accounts-permissions.md`](../accounts/accounts-permissions.md)。
|
|
37
40
|
- **预算投放**:`BudgetBudgetDeliveryMethodV2` 默认用 `STANDARD`;`ACCELERATED` 多数账户会被 Google 拒(validate 警告)。
|
|
@@ -228,8 +228,8 @@ siluzan-tso ad campaigns -a <CID> --json-out ./snap
|
|
|
228
228
|
|
|
229
229
|
### 10.2 预算分配合理性
|
|
230
230
|
|
|
231
|
-
1. 从 `campaigns` 获取每个系列的 spend、conversions、costPerConversion
|
|
232
|
-
2.
|
|
231
|
+
1. 从 `campaigns` 获取每个系列的 spend、conversions、costPerConversion、conversionsValue
|
|
232
|
+
2. ROAS 用该行 `conversionsValue / spend`(`conversionsValuePerCost` 已是这一比值,不要再平均);CPA 用 `costPerConversion`
|
|
233
233
|
3. 检查以下不合理分配:
|
|
234
234
|
|
|
235
235
|
| 问题 | 检测方式 | 处理 |
|
|
@@ -165,7 +165,7 @@ Account
|
|
|
165
165
|
**设定初始 tROAS:**
|
|
166
166
|
|
|
167
167
|
1. 先用 Max Conversion Value(不设目标)运行 2-4 周
|
|
168
|
-
2.
|
|
168
|
+
2. 平均 ROAS 直接用系列行的 `conversionsValue / spend`(或行上的 `conversionsValuePerCost`),不要用转化次数估算价值
|
|
169
169
|
3. 设 tROAS = 平均 ROAS 或低 10-20%(给算法空间)
|
|
170
170
|
4. 每 2 周提高不超过 15-20%
|
|
171
171
|
|
|
@@ -53,17 +53,15 @@ mkdir -p ./snap-scale && siluzan-tso google-analysis -a <mediaCustomerId> --sect
|
|
|
53
53
|
## 最终操作
|
|
54
54
|
|
|
55
55
|
> **金额单位(关键)**:`ad campaign-edit` / `ad adgroup-edit` 所有金额参数均为 **主币种金额**(如 `100` 表示 ¥100,支持小数 `10.5`);CLI 内部自动 `Math.round(value × 100)` 写入「分」字段。详见 `references/google-ads/google-ads-write.md`「ad campaign-edit」金额单位说明。
|
|
56
|
-
>
|
|
56
|
+
> 系列列表读 `budget`、`targetSpendBidCeilingYuan`、`targetCpaAmountYuan`(已是元)。组列表读 `maxCPCAmountYuan`、`targetCpaAmountYuan`。不要读 `targetSpend_BidCeilingAmount` / `targetCpa_BidingAmount`(分),也不要再 ÷100。
|
|
57
57
|
|
|
58
58
|
**提高系列日预算**(在原值基础上 +10 元的写法):
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
|
-
# 1)
|
|
62
|
-
|
|
63
|
-
# 2) 主币种 + 10
|
|
64
|
-
|
|
65
|
-
# 3) 主币种金额传回
|
|
66
|
-
siluzan-tso ad campaign-edit -a <mediaCustomerId> --id <campaignId> --budget ${NEW}
|
|
61
|
+
# 1) 落盘后用代码读 budget(元)。不要把 --json-out 管道给 node
|
|
62
|
+
siluzan-tso ad campaigns -a <mediaCustomerId> --json-out ./snap
|
|
63
|
+
# 2) 主币种 + 10,再传回
|
|
64
|
+
siluzan-tso ad campaign-edit -a <mediaCustomerId> --id <campaignId> --budget <新预算元>
|
|
67
65
|
```
|
|
68
66
|
|
|
69
67
|
**上调系列目标 CPA**(若策略为 tCPA 等):
|
|
@@ -78,7 +76,7 @@ siluzan-tso ad campaign-edit -a <mediaCustomerId> --id <campaignId> --target-cpa
|
|
|
78
76
|
siluzan-tso ad adgroup-edit -a <mediaCustomerId> --id <adGroupId> --target-cpa <主币种金额>
|
|
79
77
|
```
|
|
80
78
|
|
|
81
|
-
写前务必先 **`ad campaigns --json-out ./snap` / `ad groups --json-out ./snap`**
|
|
79
|
+
写前务必先 **`ad campaigns --json-out ./snap` / `ad groups --json-out ./snap`** 取当前值。系列用 `budget` / `targetCpaAmountYuan`,组用 `targetCpaAmountYuan`(都是元),按配置比例计算后再传回。
|
|
82
80
|
|
|
83
81
|
---
|
|
84
82
|
|
|
@@ -89,7 +87,7 @@ siluzan-tso ad campaigns -a <mediaCustomerId> --start <…> --end <…> --json-o
|
|
|
89
87
|
mkdir -p ./snap-scale && siluzan-tso google-analysis -a <mediaCustomerId> --sections campaigns --start <…> --end <…> --json-out ./snap-scale
|
|
90
88
|
```
|
|
91
89
|
|
|
92
|
-
确认 **`budget
|
|
90
|
+
确认 **`budget`**、**`targetCpaAmountYuan`**(组上同名字段)与 **`statusV2`** 符合预期。刚改完的系列/组列表加 `--fresh`;关键词列表没有 `--fresh`。
|
|
93
91
|
|
|
94
92
|
---
|
|
95
93
|
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
| 日预算与「当日」费用比 | **仅当** `--start` 与 `--end` 为**同一统计日**时,才可用 `spend` 对比 `budget`;**禁止**用多日 `spend` 去除以或对比日预算(会把周/月合计误当成「一天花了这么多」) |
|
|
44
44
|
| 转化次数 | **`conversions`**(窗口内合计) |
|
|
45
45
|
| 实际 CPA | 宿主侧 **`spend / conversions`**(同窗口合计;`conversions` 为 0 时不做 CPA 判断) |
|
|
46
|
-
| 目标 CPA(系列 / 组) | 系列列表 JSON
|
|
46
|
+
| 目标 CPA(系列 / 组) | 系列列表 JSON 的 **`targetCpaAmountYuan`**(元);组列表 **`targetCpaAmountYuan`**。不要读系列上的 `targetCpa_BidingAmount`(分) |
|
|
47
47
|
| 日均消耗(续航用) | 用 `balance-scan` 的 **`dailySpend`**(近 7 日合计/7),或自算 `spend / 活跃天数`;**禁止**把多日 `spend` 直接当「日消耗」 |
|
|
48
48
|
|
|
49
49
|
> **硬约束(自动化 / 预警必读)**:凡带 `--start` / `--end` 的读命令,返回的 `spend` / `impressions` / `clicks` / `conversions` 均为**该闭区间合计**。要「一天的消耗」必须 `start=end=该日`,或显式 `合计 ÷ 天数`;要「日均」用 `balance-scan.dailySpend` / `overview.averageDailyCost`。
|
|
@@ -129,7 +129,7 @@ siluzan-tso ad campaign-status -a <mediaCustomerId> --id <campaignId> --status E
|
|
|
129
129
|
3. 对候选对象:
|
|
130
130
|
- `conversions`(窗口内)**≥ 3**
|
|
131
131
|
- `actual_cpa = spend / conversions`(`conversions > 0`)
|
|
132
|
-
- 读取 **目标 CPA**(系列:`
|
|
132
|
+
- 读取 **目标 CPA**(系列:`targetCpaAmountYuan`;组:`targetCpaAmountYuan`,都是元)。`spend` 已是账户币种,与这两个字段同单位后再比。不要用 `targetCpa_BidingAmount`(分)。
|
|
133
133
|
- **IF** `actual_cpa > target_cpa * spike_threshold`(如 `1.3`),则进入写操作。
|
|
134
134
|
|
|
135
135
|
### 最终操作
|
|
@@ -160,10 +160,10 @@ siluzan-tso ad adgroup-edit -a <mediaCustomerId> --id <adGroupId> --max-cpc <主
|
|
|
160
160
|
|
|
161
161
|
### 写后复核
|
|
162
162
|
|
|
163
|
-
再次 **`ad campaigns --json-out ./snap`** 或 **`ad groups --json-out ./snap
|
|
163
|
+
再次 **`ad campaigns --json-out ./snap`** 或 **`ad groups --json-out ./snap`**(刚改完系列/组加 `--fresh`):
|
|
164
164
|
|
|
165
|
-
-
|
|
166
|
-
-
|
|
165
|
+
- 系列比对 `budget`、`targetSpendBidCeilingYuan`、`targetCpaAmountYuan`(已是元)。不要用 `targetSpend_BidCeilingAmount` / `targetCpa_BidingAmount`(分),也不要再 ×100。
|
|
166
|
+
- 组只比对 `maxCPCAmountYuan`、`targetCpaAmountYuan`。出口里没有 `maxCPCAmount` / `targetCpaAmount`。
|
|
167
167
|
|
|
168
168
|
### CLI 能力摘要
|
|
169
169
|
|
|
@@ -118,7 +118,7 @@ function valueKpis(items) {
|
|
|
118
118
|
|
|
119
119
|
禁止对行上的 `conversionsValuePerCost` 再平均(那是行级「转化价值/费用」,不是账户 ROAS)。
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
关键词、搜索字词表的转化价值读各自行的 `conversionsValue`(账户币种),ROAS = 该值 / 该行 `spend`。禁止把系列汇总摊到词上,也禁止用转化次数估算。设备、每日趋势的 outline 若没有 `conversionsValue`,该列写「该维度未返回」。关键词或搜索字词当次 outline 也没有该字段时同样写「未返回」。上期 `campaigns` 拉数失败时,账户级三格写明原因,不要留空。
|
|
122
122
|
|
|
123
123
|
英文终稿表头:转化价值 → Conversion value,单转化价值 → Value / conversion,ROAS 保持 ROAS。
|
|
124
124
|
|
|
@@ -118,7 +118,7 @@ function valueKpis(items) {
|
|
|
118
118
|
|
|
119
119
|
禁止对行上的 `conversionsValuePerCost` 再平均(那是行级「转化价值/费用」,不是账户 ROAS)。
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
关键词、搜索字词表的转化价值读各自行的 `conversionsValue`(账户币种),ROAS = 该值 / 该行 `spend`。禁止把系列汇总摊到词上,也禁止用转化次数估算。设备、每日趋势的 outline 若没有 `conversionsValue`,该列写「该维度未返回」。关键词或搜索字词当次 outline 也没有该字段时同样写「未返回」。上期 `campaigns` 拉数失败时,账户级三格写明原因,不要留空。
|
|
122
122
|
|
|
123
123
|
英文终稿表头:转化价值 → Conversion value,单转化价值 → Value / conversion,ROAS 保持 ROAS。
|
|
124
124
|
|
|
@@ -9,7 +9,7 @@ $ErrorActionPreference = 'Stop'
|
|
|
9
9
|
# -- Package info (injected at build time) ------------------------------------
|
|
10
10
|
$PKG_NAME = 'siluzan-tso-cli'
|
|
11
11
|
# PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
|
|
12
|
-
$PKG_VERSION = '1.1.62-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.62-beta.3'
|
|
13
13
|
$CLI_BIN = 'siluzan-tso'
|
|
14
14
|
$SKILL_LABEL = 'Siluzan TSO'
|
|
15
15
|
$INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
|
|
@@ -9,7 +9,7 @@ set -euo pipefail
|
|
|
9
9
|
# -- Package info (injected at build time) ------------------------------------
|
|
10
10
|
readonly PKG_NAME="siluzan-tso-cli"
|
|
11
11
|
# PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
|
|
12
|
-
readonly PKG_VERSION="1.1.62-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.62-beta.3"
|
|
13
13
|
readonly CLI_BIN="siluzan-tso"
|
|
14
14
|
readonly SKILL_LABEL="Siluzan TSO"
|
|
15
15
|
readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
|