siluzan-tso-cli 1.1.20-beta.22 → 1.1.20-beta.24
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 +242 -136
- package/dist/skill/SKILL.md +1 -0
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/account-analytics.md +1 -1
- package/dist/skill/references/google-ads-rules/google-ads-campaign-optimization.md +1 -0
- package/dist/skill/references/google-ads.md +19 -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.20-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.20-beta.24),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -111956,141 +111956,6 @@ async function runAdSearchTerms(opts) {
|
|
|
111956
111956
|
init_auth();
|
|
111957
111957
|
init_cli_json_snapshot();
|
|
111958
111958
|
init_strip_legacy_google_fields();
|
|
111959
|
-
async function runAdGeoSearch(opts) {
|
|
111960
|
-
const config = loadConfig(opts.token);
|
|
111961
|
-
const googleApiUrl = requireGoogleApi(config);
|
|
111962
|
-
const url = `${googleApiUrl}/campaignmanagement/geolocations/${opts.account}/${encodeURIComponent(opts.query)}`;
|
|
111963
|
-
let data;
|
|
111964
|
-
try {
|
|
111965
|
-
data = await apiFetch2(url, config, {}, opts.verbose);
|
|
111966
|
-
} catch (err) {
|
|
111967
|
-
console.error(`
|
|
111968
|
-
\u274C \u641C\u7D22\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
111969
|
-
`);
|
|
111970
|
-
process.exit(1);
|
|
111971
|
-
}
|
|
111972
|
-
const items = Array.isArray(data) ? data : data.data ?? [];
|
|
111973
|
-
console.log(`
|
|
111974
|
-
\u5730\u7406\u4F4D\u7F6E\u641C\u7D22\u7ED3\u679C\u300C${opts.query}\u300D\uFF08\u5171 ${items.length} \u6761\uFF09
|
|
111975
|
-
`);
|
|
111976
|
-
if (items.length === 0) {
|
|
111977
|
-
console.log(" \u672A\u627E\u5230\u5339\u914D\u4F4D\u7F6E\u3002\n");
|
|
111978
|
-
return;
|
|
111979
|
-
}
|
|
111980
|
-
for (const item of items) {
|
|
111981
|
-
const id = String(item["id"] ?? "");
|
|
111982
|
-
const name = String(item["locationName"] ?? item["canonicalName"] ?? item["name"] ?? "");
|
|
111983
|
-
const type = String(item["targetType"] ?? item["typeV2"] ?? "");
|
|
111984
|
-
console.log(` id:${id} ${name} [${type}]`);
|
|
111985
|
-
}
|
|
111986
|
-
console.log();
|
|
111987
|
-
}
|
|
111988
|
-
async function runAdGeoList(opts) {
|
|
111989
|
-
const config = loadConfig(opts.token);
|
|
111990
|
-
const googleApiUrl = requireGoogleApi(config);
|
|
111991
|
-
let url;
|
|
111992
|
-
const params = new URLSearchParams();
|
|
111993
|
-
if (opts.mode === "report") {
|
|
111994
|
-
url = `${googleApiUrl}/geotargetmanagement/v2/list/${opts.account}?${params}`;
|
|
111995
|
-
} else {
|
|
111996
|
-
params.set("startDate", toGoogleDate(opts.startDate, -30));
|
|
111997
|
-
params.set("endDate", toGoogleDate(opts.endDate, 0));
|
|
111998
|
-
url = `${googleApiUrl}/campaignmanagement/v2/${opts.mode}locations/${opts.account}?${params}`;
|
|
111999
|
-
}
|
|
112000
|
-
let data;
|
|
112001
|
-
try {
|
|
112002
|
-
data = await apiFetch2(url, config, {}, opts.verbose);
|
|
112003
|
-
} catch (err) {
|
|
112004
|
-
console.error(`
|
|
112005
|
-
\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112006
|
-
`);
|
|
112007
|
-
process.exit(1);
|
|
112008
|
-
}
|
|
112009
|
-
const rawData = data.data;
|
|
112010
|
-
let items = Array.isArray(rawData) ? rawData : rawData?.countries ?? [];
|
|
112011
|
-
if (opts.campaignId) {
|
|
112012
|
-
const targetId = opts.campaignId;
|
|
112013
|
-
items = items.filter((item) => String(item["campaignId"] ?? "") === targetId);
|
|
112014
|
-
}
|
|
112015
|
-
const modeLabel = { targeted: "\u5DF2\u5B9A\u4F4D", excluded: "\u5DF2\u6392\u9664", report: "\u6D88\u8017\u62A5\u544A" }[opts.mode];
|
|
112016
|
-
const n = items.length;
|
|
112017
|
-
const geoPayload = stripLegacyGoogleFieldsIfV2Present(
|
|
112018
|
-
wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
|
|
112019
|
-
);
|
|
112020
|
-
if (await emitCliJsonOrSnapshot(opts, {
|
|
112021
|
-
section: `ad-geo-${opts.mode}-${opts.account}`,
|
|
112022
|
-
commandLabel: "ad geo",
|
|
112023
|
-
commandHint: `${opts.mode}:${opts.account}`,
|
|
112024
|
-
payload: geoPayload,
|
|
112025
|
-
idSuffix: opts.account
|
|
112026
|
-
})) {
|
|
112027
|
-
return;
|
|
112028
|
-
}
|
|
112029
|
-
console.log(
|
|
112030
|
-
`
|
|
112031
|
-
\u5730\u7406\u4F4D\u7F6E\uFF08${modeLabel}\uFF0C\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672C\u9875 ${items.length} \u6761\uFF0C\u5171 ${items.length} \u6761\uFF09
|
|
112032
|
-
`
|
|
112033
|
-
);
|
|
112034
|
-
if (items.length === 0) {
|
|
112035
|
-
console.log(" \u6682\u65E0\u6570\u636E\u3002\n");
|
|
112036
|
-
return;
|
|
112037
|
-
}
|
|
112038
|
-
for (const item of items) {
|
|
112039
|
-
const id = String(item["id"] ?? item["countryCriteriaId"] ?? "");
|
|
112040
|
-
const name = String(
|
|
112041
|
-
item["name"] ?? item["countryOrRegion"] ?? item["country"] ?? item["location"] ?? item["canonicalName"] ?? ""
|
|
112042
|
-
);
|
|
112043
|
-
const campaign = item["campaignName"] ? ` campaign:${item["campaignName"]}` : "";
|
|
112044
|
-
const bid = item["bidModifier"] != null ? ` \u51FA\u4EF7\u500D\u7387:${item["bidModifier"]}` : "";
|
|
112045
|
-
const spend = item["spend"] != null ? ` \u6D88\u8017:${item["spend"]}` : "";
|
|
112046
|
-
const clicks = item["clicks"] != null ? ` \u70B9\u51FB:${item["clicks"]}` : "";
|
|
112047
|
-
console.log(` id:${id} ${name}${campaign}${bid}${spend}${clicks}`);
|
|
112048
|
-
}
|
|
112049
|
-
console.log();
|
|
112050
|
-
}
|
|
112051
|
-
async function runAdGeoAdd(opts) {
|
|
112052
|
-
const config = loadConfig(opts.token);
|
|
112053
|
-
const googleApiUrl = requireGoogleApi(config);
|
|
112054
|
-
const body = {
|
|
112055
|
-
CampaignId: opts.campaignId,
|
|
112056
|
-
Id: opts.locationId,
|
|
112057
|
-
TypeV2: "Location",
|
|
112058
|
-
BidModifier: opts.bidModifier ?? 0,
|
|
112059
|
-
BidModifierSpecified: opts.bidModifier !== void 0
|
|
112060
|
-
};
|
|
112061
|
-
let url = `${googleApiUrl}/campaignmanagement/criterion/${opts.account}`;
|
|
112062
|
-
const method = opts.exclude ? "POST" : "PUT";
|
|
112063
|
-
if (opts.exclude) url += "?isNegative=true";
|
|
112064
|
-
try {
|
|
112065
|
-
await apiFetch2(url, config, { method, body: JSON.stringify(body) }, opts.verbose);
|
|
112066
|
-
} catch (err) {
|
|
112067
|
-
console.error(`
|
|
112068
|
-
\u274C \u6DFB\u52A0\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112069
|
-
`);
|
|
112070
|
-
process.exit(1);
|
|
112071
|
-
}
|
|
112072
|
-
const label = opts.exclude ? "\u5DF2\u6392\u9664" : "\u5DF2\u6DFB\u52A0\u5B9A\u4F4D";
|
|
112073
|
-
console.log(`
|
|
112074
|
-
\u2705 \u5730\u7406\u4F4D\u7F6E ${opts.locationId} ${label}\uFF08\u5E7F\u544A\u7CFB\u5217\uFF1A${opts.campaignId}\uFF09
|
|
112075
|
-
`);
|
|
112076
|
-
}
|
|
112077
|
-
async function runAdGeoRemove(opts) {
|
|
112078
|
-
const config = loadConfig(opts.token);
|
|
112079
|
-
const googleApiUrl = requireGoogleApi(config);
|
|
112080
|
-
const body = { campaignId: opts.campaignId, id: opts.locationId };
|
|
112081
|
-
const url = `${googleApiUrl}/campaignmanagement/criterion/${opts.account}?campaignId=${opts.campaignId}`;
|
|
112082
|
-
try {
|
|
112083
|
-
await apiDeleteRaw(url, config, { body: JSON.stringify(body), verbose: opts.verbose });
|
|
112084
|
-
} catch (err) {
|
|
112085
|
-
console.error(`
|
|
112086
|
-
\u274C \u5220\u9664\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112087
|
-
`);
|
|
112088
|
-
process.exit(1);
|
|
112089
|
-
}
|
|
112090
|
-
console.log(`
|
|
112091
|
-
\u2705 \u5730\u7406\u4F4D\u7F6E ${opts.locationId} \u5DF2\u4ECE\u5E7F\u544A\u7CFB\u5217 ${opts.campaignId} \u4E2D\u79FB\u9664
|
|
112092
|
-
`);
|
|
112093
|
-
}
|
|
112094
111959
|
|
|
112095
111960
|
// src/commands/ad/device-bid.ts
|
|
112096
111961
|
init_auth();
|
|
@@ -112323,6 +112188,220 @@ async function runAdDeviceBidSet(opts) {
|
|
|
112323
112188
|
);
|
|
112324
112189
|
}
|
|
112325
112190
|
|
|
112191
|
+
// src/commands/ad/geo.ts
|
|
112192
|
+
async function runAdGeoSearch(opts) {
|
|
112193
|
+
const config = loadConfig(opts.token);
|
|
112194
|
+
const googleApiUrl = requireGoogleApi(config);
|
|
112195
|
+
const url = `${googleApiUrl}/campaignmanagement/geolocations/${opts.account}/${encodeURIComponent(opts.query)}`;
|
|
112196
|
+
let data;
|
|
112197
|
+
try {
|
|
112198
|
+
data = await apiFetch2(url, config, {}, opts.verbose);
|
|
112199
|
+
} catch (err) {
|
|
112200
|
+
console.error(`
|
|
112201
|
+
\u274C \u641C\u7D22\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112202
|
+
`);
|
|
112203
|
+
process.exit(1);
|
|
112204
|
+
}
|
|
112205
|
+
const items = Array.isArray(data) ? data : data.data ?? [];
|
|
112206
|
+
console.log(`
|
|
112207
|
+
\u5730\u7406\u4F4D\u7F6E\u641C\u7D22\u7ED3\u679C\u300C${opts.query}\u300D\uFF08\u5171 ${items.length} \u6761\uFF09
|
|
112208
|
+
`);
|
|
112209
|
+
if (items.length === 0) {
|
|
112210
|
+
console.log(" \u672A\u627E\u5230\u5339\u914D\u4F4D\u7F6E\u3002\n");
|
|
112211
|
+
return;
|
|
112212
|
+
}
|
|
112213
|
+
for (const item of items) {
|
|
112214
|
+
const id = String(item["id"] ?? "");
|
|
112215
|
+
const name = String(item["locationName"] ?? item["canonicalName"] ?? item["name"] ?? "");
|
|
112216
|
+
const type = String(item["targetType"] ?? item["typeV2"] ?? "");
|
|
112217
|
+
console.log(` id:${id} ${name} [${type}]`);
|
|
112218
|
+
}
|
|
112219
|
+
console.log();
|
|
112220
|
+
}
|
|
112221
|
+
async function runAdGeoList(opts) {
|
|
112222
|
+
const config = loadConfig(opts.token);
|
|
112223
|
+
const googleApiUrl = requireGoogleApi(config);
|
|
112224
|
+
let url;
|
|
112225
|
+
const params = new URLSearchParams();
|
|
112226
|
+
if (opts.mode === "report") {
|
|
112227
|
+
url = `${googleApiUrl}/geotargetmanagement/v2/list/${opts.account}?${params}`;
|
|
112228
|
+
} else {
|
|
112229
|
+
params.set("startDate", toGoogleDate(opts.startDate, -30));
|
|
112230
|
+
params.set("endDate", toGoogleDate(opts.endDate, 0));
|
|
112231
|
+
url = `${googleApiUrl}/campaignmanagement/v2/${opts.mode}locations/${opts.account}?${params}`;
|
|
112232
|
+
}
|
|
112233
|
+
let data;
|
|
112234
|
+
try {
|
|
112235
|
+
data = await apiFetch2(url, config, {}, opts.verbose);
|
|
112236
|
+
} catch (err) {
|
|
112237
|
+
console.error(`
|
|
112238
|
+
\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112239
|
+
`);
|
|
112240
|
+
process.exit(1);
|
|
112241
|
+
}
|
|
112242
|
+
const rawData = data.data;
|
|
112243
|
+
let items = Array.isArray(rawData) ? rawData : rawData?.countries ?? [];
|
|
112244
|
+
if (opts.campaignId) {
|
|
112245
|
+
const targetId = opts.campaignId;
|
|
112246
|
+
items = items.filter((item) => String(item["campaignId"] ?? "") === targetId);
|
|
112247
|
+
}
|
|
112248
|
+
const modeLabel = { targeted: "\u5DF2\u5B9A\u4F4D", excluded: "\u5DF2\u6392\u9664", report: "\u6D88\u8017\u62A5\u544A" }[opts.mode];
|
|
112249
|
+
const n = items.length;
|
|
112250
|
+
const geoPayload = stripLegacyGoogleFieldsIfV2Present(
|
|
112251
|
+
wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
|
|
112252
|
+
);
|
|
112253
|
+
if (await emitCliJsonOrSnapshot(opts, {
|
|
112254
|
+
section: `ad-geo-${opts.mode}-${opts.account}`,
|
|
112255
|
+
commandLabel: "ad geo",
|
|
112256
|
+
commandHint: `${opts.mode}:${opts.account}`,
|
|
112257
|
+
payload: geoPayload,
|
|
112258
|
+
idSuffix: opts.account
|
|
112259
|
+
})) {
|
|
112260
|
+
return;
|
|
112261
|
+
}
|
|
112262
|
+
console.log(
|
|
112263
|
+
`
|
|
112264
|
+
\u5730\u7406\u4F4D\u7F6E\uFF08${modeLabel}\uFF0C\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672C\u9875 ${items.length} \u6761\uFF0C\u5171 ${items.length} \u6761\uFF09
|
|
112265
|
+
`
|
|
112266
|
+
);
|
|
112267
|
+
if (items.length === 0) {
|
|
112268
|
+
console.log(" \u6682\u65E0\u6570\u636E\u3002\n");
|
|
112269
|
+
return;
|
|
112270
|
+
}
|
|
112271
|
+
for (const item of items) {
|
|
112272
|
+
const id = String(item["id"] ?? item["countryCriteriaId"] ?? "");
|
|
112273
|
+
const name = String(
|
|
112274
|
+
item["name"] ?? item["countryOrRegion"] ?? item["country"] ?? item["location"] ?? item["canonicalName"] ?? ""
|
|
112275
|
+
);
|
|
112276
|
+
const campaign = item["campaignName"] ? ` campaign:${item["campaignName"]}` : "";
|
|
112277
|
+
const bid = item["bidModifier"] != null && typeof item["bidModifier"] === "number" ? ` \u51FA\u4EF7\u8C03\u6574:${formatBidModifierPercent(item["bidModifier"])}` : item["bidModifier"] != null ? ` \u51FA\u4EF7\u8C03\u6574:${item["bidModifier"]}` : "";
|
|
112278
|
+
const spend = item["spend"] != null ? ` \u6D88\u8017:${item["spend"]}` : "";
|
|
112279
|
+
const clicks = item["clicks"] != null ? ` \u70B9\u51FB:${item["clicks"]}` : "";
|
|
112280
|
+
console.log(` id:${id} ${name}${campaign}${bid}${spend}${clicks}`);
|
|
112281
|
+
}
|
|
112282
|
+
console.log();
|
|
112283
|
+
}
|
|
112284
|
+
async function runAdGeoAdd(opts) {
|
|
112285
|
+
const config = loadConfig(opts.token);
|
|
112286
|
+
const googleApiUrl = requireGoogleApi(config);
|
|
112287
|
+
const body = {
|
|
112288
|
+
CampaignId: opts.campaignId,
|
|
112289
|
+
Id: opts.locationId,
|
|
112290
|
+
TypeV2: "Location",
|
|
112291
|
+
BidModifier: opts.bidModifier !== void 0 ? multiplierToAdGroupBidPercent(opts.bidModifier) : 0,
|
|
112292
|
+
BidModifierSpecified: opts.bidModifier !== void 0
|
|
112293
|
+
};
|
|
112294
|
+
let url = `${googleApiUrl}/campaignmanagement/criterion/${opts.account}`;
|
|
112295
|
+
const method = opts.exclude ? "POST" : "PUT";
|
|
112296
|
+
if (opts.exclude) url += "?isNegative=true";
|
|
112297
|
+
try {
|
|
112298
|
+
await apiFetch2(url, config, { method, body: JSON.stringify(body) }, opts.verbose);
|
|
112299
|
+
} catch (err) {
|
|
112300
|
+
console.error(`
|
|
112301
|
+
\u274C \u6DFB\u52A0\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112302
|
+
`);
|
|
112303
|
+
process.exit(1);
|
|
112304
|
+
}
|
|
112305
|
+
const label = opts.exclude ? "\u5DF2\u6392\u9664" : "\u5DF2\u6DFB\u52A0\u5B9A\u4F4D";
|
|
112306
|
+
console.log(`
|
|
112307
|
+
\u2705 \u5730\u7406\u4F4D\u7F6E ${opts.locationId} ${label}\uFF08\u5E7F\u544A\u7CFB\u5217\uFF1A${opts.campaignId}\uFF09
|
|
112308
|
+
`);
|
|
112309
|
+
}
|
|
112310
|
+
async function runAdGeoRemove(opts) {
|
|
112311
|
+
const config = loadConfig(opts.token);
|
|
112312
|
+
const googleApiUrl = requireGoogleApi(config);
|
|
112313
|
+
const body = { campaignId: opts.campaignId, id: opts.locationId };
|
|
112314
|
+
const url = `${googleApiUrl}/campaignmanagement/criterion/${opts.account}?campaignId=${opts.campaignId}`;
|
|
112315
|
+
try {
|
|
112316
|
+
await apiDeleteRaw(url, config, { body: JSON.stringify(body), verbose: opts.verbose });
|
|
112317
|
+
} catch (err) {
|
|
112318
|
+
console.error(`
|
|
112319
|
+
\u274C \u5220\u9664\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112320
|
+
`);
|
|
112321
|
+
process.exit(1);
|
|
112322
|
+
}
|
|
112323
|
+
console.log(`
|
|
112324
|
+
\u2705 \u5730\u7406\u4F4D\u7F6E ${opts.locationId} \u5DF2\u4ECE\u5E7F\u544A\u7CFB\u5217 ${opts.campaignId} \u4E2D\u79FB\u9664
|
|
112325
|
+
`);
|
|
112326
|
+
}
|
|
112327
|
+
async function fetchTargetedGeoRows(config, googleApiUrl, account, campaignId, verbose) {
|
|
112328
|
+
const params = new URLSearchParams({
|
|
112329
|
+
startDate: toGoogleDate(void 0, -30),
|
|
112330
|
+
endDate: toGoogleDate(void 0, 0)
|
|
112331
|
+
});
|
|
112332
|
+
const url = `${googleApiUrl}/campaignmanagement/v2/targetedlocations/${account}?${params}`;
|
|
112333
|
+
const data = await apiFetch2(url, config, {}, verbose);
|
|
112334
|
+
const items = Array.isArray(data.data) ? data.data : [];
|
|
112335
|
+
return items.filter((item) => String(item["campaignId"] ?? "") === campaignId);
|
|
112336
|
+
}
|
|
112337
|
+
function resolveGeoCriterionId(rows, opts) {
|
|
112338
|
+
if (opts.criterionId) return opts.criterionId;
|
|
112339
|
+
if (!opts.locationId) {
|
|
112340
|
+
throw new Error("\u8BF7\u6307\u5B9A --criterion-id\uFF0C\u6216\u914D\u5408 --location-id \u4ECE\u5DF2\u5B9A\u5411\u5217\u8868\u81EA\u52A8\u5339\u914D");
|
|
112341
|
+
}
|
|
112342
|
+
const match = rows.find((r) => String(r["id"] ?? "") === opts.locationId);
|
|
112343
|
+
if (!match?.["id"]) {
|
|
112344
|
+
throw new Error(
|
|
112345
|
+
`\u672A\u627E\u5230\u5DF2\u5B9A\u5411\u5730\u7406\u4F4D\u7F6E id=${opts.locationId}\uFF08\u7CFB\u5217 ${opts.campaignId}\uFF09\uFF0C\u8BF7\u5148\u6267\u884C ad geo list --mode targeted`
|
|
112346
|
+
);
|
|
112347
|
+
}
|
|
112348
|
+
return String(match["id"]);
|
|
112349
|
+
}
|
|
112350
|
+
async function runAdGeoSetBid(opts) {
|
|
112351
|
+
const config = loadConfig(opts.token);
|
|
112352
|
+
const googleApiUrl = requireGoogleApi(config);
|
|
112353
|
+
if (opts.bidModifier < 0) {
|
|
112354
|
+
console.error("\n\u274C --bid-modifier \u4E0D\u80FD\u4E3A\u8D1F\u6570\n");
|
|
112355
|
+
process.exit(1);
|
|
112356
|
+
}
|
|
112357
|
+
let rows;
|
|
112358
|
+
try {
|
|
112359
|
+
rows = await fetchTargetedGeoRows(
|
|
112360
|
+
config,
|
|
112361
|
+
googleApiUrl,
|
|
112362
|
+
opts.account,
|
|
112363
|
+
opts.campaignId,
|
|
112364
|
+
opts.verbose
|
|
112365
|
+
);
|
|
112366
|
+
} catch (err) {
|
|
112367
|
+
console.error(`
|
|
112368
|
+
\u274C \u62C9\u53D6\u5DF2\u5B9A\u5411\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112369
|
+
`);
|
|
112370
|
+
process.exit(1);
|
|
112371
|
+
}
|
|
112372
|
+
let criterionId;
|
|
112373
|
+
try {
|
|
112374
|
+
criterionId = resolveGeoCriterionId(rows, {
|
|
112375
|
+
criterionId: opts.criterionId,
|
|
112376
|
+
locationId: opts.locationId,
|
|
112377
|
+
campaignId: opts.campaignId
|
|
112378
|
+
});
|
|
112379
|
+
} catch (err) {
|
|
112380
|
+
console.error(`
|
|
112381
|
+
\u274C ${err instanceof Error ? err.message : String(err)}
|
|
112382
|
+
`);
|
|
112383
|
+
process.exit(1);
|
|
112384
|
+
}
|
|
112385
|
+
const putUrl = `${googleApiUrl}/campaignmanagement/${opts.account}/campaigns/${opts.campaignId}/Criteria/${criterionId}/BidModifier/${opts.bidModifier}`;
|
|
112386
|
+
try {
|
|
112387
|
+
await apiFetch2(putUrl, config, { method: "PUT" }, opts.verbose);
|
|
112388
|
+
} catch (err) {
|
|
112389
|
+
console.error(`
|
|
112390
|
+
\u274C \u4FEE\u6539\u5730\u7406\u4F4D\u7F6E\u51FA\u4EF7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112391
|
+
`);
|
|
112392
|
+
process.exit(1);
|
|
112393
|
+
}
|
|
112394
|
+
const row = rows.find((r) => String(r["id"] ?? "") === criterionId);
|
|
112395
|
+
const name = String(
|
|
112396
|
+
row?.["locationName"] ?? row?.["name"] ?? row?.["countryOrRegion"] ?? criterionId
|
|
112397
|
+
);
|
|
112398
|
+
console.log(
|
|
112399
|
+
`
|
|
112400
|
+
\u2705 \u5DF2\u66F4\u65B0\u5730\u7406\u4F4D\u7F6E\u51FA\u4EF7\uFF08\u7CFB\u5217 ${opts.campaignId} / ${name} / id ${criterionId} \u2192 \u500D\u7387 ${opts.bidModifier}\uFF0C${formatBidModifierPercent(opts.bidModifier)}\uFF09
|
|
112401
|
+
`
|
|
112402
|
+
);
|
|
112403
|
+
}
|
|
112404
|
+
|
|
112326
112405
|
// src/commands/ad/ai-creation.ts
|
|
112327
112406
|
init_auth();
|
|
112328
112407
|
init_cli_json_snapshot();
|
|
@@ -114033,7 +114112,7 @@ function register20(program2) {
|
|
|
114033
114112
|
});
|
|
114034
114113
|
}
|
|
114035
114114
|
);
|
|
114036
|
-
const geoCmd = adCmd.command("geo").description("\u5730\u7406\u4F4D\u7F6E\u5B9A\u5411\u7BA1\u7406\uFF08\u641C\u7D22/\u5217\u8868/\u6DFB\u52A0/\u5220\u9664\uFF09");
|
|
114115
|
+
const geoCmd = adCmd.command("geo").description("\u5730\u7406\u4F4D\u7F6E\u5B9A\u5411\u7BA1\u7406\uFF08\u641C\u7D22/\u5217\u8868/\u6DFB\u52A0/\u5220\u9664/\u51FA\u4EF7\u8C03\u6574\uFF09");
|
|
114037
114116
|
geoCmd.command("search").description("\u641C\u7D22\u53EF\u7528\u7684\u5730\u7406\u4F4D\u7F6E\uFF08\u83B7\u53D6 locationId \u7528\u4E8E\u6DFB\u52A0\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("-q, --query <text>", "\u5730\u7406\u4F4D\u7F6E\u540D\u79F0\uFF0C\u5982 Beijing | United States").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
|
|
114038
114117
|
await runAdGeoSearch({
|
|
114039
114118
|
token: opts.token,
|
|
@@ -114091,6 +114170,33 @@ function register20(program2) {
|
|
|
114091
114170
|
});
|
|
114092
114171
|
}
|
|
114093
114172
|
);
|
|
114173
|
+
geoCmd.command("set-bid").description(
|
|
114174
|
+
"\u4FEE\u6539\u5DF2\u5B9A\u5411\u5730\u7406\u4F4D\u7F6E\u7684\u51FA\u4EF7\u8C03\u6574\uFF08PUT Criteria/\u2026/BidModifier\uFF1B\u500D\u7387\u53E3\u5F84\u540C ad device-bid set\uFF09"
|
|
114175
|
+
).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--campaign-id <id>", "\u5E7F\u544A\u7CFB\u5217 ID").requiredOption(
|
|
114176
|
+
"--bid-modifier <n>",
|
|
114177
|
+
"Google \u51FA\u4EF7\u500D\u7387\uFF1A1.0=\u4E0D\u8C03\u6574\uFF0C1.2=+20%\uFF0C0.8=-20%",
|
|
114178
|
+
parseFloat
|
|
114179
|
+
).option("--criterion-id <id>", "campaign_criterion id\uFF08geo list --mode targeted --json \u2192 id\uFF09").option("--location-id <id>", "\u5730\u7406\u4F4D\u7F6E ID\uFF08\u4E0E list/search \u7684 id \u76F8\u540C\uFF1B\u4E0E --criterion-id \u4E8C\u9009\u4E00\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
114180
|
+
async (opts) => {
|
|
114181
|
+
if (!opts.criterionId && !opts.locationId) {
|
|
114182
|
+
console.error("\n\u274C \u987B\u6307\u5B9A --criterion-id \u6216 --location-id\n");
|
|
114183
|
+
process.exit(1);
|
|
114184
|
+
}
|
|
114185
|
+
if (Number.isNaN(opts.bidModifier)) {
|
|
114186
|
+
console.error("\n\u274C --bid-modifier \u987B\u4E3A\u6709\u6548\u6570\u5B57\n");
|
|
114187
|
+
process.exit(1);
|
|
114188
|
+
}
|
|
114189
|
+
await runAdGeoSetBid({
|
|
114190
|
+
token: opts.token,
|
|
114191
|
+
account: opts.account,
|
|
114192
|
+
campaignId: opts.campaignId,
|
|
114193
|
+
bidModifier: opts.bidModifier,
|
|
114194
|
+
criterionId: opts.criterionId,
|
|
114195
|
+
locationId: opts.locationId,
|
|
114196
|
+
verbose: opts.verbose
|
|
114197
|
+
});
|
|
114198
|
+
}
|
|
114199
|
+
);
|
|
114094
114200
|
const deviceBidCmd = adCmd.command("device-bid").description("\u8BBE\u5907\u51FA\u4EF7\u8C03\u6574\uFF08\u7CFB\u5217\u7EA7 / \u5E7F\u544A\u7EC4\u7EA7\uFF1B\u4E0E\u524D\u7AEF updateDeviceAndAddress \u540C\u6E90\uFF09");
|
|
114095
114201
|
deviceBidCmd.command("list").description("\u67E5\u8BE2\u8BBE\u5907\u51FA\u4EF7\u8C03\u6574\u5217\u8868\uFF08\u7CFB\u5217\u7EA7\u9ED8\u8BA4\uFF1B\u5E7F\u544A\u7EC4\u7EA7\u9700 --level adgroup\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option("--level <level>", "campaign | adgroup\uFF08\u9ED8\u8BA4 campaign\uFF09", "campaign").option("--campaign-id <id>", "\u53EF\u9009\uFF1A\u6309\u5E7F\u544A\u7CFB\u5217\u8FC7\u6EE4\uFF08\u7CFB\u5217\u7EA7\uFF09\u6216\u5FC5\u586B\u4E0A\u4E0B\u6587\uFF08\u5E7F\u544A\u7EC4\u7EA7\uFF09").option("--ad-group-id <id>", "\u5E7F\u544A\u7EC4 ID\uFF08--level adgroup \u65F6\u5FC5\u586B\uFF09").option("-t, --token <token>", "Auth Token").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).option(
|
|
114096
114202
|
"--json-out <path>",
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -151,6 +151,7 @@ Windows 注意:部分 Agent 客户端通过 PowerShell / cmd 代执行命令
|
|
|
151
151
|
- **常用字段**:
|
|
152
152
|
- `ad campaigns --json/--json-out` → `budget`(元,与 `campaign-edit --budget` 同口径)
|
|
153
153
|
- `ad groups --json` → `maxCPCAmountYuan` / `targetCpaAmountYuan`(元)
|
|
154
|
+
- `google-analysis overview-*.json` → **余额取 `remainingAccountBudget`,禁止取 `balance`**(`balance` 是 Google 原始字段,多数账户恒为 0)
|
|
154
155
|
- `google-analysis campaigns-*.json` → `budgetAmountYuan` / `campaignTargetCpaYuan` / `maximizeConversionsTargetCpaYuan` / `spend` / `averageCpc` / `costPerConversion`(均元)
|
|
155
156
|
- `keyword --json` → `averageCpc` / `lowTopOfPageBid` / `highTopOfPageBid` + 根级与每条 `bidAmountCurrency`(无 `-a` 为 USD;有 `-a` 为账户 `currencyCode`);`-a <mediaCustomerId>` 走账户级推荐接口;限定市场用 `keyword geo-list` + `--geo <id>`(**多 id = 汇总指标**;分市场须多次调用、每次一个 `--geo`,见 `references/keyword-planner-workflows.md`)
|
|
156
157
|
- **品牌名优先级**:(1) 用户明确提供 → (2) `list-accounts.mag.advertiserName` → (3) 用户提供网址 → 域名占位并标注 `[待确认品牌名]`。**严禁**把英文域名翻译为虚构中文品牌。
|
package/dist/skill/_meta.json
CHANGED
|
@@ -84,7 +84,7 @@ CLI 出口的所有 JSON / 表格金额已统一为**元**,关键字段:
|
|
|
84
84
|
| `ad groups --json` | `maxCPCAmountYuan`、`targetCpaAmountYuan` |
|
|
85
85
|
| `google-analysis campaigns` 落盘 `campaigns-*.json` | `budgetAmountYuan`、`campaignTargetCpaYuan`、`maximizeConversionsTargetCpaYuan`;同行 `spend` / `averageCpc` / `costPerConversion` 也是元 |
|
|
86
86
|
| `keyword suggest --json` | `averageCpc`、`lowTopOfPageBid`、`highTopOfPageBid`;根级与每条 `bidAmountCurrency`(有 `-a` 为账户币;无 `-a` 为 USD) |
|
|
87
|
-
| `balance` 等账户余额接口 | `remainingAccountBudget
|
|
87
|
+
| `balance` 等账户余额接口 | `remainingAccountBudget`(元);**注意:`google-analysis overview` 同时返回 `balance`(Google 原始字段,多数账户恒为 0)和 `remainingAccountBudget`(真实余额),余额取值必须用后者** |
|
|
88
88
|
|
|
89
89
|
旧字段 `budgetAmount`(分)、`maxCPCAmountDisplay`、`*Micros`(微元)**已不再落盘**,下游脚本无需做单位换算。金额保留 2 位小数,带货币代码(如 `¥50.00 CNY`、`$50.00 USD`),`currencyCode` 从响应读取,跨币种账户分表;细则见 `references/currency.md`。
|
|
90
90
|
|
|
@@ -249,6 +249,7 @@ siluzan-tso ad geo add -a <CID> --campaign-id <ID> --location-id <ID> --exclude
|
|
|
249
249
|
|
|
250
250
|
# 添加出价调整(+20%)
|
|
251
251
|
siluzan-tso ad geo add -a <CID> --campaign-id <ID> --location-id <ID> --bid-modifier 1.2
|
|
252
|
+
siluzan-tso ad geo set-bid -a <CID> --campaign-id <ID> --location-id <ID> --bid-modifier 1.2
|
|
252
253
|
|
|
253
254
|
# 查看地域效果报告
|
|
254
255
|
siluzan-tso google-analysis -a <CID> --sections geographic
|
|
@@ -521,10 +521,29 @@ siluzan-tso ad geo list -a <accountId> --mode targeted|excluded|report [--start/
|
|
|
521
521
|
# 添加定向
|
|
522
522
|
siluzan-tso ad geo add -a <accountId> --campaign-id <id> --location-id <id> [--bid-modifier 1.2] [--exclude]
|
|
523
523
|
|
|
524
|
+
# 修改已定向地区的出价调整(系列级 campaign_criterion)
|
|
525
|
+
siluzan-tso ad geo set-bid -a <accountId> --campaign-id <id> --location-id <id> --bid-modifier 1.2
|
|
526
|
+
# 或使用 list 返回的 criterion id
|
|
527
|
+
siluzan-tso ad geo set-bid -a <accountId> --campaign-id <id> --criterion-id <id> --bid-modifier 0.8
|
|
528
|
+
|
|
524
529
|
# 删除
|
|
525
530
|
siluzan-tso ad geo remove -a <accountId> --campaign-id <id> --location-id <id>
|
|
526
531
|
```
|
|
527
532
|
|
|
533
|
+
**`--bid-modifier` 口径(`add` / `set-bid` 均为 Google 倍率)**
|
|
534
|
+
|
|
535
|
+
| 倍率 | 含义 |
|
|
536
|
+
|------|------|
|
|
537
|
+
| `1.0` | 不调整 |
|
|
538
|
+
| `1.2` | 提高 20% |
|
|
539
|
+
| `0.8` | 降低 20% |
|
|
540
|
+
|
|
541
|
+
- `add`:写入 `PUT …/criterion/{account}` 时 CLI 会换算为后端百分比。
|
|
542
|
+
- `set-bid`:直接 `PUT …/campaigns/{campaignId}/Criteria/{criterionId}/BidModifier/{bidModifier}`,与 `ad device-bid set`(系列级)、AI 优化 `updateDeviceAndAddress` 同源。
|
|
543
|
+
- `list` 返回的 `bidModifier` 为 Google 倍率(非网页编辑弹窗里的 `+10` 百分比)。
|
|
544
|
+
|
|
545
|
+
**Sammamish 落点**:`ModifyCampaignCriteriaBidModifier` → `GoogleAdsAcctMgmtServiceProviderV2.ModifyCampaignCriteriaBidModifier`。
|
|
546
|
+
|
|
528
547
|
---
|
|
529
548
|
|
|
530
549
|
## ad device-bid — 设备出价调整
|
|
@@ -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.20-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.20-beta.24'
|
|
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.20-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.20-beta.24"
|
|
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"
|