siluzan-tso-cli 1.1.37-beta.6 → 1.1.37-beta.8
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 +197 -16
- package/dist/skill/SKILL.md +1 -1
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/campaign-create-template.md +4 -0
- package/dist/skill/references/core/agent-conventions.md +4 -2
- package/dist/skill/references/core/user-communication-guide.md +3 -1
- package/dist/skill/references/core/workflows.md +3 -3
- package/dist/skill/references/google-ads/google-ads-campaign-plan.md +16 -10
- package/dist/skill/references/google-ads/google-ads.md +20 -18
- package/dist/skill/references/google-ads/rules/README.md +1 -1
- package/dist/skill/references/google-ads/rules/google-ads-plan-source-fidelity.md +72 -2
- package/dist/skill/references/report-templates/REPORT-WORKFLOW.md +5 -0
- package/dist/skill/references/report-templates/google-inquiry-analysis.md +1 -0
- package/dist/skill/references/report-templates/google-period-report-excel.md +3 -1
- package/dist/skill/references/report-templates/meta-period-report-excel.md +2 -0
- package/dist/skill/references/report-templates/okki-weekly-google-client.md +1 -0
- package/dist/skill/report-templates/REPORT-WORKFLOW.md +5 -0
- package/dist/skill/report-templates/google-inquiry-analysis.md +1 -0
- package/dist/skill/report-templates/google-period-report-excel.md +3 -1
- package/dist/skill/report-templates/meta-period-report-excel.md +2 -0
- package/dist/skill/report-templates/okki-weekly-google-client.md +1 -0
- 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.37-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.37-beta.8),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -112422,16 +112422,26 @@ async function runAdCampaignCreate(opts) {
|
|
|
112422
112422
|
`);
|
|
112423
112423
|
process.exit(1);
|
|
112424
112424
|
}
|
|
112425
|
+
const taskId = data?.["id"] ?? data?.["data"];
|
|
112426
|
+
const agentWorkflow = {
|
|
112427
|
+
status: "Creating",
|
|
112428
|
+
terminal: false,
|
|
112429
|
+
shouldRunBatchDiff: false,
|
|
112430
|
+
shouldPollBatchGet: true,
|
|
112431
|
+
batchId: taskId != null ? String(taskId) : null,
|
|
112432
|
+
configFile: opts.configFile,
|
|
112433
|
+
nextCommand: taskId != null ? `siluzan-tso ad batch get --id ${String(taskId)} --config-file ${opts.configFile} --json-out ./snap-campaign` : null,
|
|
112434
|
+
agentHint: "\u4EFB\u52A1\u5DF2\u63D0\u4EA4\uFF1A\u6BCF 5s \u8F6E\u8BE2 nextCommand\uFF08ad batch get\uFF0C\u987B\u5E26\u540C\u4E00 --config-file \u4E0E --json-out\uFF09\uFF1B\u8BFB\u843D\u76D8 agentWorkflow\uFF1ACreating \u7EE7\u7EED\u8F6E\u8BE2\uFF1BSuccessfully/HasFailed \u6267\u884C nextCommand \u7684 batch diff\uFF08\u542B\u6295\u653E\u56FD\u5BB6\uFF09\uFF1BFailed \u53EA\u770B reason/errors\u3002"
|
|
112435
|
+
};
|
|
112425
112436
|
if (await emitCliJsonOrSnapshot(opts, {
|
|
112426
112437
|
section: `ad-campaign-create-async-${cfg.account}`,
|
|
112427
112438
|
commandLabel: "ad campaign-create",
|
|
112428
112439
|
commandHint: cfg.account,
|
|
112429
|
-
payload: data,
|
|
112440
|
+
payload: { ...data, agentWorkflow },
|
|
112430
112441
|
idSuffix: cfg.account
|
|
112431
112442
|
})) {
|
|
112432
112443
|
return;
|
|
112433
112444
|
}
|
|
112434
|
-
const taskId = data?.["id"] ?? data?.["data"];
|
|
112435
112445
|
const campaign = cfg.campaign;
|
|
112436
112446
|
const adGroups = campaign["AdGroupsForBatchJob"] ?? [];
|
|
112437
112447
|
let kwCount = 0;
|
|
@@ -114605,6 +114615,7 @@ function buildExtensionRemediateCommandLine(opts) {
|
|
|
114605
114615
|
parts.push(levelFlag);
|
|
114606
114616
|
if (campaignFlag) parts.push(campaignFlag);
|
|
114607
114617
|
parts.push(`--json-out ${jsonOut}`);
|
|
114618
|
+
parts.push(`--commit ${shellArg("batch-diff remediate extension")}`);
|
|
114608
114619
|
return parts.join(" ");
|
|
114609
114620
|
}
|
|
114610
114621
|
if (type === "CALLOUT") {
|
|
@@ -114619,6 +114630,7 @@ function buildExtensionRemediateCommandLine(opts) {
|
|
|
114619
114630
|
];
|
|
114620
114631
|
if (campaignFlag) parts.push(campaignFlag);
|
|
114621
114632
|
parts.push(`--json-out ${jsonOut}`);
|
|
114633
|
+
parts.push(`--commit ${shellArg("batch-diff remediate extension")}`);
|
|
114622
114634
|
return parts.join(" ");
|
|
114623
114635
|
}
|
|
114624
114636
|
if (type === "CALL") {
|
|
@@ -114635,6 +114647,7 @@ function buildExtensionRemediateCommandLine(opts) {
|
|
|
114635
114647
|
];
|
|
114636
114648
|
if (campaignFlag) parts.push(campaignFlag);
|
|
114637
114649
|
parts.push(`--json-out ${jsonOut}`);
|
|
114650
|
+
parts.push(`--commit ${shellArg("batch-diff remediate extension")}`);
|
|
114638
114651
|
return parts.join(" ");
|
|
114639
114652
|
}
|
|
114640
114653
|
if (type === "STRUCTURED_SNIPPET") {
|
|
@@ -114654,6 +114667,7 @@ function buildExtensionRemediateCommandLine(opts) {
|
|
|
114654
114667
|
];
|
|
114655
114668
|
if (campaignFlag) parts.push(campaignFlag);
|
|
114656
114669
|
parts.push(`--json-out ${jsonOut}`);
|
|
114670
|
+
parts.push(`--commit ${shellArg("batch-diff remediate extension")}`);
|
|
114657
114671
|
return parts.join(" ");
|
|
114658
114672
|
}
|
|
114659
114673
|
return null;
|
|
@@ -114666,6 +114680,7 @@ function asRecord3(v) {
|
|
|
114666
114680
|
function pickString2(...vals) {
|
|
114667
114681
|
for (const v of vals) {
|
|
114668
114682
|
if (typeof v === "string" && v.trim()) return v.trim();
|
|
114683
|
+
if (typeof v === "number" && Number.isFinite(v)) return String(v);
|
|
114669
114684
|
}
|
|
114670
114685
|
return "";
|
|
114671
114686
|
}
|
|
@@ -114711,6 +114726,28 @@ function liveAdHasHeadline(ad, headline) {
|
|
|
114711
114726
|
if (!h) return false;
|
|
114712
114727
|
return collectRsaHeadlines(ad).includes(h);
|
|
114713
114728
|
}
|
|
114729
|
+
function liveLocationId(item) {
|
|
114730
|
+
return pickString2(
|
|
114731
|
+
item["id"],
|
|
114732
|
+
item["Id"],
|
|
114733
|
+
item["countryCriteriaId"],
|
|
114734
|
+
item["locationId"],
|
|
114735
|
+
item["criterionId"],
|
|
114736
|
+
item["geoTargetConstantId"]
|
|
114737
|
+
);
|
|
114738
|
+
}
|
|
114739
|
+
function buildGeoRemediateCommandLine(opts) {
|
|
114740
|
+
return [
|
|
114741
|
+
"siluzan-tso",
|
|
114742
|
+
"ad",
|
|
114743
|
+
"geo",
|
|
114744
|
+
"add",
|
|
114745
|
+
`-a ${opts.accountId}`,
|
|
114746
|
+
`--campaign-id ${opts.campaignId}`,
|
|
114747
|
+
`--location-id ${opts.locationId}`,
|
|
114748
|
+
`--commit "batch-diff remediate geo"`
|
|
114749
|
+
].join(" ");
|
|
114750
|
+
}
|
|
114714
114751
|
function formatExtensionPlanned(ext) {
|
|
114715
114752
|
if (!ext) return "\u2014";
|
|
114716
114753
|
const type = pickString2(ext["typeV2"], ext["AssetFieldType"], ext["type"]);
|
|
@@ -114925,11 +114962,48 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
114925
114962
|
});
|
|
114926
114963
|
}
|
|
114927
114964
|
}
|
|
114965
|
+
const plannedLocs = campaign["targetedLocations"];
|
|
114966
|
+
const locationNames = Array.isArray(cfg.locations) ? cfg.locations.map((x) => typeof x === "string" ? x.trim() : String(x ?? "")) : [];
|
|
114967
|
+
const liveLocRows = (live.targetedLocations ?? []).filter(
|
|
114968
|
+
(loc) => belongsToCampaign(loc, campaignId, campaignName)
|
|
114969
|
+
);
|
|
114970
|
+
const liveLocIds = new Set(
|
|
114971
|
+
liveLocRows.map((loc) => liveLocationId(loc)).filter((id) => id.length > 0)
|
|
114972
|
+
);
|
|
114973
|
+
let plannedLocCount = 0;
|
|
114974
|
+
if (Array.isArray(plannedLocs)) {
|
|
114975
|
+
plannedLocCount = plannedLocs.length;
|
|
114976
|
+
for (let li = 0; li < plannedLocs.length; li++) {
|
|
114977
|
+
const loc = asRecord3(plannedLocs[li]);
|
|
114978
|
+
if (!loc) continue;
|
|
114979
|
+
const id = pickString2(loc["id"], loc["Id"]);
|
|
114980
|
+
if (!id) continue;
|
|
114981
|
+
if (liveLocIds.has(id)) continue;
|
|
114982
|
+
const displayName = locationNames[li] || pickString2(loc["locationName"], loc["name"], loc["countryOrRegion"]) || "";
|
|
114983
|
+
const remediateCommand = accountId && campaignId ? buildGeoRemediateCommandLine({ accountId, campaignId, locationId: id }) : void 0;
|
|
114984
|
+
missing.push({
|
|
114985
|
+
layer: "location",
|
|
114986
|
+
path: `campaign.targetedLocations[${li}]`,
|
|
114987
|
+
plannedContent: `locationId: ${id}${displayName ? ` | \u540D\u79F0: ${displayName}` : ""}`,
|
|
114988
|
+
liveNote: `\u7CFB\u5217\u5DF2\u5B9A\u5411 ${liveLocIds.size}/${plannedLocCount} \u4E2A\u5730\u57DF`,
|
|
114989
|
+
summary: `\u6295\u653E\u56FD\u5BB6/\u5730\u57DF\u672A\u751F\u6548\uFF1A${displayName || id}\uFF08id=${id}\uFF09`,
|
|
114990
|
+
remediateCommand
|
|
114991
|
+
});
|
|
114992
|
+
}
|
|
114993
|
+
}
|
|
114928
114994
|
const plannedKeywords = listPlannedKeywords(campaign);
|
|
114929
114995
|
const liveKwCount = live.keywords.filter(
|
|
114930
114996
|
(k) => belongsToCampaign(k, campaignId, campaignName)
|
|
114931
114997
|
).length;
|
|
114932
114998
|
const liveAdCount = live.ads.filter((a) => belongsToCampaign(a, campaignId, campaignName)).length;
|
|
114999
|
+
const ok = missing.length === 0;
|
|
115000
|
+
const remediateCount = missing.filter((m) => m.remediateCommand).length;
|
|
115001
|
+
let agentHint;
|
|
115002
|
+
if (!ok) {
|
|
115003
|
+
agentHint = `\u53D1\u73B0 ${missing.length} \u9879\u4E0E\u8BA1\u5212\u4E0D\u4E00\u81F4` + (remediateCount > 0 ? `\uFF1B\u5176\u4E2D ${remediateCount} \u6761\u542B remediateCommand\uFF0CAgent \u5E94\u76F4\u63A5\u6267\u884C\u8865\u5EFA\uFF08\u52FF\u4EC5\u8BE2\u95EE\u7528\u6237\uFF09` : "\uFF1B\u6309 layer \u7528 ad adgroup-create / keyword-create / ad-create / keyword-negative-create / ad geo add / ad extension * \u8865\u5EFA") + "\uFF1B\u8865\u5EFA\u540E\u518D\u6B21 ad batch diff\u3002";
|
|
115004
|
+
} else {
|
|
115005
|
+
agentHint = "\u4E0E JSON \u8BA1\u5212\u4E00\u81F4\uFF08\u542B\u6295\u653E\u5730\u57DF\uFF09\uFF1B\u53EF\u5411\u7528\u6237\u786E\u8BA4\u521B\u5EFA\u5B8C\u6210\u3002";
|
|
115006
|
+
}
|
|
114933
115007
|
return {
|
|
114934
115008
|
accountId,
|
|
114935
115009
|
campaignId,
|
|
@@ -114937,6 +115011,8 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
114937
115011
|
configFile: meta?.configFile ?? "",
|
|
114938
115012
|
batchId: meta?.batchId,
|
|
114939
115013
|
batchStatus: meta?.batchStatus,
|
|
115014
|
+
ok,
|
|
115015
|
+
agentHint,
|
|
114940
115016
|
live,
|
|
114941
115017
|
missing,
|
|
114942
115018
|
counts: {
|
|
@@ -114955,7 +115031,9 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
114955
115031
|
(k) => belongsToCampaign(k, campaignId, campaignName)
|
|
114956
115032
|
).length,
|
|
114957
115033
|
plannedExtensions: plannedExt,
|
|
114958
|
-
liveExtensions: liveExt
|
|
115034
|
+
liveExtensions: liveExt,
|
|
115035
|
+
plannedLocations: plannedLocCount,
|
|
115036
|
+
liveLocations: liveLocIds.size
|
|
114959
115037
|
}
|
|
114960
115038
|
};
|
|
114961
115039
|
}
|
|
@@ -114973,7 +115051,7 @@ async function fetchLiveSnapshotForCampaign(config, accountId, campaignId, campa
|
|
|
114973
115051
|
params.set("startDate", toGoogleDate(void 0, -30));
|
|
114974
115052
|
params.set("endDate", toGoogleDate(void 0, 0));
|
|
114975
115053
|
const qs = params.toString();
|
|
114976
|
-
const [campaigns, adGroups, keywords, negativeKeywords, ads, extensions] = await Promise.all([
|
|
115054
|
+
const [campaigns, adGroups, keywords, negativeKeywords, ads, extensions, targetedLocations] = await Promise.all([
|
|
114977
115055
|
fetchList(
|
|
114978
115056
|
config,
|
|
114979
115057
|
`${googleApiUrl}/campaignmanagement/v2/list/${accountId}?${qs}`,
|
|
@@ -115003,6 +115081,11 @@ async function fetchLiveSnapshotForCampaign(config, accountId, campaignId, campa
|
|
|
115003
115081
|
config,
|
|
115004
115082
|
`${googleApiUrl}/extensionmanagement/v2/list/${accountId}`,
|
|
115005
115083
|
verbose
|
|
115084
|
+
),
|
|
115085
|
+
fetchList(
|
|
115086
|
+
config,
|
|
115087
|
+
`${googleApiUrl}/campaignmanagement/v2/targetedlocations/${accountId}?${qs}`,
|
|
115088
|
+
verbose
|
|
115006
115089
|
)
|
|
115007
115090
|
]);
|
|
115008
115091
|
const campaignFound = campaigns.some(
|
|
@@ -115017,12 +115100,78 @@ async function fetchLiveSnapshotForCampaign(config, accountId, campaignId, campa
|
|
|
115017
115100
|
(k) => belongsToCampaign(k, campaignId, campaignName)
|
|
115018
115101
|
),
|
|
115019
115102
|
ads: ads.filter((a) => belongsToCampaign(a, campaignId, campaignName)),
|
|
115020
|
-
extensions: extensions.filter((e) => belongsToCampaign(e, campaignId, campaignName))
|
|
115103
|
+
extensions: extensions.filter((e) => belongsToCampaign(e, campaignId, campaignName)),
|
|
115104
|
+
targetedLocations: targetedLocations.filter(
|
|
115105
|
+
(loc) => belongsToCampaign(loc, campaignId, campaignName)
|
|
115106
|
+
)
|
|
115021
115107
|
};
|
|
115022
115108
|
}
|
|
115023
115109
|
function resolveCampaignIdFromBatch(record2) {
|
|
115024
115110
|
const campaign = asRecord3(record2["campaign"]);
|
|
115025
|
-
|
|
115111
|
+
const direct = pickString2(
|
|
115112
|
+
campaign?.["Id"],
|
|
115113
|
+
campaign?.["id"],
|
|
115114
|
+
record2["campaignId"],
|
|
115115
|
+
record2["CampaignId"]
|
|
115116
|
+
);
|
|
115117
|
+
if (direct) return direct;
|
|
115118
|
+
const groups = campaign?.["AdGroupsForBatchJob"];
|
|
115119
|
+
if (Array.isArray(groups)) {
|
|
115120
|
+
for (const g of groups) {
|
|
115121
|
+
const gr = asRecord3(g);
|
|
115122
|
+
const cid = pickString2(gr?.["CampaignId"], gr?.["campaignId"]);
|
|
115123
|
+
if (cid) return cid;
|
|
115124
|
+
}
|
|
115125
|
+
}
|
|
115126
|
+
return "";
|
|
115127
|
+
}
|
|
115128
|
+
function resolveCampaignNameFromBatch(record2) {
|
|
115129
|
+
const campaign = asRecord3(record2["campaign"]);
|
|
115130
|
+
return pickString2(
|
|
115131
|
+
record2["campaignName"],
|
|
115132
|
+
record2["CampaignName"],
|
|
115133
|
+
campaign?.["Name"],
|
|
115134
|
+
campaign?.["name"]
|
|
115135
|
+
);
|
|
115136
|
+
}
|
|
115137
|
+
function buildBatchGetAgentWorkflow(opts) {
|
|
115138
|
+
const status = pickString2(opts.record["status"]);
|
|
115139
|
+
const campaignId = resolveCampaignIdFromBatch(opts.record) || null;
|
|
115140
|
+
const campaignName = resolveCampaignNameFromBatch(opts.record) || null;
|
|
115141
|
+
const runDiff = shouldRunBatchDiff(status);
|
|
115142
|
+
let nextCommand = null;
|
|
115143
|
+
let agentHint;
|
|
115144
|
+
if (status === "Creating") {
|
|
115145
|
+
agentHint = "\u4EFB\u52A1\u4ECD\u5728 Creating\uFF1A\u6BCF 5s \u8F6E\u8BE2 ad batch get\uFF08\u987B\u5E26\u540C\u4E00 --config-file\uFF09\uFF0C\u52FF\u6267\u884C batch diff\u3002";
|
|
115146
|
+
nextCommand = opts.configFile ? `siluzan-tso ad batch get --id ${opts.batchId} --config-file ${shellArgPath(opts.configFile)} --json-out ./snap-campaign` : `siluzan-tso ad batch get --id ${opts.batchId} --config-file <campaign.json> --json-out ./snap-campaign`;
|
|
115147
|
+
} else if (status === "Failed") {
|
|
115148
|
+
agentHint = "\u5168\u90E8 Failed\uFF1A\u8BFB reason/errors\uFF0C\u6539 JSON \u540E\u91CD\u63D0 campaign-create\uFF1B\u4E0D\u8981\u8DD1 batch diff\u3002";
|
|
115149
|
+
nextCommand = null;
|
|
115150
|
+
} else if (runDiff) {
|
|
115151
|
+
if (!opts.configFile) {
|
|
115152
|
+
agentHint = "\u5DF2\u53EF\u6BD4\u5BF9\uFF0C\u4F46\u7F3A\u5C11 --config-file\uFF1A\u8BF7\u7528\u63D0\u4EA4\u65F6\u7684 campaign.json \u6267\u884C ad batch diff\uFF08\u5FC5\u505A\uFF09\u3002";
|
|
115153
|
+
nextCommand = `siluzan-tso ad batch diff --batch-id ${opts.batchId} --config-file <campaign.json>${campaignId ? ` --campaign-id ${campaignId}` : ""} --json-out ./snap-campaign`;
|
|
115154
|
+
} else {
|
|
115155
|
+
nextCommand = buildBatchDiffCommandLine({
|
|
115156
|
+
batchId: opts.batchId,
|
|
115157
|
+
configFile: opts.configFile,
|
|
115158
|
+
campaignId: campaignId ?? void 0
|
|
115159
|
+
});
|
|
115160
|
+
agentHint = `\u5FC5\u505A\u4E0B\u4E00\u6B65\uFF1A\u6267\u884C nextCommand\uFF08ad batch diff\uFF09\uFF0C\u6838\u5BF9\u7EC4/\u8BCD/RSA/\u5426\u8BCD/\u9644\u52A0\u4FE1\u606F/\u6295\u653E\u56FD\u5BB6\uFF1B\u6709 remediateCommand \u5219\u76F4\u63A5\u8865\u5EFA\u3002`;
|
|
115161
|
+
}
|
|
115162
|
+
} else {
|
|
115163
|
+
agentHint = `\u5F53\u524D status=${status || "\u2014"}\uFF0C\u6309 google-ads.md \xA7 batch \u5904\u7406\u3002`;
|
|
115164
|
+
}
|
|
115165
|
+
return {
|
|
115166
|
+
status: status || null,
|
|
115167
|
+
terminal: isBatchTerminalStatus(status),
|
|
115168
|
+
shouldRunBatchDiff: runDiff,
|
|
115169
|
+
campaignId,
|
|
115170
|
+
campaignName,
|
|
115171
|
+
configFile: opts.configFile ?? null,
|
|
115172
|
+
nextCommand,
|
|
115173
|
+
agentHint
|
|
115174
|
+
};
|
|
115026
115175
|
}
|
|
115027
115176
|
function shellArgPath(p) {
|
|
115028
115177
|
return /[\s"]/.test(p) ? `"${p.replace(/"/g, '\\"')}"` : p;
|
|
@@ -115038,6 +115187,9 @@ function buildBatchDiffCommandLine(opts) {
|
|
|
115038
115187
|
}
|
|
115039
115188
|
return parts.join(" ");
|
|
115040
115189
|
}
|
|
115190
|
+
function isBatchTerminalStatus(status) {
|
|
115191
|
+
return status === "Successfully" || status === "Failed" || status === "HasFailed";
|
|
115192
|
+
}
|
|
115041
115193
|
function shouldRunBatchDiff(status) {
|
|
115042
115194
|
return status === "Successfully" || status === "HasFailed";
|
|
115043
115195
|
}
|
|
@@ -115067,17 +115219,23 @@ function printBatchDiffFollowUpHint(opts) {
|
|
|
115067
115219
|
campaignId: opts.campaignId
|
|
115068
115220
|
});
|
|
115069
115221
|
if (opts.status === "Successfully") {
|
|
115070
|
-
console.log(
|
|
115222
|
+
console.log(
|
|
115223
|
+
" \u6838\u5BF9\u8BA1\u5212\u4E0E\u8D26\u6237\u662F\u5426\u4E00\u81F4\uFF08\u542B\u6295\u653E\u56FD\u5BB6/\u5730\u57DF\uFF1B\u65E0\u7F3A\u5931\u5E94\u663E\u793A \u2705\uFF0C--json-out \u65F6\u8BFB ok=true\uFF09\u3002\n"
|
|
115224
|
+
);
|
|
115071
115225
|
} else {
|
|
115072
115226
|
console.log(
|
|
115073
|
-
" \u5217\u51FA\u7F3A\u5931\u9879\u540E\u81EA\u52A8\u8865\u5EFA\uFF08\u9644\u52A0\u4FE1\u606F\u7528 ad extension *\uFF1B\u7EC4/\u8BCD/RSA \u7528\u5BF9\u5E94 create
|
|
115227
|
+
" \u5217\u51FA\u7F3A\u5931\u9879\u540E\u81EA\u52A8\u8865\u5EFA\uFF08\u5730\u57DF\u7528 ad geo add\uFF1B\u9644\u52A0\u4FE1\u606F\u7528 ad extension *\uFF1B\u7EC4/\u8BCD/RSA \u7528\u5BF9\u5E94 create\uFF09\u3002\n"
|
|
115074
115228
|
);
|
|
115075
115229
|
}
|
|
115076
115230
|
console.log(` ${cmd}
|
|
115077
115231
|
`);
|
|
115232
|
+
console.log(
|
|
115233
|
+
" Agent\uFF1A\u51E0\u4E4E\u603B\u662F\u5E26 --json-out\uFF1B\u8BF7\u8BFB\u843D\u76D8 JSON \u7684 agentWorkflow.nextCommand / ok / missing[].remediateCommand\u3002\n"
|
|
115234
|
+
);
|
|
115078
115235
|
}
|
|
115079
115236
|
var LAYER_ORDER = [
|
|
115080
115237
|
"campaign",
|
|
115238
|
+
"location",
|
|
115081
115239
|
"adgroup",
|
|
115082
115240
|
"keyword",
|
|
115083
115241
|
"ad",
|
|
@@ -115086,6 +115244,7 @@ var LAYER_ORDER = [
|
|
|
115086
115244
|
];
|
|
115087
115245
|
var LAYER_TITLE = {
|
|
115088
115246
|
campaign: "\u7CFB\u5217",
|
|
115247
|
+
location: "\u6295\u653E\u56FD\u5BB6/\u5730\u57DF",
|
|
115089
115248
|
adgroup: "\u5E7F\u544A\u7EC4",
|
|
115090
115249
|
keyword: "\u5173\u952E\u8BCD",
|
|
115091
115250
|
ad: "\u521B\u610F",
|
|
@@ -115131,11 +115290,13 @@ function formatBatchDiffGrouped(result) {
|
|
|
115131
115290
|
console.log(` \u914D\u7F6E\u6587\u4EF6\uFF1A${result.configFile}`);
|
|
115132
115291
|
const c = result.counts;
|
|
115133
115292
|
console.log(
|
|
115134
|
-
` \u5E7F\u544A\u7EC4 ${c.liveAdGroups}/${c.plannedAdGroups} | \u5173\u952E\u8BCD ${c.liveKeywords}/${c.plannedKeywords} | \u521B\u610F ${c.liveAds}/${c.plannedAds} | \u5426\u5B9A\u8BCD ${c.liveNegativeKeywords}/${c.plannedNegativeKeywords} | \u9644\u52A0\u4FE1\u606F ${c.liveExtensions}/${c.plannedExtensions}`
|
|
115293
|
+
` \u5E7F\u544A\u7EC4 ${c.liveAdGroups}/${c.plannedAdGroups} | \u5173\u952E\u8BCD ${c.liveKeywords}/${c.plannedKeywords} | \u521B\u610F ${c.liveAds}/${c.plannedAds} | \u5426\u5B9A\u8BCD ${c.liveNegativeKeywords}/${c.plannedNegativeKeywords} | \u9644\u52A0\u4FE1\u606F ${c.liveExtensions}/${c.plannedExtensions} | \u5730\u57DF ${c.liveLocations}/${c.plannedLocations}`
|
|
115135
115294
|
);
|
|
115136
115295
|
console.log();
|
|
115137
115296
|
if (result.missing.length === 0) {
|
|
115138
|
-
console.log(" \u2705 \u672A\u53D1\u73B0\u7F3A\u5931\u9879\uFF08\u4E0E JSON \u8BA1\u5212\u4E00\u81F4\uFF0C\
|
|
115297
|
+
console.log(" \u2705 \u672A\u53D1\u73B0\u7F3A\u5931\u9879\uFF08\u4E0E JSON \u8BA1\u5212\u4E00\u81F4\uFF0C\u542B\u6295\u653E\u5730\u57DF\uFF09\u3002\n");
|
|
115298
|
+
if (result.agentHint) console.log(` ${result.agentHint}
|
|
115299
|
+
`);
|
|
115139
115300
|
return;
|
|
115140
115301
|
}
|
|
115141
115302
|
console.log(`\u2500\u2500 \u7F3A\u5931\u660E\u7EC6\uFF08\u5171 ${result.missing.length} \u6761\uFF09\u2500\u2500`);
|
|
@@ -115163,7 +115324,7 @@ function formatBatchDiffGrouped(result) {
|
|
|
115163
115324
|
printMissingGroupByAdGroup(configFile, layer, layerItems);
|
|
115164
115325
|
continue;
|
|
115165
115326
|
}
|
|
115166
|
-
if (layer === "negative_keyword" || layer === "extension") {
|
|
115327
|
+
if (layer === "location" || layer === "negative_keyword" || layer === "extension") {
|
|
115167
115328
|
console.log(`
|
|
115168
115329
|
[${LAYER_TITLE[layer]}] \u7CFB\u5217\u7EA7\uFF08${layerItems.length} \u6761\uFF09`);
|
|
115169
115330
|
for (const m of layerItems) {
|
|
@@ -115174,8 +115335,17 @@ function formatBatchDiffGrouped(result) {
|
|
|
115174
115335
|
"\n \u{1F4A1} \u9644\u52A0\u4FE1\u606F BatchJob \u5E38\u5931\u8D25\uFF1AAgent \u5E94**\u76F4\u63A5\u6267\u884C**\u4E0A\u65B9\u300C\u8865\u5EFA\u300D\u547D\u4EE4\uFF08\u7528\u6237\u5DF2\u786E\u8BA4\u8FC7 JSON\uFF09\uFF0C\u52FF\u4EC5\u8BE2\u95EE\u662F\u5426\u8865\u5EFA\u3002"
|
|
115175
115336
|
);
|
|
115176
115337
|
}
|
|
115338
|
+
if (layer === "location" && layerItems.some((m) => m.remediateCommand)) {
|
|
115339
|
+
console.log(
|
|
115340
|
+
"\n \u{1F4A1} \u6295\u653E\u5730\u57DF\u7F3A\u5931\uFF1AAgent \u5E94**\u76F4\u63A5\u6267\u884C**\u4E0A\u65B9\u300C\u8865\u5EFA\u300D`ad geo add`\uFF08\u7528\u6237\u5DF2\u786E\u8BA4\u8FC7 JSON\uFF09\uFF0C\u52FF\u8DF3\u8FC7\u3002"
|
|
115341
|
+
);
|
|
115342
|
+
}
|
|
115177
115343
|
}
|
|
115178
115344
|
}
|
|
115345
|
+
if (result.agentHint) {
|
|
115346
|
+
console.log(`
|
|
115347
|
+
\u{1F449} ${result.agentHint}`);
|
|
115348
|
+
}
|
|
115179
115349
|
console.log();
|
|
115180
115350
|
}
|
|
115181
115351
|
async function runCampaignBatchDiff(opts) {
|
|
@@ -115267,9 +115437,11 @@ async function runCampaignBatchDiff(opts) {
|
|
|
115267
115437
|
payload: result,
|
|
115268
115438
|
idSuffix: campaignId
|
|
115269
115439
|
})) {
|
|
115440
|
+
if (!result.ok) process.exit(2);
|
|
115270
115441
|
return;
|
|
115271
115442
|
}
|
|
115272
115443
|
formatBatchDiffGrouped(result);
|
|
115444
|
+
if (!result.ok) process.exit(2);
|
|
115273
115445
|
}
|
|
115274
115446
|
|
|
115275
115447
|
// src/commands/ad/ai-creation.ts
|
|
@@ -115402,11 +115574,17 @@ async function runAiCreationGet(opts) {
|
|
|
115402
115574
|
`);
|
|
115403
115575
|
process.exit(1);
|
|
115404
115576
|
}
|
|
115577
|
+
const agentWorkflow = buildBatchGetAgentWorkflow({
|
|
115578
|
+
batchId: opts.id,
|
|
115579
|
+
record: data,
|
|
115580
|
+
configFile: opts.configFile
|
|
115581
|
+
});
|
|
115582
|
+
const payload = { ...data, agentWorkflow };
|
|
115405
115583
|
if (await emitCliJsonOrSnapshot(opts, {
|
|
115406
115584
|
section: `ad-batch-get-${opts.id}`,
|
|
115407
115585
|
commandLabel: "ad batch get",
|
|
115408
115586
|
commandHint: opts.id,
|
|
115409
|
-
payload
|
|
115587
|
+
payload,
|
|
115410
115588
|
idSuffix: opts.id
|
|
115411
115589
|
})) {
|
|
115412
115590
|
return;
|
|
@@ -119087,10 +119265,13 @@ function register20(program2) {
|
|
|
119087
119265
|
}
|
|
119088
119266
|
);
|
|
119089
119267
|
adBatchCmd.command("get").description(
|
|
119090
|
-
"\u83B7\u53D6\u5355\u6761\u8BB0\u5F55/\u8349\u7A3F\u8BE6\u60C5\uFF08\u9ED8\u8BA4\uFF1A\u6458\u8981\u8868 + \u540E\u7AEF\u539F\u59CB reason/errors\uFF1B--json-out \u8F93\u51FA\u5B8C\u6574 JSON\uFF09\n \u6210\u529F\u6216\u90E8\u5206\u6210\u529F\u540E\u987B\u6267\u884C ad batch diff\uFF08\u89C1 --config-file\uFF09\uFF1BFailed \u4EC5\u770B reason/errors \u540E\u91CD\u63D0\n\n \u793A\u4F8B\uFF1A\n siluzan-tso ad batch list\n siluzan-tso ad batch get --id <taskId> --config-file ./campaign.json
|
|
119091
|
-
).requiredOption("--id <id>", "\u8BB0\u5F55 ID\uFF08\u6765\u81EA ad batch list --json-out \u7684 results[].id\uFF09").option(
|
|
119268
|
+
"\u83B7\u53D6\u5355\u6761\u8BB0\u5F55/\u8349\u7A3F\u8BE6\u60C5\uFF08\u9ED8\u8BA4\uFF1A\u6458\u8981\u8868 + \u540E\u7AEF\u539F\u59CB reason/errors\uFF1B--json-out \u8F93\u51FA\u5B8C\u6574 JSON\uFF09\n \u6210\u529F\u6216\u90E8\u5206\u6210\u529F\u540E\u987B\u6267\u884C ad batch diff\uFF08\u89C1 --config-file\uFF09\uFF1BFailed \u4EC5\u770B reason/errors \u540E\u91CD\u63D0\n --json-out \u843D\u76D8\u542B agentWorkflow.nextCommand / agentHint\uFF08Agent \u5FC5\u8BFB\uFF0C\u52FF\u56E0\u65E9\u9000\u8DF3\u8FC7 diff\uFF09\n\n \u793A\u4F8B\uFF1A\n siluzan-tso ad batch list\n siluzan-tso ad batch get --id <taskId> --config-file ./campaign.json --json-out ./snap-campaign\n"
|
|
119269
|
+
).requiredOption("--id <id>", "\u8BB0\u5F55 ID\uFF08\u6765\u81EA ad batch list --json-out \u7684 results[].id\uFF09").option(
|
|
119270
|
+
"--config-file <path>",
|
|
119271
|
+
"\u672C\u6B21 campaign-create JSON\uFF08\u5199\u5165 agentWorkflow.nextCommand\uFF1B\u4EBA\u8BFB\u6A21\u5F0F\u4E5F\u6253\u5370 batch diff\uFF09"
|
|
119272
|
+
).option(
|
|
119092
119273
|
"--json-out <path>",
|
|
119093
|
-
"\u843D\u76D8\
|
|
119274
|
+
"\u843D\u76D8\u5B8C\u6574\u8BB0\u5F55 + agentWorkflow\uFF1B\u76EE\u5F55\u6216 *.json\uFF1Bstdout \u4E00\u884C\u6458\u8981",
|
|
119094
119275
|
void 0
|
|
119095
119276
|
).option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
119096
119277
|
async (opts) => {
|
|
@@ -119104,7 +119285,7 @@ function register20(program2) {
|
|
|
119104
119285
|
}
|
|
119105
119286
|
);
|
|
119106
119287
|
adBatchCmd.command("diff").description(
|
|
119107
|
-
"\u6210\u529F\u6216\u90E8\u5206\u6210\u529F\uFF1A\u6309 campaign-create JSON \u4E0E\u8D26\u6237\u5B9E\u51B5\u9010\u5C42\u6BD4\u5BF9\n Failed \u4E0D\u9002\u7528\uFF08\u7CFB\u5217\u672A\u521B\u5EFA\uFF09\uFF0C\u8BF7\u7528 ad batch get \u67E5\u770B reason/errors \u540E\u6539 JSON \u91CD\u63D0\n\n \u793A\u4F8B\uFF1A\n siluzan-tso ad batch diff --
|
|
119288
|
+
"\u6210\u529F\u6216\u90E8\u5206\u6210\u529F\uFF1A\u6309 campaign-create JSON \u4E0E\u8D26\u6237\u5B9E\u51B5\u9010\u5C42\u6BD4\u5BF9\uFF08\u542B\u6295\u653E\u56FD\u5BB6/\u5730\u57DF\uFF09\n \u6709\u7F3A\u5931\u65F6 exit 2\uFF1B--json-out \u8BFB ok / agentHint / missing[].remediateCommand\uFF08\u542B ad geo add\uFF09\n Failed \u4E0D\u9002\u7528\uFF08\u7CFB\u5217\u672A\u521B\u5EFA\uFF09\uFF0C\u8BF7\u7528 ad batch get \u67E5\u770B reason/errors \u540E\u6539 JSON \u91CD\u63D0\n\n \u793A\u4F8B\uFF1A\n siluzan-tso ad batch diff --batch-id <taskId> --config-file ./campaign.json --json-out ./snap-campaign\n siluzan-tso ad batch diff --campaign-id <id> --config-file ./campaign.json"
|
|
119108
119289
|
).requiredOption("--config-file <path>", "\u63D0\u4EA4\u65F6\u4F7F\u7528\u7684 campaign-create JSON").option(
|
|
119109
119290
|
"--campaign-id <id>",
|
|
119110
119291
|
"Google \u5E7F\u544A\u7CFB\u5217 ID\uFF08\u53EF\u4ECE ad campaigns --json-out \u83B7\u53D6\uFF1B\u7701\u7565\u65F6\u6309\u7CFB\u5217\u540D\u5339\u914D\uFF09"
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -82,7 +82,7 @@ siluzan-tso -h # 查看帮助
|
|
|
82
82
|
|
|
83
83
|
| 用户意图(关键词) | 工作流 | 必读文档 |
|
|
84
84
|
| ------------------ | ------ | -------- |
|
|
85
|
-
| 新建搜索系列 / 出投放方案 / **按 Excel·xlsx·表格投放方案创建广告** / **根据官网·网站·URL 生成 Google 搜索广告** / 搜索广告文案·关键词·**表格** / 写搜索广告计划 | **W3** | `references/google-ads/google-ads-campaign-plan.md` + **`assets/campaign-create-template.json`**(先 Read)+ `assets/campaign-create-template.md`;**有方案文件时必读** `references/google-ads/rules/google-ads-plan-source-fidelity.md`(Agent 写脚本 Excel→campaign JSON + **`ad geo resolve
|
|
85
|
+
| 新建搜索系列 / 出投放方案 / **按 Excel·xlsx·表格投放方案创建广告** / **根据官网·网站·URL 生成 Google 搜索广告** / 搜索广告文案·关键词·**表格** / 写搜索广告计划 | **W3** | `references/google-ads/google-ads-campaign-plan.md` + **`assets/campaign-create-template.json`**(先 Read)+ `assets/campaign-create-template.md`;**有方案文件时必读** `references/google-ads/rules/google-ads-plan-source-fidelity.md`(Agent 写脚本 Excel→campaign JSON + **`ad geo resolve`**;不合规须问「您自己改还是我帮您改」;代改则创建后出 from→to 报告);表格交付读 `references/google-ads/rules/google-ads-launch-plan-template.md`。**≠ P8 / ≠ P9 / ≠ W5** |
|
|
86
86
|
| 广告系列/组/广告/关键词 CRUD / 拒审 | W3 | `references/google-ads/google-ads.md` |
|
|
87
87
|
| PMax 系列 | W3 | **`assets/pmax-create-template.json`**(先 Read)+ `assets/pmax-create-template.md` + `references/google-ads/pmax-api.md` |
|
|
88
88
|
| AI 智投草稿 list/get/update/publish | W4 | `references/google-ads/google-ads.md`(§ ad batch) |
|
package/dist/skill/_meta.json
CHANGED
|
@@ -40,6 +40,8 @@ JSON 模板:同目录 [`campaign-create-template.json`](campaign-create-templa
|
|
|
40
40
|
| 编造 `targetedLocations[].id` 或对 30 国逐条 `geo search` | **一次** `ad geo resolve --from-file … --json-out`;见 `google-ads-plan-source-fidelity.md` |
|
|
41
41
|
| 对话里手写完整 campaign JSON | Agent **写转换脚本** Excel → `campaign.json`(落盘),再 validate |
|
|
42
42
|
| 方案有 Exact/Phrase,JSON 却一律 `BROAD` | 转换脚本按方案列/词面写入 EXACT/PHRASE/BROAD 分块 |
|
|
43
|
+
| validate 失败后未问用户就改词/消重标题/截断 | **询问**「您自己改还是我帮您改?」;见 `google-ads-plan-source-fidelity.md` § 用户方案不合规 |
|
|
44
|
+
| 代改后只说「创建成功」不交代改了啥 | 创建完成后出报告:创建清单 + **从 xxx→xxx + 原因**(代改时维护变更账本) |
|
|
43
45
|
|
|
44
46
|
**多词短语(与 `keyword-negative-create` 同类误判)**:`KeywordText` 是 **JSON 字符串数组**,**空格多词完全合法**(如 `"how to make"`、`"home cooking"`)。PHRASE 写 `"\"vegan recipe\""` 或裸词 + `MatchTypeV2: "PHRASE"`(validate 会补引号)。**禁止**因「多词」就省略否词或让用户去 Google Ads 后台手补——那是 Agent 误判,不是 `campaign-create` 限制。
|
|
45
47
|
|
|
@@ -93,7 +95,9 @@ siluzan-tso ad campaign-validate --config-file ./campaign.json
|
|
|
93
95
|
# 用户确认方案后:
|
|
94
96
|
siluzan-tso ad campaign-create --config-file ./campaign.json --commit '<campaign create description>'
|
|
95
97
|
siluzan-tso ad batch get --id <taskId> --config-file ./campaign.json --json-out ./snap-campaign
|
|
98
|
+
# 读落盘 agentWorkflow.nextCommand(Creating 继续轮询;成功/部分成功 → batch diff)
|
|
96
99
|
siluzan-tso ad batch diff --batch-id <taskId> --config-file ./campaign.json --json-out ./snap-campaign
|
|
100
|
+
# 读 ok / missing(含 layer=location 投放国家);有缺失 exit 2,执行 remediateCommand 后复 diff
|
|
97
101
|
```
|
|
98
102
|
|
|
99
103
|
`Creating` 时每 5–10s 轮询 `batch get`,**勿**重复 `campaign-create`。
|
|
@@ -98,6 +98,8 @@
|
|
|
98
98
|
|
|
99
99
|
**报告/Excel 全流程走本 Skill**:按工作流卡片与 `report-templates/*.md` 拉数、落盘、脚本转换;**禁止**加载宿主第三方 xlsx/Excel Skill 代劳(不知 TSO 字段口径与账户核验)。
|
|
100
100
|
|
|
101
|
+
**写 Excel 时所有 ID 必须是文本(字符串),禁止数字类型**:含 `mediaCustomerId` / `entityId` / `accountId` / `campaignId` / `adGroupId` / `keywordId` / `criterionId` / geo `locationId` 及一切以 `Id`/`ID`/`id` 结尾的标识列。写入前一律 `String(id)`;`exceljs` 设 `numFmt: '@'`(文本),`openpyxl` 写字符串且勿让推断成 number。**禁止**写成 JSON number / Excel 数值单元格(会科学计数法或丢精度,如 `2.77E+09`)。金额、次数、比率等度量字段仍用数字类型。
|
|
102
|
+
|
|
101
103
|
**中间结果一律落盘**:跨步骤数据不靠对话记忆;Windows 避免管道传 JSON,优先 `--json-out` + `node -e` 读文件。
|
|
102
104
|
|
|
103
105
|
---
|
|
@@ -122,7 +124,7 @@
|
|
|
122
124
|
- 当前凭据未返回手机号且用户指定了手机号 → 视同未校验通过,引导重新用手机号登录。
|
|
123
125
|
- **Google 新建搜索系列**:流程在 `references/google-ads/google-ads-campaign-plan.md`;填 JSON 前**必须先 Read** `assets/campaign-create-template.json`,再 Read `assets/campaign-create-template.md`。**禁止**只读 `.md` 手写 JSON。
|
|
124
126
|
- **「根据官网生成 Google 搜索广告 / 表格格式」**:仍属新建搜索系列 → **W3 + 本文件上条**;用户要的「表格」是 `google-ads-launch-plan-template.md` 对 JSON 的投影,**不是**可跳过 JSON/`campaign-validate` 的独立交付物。**禁止**与 P8 网站诊断、P9 市场分析、W5 仅拓词混用。
|
|
125
|
-
- **Excel/表格投放方案 → 创建广告**:必读 `references/google-ads/rules/google-ads-plan-source-fidelity.md`。Agent **写代码**直接转成 campaign-create JSON;地域用 **`ad geo resolve`**;**禁止**对话手填完整 JSON、**禁止**编造 geo id;有方案匹配类型时勿压成一律 BROAD
|
|
127
|
+
- **Excel/表格投放方案 → 创建广告**:必读 `references/google-ads/rules/google-ads-plan-source-fidelity.md`。Agent **写代码**直接转成 campaign-create JSON;地域用 **`ad geo resolve`**;**禁止**对话手填完整 JSON、**禁止**编造 geo id;有方案匹配类型时勿压成一律 BROAD。**方案不合规时必须询问**:「您自己改还是我帮您改?」——**禁止**未问就静默改用户方案内容后 create。用户选「我帮您改」时:代改同步落盘变更账本;**创建完成后必出报告**(创建了哪些 + 从 xxx→xxx + 原因)。
|
|
126
128
|
- **「行业分析 / 行业分析报告 / 生成 XX 行业报告」**(例:「帮我生成一份电商行业的行业分析报告」)→ **P9 战略市场分析**。**必须**先 `siluzan-tso market-analysis collect … --json-out`,再 WebSearch 补数据、写 `market-report.json`,最后 `market-analysis render` 出 HTML。**禁止**不调用 CLI、仅在对话里用 WebSearch 写 Markdown 充当终稿。**不是** `google-analysis`、**不是** P8 网站诊断。
|
|
127
129
|
- **开户首次响应**:对话内首次进入开户话题时,**必须先**按 `references/accounts/open-account-by-media.md` §「首次响应硬规范」输出**完整必填清单**(未指明媒体则列全平台六表),再收集资料;**禁止**未列清单就执行 `open-account` 或零散追问。
|
|
128
130
|
- **Google 开户**:`open-account google-wizard` 仅限真实 TTY;Agent/自动化用非交互 `open-account google ...`,审核进度用 `account-history`。
|
|
@@ -175,7 +177,7 @@
|
|
|
175
177
|
|
|
176
178
|
**A · 币种**:首行含 `统计区间:…(货币:CNY|USD)`;全文符号与 `currencyCode` 一致(CNY=¥、USD=$,未混用);与当次 `list-accounts -k` 结果相同;多账户分币种分表、无跨币种「总计」行。
|
|
177
179
|
|
|
178
|
-
**B · 结构完整**(对照当次 `report-templates/*.md`):模板要求的每一章/Sheet 都存在;无整章空白(缺数据章节写 `[ 数据不可用:… ]`,禁止编造数字填坑);优化建议独立成节、引用当次数字(Meta:4 条建议各 ≥150 字 + 7 维补充;Google 诊断:每模块除表格外有「分析」+「建议」);Excel 的表头列须能在当次 `*.outline.txt` 找到对应字段、产物内账户 ID = 用户当轮给出的 `mediaCustomerId
|
|
180
|
+
**B · 结构完整**(对照当次 `report-templates/*.md`):模板要求的每一章/Sheet 都存在;无整章空白(缺数据章节写 `[ 数据不可用:… ]`,禁止编造数字填坑);优化建议独立成节、引用当次数字(Meta:4 条建议各 ≥150 字 + 7 维补充;Google 诊断:每模块除表格外有「分析」+「建议」);Excel 的表头列须能在当次 `*.outline.txt` 找到对应字段、产物内账户 ID = 用户当轮给出的 `mediaCustomerId`;**Excel 内全部 ID 列为文本**(见上文「写 Excel 时所有 ID 必须是文本」)。
|
|
179
181
|
|
|
180
182
|
**C · 数字可信**(抽样,不读大 JSON):总消耗/CPA 数量级与生成过程中脚本 stdout 打印的汇总一致(若无,补跑一次极小 `node -e` 只打印 totals);账户 ID、区间与用户需求一致;无「示例账户」「占位 123456」等模板残留;表格行数符合预期(如 P3 每个 `-a` ID 占一行)。
|
|
181
183
|
|
|
@@ -40,7 +40,9 @@
|
|
|
40
40
|
| ---- | ---- |
|
|
41
41
|
| 查数、诊断、报告、列表 | **先拉数再汇报**;缺账户 ID 才问;缺时间范围按 `agent-conventions.md` §五 处理(能默认则默认并注明区间) |
|
|
42
42
|
| 模糊意图(「帮我看一下」「最近怎么样」) | 选**最可能**的工作流先执行(有 Google 户 → 画像/总览);结论后附「您是不是还想…」 |
|
|
43
|
-
| **campaign-create 后 Sitelink
|
|
43
|
+
| **campaign-create 后 Sitelink / 投放国家 batch 漏建** | **自动**读 `batch get` 的 `agentWorkflow` → `ad batch diff` → 执行 `ad geo add` / `ad extension *` 补建 → 汇报已补挂条数;**勿**反问「需要我现在补上吗?」(用户已确认过 JSON) |
|
|
44
|
+
| **用户提供的投放方案(Excel 等)有不合规** | 列出问题 → **二选一询问**:「您自己改方案,还是我帮您改?」→ 停等回复;**禁止**未问就静默改词/标题/删行后直接 create(见 `google-ads-plan-source-fidelity.md`) |
|
|
45
|
+
| **用户选「我帮您改」且广告已创建完成** | **必出报告**:① 创建了哪些;② 修改了哪些(从 xxx → xxx + 原因)。代改时维护变更账本,创建后按账本汇报,勿凭记忆(见同文件 § 创建完成报告) |
|
|
44
46
|
| 写入/删除/充值/开户提交 | **必须先确认**(`agent-conventions.md` §四);话术用业务语言说明「将要做什么」 |
|
|
45
47
|
| 对象不清且无法推断 | **只问一次**,给 2~3 个选项,不要问卷式连问 |
|
|
46
48
|
|
|
@@ -74,13 +74,13 @@
|
|
|
74
74
|
- **步骤(Search · 方案文件 → 创建)**:
|
|
75
75
|
1. Read `campaign-create-template.json`;Agent 写转换脚本读 Excel → 写出 `campaign.json`(匹配类型按方案分块)。
|
|
76
76
|
2. `ad geo resolve -a <id> --from-file ./locations.json --json-out ./snap-geo`,把 id 写回 JSON。
|
|
77
|
-
3. `ad campaign-validate
|
|
77
|
+
3. `ad campaign-validate`;**有不合规 → 询问「您自己改还是我帮您改?」**(见 `google-ads-plan-source-fidelity.md`)→ 确认摘要 → `campaign-create` → `batch get` / `batch diff` / 自动补扩展;若 Agent 代改过 → **创建完成报告**(创建清单 + 从 xxx→xxx + 原因)。
|
|
78
78
|
- **步骤(Search 一体化创建,无现成方案文件)**:
|
|
79
79
|
1. 地域:多国 `ad geo resolve`;单国 `ad geo search -a <id> -q "United States"`(仍禁止编造)。
|
|
80
80
|
2. 门禁:`ad campaign-validate --config-file ./campaign.json`(必跑)。
|
|
81
81
|
3. 用户确认后创建:`ad campaign-create --config-file ./campaign.json`,记录返回 taskId。
|
|
82
|
-
4. 轮询:`ad batch get --id <taskId
|
|
83
|
-
5. **必做** `ad batch diff
|
|
82
|
+
4. 轮询:`ad batch get --id <taskId> --config-file … --json-out …` 直至非 Creating;**读落盘 `agentWorkflow.nextCommand`**(`--json-out` 时人读提示不会出现)。
|
|
83
|
+
5. **必做** `ad batch diff`(含投放国家);`ok===false` / exit 2 时按 `missing[].remediateCommand` **自动** `ad geo add` / `ad extension *` 补建,勿反问用户(见 `google-ads-campaign-plan.md` § batch diff 后自动补建)。
|
|
84
84
|
6. 复核:`ad campaigns` / `ad extension list` 确认系列与扩展齐全。
|
|
85
85
|
- **精细管理与日常运营**:`ad adgroup-create` / `ad keyword-create` / `ad keyword-negative-create` / `ad ad-create`(拓词辅助见 **W5**);调整用 `ad adgroup-status` / `ad campaign-status` / `ad ad-delete` / `ad keyword-negative-delete`。完整参数见 `references/google-ads/google-ads.md`。
|
|
86
86
|
- **交付/确认**:关键词匹配格式 `running shoes`=广泛 / `"..."`=词组 / `[...]`=精确;结构性写操作(新建/暂停/删除)须用户确认;写后用成对读命令复核。
|
|
@@ -47,12 +47,15 @@
|
|
|
47
47
|
| 3 | 无方案文件时:多国 **`ad geo resolve`**(或单国 `ad geo search`)写入 `locations` + `targetedLocations` | **禁止编造 id** |
|
|
48
48
|
| 4 | 无方案文件时:按分层写入 `KeywordsForBatchJob`(EXACT/PHRASE/BROAD);否词进 `NegativeKeywordsForBatchJob` | 参考 `google-ads-keyword-taxonomy.md`;有方案源则走步骤 0 |
|
|
49
49
|
| 5 | 得到与模板同构的 `campaign-create` JSON | **`assets/campaign-create-template.json`** |
|
|
50
|
-
| 6 | **`ad campaign-validate --config-file <json
|
|
50
|
+
| 6 | **`ad campaign-validate --config-file <json>`** | 下文「校验」 |
|
|
51
|
+
| 6b | **方案来自用户且不合规**:列出问题 → **询问**「您自己改还是我帮您改?」→ 按选择处理后再 validate(**禁止**未问就静默改方案) | **`rules/google-ads-plan-source-fidelity.md`** § 用户方案不合规 |
|
|
52
|
+
| 6c | 用户选「我帮您改」:改 JSON 时**同步落盘变更账本**(from → to + reason) | 同上 § Agent 代改后:创建完成报告 |
|
|
51
53
|
| 7 | 给人看:**国家↔id** + **匹配类型条数** + 可选 Markdown 投影;**勿**贴整份 JSON 当主交付 | `google-ads-launch-plan-template.md` |
|
|
52
54
|
| 8 | 用户确认后 **`ad campaign-create`** | `references/google-ads/google-ads.md` |
|
|
53
55
|
| 9 | 每隔5s 获取创建结果 | `ad batch get --id <taskId> --config-file ./campaign.json` |
|
|
54
56
|
| 10 | 成功或部分成功后 **`ad batch diff`** 对照 JSON 与账户实况 | |
|
|
55
57
|
| 11 | **自动补建缺失项**(见下文「batch diff 后自动补建」):附加信息(Sitelink/Callout 等)**直接**执行 `ad extension *`;组/词/RSA 用对应 create 命令。**勿**仅汇报失败并反问用户是否补建 | `references/google-ads/google-ads.md` § batch diff 后自动补建 |
|
|
58
|
+
| 12 | **若曾由 Agent 代改方案**:出「创建与修改报告」——创建了哪些 + 修改了哪些(从 xxx → xxx + 原因) | **`rules/google-ads-plan-source-fidelity.md`** § 创建完成报告 |
|
|
56
59
|
|
|
57
60
|
多系列:每系列一个 JSON;可选 `campaign-manifest.json`(`role: brand|competitor|generic`)仅作文件组织参考。
|
|
58
61
|
|
|
@@ -110,8 +113,9 @@ validate 与 create **共用** `runCampaignCreateValidation`:词面规范化 +
|
|
|
110
113
|
|
|
111
114
|
1. 使用 **`ad campaign-validate --config-file <json> --json-out <dir>`**(与 create/batch 同一落盘目录),读落盘文件中的 `lengthViolations`(每项含 `path`、`limit`、`actual`、**完整** `text`)。
|
|
112
115
|
2. Agent 将 **全部** 超长条目整理成表(路径、原文、上限、超出量),并为每条给出 **1–2 个改写方案**(保留卖点、符合字符计数;CJK 按 2 计见 `google-ads-compliance.md` §3.2.1)。
|
|
113
|
-
3.
|
|
114
|
-
4.
|
|
116
|
+
3. **方案来自用户时**:先按 `google-ads-plan-source-fidelity.md` 询问「您自己改还是我帮您改?」;选「我帮您改」并确认改写方案后,再改 JSON。
|
|
117
|
+
4. **用户确认**选用方案后,Agent **只改 JSON 对应字段**,再执行 `campaign-validate`;通过后再 `campaign-create`。
|
|
118
|
+
5. **禁止**:未确认前 `slice`/省略号截断、仅改 `--write-normalized` 而不经用户确认。
|
|
115
119
|
|
|
116
120
|
人读模式失败时 CLI 会额外打印「📏 超长内容清单」;`--json-out` 时见 `lengthViolations` + `agentHint`。
|
|
117
121
|
|
|
@@ -119,18 +123,20 @@ validate 与 create **共用** `runCampaignCreateValidation`:词面规范化 +
|
|
|
119
123
|
|
|
120
124
|
## batch diff 后自动补建(Agent 必遵)
|
|
121
125
|
|
|
122
|
-
`campaign-create` BatchJob
|
|
126
|
+
`campaign-create` BatchJob **常漏附加信息**(Sitelink)与 **投放国家**;系列/组/关键词/RSA 已创建成功时,**不要**停在「需要我现在补上吗?」——用户确认 JSON 时已包含这些内容,Agent **应自动补建**。
|
|
123
127
|
|
|
124
128
|
| 步骤 | 动作 |
|
|
125
129
|
| ---- | ---- |
|
|
126
|
-
| 1 | `ad batch get
|
|
127
|
-
| 2 | `ad batch diff
|
|
128
|
-
| 3 |
|
|
129
|
-
| 4 | 无 `remediateCommand` 时,按 JSON
|
|
130
|
-
| 5 |
|
|
131
|
-
| 6 |
|
|
130
|
+
| 1 | `ad batch get --id <taskId> --config-file ./campaign.json --json-out ./snap-campaign` 直至终端态(Creating 时每 5s 轮询;**读落盘 `agentWorkflow.nextCommand`**,勿只看人读提示) |
|
|
131
|
+
| 2 | `Successfully` / `HasFailed`:执行 `agentWorkflow.nextCommand`(即 `ad batch diff … --json-out`);**Failed** 勿 diff |
|
|
132
|
+
| 3 | 读落盘 `ok` / `missing[]`;`layer=location` → 执行 `ad geo add`;`layer=extension` → 执行 `remediateCommand` |
|
|
133
|
+
| 4 | 无 `remediateCommand` 时,按 JSON 路径映射 `ad extension *` / `adgroup-create` / `keyword-create` / `ad-create` / `keyword-negative-create` |
|
|
134
|
+
| 5 | 有缺失时 CLI **exit 2**;补建后再 `ad batch diff`,直到 `ok===true`(含 `counts.liveLocations === plannedLocations`) |
|
|
135
|
+
| 6 | 汇总告诉用户「已自动补上 N 个投放国家 / M 条 Sitelink」 |
|
|
132
136
|
| 7 | 仅当补建命令也失败时,才向用户说明原因并给改写方案 |
|
|
133
137
|
|
|
138
|
+
**Agent 易漏点**:`--json-out` 时人读「下一步」不会打印——**必须以落盘 `agentWorkflow` 为准**;`campaign-create` 落盘同样带 `agentWorkflow.nextCommand`(首轮 `batch get`)。
|
|
139
|
+
|
|
134
140
|
**Sitelink 单条示例**(字段来自 `ExtensionsForBatchJob[i].Properties`):
|
|
135
141
|
|
|
136
142
|
```bash
|
|
@@ -357,12 +357,13 @@ siluzan-tso keyword geo-list [--country-code <US,CN,...>] [--name-contains <text
|
|
|
357
357
|
# - 广告组在 campaign.AdGroupsForBatchJob 数组中
|
|
358
358
|
siluzan-tso ad campaign-create --config-file ./campaign.json
|
|
359
359
|
|
|
360
|
-
# 2. 查异步任务(Creating 时每 5–10s 轮询,直至非 Creating)
|
|
361
|
-
siluzan-tso ad batch get --id <taskId> --config-file ./campaign.json
|
|
360
|
+
# 2. 查异步任务(Creating 时每 5–10s 轮询,直至非 Creating;Agent 几乎总带 --json-out)
|
|
361
|
+
siluzan-tso ad batch get --id <taskId> --config-file ./campaign.json --json-out ./snap-campaign
|
|
362
|
+
# 读落盘 agentWorkflow:Creating → 继续 nextCommand;Successfully/HasFailed → 执行 nextCommand(batch diff)
|
|
362
363
|
|
|
363
|
-
# 3.
|
|
364
|
-
siluzan-tso ad batch diff --batch-id <taskId> --config-file ./campaign.json
|
|
365
|
-
#
|
|
364
|
+
# 3. 成功或部分成功后必做比对(含投放国家;有缺失 exit 2)
|
|
365
|
+
siluzan-tso ad batch diff --batch-id <taskId> --config-file ./campaign.json --json-out ./snap-campaign
|
|
366
|
+
# 读 ok / missing[].remediateCommand(地域 → ad geo add;扩展 → ad extension *)
|
|
366
367
|
|
|
367
368
|
# Failed:系列未创建,无需 diff;根据 get 的 reason/errors 改 JSON 后重提
|
|
368
369
|
# siluzan-tso ad campaign-create --config-file ./campaign.json
|
|
@@ -372,10 +373,10 @@ siluzan-tso ad batch diff --batch-id <taskId> --config-file ./campaign.json
|
|
|
372
373
|
|
|
373
374
|
| `status` | 含义 | 做法 |
|
|
374
375
|
| -------------- | -------- | ------------------------------------------------------------------------------------------------------ |
|
|
375
|
-
| `Creating` | 仍在执行 | 继续 `ad batch get`,勿重复 `campaign-create`
|
|
376
|
-
| `Successfully` | 全部成功 | **须** `ad batch diff`
|
|
376
|
+
| `Creating` | 仍在执行 | 继续 `ad batch get`(带同一 `--config-file` + `--json-out`),读 `agentWorkflow`,勿重复 `campaign-create` |
|
|
377
|
+
| `Successfully` | 全部成功 | **须** `ad batch diff`(含投放国家);`--json-out` 时 `ok===true` 才算齐 |
|
|
377
378
|
| `Failed` | 全部失败 | `ad batch get` 读 `reason`/`errors` → **只改 JSON** → 再 `ad campaign-create`(**不要** `batch diff`) |
|
|
378
|
-
| `HasFailed` | 部分成功 | **须** `ad batch diff` 列出缺失 →
|
|
379
|
+
| `HasFailed` | 部分成功 | **须** `ad batch diff` 列出缺失 → 分步补建或删系列重提(`ok===false` / exit 2) |
|
|
379
380
|
|
|
380
381
|
**`HasFailed` 分支:**
|
|
381
382
|
|
|
@@ -386,24 +387,25 @@ siluzan-tso ad batch diff --batch-id <taskId> --config-file ./campaign.json
|
|
|
386
387
|
- 缺组:`ad adgroup-create`(`--json-out` 取 `id`)
|
|
387
388
|
- 缺词:`ad keyword-create`(词面格式同 JSON `KeywordsForBatchJob`)
|
|
388
389
|
- 缺 RSA:`ad ad-create`(`--headlines` / `--descriptions` 从 JSON `AdsForBatchJob` 抄)
|
|
390
|
+
- **缺投放国家/地域**:**自动**执行 `ad geo add -a <account> --campaign-id <id> --location-id <geoId>`(`layer=location` 的 `remediateCommand`)
|
|
389
391
|
- **缺附加信息(常见)**:**自动**执行 `ad extension sitelink` / `callout` / `call` / `snippet`(见下节);`batch diff` 输出含 `补建:` 行或 `--json-out` 的 `missing[].remediateCommand`
|
|
390
|
-
补建前可用 `ad groups` / `ad keywords` / `ad extension list` 核对账户现状。
|
|
392
|
+
补建前可用 `ad groups` / `ad keywords` / `ad geo list --mode targeted` / `ad extension list` 核对账户现状。
|
|
391
393
|
|
|
392
|
-
### batch diff
|
|
394
|
+
### batch diff 后自动补建(Agent 必遵)
|
|
393
395
|
|
|
394
|
-
BatchJob
|
|
396
|
+
BatchJob 常漏 **附加信息**(Sitelink)与 **投放地域**;系列/组/词/RSA 往往已成功。用户确认创建时已认可 JSON → Agent **须直接补建**,**禁止**仅汇报「未创建,需要我现在补上吗?」。
|
|
395
397
|
|
|
396
|
-
1.
|
|
397
|
-
2.
|
|
398
|
-
3.
|
|
399
|
-
4.
|
|
400
|
-
5.
|
|
398
|
+
1. `ad campaign-create` / `ad batch get` 的 `--json-out` 落盘含 **`agentWorkflow`**:读 `nextCommand` / `shouldRunBatchDiff` / `agentHint`(人读提示在 `--json-out` 早退时不可见)。
|
|
399
|
+
2. 跑 `ad batch diff`;对 `missing[]` 中带 `remediateCommand` 的项**直接执行**(`location` → `ad geo add`;`extension` → `ad extension *`)。
|
|
400
|
+
3. 扩展字段映射:`Properties.Text` + `DestinationUrl` → `ad extension sitelink`;`CALLOUT` → `callout`;`CALL` → `call`;`STRUCTURED_SNIPPET` → `snippet`。
|
|
401
|
+
4. 有缺失时 CLI **exit 2**;补建后再跑一次 `ad batch diff`,直到 `ok===true`。
|
|
402
|
+
5. 向用户汇报已补挂条数(地域 + Sitelink 等)。
|
|
401
403
|
|
|
402
404
|
完整流水线见 `references/google-ads/google-ads-campaign-plan.md` § batch diff 后自动补建。
|
|
403
405
|
|
|
404
|
-
**`ad batch diff` 比对维度:**系列是否存在 → 各 `AdGroupsForBatchJob[].Name` → 组内关键词(匹配类型+词面)→ RSA 首条标题 → 系列否定词 →
|
|
406
|
+
**`ad batch diff` 比对维度:**系列是否存在 → **`targetedLocations`(投放国家/地域 id)** → 各 `AdGroupsForBatchJob[].Name` → 组内关键词(匹配类型+词面)→ RSA 首条标题 → 系列否定词 → 附加信息条数。`--json-out` 含 `ok` / `agentHint` / `counts.plannedLocations|liveLocations`。
|
|
405
407
|
|
|
406
|
-
**`ad batch get` 输出:**摘要表 +
|
|
408
|
+
**`ad batch get` 输出:**摘要表 + `reason`/`errors`;`--json-out` 另含 **`agentWorkflow`**(`nextCommand` 指向 batch diff 或继续轮询)。
|
|
407
409
|
|
|
408
410
|
**CLI 在提交前只做三件事:**
|
|
409
411
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
| `google-ads-keyword-strategy.md` | 关键词策略规划 |
|
|
10
10
|
| `google-ads-keyword-optimization.md` | 关键词出价/否定/扩词优化 |
|
|
11
11
|
| `google-ads-keyword-taxonomy.md` | 词类分层与命名 |
|
|
12
|
-
| `google-ads-plan-source-fidelity.md` | **Excel/表格方案 → JSON**:Agent 写脚本直转 + `ad geo resolve
|
|
12
|
+
| `google-ads-plan-source-fidelity.md` | **Excel/表格方案 → JSON**:Agent 写脚本直转 + `ad geo resolve`;不合规须问「自己改 / 帮您改」(有方案文件时必读) |
|
|
13
13
|
| `google-ads-creative-optimization.md` | 创意/文案优化 |
|
|
14
14
|
| `google-ads-audience-strategy.md` | 受众策略 |
|
|
15
15
|
| `google-ads-pmax-guide.md` | PMax 运营指南(配合 `assets/pmax-create-template.md`) |
|
|
@@ -19,17 +19,87 @@
|
|
|
19
19
|
campaign.json (campaign-create 契约)
|
|
20
20
|
│
|
|
21
21
|
▼
|
|
22
|
-
ad campaign-validate
|
|
22
|
+
ad campaign-validate
|
|
23
|
+
│
|
|
24
|
+
├─ 有不合规 → 【必问】用户自己改,还是 Agent 代改?(见下文)
|
|
25
|
+
│
|
|
26
|
+
└─ 通过 → 确认摘要 → ad campaign-create
|
|
23
27
|
```
|
|
24
28
|
|
|
25
29
|
| 步 | 谁做 | 动作 |
|
|
26
30
|
| -- | ---- | ---- |
|
|
27
|
-
| 1 | Agent + 脚本 | 读全 Sheet,按语义抽出预算/组/词+匹配类型/RSA/否词/国家等,写出 `campaign.json
|
|
31
|
+
| 1 | Agent + 脚本 | 读全 Sheet,按语义抽出预算/组/词+匹配类型/RSA/否词/国家等,写出 `campaign.json`(**先忠实落盘,勿静默改用户方案**) |
|
|
28
32
|
| 2 | CLI | 国家列表 → `ad geo resolve -a <id> --from-file … --json-out …`,把返回的 `locations` / `targetedLocations` 写入 JSON(**禁止**编造 id) |
|
|
29
33
|
| 3 | CLI | `ad campaign-validate --config-file ./campaign.json` |
|
|
34
|
+
| 3b | Agent → 用户 | **若有不合规**:列出问题清单 → **询问**「您自己改方案/文件,还是我帮您改?」→ 按用户选择处理后再 validate(见下节) |
|
|
30
35
|
| 4 | Agent → 用户 | 摘要确认:国家↔id、Exact/Phrase/Broad 条数(可抽样几条),**勿**贴整份 JSON |
|
|
31
36
|
| 5 | CLI | 用户确认后 `ad campaign-create` → `batch get` / `batch diff` |
|
|
32
37
|
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 用户方案不合规时:必须二选一询问(Agent 必遵)
|
|
41
|
+
|
|
42
|
+
**适用**:计划来源是用户提供的(Excel / 表格 / 粘贴方案 / 用户自写的 JSON),且出现下列任一情况:
|
|
43
|
+
|
|
44
|
+
| 类型 | 示例 |
|
|
45
|
+
| ---- | ---- |
|
|
46
|
+
| `campaign-validate` error / warning | 词面非法字符、嵌套引号、RSA 跨组标题重复、超长、匹配类型与符号不一致、locations 数量不一致等 |
|
|
47
|
+
| 政策 / 合规明显风险 | 违禁宣称、落地页与文案严重不符等(见 `google-ads-compliance.md`) |
|
|
48
|
+
| 转换时发现方案自相矛盾 | 同组同词多匹配类型冲突、必填块缺失等 |
|
|
49
|
+
|
|
50
|
+
**禁止**:未询问就替用户改方案内容(改词面、改标题消重、截断、删词、改匹配类型等)然后直接 create。
|
|
51
|
+
**禁止**:只丢一句「校验失败」就停住,不给选项。
|
|
52
|
+
|
|
53
|
+
**必做话术结构**(业务语言,勿贴整段 CLI):
|
|
54
|
+
|
|
55
|
+
1. 说明:方案里有 **N 处**不合规(可表格:位置 / 原文要点 / 原因)。
|
|
56
|
+
2. **明确二选一**(一次问清):
|
|
57
|
+
- **A. 您自己改**:改 Excel/原文后发我,或告诉我改哪几处;我再重新转换/校验。
|
|
58
|
+
- **B. 我帮您改**:您同意后,我按建议改 `campaign.json`,再 validate → 请您确认 → create;**创建完成后必须出「创建与修改报告」**(见下节)。
|
|
59
|
+
3. **停等用户回复**;选 B 时再动手改;选 A 时不要擅自改 JSON。
|
|
60
|
+
4. **选 B 时立刻落盘变更账本**(见下节「变更账本」):每改一处就记一行「原值 → 新值 + 原因」,勿等创建完再凭记忆补写。
|
|
61
|
+
|
|
62
|
+
**例外(仍可静默做,不算「改方案」,也不进修改报告主表)**:`ad geo resolve` 取地域 id、剥 `_` 注解键、提交前金额元→分——这些是契约/网关要求,不是改用户业务内容。
|
|
63
|
+
|
|
64
|
+
**与「创建后自动补建」的区别**:用户已确认过 JSON 并 `campaign-create` 之后,BatchJob 漏掉的 Sitelink/地域用 `batch diff` **自动补建**(见 `google-ads-campaign-plan.md`);那是执行层补洞,不是改用户方案。不合规询问发生在 **create 之前**。补建条数可写在报告「创建结果」里,但**不要**混进「相对原方案的修改」表。
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Agent 代改后:创建完成报告(必出)
|
|
69
|
+
|
|
70
|
+
**何时必出**:用户选择「我帮您改」且最终完成了 `campaign-create`(含 batch get / batch diff / 必要补建)之后。
|
|
71
|
+
**何时不必出完整修改表**:用户全程自己改方案、Agent 未改业务内容——只需常规创建结果摘要。
|
|
72
|
+
|
|
73
|
+
### 变更账本(代改过程中维护)
|
|
74
|
+
|
|
75
|
+
落盘建议:`./snap-campaign/plan-edits.json`(或同目录 Markdown)。每条至少:
|
|
76
|
+
|
|
77
|
+
| 字段 | 说明 |
|
|
78
|
+
| ---- | ---- |
|
|
79
|
+
| `path` | JSON 路径或业务位置(如组名 + 关键词 / RSA 标题序号) |
|
|
80
|
+
| `from` | 用户原方案中的值(改前) |
|
|
81
|
+
| `to` | 实际写入并创建的值(改后) |
|
|
82
|
+
| `reason` | 修改原因(对应哪条 validate/合规规则,一句话) |
|
|
83
|
+
|
|
84
|
+
**禁止**:创建结束后凭印象写「改了一些标题」;必须以账本为准。
|
|
85
|
+
|
|
86
|
+
### 面向用户的报告结构(业务语言)
|
|
87
|
+
|
|
88
|
+
创建与核对结束后,用一份短报告回复用户,至少含三块:
|
|
89
|
+
|
|
90
|
+
1. **创建了哪些**
|
|
91
|
+
账户 ID/名称、系列名与 `campaignId`、广告组数、关键词数(可附 Exact/Phrase/Broad)、否词数、投放国家数、RSA/附加信息条数;batch 状态;若有自动补建(Sitelink/地域等)写明补了几条。
|
|
92
|
+
|
|
93
|
+
2. **修改了哪些(相对您的原方案)**
|
|
94
|
+
表格逐条:**位置 | 从(原值) | 改成(新值) | 原因**。
|
|
95
|
+
- `from` / `to` 写清原文,勿只写「已优化」。
|
|
96
|
+
- 无修改则写「相对您的原方案无业务内容修改」(仅 geo id 解析等不写进此表)。
|
|
97
|
+
|
|
98
|
+
3. **未改动的说明(可选一句)**
|
|
99
|
+
如:匹配类型/国家列表按您的方案保留。
|
|
100
|
+
|
|
101
|
+
**禁止**:只说「已创建成功」而不交代代改明细;**禁止**把创建结果与修改混成一团让用户自己猜改了啥。
|
|
102
|
+
|
|
33
103
|
可选脚手架(非必须):`assets/plan-extract.example.json` + `assets/scripts/assemble-campaign-from-plan.mjs`——若 Agent 更习惯先抽中间件再组装,可以用;**不强制**。
|
|
34
104
|
|
|
35
105
|
---
|
|
@@ -50,6 +50,11 @@
|
|
|
50
50
|
2. Read 本次每个 section 的 **`*.outline.txt`**(见 `google-period-report-excel.md` §执行门禁)。
|
|
51
51
|
3. **禁止** `cat|json.tool|head` 或 Read 业务 `*.json` 探字段;**禁止**加载宿主第三方 xlsx Skill。
|
|
52
52
|
|
|
53
|
+
### 写 xlsx 硬规则(全报告类型共用)
|
|
54
|
+
|
|
55
|
+
- **所有 ID 列必须写成字符串(文本)**,禁止 Excel 数字类型:`mediaCustomerId` / `campaignId` / `adGroupId` / `keywordId` / geo id 等一律 `String(...)`;`exceljs` 用 `numFmt: '@'`,`openpyxl` 写 str。详见 `references/core/agent-conventions.md`。
|
|
56
|
+
- 金额、次数、比率等度量字段仍用数字类型。
|
|
57
|
+
|
|
53
58
|
### 步骤 4:拉取数据
|
|
54
59
|
|
|
55
60
|
- **`google-analysis` / `facebook-analysis` / `tiktok-analysis` / `bing-analysis` / `report …` 账户分析子命令**:统一 **`--json-out <目录>`** 落盘,再由脚本读 **`manifest-<accountId>.json` / `report-manifest-<accountId>.json`**(清单文件名见 stdout 摘要的 `manifestFile`)与各 **`<section>-<accountId>.json`**(见 `references/analytics/account-analytics.md`)。
|
|
@@ -187,6 +187,7 @@ Sheet 5 整段系列明细读:`./snap-inquiry/campaigns-<accountId>_<S>-<E>.js
|
|
|
187
187
|
|
|
188
188
|
- **金额、CPL / CPA / CPC / 平均费用**:保留 **2 位小数**,写明货币代码(如 `¥123.45 CNY`)。
|
|
189
189
|
- **点击率 / 转化率 / 互动率**(`ctr` / `conversionRate` / `interactionRate`):CLI 已归一为 **0~1 小数**(schemaVersion ≥ 2),写入 Excel 时**直接写 0~1**(与运营样表对齐,Excel 单元格用百分比格式自动渲染)。话术 `x%` 用 `(v * 100).toFixed(2) + "%"`。
|
|
190
|
+
- **所有 ID**:账户 / 系列 / 组 / 关键词 / 地域等 id **一律字符串(文本单元格)**,`String(id)` + `numFmt: '@'`(或 openpyxl 写 str);**禁止**数字类型(防科学计数法)。见 `agent-conventions.md`。
|
|
190
191
|
- **空值**:`interactions === 0` 时「平均费用」「互动率」填 `—`,**禁止**除零。
|
|
191
192
|
|
|
192
193
|
### 分析输出区位置(**全局硬约束**)
|
|
@@ -101,7 +101,8 @@ siluzan-tso google-analysis -a <id> --start <S> --end <E> --json-out ./snap-p4 \
|
|
|
101
101
|
2. **首行或 R2** 含统计区间 + 币种(与 `list-accounts` 一致)。
|
|
102
102
|
3. **列表 Sheet**:R1 为表头;列名与 outline 字段含义对应,不造 outline 中不存在的列。
|
|
103
103
|
4. **数值**:金额 2 位小数;`ctr`/`conversionRate` 按上文 0~1 口径;禁止对 JSON 比率再 ÷100。
|
|
104
|
-
5.
|
|
104
|
+
5. **所有 ID 列写字符串(文本)**:账户/系列/组/关键词/地域等 id 用 `String(id)` + 文本格式(`exceljs` `numFmt: '@'`);**禁止**写成数字(防科学计数法/丢精度)。见 `agent-conventions.md`。
|
|
105
|
+
6. 某维度拉数失败:该 Sheet 写 `[ 数据不可用:<原因> ]`,禁止留空或编造。
|
|
105
106
|
|
|
106
107
|
有 OKKI 固定话术时改走 **P6**(`okki-weekly-google-client.md`),**不**用本文。
|
|
107
108
|
|
|
@@ -114,6 +115,7 @@ siluzan-tso google-analysis -a <id> --start <S> --end <E> --json-out ./snap-p4 \
|
|
|
114
115
|
- [ ] `google-analysis` 的 `accountId`(stdout 摘要)= 用户要求的 `mediaCustomerId`
|
|
115
116
|
- [ ] 每个 Sheet 列均能在对应 `*.outline.txt` 中找到字段源
|
|
116
117
|
- [ ] 脚本 stdout 打印的 `itemCount` 与 Excel 数据行数一致(±合计行)
|
|
118
|
+
- [ ] 全部 ID 列为文本(非数字类型 / 无科学计数法)
|
|
117
119
|
- [ ] 未使用第三方 xlsx Skill 替代本 Skill 拉数与字段口径
|
|
118
120
|
|
|
119
121
|
---
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
> 对照业务交付物:`无锡顺晟Facebook4月报告.xlsx`(5 个 Sheet)。
|
|
7
7
|
> Excel 流程:CLI 拉数 → Agent 分析并写 `meta-period-report.json` → **Agent 脚本**按本规格写 `.xlsx`(**无** CLI 内置 excel 子命令)。
|
|
8
|
+
>
|
|
9
|
+
> **ID 列硬规则**:账户 / 系列 / 广告组 / 广告等 id **一律写成字符串(文本)**,禁止 Excel 数字类型;见 `references/core/agent-conventions.md`。
|
|
8
10
|
|
|
9
11
|
---
|
|
10
12
|
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
- **`siluzan-tso` 不提供**「一键生成 OKKI Excel」的子命令,**也不要**在 CLI 里新增。
|
|
25
25
|
- 需交付 Excel 时:由 **Agent(含 WorkBuddy 编排)** 在下列 CLI **`--json-out` 落盘后**,自行编写 **Node.js / Python** 脚本(如 `exceljs`、`xlsx`、`openpyxl`)读取 JSON → 写 `.xlsx`。
|
|
26
26
|
- **数值一律来自落盘 JSON,禁止在脚本里写死业务数字。**
|
|
27
|
+
- **所有 ID 列写字符串(文本)**,禁止 Excel 数字类型(防科学计数法);见 `references/core/agent-conventions.md`。
|
|
27
28
|
|
|
28
29
|
---
|
|
29
30
|
|
|
@@ -50,6 +50,11 @@
|
|
|
50
50
|
2. Read 本次每个 section 的 **`*.outline.txt`**(见 `google-period-report-excel.md` §执行门禁)。
|
|
51
51
|
3. **禁止** `cat|json.tool|head` 或 Read 业务 `*.json` 探字段;**禁止**加载宿主第三方 xlsx Skill。
|
|
52
52
|
|
|
53
|
+
### 写 xlsx 硬规则(全报告类型共用)
|
|
54
|
+
|
|
55
|
+
- **所有 ID 列必须写成字符串(文本)**,禁止 Excel 数字类型:`mediaCustomerId` / `campaignId` / `adGroupId` / `keywordId` / geo id 等一律 `String(...)`;`exceljs` 用 `numFmt: '@'`,`openpyxl` 写 str。详见 `references/core/agent-conventions.md`。
|
|
56
|
+
- 金额、次数、比率等度量字段仍用数字类型。
|
|
57
|
+
|
|
53
58
|
### 步骤 4:拉取数据
|
|
54
59
|
|
|
55
60
|
- **`google-analysis` / `facebook-analysis` / `tiktok-analysis` / `bing-analysis` / `report …` 账户分析子命令**:统一 **`--json-out <目录>`** 落盘,再由脚本读 **`manifest-<accountId>.json` / `report-manifest-<accountId>.json`**(清单文件名见 stdout 摘要的 `manifestFile`)与各 **`<section>-<accountId>.json`**(见 `references/analytics/account-analytics.md`)。
|
|
@@ -187,6 +187,7 @@ Sheet 5 整段系列明细读:`./snap-inquiry/campaigns-<accountId>_<S>-<E>.js
|
|
|
187
187
|
|
|
188
188
|
- **金额、CPL / CPA / CPC / 平均费用**:保留 **2 位小数**,写明货币代码(如 `¥123.45 CNY`)。
|
|
189
189
|
- **点击率 / 转化率 / 互动率**(`ctr` / `conversionRate` / `interactionRate`):CLI 已归一为 **0~1 小数**(schemaVersion ≥ 2),写入 Excel 时**直接写 0~1**(与运营样表对齐,Excel 单元格用百分比格式自动渲染)。话术 `x%` 用 `(v * 100).toFixed(2) + "%"`。
|
|
190
|
+
- **所有 ID**:账户 / 系列 / 组 / 关键词 / 地域等 id **一律字符串(文本单元格)**,`String(id)` + `numFmt: '@'`(或 openpyxl 写 str);**禁止**数字类型(防科学计数法)。见 `agent-conventions.md`。
|
|
190
191
|
- **空值**:`interactions === 0` 时「平均费用」「互动率」填 `—`,**禁止**除零。
|
|
191
192
|
|
|
192
193
|
### 分析输出区位置(**全局硬约束**)
|
|
@@ -101,7 +101,8 @@ siluzan-tso google-analysis -a <id> --start <S> --end <E> --json-out ./snap-p4 \
|
|
|
101
101
|
2. **首行或 R2** 含统计区间 + 币种(与 `list-accounts` 一致)。
|
|
102
102
|
3. **列表 Sheet**:R1 为表头;列名与 outline 字段含义对应,不造 outline 中不存在的列。
|
|
103
103
|
4. **数值**:金额 2 位小数;`ctr`/`conversionRate` 按上文 0~1 口径;禁止对 JSON 比率再 ÷100。
|
|
104
|
-
5.
|
|
104
|
+
5. **所有 ID 列写字符串(文本)**:账户/系列/组/关键词/地域等 id 用 `String(id)` + 文本格式(`exceljs` `numFmt: '@'`);**禁止**写成数字(防科学计数法/丢精度)。见 `agent-conventions.md`。
|
|
105
|
+
6. 某维度拉数失败:该 Sheet 写 `[ 数据不可用:<原因> ]`,禁止留空或编造。
|
|
105
106
|
|
|
106
107
|
有 OKKI 固定话术时改走 **P6**(`okki-weekly-google-client.md`),**不**用本文。
|
|
107
108
|
|
|
@@ -114,6 +115,7 @@ siluzan-tso google-analysis -a <id> --start <S> --end <E> --json-out ./snap-p4 \
|
|
|
114
115
|
- [ ] `google-analysis` 的 `accountId`(stdout 摘要)= 用户要求的 `mediaCustomerId`
|
|
115
116
|
- [ ] 每个 Sheet 列均能在对应 `*.outline.txt` 中找到字段源
|
|
116
117
|
- [ ] 脚本 stdout 打印的 `itemCount` 与 Excel 数据行数一致(±合计行)
|
|
118
|
+
- [ ] 全部 ID 列为文本(非数字类型 / 无科学计数法)
|
|
117
119
|
- [ ] 未使用第三方 xlsx Skill 替代本 Skill 拉数与字段口径
|
|
118
120
|
|
|
119
121
|
---
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
> 对照业务交付物:`无锡顺晟Facebook4月报告.xlsx`(5 个 Sheet)。
|
|
7
7
|
> Excel 流程:CLI 拉数 → Agent 分析并写 `meta-period-report.json` → **Agent 脚本**按本规格写 `.xlsx`(**无** CLI 内置 excel 子命令)。
|
|
8
|
+
>
|
|
9
|
+
> **ID 列硬规则**:账户 / 系列 / 广告组 / 广告等 id **一律写成字符串(文本)**,禁止 Excel 数字类型;见 `references/core/agent-conventions.md`。
|
|
8
10
|
|
|
9
11
|
---
|
|
10
12
|
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
- **`siluzan-tso` 不提供**「一键生成 OKKI Excel」的子命令,**也不要**在 CLI 里新增。
|
|
25
25
|
- 需交付 Excel 时:由 **Agent(含 WorkBuddy 编排)** 在下列 CLI **`--json-out` 落盘后**,自行编写 **Node.js / Python** 脚本(如 `exceljs`、`xlsx`、`openpyxl`)读取 JSON → 写 `.xlsx`。
|
|
26
26
|
- **数值一律来自落盘 JSON,禁止在脚本里写死业务数字。**
|
|
27
|
+
- **所有 ID 列写字符串(文本)**,禁止 Excel 数字类型(防科学计数法);见 `references/core/agent-conventions.md`。
|
|
27
28
|
|
|
28
29
|
---
|
|
29
30
|
|
|
@@ -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.37-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.37-beta.8'
|
|
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.37-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.37-beta.8"
|
|
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"
|