siluzan-tso-cli 1.1.58-beta.1 → 1.1.58-beta.2
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 +362 -226
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/google-ads/google-ads-read.md +8 -8
- package/dist/skill/references/google-ads/google-ads-write.md +40 -36
- package/dist/skill/references/google-ads/google-ads.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.58-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.58-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -104889,18 +104889,68 @@ async function writeProgress(paths, progress) {
|
|
|
104889
104889
|
function readProgress(paths) {
|
|
104890
104890
|
return readJsonIfExists(paths.progressFile);
|
|
104891
104891
|
}
|
|
104892
|
-
|
|
104892
|
+
function clampTaskLogEntry(entry) {
|
|
104893
|
+
const message = entry.lastError;
|
|
104894
|
+
if (!message || message.length <= MAX_TASK_LOG_ERROR_CHARS) return entry;
|
|
104895
|
+
return {
|
|
104896
|
+
...entry,
|
|
104897
|
+
lastError: `${message.slice(0, MAX_TASK_LOG_ERROR_CHARS)}\u2026(truncated)`
|
|
104898
|
+
};
|
|
104899
|
+
}
|
|
104900
|
+
function isTaskLogEntry(value) {
|
|
104901
|
+
if (!value || typeof value !== "object") return false;
|
|
104902
|
+
const row = value;
|
|
104903
|
+
return typeof row.at === "string" && typeof row.id === "string" && typeof row.accountId === "string" && typeof row.section === "string" && typeof row.state === "string" && TASK_LOG_STATES.has(row.state) && typeof row.attempts === "number";
|
|
104904
|
+
}
|
|
104905
|
+
async function writeTaskLogLine(paths, entry) {
|
|
104893
104906
|
await fs31.mkdir(paths.stateDir, { recursive: true });
|
|
104894
|
-
await fs31.appendFile(paths.tasksLogFile, JSON.stringify(entry) + "\n", "utf8");
|
|
104907
|
+
await fs31.appendFile(paths.tasksLogFile, JSON.stringify(clampTaskLogEntry(entry)) + "\n", "utf8");
|
|
104908
|
+
}
|
|
104909
|
+
async function appendTaskLog(paths, entry) {
|
|
104910
|
+
const file = paths.tasksLogFile;
|
|
104911
|
+
const prev = taskLogAppendQueue.get(file) ?? Promise.resolve();
|
|
104912
|
+
const current = prev.then(() => writeTaskLogLine(paths, entry));
|
|
104913
|
+
taskLogAppendQueue.set(
|
|
104914
|
+
file,
|
|
104915
|
+
current.then(
|
|
104916
|
+
() => void 0,
|
|
104917
|
+
() => void 0
|
|
104918
|
+
)
|
|
104919
|
+
);
|
|
104920
|
+
await current;
|
|
104895
104921
|
}
|
|
104896
104922
|
async function readTaskLog(paths) {
|
|
104923
|
+
let raw;
|
|
104897
104924
|
try {
|
|
104898
|
-
|
|
104899
|
-
return raw.split("\n").filter((s) => s.trim() !== "").map((line) => JSON.parse(line));
|
|
104925
|
+
raw = await fs31.readFile(paths.tasksLogFile, "utf8");
|
|
104900
104926
|
} catch (e) {
|
|
104901
104927
|
if (e.code === "ENOENT") return [];
|
|
104902
104928
|
throw e;
|
|
104903
104929
|
}
|
|
104930
|
+
const entries = [];
|
|
104931
|
+
let skipped = 0;
|
|
104932
|
+
for (const line of raw.split("\n")) {
|
|
104933
|
+
const text2 = line.trim();
|
|
104934
|
+
if (!text2) continue;
|
|
104935
|
+
try {
|
|
104936
|
+
const parsed = JSON.parse(text2);
|
|
104937
|
+
if (!isTaskLogEntry(parsed)) {
|
|
104938
|
+
skipped += 1;
|
|
104939
|
+
continue;
|
|
104940
|
+
}
|
|
104941
|
+
entries.push(parsed);
|
|
104942
|
+
} catch {
|
|
104943
|
+
skipped += 1;
|
|
104944
|
+
}
|
|
104945
|
+
}
|
|
104946
|
+
if (skipped > 0) {
|
|
104947
|
+
process.stderr.write(
|
|
104948
|
+
`
|
|
104949
|
+
\u26A0\uFE0F tasks.jsonl \u6709 ${skipped} \u884C\u65E0\u6CD5\u89E3\u6790\uFF0C\u5DF2\u8DF3\u8FC7\u3002\u6CA1\u6709\u5B8C\u6574\u6210\u529F\u8BB0\u5F55\u7684\u4EFB\u52A1\u4F1A\u5728 resume \u65F6\u91CD\u8DD1\u3002
|
|
104950
|
+
`
|
|
104951
|
+
);
|
|
104952
|
+
}
|
|
104953
|
+
return entries;
|
|
104904
104954
|
}
|
|
104905
104955
|
function replayTaskStates(entries) {
|
|
104906
104956
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -104964,11 +105014,21 @@ async function tryAcquireLock(paths) {
|
|
|
104964
105014
|
async function releaseLock(paths) {
|
|
104965
105015
|
await fs31.rm(paths.lockFile, { force: true }).catch(() => void 0);
|
|
104966
105016
|
}
|
|
104967
|
-
var BATCH_SCHEMA_VERSION;
|
|
105017
|
+
var BATCH_SCHEMA_VERSION, MAX_TASK_LOG_ERROR_CHARS, TASK_LOG_STATES, taskLogAppendQueue;
|
|
104968
105018
|
var init_batch_manifest = __esm({
|
|
104969
105019
|
"src/utils/batch-manifest.ts"() {
|
|
104970
105020
|
"use strict";
|
|
104971
105021
|
BATCH_SCHEMA_VERSION = 1;
|
|
105022
|
+
MAX_TASK_LOG_ERROR_CHARS = 500;
|
|
105023
|
+
TASK_LOG_STATES = /* @__PURE__ */ new Set([
|
|
105024
|
+
"pending",
|
|
105025
|
+
"running",
|
|
105026
|
+
"success",
|
|
105027
|
+
"failed_retryable",
|
|
105028
|
+
"failed_permanent",
|
|
105029
|
+
"skipped"
|
|
105030
|
+
]);
|
|
105031
|
+
taskLogAppendQueue = /* @__PURE__ */ new Map();
|
|
104972
105032
|
}
|
|
104973
105033
|
});
|
|
104974
105034
|
|
|
@@ -113899,8 +113959,68 @@ init_cli_json_snapshot();
|
|
|
113899
113959
|
init_strip_legacy_google_fields();
|
|
113900
113960
|
init_cli_table();
|
|
113901
113961
|
|
|
113962
|
+
// src/commands/ad/search-field-patch.ts
|
|
113963
|
+
function buildSearchFieldPatch(pairs) {
|
|
113964
|
+
const updateMask = [];
|
|
113965
|
+
const fields = {};
|
|
113966
|
+
for (const [name2, value] of pairs) {
|
|
113967
|
+
updateMask.push(name2);
|
|
113968
|
+
fields[name2] = value;
|
|
113969
|
+
}
|
|
113970
|
+
return { updateMask, fields };
|
|
113971
|
+
}
|
|
113972
|
+
function campaignEditPatchPairs(input) {
|
|
113973
|
+
const pairs = [];
|
|
113974
|
+
if (input.name !== void 0) pairs.push(["name", input.name]);
|
|
113975
|
+
if (input.budgetCents !== void 0) pairs.push(["budgetAmount", input.budgetCents]);
|
|
113976
|
+
if (input.biddingStrategyTypeV2 !== void 0) {
|
|
113977
|
+
pairs.push(["biddingStrategyTypeV2", input.biddingStrategyTypeV2]);
|
|
113978
|
+
}
|
|
113979
|
+
if (input.targetSpendBidCeilingCents !== void 0) {
|
|
113980
|
+
pairs.push(["targetSpend_BidCeilingAmount", input.targetSpendBidCeilingCents]);
|
|
113981
|
+
}
|
|
113982
|
+
if (input.targetCpaCents !== void 0) {
|
|
113983
|
+
pairs.push(["targetCpa_BidingAmount", input.targetCpaCents]);
|
|
113984
|
+
}
|
|
113985
|
+
if (input.targetRoas !== void 0) pairs.push(["targetRoas", input.targetRoas]);
|
|
113986
|
+
if (input.manualCpcEnhanced !== void 0) {
|
|
113987
|
+
pairs.push(["manualCpc_EnhancedCpcEnabled", input.manualCpcEnhanced]);
|
|
113988
|
+
}
|
|
113989
|
+
return pairs;
|
|
113990
|
+
}
|
|
113991
|
+
function campaignStatusPatchPairs(status) {
|
|
113992
|
+
return [["status", status]];
|
|
113993
|
+
}
|
|
113994
|
+
function adGroupEditPatchPairs(input) {
|
|
113995
|
+
const pairs = [];
|
|
113996
|
+
if (input.name !== void 0) pairs.push(["name", input.name]);
|
|
113997
|
+
if (input.maxCpcCents !== void 0) pairs.push(["maxCpc", input.maxCpcCents]);
|
|
113998
|
+
if (input.targetCpaCents !== void 0) pairs.push(["targetCpa", input.targetCpaCents]);
|
|
113999
|
+
return pairs;
|
|
114000
|
+
}
|
|
114001
|
+
function adGroupStatusPatchPairs(status) {
|
|
114002
|
+
return [["status", status]];
|
|
114003
|
+
}
|
|
114004
|
+
function keywordFieldPatchPairs(input) {
|
|
114005
|
+
const pairs = [];
|
|
114006
|
+
if (input.status !== void 0) pairs.push(["userStatus", input.status]);
|
|
114007
|
+
if (input.maxCpcCents !== void 0) pairs.push(["maxCpc", input.maxCpcCents]);
|
|
114008
|
+
if (input.finalUrl !== void 0) pairs.push(["finalUrl", input.finalUrl]);
|
|
114009
|
+
return pairs;
|
|
114010
|
+
}
|
|
114011
|
+
function adEditPatchPairs(input) {
|
|
114012
|
+
const pairs = [];
|
|
114013
|
+
if (input.status !== void 0) pairs.push(["status", input.status]);
|
|
114014
|
+
if (input.finalUrl !== void 0) pairs.push(["finalUrl", input.finalUrl]);
|
|
114015
|
+
if (input.headlines !== void 0) pairs.push(["headlines", [...input.headlines]]);
|
|
114016
|
+
if (input.descriptions !== void 0) pairs.push(["descriptions", [...input.descriptions]]);
|
|
114017
|
+
if (input.path1 !== void 0) pairs.push(["path1", input.path1]);
|
|
114018
|
+
if (input.path2 !== void 0) pairs.push(["path2", input.path2]);
|
|
114019
|
+
return pairs;
|
|
114020
|
+
}
|
|
114021
|
+
|
|
113902
114022
|
// src/commands/ad/write-verification.ts
|
|
113903
|
-
var CAMPAIGN_WRITE_SCOPE = "
|
|
114023
|
+
var CAMPAIGN_WRITE_SCOPE = "search-field-patch";
|
|
113904
114024
|
function sameWriteValue(expected, actual) {
|
|
113905
114025
|
if (expected === actual) return true;
|
|
113906
114026
|
if (typeof expected === "string" && typeof actual === "string") {
|
|
@@ -114537,71 +114657,91 @@ async function runAdKeywordEdit(opts) {
|
|
|
114537
114657
|
);
|
|
114538
114658
|
process.exit(1);
|
|
114539
114659
|
}
|
|
114660
|
+
const patchPairs = keywordFieldPatchPairs({
|
|
114661
|
+
status: opts.status,
|
|
114662
|
+
maxCpcCents: opts.maxCpc !== void 0 ? toCentAmount(opts.maxCpc) : void 0,
|
|
114663
|
+
finalUrl: opts.finalUrl
|
|
114664
|
+
});
|
|
114665
|
+
const replacesText = opts.text !== void 0 || opts.matchType !== void 0;
|
|
114540
114666
|
const params = new URLSearchParams();
|
|
114541
114667
|
params.set("startDate", toGoogleDate(opts.startDate, -30));
|
|
114542
114668
|
params.set("endDate", toGoogleDate(opts.endDate, 0));
|
|
114543
114669
|
const listUrl = `${googleApiUrl}/keywordmanagement/v2/list/${opts.account}?${params}`;
|
|
114544
|
-
let keyword;
|
|
114545
|
-
try {
|
|
114546
|
-
keyword = await findItemInList(listUrl, config, opts.id, opts.verbose);
|
|
114547
|
-
} catch (err) {
|
|
114548
|
-
console.error(`
|
|
114549
|
-
\u274C \u67E5\u8BE2\u5173\u952E\u8BCD\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
114550
|
-
`);
|
|
114551
|
-
process.exit(1);
|
|
114552
|
-
}
|
|
114553
|
-
const body = { ...keyword };
|
|
114554
|
-
if (opts.matchType !== void 0) {
|
|
114555
|
-
const base = opts.text !== void 0 ? opts.text : firstKeywordTextFromRecord(keyword);
|
|
114556
|
-
if (!String(base).trim()) {
|
|
114557
|
-
console.error("\n\u274C \u65E0\u6CD5\u89E3\u6790\u5F53\u524D\u5173\u952E\u8BCD\u6587\u6848\uFF0C\u8BF7\u540C\u65F6\u4F20 --text <\u8BCD\u5E72>\n");
|
|
114558
|
-
process.exit(1);
|
|
114559
|
-
}
|
|
114560
|
-
body["keywordText"] = [formatKeywordTextForMatchType(base, opts.matchType)];
|
|
114561
|
-
body["matchTypeV2"] = opts.matchType;
|
|
114562
|
-
} else if (opts.text !== void 0) {
|
|
114563
|
-
body["keywordText"] = [opts.text];
|
|
114564
|
-
}
|
|
114565
|
-
if (opts.maxCpc !== void 0) body["maxCPC"] = toCentAmount(opts.maxCpc);
|
|
114566
|
-
if (opts.finalUrl !== void 0) body["finalURL"] = opts.finalUrl;
|
|
114567
|
-
if (opts.status !== void 0) body["userStatusV2"] = opts.status;
|
|
114568
114670
|
const notAppliedHint = ` \u8BF7\u52FF\u6539\u7528\u300C\u5220\u9664\u540E\u91CD\u5EFA\u300D\u7ED5\u8FC7\uFF08\u4F1A\u4E22\u5931\u5173\u952E\u8BCD id \u4E0E\u5386\u53F2\u6570\u636E\uFF09\uFF0C\u8BF7\u53CD\u9988\u5E73\u53F0\u6392\u67E5\u3002
|
|
114569
114671
|
`;
|
|
114570
|
-
const url = `${googleApiUrl}/keywordmanagement/Keyword/${opts.account}/batch`;
|
|
114571
114672
|
let currentId = opts.id;
|
|
114572
|
-
|
|
114573
|
-
|
|
114574
|
-
|
|
114575
|
-
|
|
114576
|
-
|
|
114577
|
-
|
|
114578
|
-
|
|
114579
|
-
|
|
114580
|
-
const returnedId = first && typeof first["id"] === "string" ? first["id"] : void 0;
|
|
114581
|
-
if (returnedId && returnedId !== opts.id) {
|
|
114582
|
-
currentId = returnedId;
|
|
114583
|
-
console.log(
|
|
114584
|
-
`
|
|
114585
|
-
\u2139\uFE0F \u7F51\u5173\u8FD4\u56DE\u7684\u5173\u952E\u8BCD id \u5DF2\u53D8\u66F4\u4E3A ${returnedId}\uFF08\u8BF7\u6C42\u65F6 id \u4E3A ${opts.id}\uFF0CchangeKind=replace\uFF09\uFF0C\u540E\u7EED\u67E5\u8BE2\u8BF7\u4F7F\u7528\u65B0 id\u3002
|
|
114586
|
-
`
|
|
114673
|
+
if (replacesText) {
|
|
114674
|
+
let keyword;
|
|
114675
|
+
try {
|
|
114676
|
+
keyword = await findItemInList(
|
|
114677
|
+
listUrl,
|
|
114678
|
+
config,
|
|
114679
|
+
opts.id,
|
|
114680
|
+
opts.verbose
|
|
114587
114681
|
);
|
|
114682
|
+
} catch (err) {
|
|
114683
|
+
console.error(`
|
|
114684
|
+
\u274C \u67E5\u8BE2\u5173\u952E\u8BCD\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
114685
|
+
`);
|
|
114686
|
+
process.exit(1);
|
|
114588
114687
|
}
|
|
114589
|
-
const
|
|
114590
|
-
if (
|
|
114591
|
-
|
|
114592
|
-
|
|
114593
|
-
\u274C \
|
|
114594
|
-
|
|
114595
|
-
|
|
114688
|
+
const body = { ...keyword };
|
|
114689
|
+
if (opts.matchType !== void 0) {
|
|
114690
|
+
const base = opts.text !== void 0 ? opts.text : firstKeywordTextFromRecord(keyword);
|
|
114691
|
+
if (!String(base).trim()) {
|
|
114692
|
+
console.error("\n\u274C \u65E0\u6CD5\u89E3\u6790\u5F53\u524D\u5173\u952E\u8BCD\u6587\u6848\uFF0C\u8BF7\u540C\u65F6\u4F20 --text <\u8BCD\u5E72>\n");
|
|
114693
|
+
process.exit(1);
|
|
114694
|
+
}
|
|
114695
|
+
body["keywordText"] = [formatKeywordTextForMatchType(base, opts.matchType)];
|
|
114696
|
+
body["matchTypeV2"] = opts.matchType;
|
|
114697
|
+
} else if (opts.text !== void 0) {
|
|
114698
|
+
body["keywordText"] = [opts.text];
|
|
114699
|
+
}
|
|
114700
|
+
const url = `${googleApiUrl}/keywordmanagement/Keyword/${opts.account}/batch`;
|
|
114701
|
+
try {
|
|
114702
|
+
const raw = await apiFetch2(
|
|
114703
|
+
url,
|
|
114704
|
+
config,
|
|
114705
|
+
{ method: "PUT", body: JSON.stringify([body]) },
|
|
114706
|
+
opts.verbose
|
|
114596
114707
|
);
|
|
114708
|
+
const first = Array.isArray(raw) && raw.length > 0 && raw[0] && typeof raw[0] === "object" ? raw[0] : null;
|
|
114709
|
+
const returnedId = first && typeof first["id"] === "string" ? first["id"] : void 0;
|
|
114710
|
+
if (returnedId && returnedId !== opts.id) {
|
|
114711
|
+
currentId = returnedId;
|
|
114712
|
+
console.log(
|
|
114713
|
+
`
|
|
114714
|
+
\u2139\uFE0F \u7F51\u5173\u8FD4\u56DE\u7684\u5173\u952E\u8BCD id \u5DF2\u53D8\u66F4\u4E3A ${returnedId}\uFF08\u8BF7\u6C42\u65F6 id \u4E3A ${opts.id}\uFF0CchangeKind=replace\uFF09\uFF0C\u540E\u7EED\u67E5\u8BE2\u8BF7\u4F7F\u7528\u65B0 id\u3002
|
|
114715
|
+
`
|
|
114716
|
+
);
|
|
114717
|
+
}
|
|
114718
|
+
} catch (err) {
|
|
114719
|
+
console.error(`
|
|
114720
|
+
\u274C \u7F16\u8F91\u5173\u952E\u8BCD\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
114721
|
+
`);
|
|
114597
114722
|
process.exit(1);
|
|
114598
114723
|
}
|
|
114599
|
-
}
|
|
114600
|
-
|
|
114724
|
+
}
|
|
114725
|
+
if (patchPairs.length > 0) {
|
|
114726
|
+
const patchUrl = `${googleApiUrl}/keywordmanagement/Keyword/${opts.account}/${currentId}`;
|
|
114727
|
+
try {
|
|
114728
|
+
await apiFetch2(
|
|
114729
|
+
patchUrl,
|
|
114730
|
+
config,
|
|
114731
|
+
{ method: "PATCH", body: JSON.stringify(buildSearchFieldPatch(patchPairs)) },
|
|
114732
|
+
opts.verbose
|
|
114733
|
+
);
|
|
114734
|
+
} catch (err) {
|
|
114735
|
+
console.error(`
|
|
114601
114736
|
\u274C \u7F16\u8F91\u5173\u952E\u8BCD\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
114602
114737
|
`);
|
|
114603
|
-
|
|
114738
|
+
process.exit(1);
|
|
114739
|
+
}
|
|
114604
114740
|
}
|
|
114741
|
+
const readbackExpected = {
|
|
114742
|
+
finalURL: opts.finalUrl,
|
|
114743
|
+
maxCPC: opts.maxCpc !== void 0 ? toCentAmount(opts.maxCpc) : void 0
|
|
114744
|
+
};
|
|
114605
114745
|
if (opts.finalUrl !== void 0 || opts.maxCpc !== void 0 || opts.status !== void 0) {
|
|
114606
114746
|
let fresh;
|
|
114607
114747
|
try {
|
|
@@ -114620,7 +114760,7 @@ ${mismatches.map((m) => ` - ${m}`).join("\n")}
|
|
|
114620
114760
|
);
|
|
114621
114761
|
process.exit(1);
|
|
114622
114762
|
}
|
|
114623
|
-
const mismatches = findKeywordEditMismatches(
|
|
114763
|
+
const mismatches = findKeywordEditMismatches(readbackExpected, fresh, opts);
|
|
114624
114764
|
if (mismatches.length > 0) {
|
|
114625
114765
|
console.error(
|
|
114626
114766
|
`
|
|
@@ -114658,26 +114798,17 @@ async function runAdKeywordStatus(opts) {
|
|
|
114658
114798
|
opts = withResolvedAdAccount(opts);
|
|
114659
114799
|
const config = await ensureDataPermission(loadConfig(opts.token));
|
|
114660
114800
|
const googleApiUrl = requireGoogleApi(config);
|
|
114661
|
-
const
|
|
114662
|
-
params.set("startDate", toGoogleDate(opts.startDate, -30));
|
|
114663
|
-
params.set("endDate", toGoogleDate(opts.endDate, 0));
|
|
114664
|
-
const listUrl = `${googleApiUrl}/keywordmanagement/v2/list/${opts.account}?${params}`;
|
|
114665
|
-
let keyword;
|
|
114666
|
-
try {
|
|
114667
|
-
keyword = await findItemInList(listUrl, config, opts.id, opts.verbose);
|
|
114668
|
-
} catch (err) {
|
|
114669
|
-
console.error(`
|
|
114670
|
-
\u274C \u67E5\u8BE2\u5173\u952E\u8BCD\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
114671
|
-
`);
|
|
114672
|
-
process.exit(1);
|
|
114673
|
-
}
|
|
114674
|
-
const body = { ...keyword, userStatusV2: opts.status };
|
|
114675
|
-
const url = `${googleApiUrl}/keywordmanagement/Keyword/${opts.account}/batch`;
|
|
114801
|
+
const url = `${googleApiUrl}/keywordmanagement/Keyword/${opts.account}/${opts.id}`;
|
|
114676
114802
|
try {
|
|
114677
114803
|
await apiFetch2(
|
|
114678
114804
|
url,
|
|
114679
114805
|
config,
|
|
114680
|
-
{
|
|
114806
|
+
{
|
|
114807
|
+
method: "PATCH",
|
|
114808
|
+
body: JSON.stringify(
|
|
114809
|
+
buildSearchFieldPatch(keywordFieldPatchPairs({ status: opts.status }))
|
|
114810
|
+
)
|
|
114811
|
+
},
|
|
114681
114812
|
opts.verbose
|
|
114682
114813
|
);
|
|
114683
114814
|
} catch (err) {
|
|
@@ -116331,7 +116462,7 @@ async function reportFailedCampaignPut(opts) {
|
|
|
116331
116462
|
`);
|
|
116332
116463
|
process.exit(1);
|
|
116333
116464
|
}
|
|
116334
|
-
const hint = judged.kind === "partial" ? "\u56DE\u8BFB\u9884\u7B97\u5DF2\u76F8\u5BF9\u63D0\u4EA4\u524D\u53D8\u5316\u3002\u9884\u7B97\u53EF\u80FD\u5DF2\u5199\u5165\uFF0C\
|
|
116465
|
+
const hint = judged.kind === "partial" ? "\u56DE\u8BFB\u9884\u7B97\u5DF2\u76F8\u5BF9\u63D0\u4EA4\u524D\u53D8\u5316\u3002\u9884\u7B97\u53EF\u80FD\u5DF2\u5199\u5165\uFF0C\u5176\u4F59\u66F4\u65B0\u672A\u5B8C\u6210\u3002\u4E0D\u8981\u7ACB\u523B\u91CD\u53D1\u3002" : "\u56DE\u8BFB\u5931\u8D25\u6216\u65E0\u6CD5\u5BF9\u6BD4\u9884\u7B97\uFF0C\u5199\u5165\u7ED3\u679C\u672A\u77E5\u3002\u4E0D\u8981\u7ACB\u523B\u91CD\u53D1\u3002";
|
|
116335
116466
|
await reportCampaignWrite({
|
|
116336
116467
|
jsonOut: opts.jsonOut,
|
|
116337
116468
|
account: opts.account,
|
|
@@ -116403,9 +116534,14 @@ async function runAdCampaignStatus(opts) {
|
|
|
116403
116534
|
);
|
|
116404
116535
|
process.exit(1);
|
|
116405
116536
|
}
|
|
116406
|
-
const body =
|
|
116537
|
+
const body = buildSearchFieldPatch(campaignStatusPatchPairs(opts.status));
|
|
116407
116538
|
try {
|
|
116408
|
-
await apiFetch2(
|
|
116539
|
+
await apiFetch2(
|
|
116540
|
+
detailUrl,
|
|
116541
|
+
config,
|
|
116542
|
+
{ method: "PATCH", body: JSON.stringify(body) },
|
|
116543
|
+
opts.verbose
|
|
116544
|
+
);
|
|
116409
116545
|
} catch (err) {
|
|
116410
116546
|
await reportFailedCampaignPut({
|
|
116411
116547
|
err,
|
|
@@ -116721,24 +116857,17 @@ async function runAdCampaignEdit(opts) {
|
|
|
116721
116857
|
);
|
|
116722
116858
|
process.exit(1);
|
|
116723
116859
|
}
|
|
116724
|
-
const
|
|
116725
|
-
|
|
116726
|
-
|
|
116727
|
-
|
|
116728
|
-
|
|
116729
|
-
|
|
116730
|
-
|
|
116731
|
-
|
|
116732
|
-
|
|
116733
|
-
|
|
116734
|
-
if (
|
|
116735
|
-
body["manualCpc_EnhancedCpcEnabled"] = opts.manualCpcEnhanced;
|
|
116736
|
-
if (opts.targetSearchNetwork !== void 0)
|
|
116737
|
-
body["targetSearchNetwork"] = opts.targetSearchNetwork;
|
|
116738
|
-
if (opts.targetContentNetwork !== void 0)
|
|
116739
|
-
body["targetContentNetwork"] = opts.targetContentNetwork;
|
|
116740
|
-
const hasChange = opts.name !== void 0 || opts.budget !== void 0 || opts.biddingStrategy !== void 0 || opts.targetSpendBidCeiling !== void 0 || opts.targetCpa !== void 0 || opts.targetRoas !== void 0 || opts.manualCpcEnhanced !== void 0 || opts.targetSearchNetwork !== void 0 || opts.targetContentNetwork !== void 0;
|
|
116741
|
-
if (!hasChange) {
|
|
116860
|
+
const patchPairs = campaignEditPatchPairs({
|
|
116861
|
+
name: opts.name,
|
|
116862
|
+
budgetCents: opts.budget !== void 0 ? toCentAmount(opts.budget) : void 0,
|
|
116863
|
+
biddingStrategyTypeV2: opts.biddingStrategy?.toUpperCase(),
|
|
116864
|
+
targetSpendBidCeilingCents: opts.targetSpendBidCeiling !== void 0 ? toCentAmount(opts.targetSpendBidCeiling) : void 0,
|
|
116865
|
+
targetCpaCents: opts.targetCpa !== void 0 ? toCentAmount(opts.targetCpa) : void 0,
|
|
116866
|
+
targetRoas: opts.targetRoas,
|
|
116867
|
+
manualCpcEnhanced: opts.manualCpcEnhanced
|
|
116868
|
+
});
|
|
116869
|
+
const wantsNetwork = opts.targetSearchNetwork !== void 0 || opts.targetContentNetwork !== void 0;
|
|
116870
|
+
if (patchPairs.length === 0 && !wantsNetwork) {
|
|
116742
116871
|
console.error("\n\u274C \u8BF7\u81F3\u5C11\u6307\u5B9A\u4E00\u4E2A\u4FEE\u6539\u9879\uFF08--name / --budget / --bidding / \u2026\uFF09\n");
|
|
116743
116872
|
process.exit(1);
|
|
116744
116873
|
}
|
|
@@ -116748,23 +116877,66 @@ async function runAdCampaignEdit(opts) {
|
|
|
116748
116877
|
);
|
|
116749
116878
|
process.exit(1);
|
|
116750
116879
|
}
|
|
116751
|
-
|
|
116752
|
-
|
|
116753
|
-
|
|
116754
|
-
|
|
116755
|
-
|
|
116756
|
-
|
|
116757
|
-
|
|
116758
|
-
|
|
116759
|
-
|
|
116760
|
-
|
|
116761
|
-
|
|
116762
|
-
|
|
116763
|
-
|
|
116764
|
-
|
|
116765
|
-
|
|
116766
|
-
|
|
116767
|
-
|
|
116880
|
+
if (patchPairs.length > 0) {
|
|
116881
|
+
try {
|
|
116882
|
+
await apiFetch2(
|
|
116883
|
+
detailUrl,
|
|
116884
|
+
config,
|
|
116885
|
+
{
|
|
116886
|
+
method: "PATCH",
|
|
116887
|
+
body: JSON.stringify(buildSearchFieldPatch(patchPairs))
|
|
116888
|
+
},
|
|
116889
|
+
opts.verbose
|
|
116890
|
+
);
|
|
116891
|
+
} catch (err) {
|
|
116892
|
+
await reportFailedCampaignPut({
|
|
116893
|
+
err,
|
|
116894
|
+
detailUrl,
|
|
116895
|
+
config,
|
|
116896
|
+
verbose: opts.verbose,
|
|
116897
|
+
budgetCentsBefore: readCampaignBudgetCents(campaign),
|
|
116898
|
+
jsonOut: opts.jsonOut,
|
|
116899
|
+
account: opts.account,
|
|
116900
|
+
id: opts.id,
|
|
116901
|
+
section: "ad-campaign-edit",
|
|
116902
|
+
commandLabel: "ad campaign-edit",
|
|
116903
|
+
failureLabel: "\u7F16\u8F91\u5E7F\u544A\u7CFB\u5217\u5931\u8D25"
|
|
116904
|
+
});
|
|
116905
|
+
return;
|
|
116906
|
+
}
|
|
116907
|
+
}
|
|
116908
|
+
if (wantsNetwork) {
|
|
116909
|
+
try {
|
|
116910
|
+
const fresh = await fetchCampaignRecord(detailUrl, config, opts.verbose);
|
|
116911
|
+
const networkBody = { ...fresh };
|
|
116912
|
+
if (opts.targetSearchNetwork !== void 0) {
|
|
116913
|
+
networkBody["targetSearchNetwork"] = opts.targetSearchNetwork;
|
|
116914
|
+
}
|
|
116915
|
+
if (opts.targetContentNetwork !== void 0) {
|
|
116916
|
+
networkBody["targetContentNetwork"] = opts.targetContentNetwork;
|
|
116917
|
+
}
|
|
116918
|
+
await apiFetch2(
|
|
116919
|
+
detailUrl,
|
|
116920
|
+
config,
|
|
116921
|
+
{ method: "PUT", body: JSON.stringify(networkBody) },
|
|
116922
|
+
opts.verbose
|
|
116923
|
+
);
|
|
116924
|
+
} catch (err) {
|
|
116925
|
+
await reportFailedCampaignPut({
|
|
116926
|
+
err,
|
|
116927
|
+
detailUrl,
|
|
116928
|
+
config,
|
|
116929
|
+
verbose: opts.verbose,
|
|
116930
|
+
budgetCentsBefore: readCampaignBudgetCents(campaign),
|
|
116931
|
+
jsonOut: opts.jsonOut,
|
|
116932
|
+
account: opts.account,
|
|
116933
|
+
id: opts.id,
|
|
116934
|
+
section: "ad-campaign-edit",
|
|
116935
|
+
commandLabel: "ad campaign-edit",
|
|
116936
|
+
failureLabel: "\u7F16\u8F91\u5E7F\u544A\u7CFB\u5217\u5931\u8D25"
|
|
116937
|
+
});
|
|
116938
|
+
return;
|
|
116939
|
+
}
|
|
116768
116940
|
}
|
|
116769
116941
|
const parts = [];
|
|
116770
116942
|
if (opts.name !== void 0) parts.push(`\u540D\u79F0\u2192${opts.name}`);
|
|
@@ -116954,26 +117126,15 @@ async function runAdGroupStatus(opts) {
|
|
|
116954
117126
|
opts = withResolvedAdAccount(opts);
|
|
116955
117127
|
const config = loadConfig(opts.token);
|
|
116956
117128
|
const googleApiUrl = requireGoogleApi(config);
|
|
116957
|
-
|
|
116958
|
-
|
|
116959
|
-
|
|
116960
|
-
|
|
116961
|
-
|
|
116962
|
-
|
|
116963
|
-
|
|
116964
|
-
|
|
116965
|
-
|
|
116966
|
-
}
|
|
116967
|
-
const putUrl = `${googleApiUrl}/adgroupnmanagement/adgroup/${opts.account}/${opts.id}`;
|
|
116968
|
-
const body = { ...adgroup, statusV2: opts.status };
|
|
116969
|
-
try {
|
|
116970
|
-
await apiFetch2(putUrl, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
|
|
116971
|
-
} catch (err) {
|
|
116972
|
-
console.error(`
|
|
116973
|
-
\u274C \u72B6\u6001\u66F4\u65B0\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
116974
|
-
`);
|
|
116975
|
-
process.exit(1);
|
|
116976
|
-
}
|
|
117129
|
+
await patchAdGroup(
|
|
117130
|
+
googleApiUrl,
|
|
117131
|
+
config,
|
|
117132
|
+
opts.account,
|
|
117133
|
+
opts.id,
|
|
117134
|
+
adGroupStatusPatchPairs(opts.status),
|
|
117135
|
+
opts.verbose,
|
|
117136
|
+
"\u72B6\u6001\u66F4\u65B0\u5931\u8D25"
|
|
117137
|
+
);
|
|
116977
117138
|
console.log(`
|
|
116978
117139
|
\u2705 \u5E7F\u544A\u7EC4 ${opts.id} \u5DF2\u8BBE\u7F6E\u4E3A ${opts.status}
|
|
116979
117140
|
`);
|
|
@@ -117009,26 +117170,15 @@ async function runAdGroupRename(opts) {
|
|
|
117009
117170
|
opts = withResolvedAdAccount(opts);
|
|
117010
117171
|
const config = loadConfig(opts.token);
|
|
117011
117172
|
const googleApiUrl = requireGoogleApi(config);
|
|
117012
|
-
|
|
117013
|
-
|
|
117014
|
-
|
|
117015
|
-
|
|
117016
|
-
|
|
117017
|
-
|
|
117018
|
-
|
|
117019
|
-
|
|
117020
|
-
|
|
117021
|
-
}
|
|
117022
|
-
const putUrl = `${googleApiUrl}/adgroupnmanagement/adgroup/${opts.account}/${opts.id}`;
|
|
117023
|
-
const body = { ...adgroup, name: opts.name };
|
|
117024
|
-
try {
|
|
117025
|
-
await apiFetch2(putUrl, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
|
|
117026
|
-
} catch (err) {
|
|
117027
|
-
console.error(`
|
|
117028
|
-
\u274C \u6539\u540D\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
117029
|
-
`);
|
|
117030
|
-
process.exit(1);
|
|
117031
|
-
}
|
|
117173
|
+
await patchAdGroup(
|
|
117174
|
+
googleApiUrl,
|
|
117175
|
+
config,
|
|
117176
|
+
opts.account,
|
|
117177
|
+
opts.id,
|
|
117178
|
+
adGroupEditPatchPairs({ name: opts.name }),
|
|
117179
|
+
opts.verbose,
|
|
117180
|
+
"\u6539\u540D\u5931\u8D25"
|
|
117181
|
+
);
|
|
117032
117182
|
console.log(`
|
|
117033
117183
|
\u2705 \u5E7F\u544A\u7EC4 ${opts.id} \u5DF2\u6539\u540D\u4E3A\u300C${opts.name}\u300D
|
|
117034
117184
|
`);
|
|
@@ -117052,32 +117202,38 @@ async function runAdGroupEdit(opts) {
|
|
|
117052
117202
|
}
|
|
117053
117203
|
const config = loadConfig(opts.token);
|
|
117054
117204
|
const googleApiUrl = requireGoogleApi(config);
|
|
117055
|
-
|
|
117056
|
-
|
|
117057
|
-
|
|
117058
|
-
|
|
117059
|
-
|
|
117060
|
-
|
|
117061
|
-
|
|
117205
|
+
await patchAdGroup(
|
|
117206
|
+
googleApiUrl,
|
|
117207
|
+
config,
|
|
117208
|
+
opts.account,
|
|
117209
|
+
opts.id,
|
|
117210
|
+
adGroupEditPatchPairs({
|
|
117211
|
+
name: hasName ? opts.name.trim() : void 0,
|
|
117212
|
+
maxCpcCents: hasMax ? toCentAmount(opts.maxCPCAmount) : void 0,
|
|
117213
|
+
targetCpaCents: hasTcpa ? toCentAmount(opts.targetCpaAmount) : void 0
|
|
117214
|
+
}),
|
|
117215
|
+
opts.verbose,
|
|
117216
|
+
"\u7F16\u8F91\u5E7F\u544A\u7EC4\u5931\u8D25"
|
|
117217
|
+
);
|
|
117218
|
+
console.log(`
|
|
117219
|
+
\u2705 \u5E7F\u544A\u7EC4 ${opts.id} \u5DF2\u66F4\u65B0
|
|
117062
117220
|
`);
|
|
117063
|
-
|
|
117064
|
-
|
|
117065
|
-
const
|
|
117066
|
-
if (hasName) body["name"] = opts.name.trim();
|
|
117067
|
-
if (hasMax) body["maxCPCAmount"] = toCentAmount(opts.maxCPCAmount);
|
|
117068
|
-
if (hasTcpa) body["targetCpaAmount"] = toCentAmount(opts.targetCpaAmount);
|
|
117069
|
-
const putUrl = `${googleApiUrl}/adgroupnmanagement/adgroup/${opts.account}/${opts.id}`;
|
|
117221
|
+
}
|
|
117222
|
+
async function patchAdGroup(googleApiUrl, config, account, id, pairs, verbose, failureLabel) {
|
|
117223
|
+
const url = `${googleApiUrl}/adgroupnmanagement/adgroup/${account}/${id}`;
|
|
117070
117224
|
try {
|
|
117071
|
-
await apiFetch2(
|
|
117225
|
+
await apiFetch2(
|
|
117226
|
+
url,
|
|
117227
|
+
config,
|
|
117228
|
+
{ method: "PATCH", body: JSON.stringify(buildSearchFieldPatch(pairs)) },
|
|
117229
|
+
verbose
|
|
117230
|
+
);
|
|
117072
117231
|
} catch (err) {
|
|
117073
117232
|
console.error(`
|
|
117074
|
-
\u274C \
|
|
117233
|
+
\u274C ${failureLabel}\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
117075
117234
|
`);
|
|
117076
117235
|
process.exit(1);
|
|
117077
117236
|
}
|
|
117078
|
-
console.log(`
|
|
117079
|
-
\u2705 \u5E7F\u544A\u7EC4 ${opts.id} \u5DF2\u66F4\u65B0
|
|
117080
|
-
`);
|
|
117081
117237
|
}
|
|
117082
117238
|
|
|
117083
117239
|
// src/commands/ad/ad-creative.ts
|
|
@@ -117304,23 +117460,17 @@ async function runAdStatus(opts) {
|
|
|
117304
117460
|
opts = withResolvedAdAccount(opts);
|
|
117305
117461
|
const config = loadConfig(opts.token);
|
|
117306
117462
|
const googleApiUrl = requireGoogleApi(config);
|
|
117307
|
-
const
|
|
117308
|
-
params.set("startDate", toGoogleDate(opts.startDate, -30));
|
|
117309
|
-
params.set("endDate", toGoogleDate(opts.endDate, 0));
|
|
117310
|
-
const listUrl = `${googleApiUrl}/admanagement/v2/list/${opts.account}?${params}`;
|
|
117311
|
-
let ad;
|
|
117312
|
-
try {
|
|
117313
|
-
ad = await findItemInList(listUrl, config, opts.id, opts.verbose);
|
|
117314
|
-
} catch (err) {
|
|
117315
|
-
console.error(`
|
|
117316
|
-
\u274C \u67E5\u8BE2\u5E7F\u544A\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
117317
|
-
`);
|
|
117318
|
-
process.exit(1);
|
|
117319
|
-
}
|
|
117320
|
-
const putUrl = `${googleApiUrl}/admanagement/campaign/${opts.account}/${opts.id}`;
|
|
117321
|
-
const body = { ...ad, statusV2: opts.status };
|
|
117463
|
+
const url = `${googleApiUrl}/admanagement/campaign/${opts.account}/${opts.id}`;
|
|
117322
117464
|
try {
|
|
117323
|
-
await apiFetch2(
|
|
117465
|
+
await apiFetch2(
|
|
117466
|
+
url,
|
|
117467
|
+
config,
|
|
117468
|
+
{
|
|
117469
|
+
method: "PATCH",
|
|
117470
|
+
body: JSON.stringify(buildSearchFieldPatch(adEditPatchPairs({ status: opts.status })))
|
|
117471
|
+
},
|
|
117472
|
+
opts.verbose
|
|
117473
|
+
);
|
|
117324
117474
|
} catch (err) {
|
|
117325
117475
|
console.error(`
|
|
117326
117476
|
\u274C \u72B6\u6001\u66F4\u65B0\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
@@ -117361,6 +117511,11 @@ async function runAdDelete(opts) {
|
|
|
117361
117511
|
\u2705 \u5E7F\u544A ${opts.id} \u5DF2\u5220\u9664
|
|
117362
117512
|
`);
|
|
117363
117513
|
}
|
|
117514
|
+
function emptyPathClears(value) {
|
|
117515
|
+
if (value === void 0) return void 0;
|
|
117516
|
+
const trimmed = value.trim();
|
|
117517
|
+
return trimmed === "" ? null : trimmed;
|
|
117518
|
+
}
|
|
117364
117519
|
async function runAdEdit(opts) {
|
|
117365
117520
|
opts = withResolvedAdAccount(opts);
|
|
117366
117521
|
const config = await ensureDataPermission(loadConfig(opts.token));
|
|
@@ -117382,40 +117537,19 @@ async function runAdEdit(opts) {
|
|
|
117382
117537
|
console.error(formatRsaCliValidationError(rsaErrors));
|
|
117383
117538
|
process.exit(1);
|
|
117384
117539
|
}
|
|
117385
|
-
const
|
|
117386
|
-
|
|
117387
|
-
|
|
117388
|
-
|
|
117389
|
-
|
|
117390
|
-
|
|
117391
|
-
|
|
117392
|
-
|
|
117393
|
-
|
|
117394
|
-
|
|
117395
|
-
|
|
117396
|
-
process.exit(1);
|
|
117397
|
-
}
|
|
117398
|
-
const body = { ...ad };
|
|
117399
|
-
if (opts.headlines) {
|
|
117400
|
-
const [h1, h2, h3, ...more] = opts.headlines;
|
|
117401
|
-
body["headlinePart1"] = h1;
|
|
117402
|
-
body["headlinePart2"] = h2;
|
|
117403
|
-
body["headlinePart3"] = h3;
|
|
117404
|
-
if (more.length > 0) body["AddtionalHeadlines"] = more;
|
|
117405
|
-
}
|
|
117406
|
-
if (opts.descriptions) {
|
|
117407
|
-
const [d1, d2, ...more] = opts.descriptions;
|
|
117408
|
-
body["adDescription"] = d1;
|
|
117409
|
-
body["adDescription2"] = d2;
|
|
117410
|
-
if (more.length > 0) body["AddtionalAdDescriptions"] = more;
|
|
117411
|
-
}
|
|
117412
|
-
if (opts.finalUrl !== void 0) body["finalUrl"] = opts.finalUrl;
|
|
117413
|
-
if (opts.path1 !== void 0) body["path1"] = opts.path1;
|
|
117414
|
-
if (opts.path2 !== void 0) body["path2"] = opts.path2;
|
|
117415
|
-
if (opts.status !== void 0) body["statusV2"] = opts.status;
|
|
117416
|
-
const putUrl = `${googleApiUrl}/admanagement/campaign/${opts.account}/${opts.id}`;
|
|
117540
|
+
const url = `${googleApiUrl}/admanagement/campaign/${opts.account}/${opts.id}`;
|
|
117541
|
+
const body = buildSearchFieldPatch(
|
|
117542
|
+
adEditPatchPairs({
|
|
117543
|
+
status: opts.status,
|
|
117544
|
+
finalUrl: opts.finalUrl,
|
|
117545
|
+
headlines: opts.headlines,
|
|
117546
|
+
descriptions: opts.descriptions,
|
|
117547
|
+
path1: emptyPathClears(opts.path1),
|
|
117548
|
+
path2: emptyPathClears(opts.path2)
|
|
117549
|
+
})
|
|
117550
|
+
);
|
|
117417
117551
|
try {
|
|
117418
|
-
await apiFetch2(
|
|
117552
|
+
await apiFetch2(url, config, { method: "PATCH", body: JSON.stringify(body) }, opts.verbose);
|
|
117419
117553
|
} catch (err) {
|
|
117420
117554
|
console.error(`
|
|
117421
117555
|
\u274C \u7F16\u8F91\u5E7F\u544A\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
@@ -124642,7 +124776,7 @@ function register21(program2) {
|
|
|
124642
124776
|
});
|
|
124643
124777
|
}
|
|
124644
124778
|
);
|
|
124645
|
-
adCmd.command("campaign-status").description("\u5207\u6362\u5E7F\u544A\u7CFB\u5217\u72B6\u6001\uFF08Enabled / Paused\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").requiredOption("--status <status>", "\u76EE\u6807\u72B6\u6001\uFF1AEnabled | Paused").option("--json-out <path>", "\u843D\u76D8\u6838\u9A8C\u7ED3\u679C\uFF08writeScope=
|
|
124779
|
+
adCmd.command("campaign-status").description("\u5207\u6362\u5E7F\u544A\u7CFB\u5217\u72B6\u6001\uFF08Enabled / Paused\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").requiredOption("--status <status>", "\u76EE\u6807\u72B6\u6001\uFF1AEnabled | Paused").option("--json-out <path>", "\u843D\u76D8\u6838\u9A8C\u7ED3\u679C\uFF08writeScope=search-field-patch\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
124646
124780
|
async (opts) => {
|
|
124647
124781
|
if (opts.status !== "Enabled" && opts.status !== "Paused") {
|
|
124648
124782
|
console.error("\n\u274C --status \u53EA\u63A5\u53D7 Enabled \u6216 Paused\n");
|
|
@@ -125148,7 +125282,9 @@ function register21(program2) {
|
|
|
125148
125282
|
verbose: opts.verbose
|
|
125149
125283
|
});
|
|
125150
125284
|
});
|
|
125151
|
-
adCmd.command("campaign-edit").description(
|
|
125285
|
+
adCmd.command("campaign-edit").description(
|
|
125286
|
+
"\u7F16\u8F91\u641C\u7D22\u5E7F\u544A\u7CFB\u5217\u3002\u540D\u79F0/\u9884\u7B97/\u51FA\u4EF7\u8D70\u5B57\u6BB5\u7EA7 PATCH\uFF1B\u6295\u653E\u7F51\u7EDC\u4ECD\u7528\u6574\u5305 PUT\u3002\u81F3\u5C11\u4F20\u4E00\u4E2A\u4FEE\u6539\u9879"
|
|
125287
|
+
).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").option("--name <name>", "\u65B0\u540D\u79F0").option(
|
|
125152
125288
|
"--budget <amount>",
|
|
125153
125289
|
"\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",
|
|
125154
125290
|
parseFloat
|
|
@@ -125171,7 +125307,7 @@ function register21(program2) {
|
|
|
125171
125307
|
"--search-network <bool>",
|
|
125172
125308
|
"\u6295\u653E\u5230 Google \u641C\u7D22\uFF1Atrue | false",
|
|
125173
125309
|
(v) => v === "true"
|
|
125174
|
-
).option("--content-network <bool>", "\u6295\u653E\u5230\u5C55\u793A\u7F51\u7EDC\uFF1Atrue | false", (v) => v === "true").option("--json-out <path>", "\u843D\u76D8\u6838\u9A8C\u7ED3\u679C\uFF08writeScope=
|
|
125310
|
+
).option("--content-network <bool>", "\u6295\u653E\u5230\u5C55\u793A\u7F51\u7EDC\uFF1Atrue | false", (v) => v === "true").option("--json-out <path>", "\u843D\u76D8\u6838\u9A8C\u7ED3\u679C\uFF08writeScope=search-field-patch\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
125175
125311
|
async (opts) => {
|
|
125176
125312
|
await runAdCampaignEdit({
|
|
125177
125313
|
token: opts.token,
|
|
@@ -125204,7 +125340,7 @@ function register21(program2) {
|
|
|
125204
125340
|
});
|
|
125205
125341
|
}
|
|
125206
125342
|
);
|
|
125207
|
-
adCmd.command("adgroup-edit").description("\u7F16\u8F91\u5E7F\u544A\u7EC4\uFF08\u540D\u79F0\u3001\u6700\u9AD8 CPC\u3001\u7EC4\u7EA7\u76EE\u6807 CPA\uFF1B\u81F3\u5C11\u4F20\u4E00\u9879\uFF1B\
|
|
125343
|
+
adCmd.command("adgroup-edit").description("\u7F16\u8F91\u641C\u7D22\u5E7F\u544A\u7EC4\uFF08\u540D\u79F0\u3001\u6700\u9AD8 CPC\u3001\u7EC4\u7EA7\u76EE\u6807 CPA\uFF1B\u81F3\u5C11\u4F20\u4E00\u9879\uFF1B\u5B57\u6BB5\u7EA7 PATCH\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <adgroupId>", "\u5E7F\u544A\u7EC4 ID").option("--name <name>", "\u65B0\u540D\u79F0").option(
|
|
125208
125344
|
"--max-cpc <n>",
|
|
125209
125345
|
"\u6700\u9AD8 CPC\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08\u5982 1.5 \u8868\u793A \xA51.50\uFF1BCLI \u5185\u90E8 \xD7100 \u5199\u5165\u300C\u5206\u300D\u5B57\u6BB5\uFF09",
|
|
125210
125346
|
parseFloat
|
|
@@ -125228,7 +125364,7 @@ function register21(program2) {
|
|
|
125228
125364
|
}
|
|
125229
125365
|
);
|
|
125230
125366
|
adCmd.command("ad-edit").description(
|
|
125231
|
-
"\u7F16\u8F91\u81EA\u9002\u5E94\u641C\u7D22\u5E7F\u544A\uFF08RESPONSIVE_SEARCH_AD\uFF09\uFF1A\u6807\u9898/\u63CF\u8FF0/\u843D\u5730\u9875/\u8DEF\u5F84/\u72B6\u6001\uFF1B\
|
|
125367
|
+
"\u7F16\u8F91\u81EA\u9002\u5E94\u641C\u7D22\u5E7F\u544A\uFF08RESPONSIVE_SEARCH_AD\uFF09\uFF1A\u6807\u9898/\u63CF\u8FF0/\u843D\u5730\u9875/\u8DEF\u5F84/\u72B6\u6001\uFF1B\u5B57\u6BB5\u7EA7 PATCH\uFF0C\u53EA\u5199\u672C\u6B21\u4F20\u5165\u7684\u5B57\u6BB5"
|
|
125232
125368
|
).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <adId>", "\u5E7F\u544A ID").option(
|
|
125233
125369
|
"--headlines <h1,h2,h3,...>",
|
|
125234
125370
|
"\u65B0\u6807\u9898\u5217\u8868\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u81F3\u5C11 3 \u4E2A\uFF0C\u6700\u591A 15 \u4E2A\uFF0C\u6BCF\u4E2A \u226430 \u5B57\u7B26\uFF1B\u6620\u5C04 headlinePart1/2/3 \u4E0E AddtionalHeadlines\uFF09",
|
|
@@ -125273,7 +125409,7 @@ function register21(program2) {
|
|
|
125273
125409
|
});
|
|
125274
125410
|
}
|
|
125275
125411
|
);
|
|
125276
|
-
adCmd.command("keyword-edit").description("\u7F16\u8F91\u641C\u7D22\u5173\u952E\u8BCD\
|
|
125412
|
+
adCmd.command("keyword-edit").description("\u7F16\u8F91\u641C\u7D22\u5173\u952E\u8BCD\u3002\u72B6\u6001/\u51FA\u4EF7/\u7F51\u5740\u8D70\u5B57\u6BB5\u7EA7 PATCH\uFF1B\u6539\u8BCD\u9762\u6216\u5339\u914D\u7C7B\u578B\u4ECD\u7528\u6279\u91CF PUT").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <keywordId>", "\u5173\u952E\u8BCD ID\uFF08\u6765\u81EA ad keywords --json-out \u2192 id\uFF09").option("--text <text>", "\u65B0\u5173\u952E\u8BCD\u6587\u672C\uFF08\u5199\u5165 keywordText \u6570\u7EC4\uFF09").option(
|
|
125277
125413
|
"--match-type <type>",
|
|
125278
125414
|
'\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'
|
|
125279
125415
|
).option(
|
|
@@ -125312,7 +125448,7 @@ function register21(program2) {
|
|
|
125312
125448
|
});
|
|
125313
125449
|
}
|
|
125314
125450
|
);
|
|
125315
|
-
adCmd.command("keyword-status").description("\u5207\u6362\u641C\u7D22\u5173\u952E\u8BCD\u72B6\u6001\uFF08Enabled / Paused\uFF0C\
|
|
125451
|
+
adCmd.command("keyword-status").description("\u5207\u6362\u641C\u7D22\u5173\u952E\u8BCD\u72B6\u6001\uFF08Enabled / Paused\uFF0C\u5B57\u6BB5\u7EA7 PATCH userStatus\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <keywordId>", "\u5173\u952E\u8BCD ID\uFF08\u6765\u81EA ad keywords --json-out \u2192 id\uFF09").requiredOption("--status <status>", "\u76EE\u6807\u72B6\u6001\uFF1AEnabled | Paused").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(
|
|
125316
125452
|
async (opts) => {
|
|
125317
125453
|
if (opts.status !== "Enabled" && opts.status !== "Paused") {
|
|
125318
125454
|
console.error("\n\u274C --status \u53EA\u63A5\u53D7 Enabled \u6216 Paused\n");
|
package/dist/skill/_meta.json
CHANGED
|
@@ -111,7 +111,7 @@ siluzan-tso ad adgroup-status -a <accountId> --id <adGroupId> --status <Enabled|
|
|
|
111
111
|
|
|
112
112
|
### 编辑
|
|
113
113
|
|
|
114
|
-
先用 `ad groups --json-out ./snap`
|
|
114
|
+
先用 `ad groups --json-out ./snap` 查看当前值。编辑和启停走字段级 PATCH,只写本次传入的字段,不必把列表里的整行再送回去。
|
|
115
115
|
|
|
116
116
|
```bash
|
|
117
117
|
siluzan-tso ad adgroup-edit \
|
|
@@ -120,7 +120,7 @@ siluzan-tso ad adgroup-edit \
|
|
|
120
120
|
[--start/--end <YYYY-MM-DD>]
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
`--max-cpc` / `--target-cpa` 与 `ad groups --json-out ./snap` 中 `maxCPCAmountYuan` / `targetCpaAmountYuan` 对齐(**元**,CLI
|
|
123
|
+
`--max-cpc` / `--target-cpa` 与 `ad groups --json-out ./snap` 中 `maxCPCAmountYuan` / `targetCpaAmountYuan` 对齐(**元**,CLI 出口已统一)。列表里的 `0` 多半是未设置。`--target-cpa 0` 会照写,Google 常回 `Too low`,这个 PATCH 也不能把组级目标 CPA 清回未设置。
|
|
124
124
|
|
|
125
125
|
### 删除
|
|
126
126
|
|
|
@@ -295,12 +295,12 @@ siluzan-tso ad geo remove -a <accountId> --campaign-id <id> --location-id <id>
|
|
|
295
295
|
|
|
296
296
|
每行 `status`(search 为 `pickStatus`)的取值和处理方式:
|
|
297
297
|
|
|
298
|
-
| status | 含义
|
|
299
|
-
| -------------- |
|
|
300
|
-
| `ok` | 唯一精确命中,已写入 `targetedLocations`
|
|
301
|
-
| `needs_review` | 有候选,但没有精确同名(如只搜到城市/省份)
|
|
302
|
-
| `unserved` | Google Ads 不支持投放的地区(伊朗、古巴、朝鲜、克里米亚等)
|
|
303
|
-
| `not_found` | 网关无候选
|
|
298
|
+
| status | 含义 | 处理 |
|
|
299
|
+
| -------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
300
|
+
| `ok` | 唯一精确命中,已写入 `targetedLocations` | 直接用 |
|
|
301
|
+
| `needs_review` | 有候选,但没有精确同名(如只搜到城市/省份) | 看 `topCandidates`(前 5 个),**问用户**选哪个或换英文写法;禁止自行挑第一个 |
|
|
302
|
+
| `unserved` | Google Ads 不支持投放的地区(伊朗、古巴、朝鲜、克里米亚等) | 从方案中去掉,并告诉用户原因;不要找替代 id |
|
|
303
|
+
| `not_found` | 网关无候选 | 换英文官方名再试,仍无则问用户 |
|
|
304
304
|
|
|
305
305
|
落盘汇总里的 `unserved[]`、`needsReview[]`、`failed[]` 必须在创建前逐项向用户说明。`failed` 不包含 `unserved`。
|
|
306
306
|
|
|
@@ -81,21 +81,21 @@
|
|
|
81
81
|
|
|
82
82
|
### 已上线 PMax 管理(CLI)
|
|
83
83
|
|
|
84
|
-
| 能力 | 命令
|
|
85
|
-
| ---------------- |
|
|
86
|
-
| 详情 | `ad pmax-get -a <id> --campaign-id <cid>`(读 `_brandGuidelinesActive`)
|
|
87
|
-
| 改活动 | `ad pmax-edit`(`--patch-file` 或 `--status` / `--budget` + `--budget-id`;改 `Removed` 须用户同意 + `--i-confirm`)
|
|
88
|
-
| 改 Campaign 品牌 | `ad pmax-brand-assets-edit`(BG 已开启)
|
|
89
|
-
| 启用 BG | `ad pmax-brand-guidelines-enable --config-file …`(存量活动)
|
|
90
|
-
| 新资产组 | `ad pmax-asset-group-create --config-file …`(BG 下自动省略品牌)
|
|
91
|
-
| 改资产组 | `ad pmax-asset-group-edit`
|
|
92
|
-
| 删资产组 | `ad pmax-asset-group-edit --status REMOVED`(软删;网关无 DELETE 端点)
|
|
93
|
-
| 改资产 | `ad pmax-assets-update --config-file …`
|
|
94
|
-
| YouTube 追加 | `ad pmax-youtube-link`(单条 `--youtube` / `--video-path`);批量见 `ad pmax-assets-update`
|
|
95
|
-
| 信号 | `ad pmax-signals-get` / `ad pmax-signals-set`;受众下拉 `ad pmax-audiences`
|
|
96
|
-
| 附加资产 | `ad extension pmax-types`;`callout` / `snippet` / `lead-form` / `whatsapp`(见 `pmax-api.md` § 附加资产)
|
|
97
|
-
| 图片库 | `ad pmax-image-upload`
|
|
98
|
-
| 报表 | `ad pmax-report-asset-groups` / `ad pmax-report-geo`
|
|
84
|
+
| 能力 | 命令 |
|
|
85
|
+
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
86
|
+
| 详情 | `ad pmax-get -a <id> --campaign-id <cid>`(读 `_brandGuidelinesActive`) |
|
|
87
|
+
| 改活动 | `ad pmax-edit`(`--patch-file` 或 `--status` / `--budget` + `--budget-id`;改 `Removed` 须用户同意 + `--i-confirm`) |
|
|
88
|
+
| 改 Campaign 品牌 | `ad pmax-brand-assets-edit`(BG 已开启) |
|
|
89
|
+
| 启用 BG | `ad pmax-brand-guidelines-enable --config-file …`(存量活动) |
|
|
90
|
+
| 新资产组 | `ad pmax-asset-group-create --config-file …`(BG 下自动省略品牌) |
|
|
91
|
+
| 改资产组 | `ad pmax-asset-group-edit` |
|
|
92
|
+
| 删资产组 | `ad pmax-asset-group-edit --status REMOVED`(软删;网关无 DELETE 端点) |
|
|
93
|
+
| 改资产 | `ad pmax-assets-update --config-file …` |
|
|
94
|
+
| YouTube 追加 | `ad pmax-youtube-link`(单条 `--youtube` / `--video-path`);批量见 `ad pmax-assets-update` |
|
|
95
|
+
| 信号 | `ad pmax-signals-get` / `ad pmax-signals-set`;受众下拉 `ad pmax-audiences` |
|
|
96
|
+
| 附加资产 | `ad extension pmax-types`;`callout` / `snippet` / `lead-form` / `whatsapp`(见 `pmax-api.md` § 附加资产) |
|
|
97
|
+
| 图片库 | `ad pmax-image-upload` |
|
|
98
|
+
| 报表 | `ad pmax-report-asset-groups` / `ad pmax-report-geo` |
|
|
99
99
|
| 删活动 | 用户同意后 `ad campaign-delete -a <id> --id <campaignId> --i-confirm --commit "…"`(与 Search 共用;勿用 `ad campaign-edit`) |
|
|
100
100
|
|
|
101
101
|
模板与 HTTP 对照:`references/google-ads/pmax-api.md`。
|
|
@@ -260,7 +260,11 @@ siluzan-tso ad campaign-edit -a <accountId> --id <campaignId> \
|
|
|
260
260
|
|
|
261
261
|
相对运算:先 `ad campaigns --json-out ./snap` 读 `budget`(已为主币种元)、`biddingStrategyTypeV2`,再传入。
|
|
262
262
|
|
|
263
|
-
`campaign-edit`
|
|
263
|
+
`campaign-edit` 的名称、预算、出价,以及 `campaign-status`,走字段级 PATCH(`writeScope=search-field-patch`)。请求只带本次传入的字段,没传的字段保持原样。金额仍是主币种元,CLI ×100 写入网关「分」。`budgetShared=true` 时本命令直接拒绝 `--budget`,不会改共享预算。PMax 不要用本命令。
|
|
264
|
+
|
|
265
|
+
写完会再 GET:`verification=verified` 才表示对上了本次字段;没改预算但回读预算变了则是 `partial`;读不到是 `unknown`(再查 `ad campaigns`,不要当成没改成,也不要立刻重发)。请求失败时会再 GET 一次预算:金额相对提交前变了是 `partial`(退出码 0),读不到或无法对比是 `unknown`(退出码 0),都不要立刻重发;金额没变才是失败(退出码 1)。
|
|
266
|
+
|
|
267
|
+
`--search-network` / `--content-network` 不在字段级 PATCH 里。只在传入这两项时,CLI 会在 PATCH 之后再 GET 一次并整包 PUT 投放网络。不要用它们顺手改其它字段。
|
|
264
268
|
|
|
265
269
|
> PMax / 部分智能系列类型 Google 可能拒绝切换出价策略;以 API 返回错误为准。已上线 PMax 用 `ad pmax-edit`,不要用本命令。
|
|
266
270
|
|
|
@@ -312,14 +316,14 @@ siluzan-tso ad ad-delete -a <accountId> --id <adId>
|
|
|
312
316
|
|
|
313
317
|
## ad ad-edit — 广告创意编辑
|
|
314
318
|
|
|
315
|
-
|
|
319
|
+
字段级 PATCH,只写本次传入的字段。标题、描述、最终网址、路径只适用于响应式搜索广告;只改 `--status` 时其它广告类型也可以。`--path1` / `--path2` 传空字符串会清空该段路径。
|
|
316
320
|
|
|
317
|
-
`ad list --json-out ./snap`
|
|
321
|
+
`ad list --json-out ./snap` 用来读当前文案,不必把未改字段再传回去:
|
|
318
322
|
|
|
319
|
-
- `headlinePart1~3` + `AddtionalHeadlines` → `--headlines`(≥3
|
|
320
|
-
- `adDescription`/`adDescription2` + `AddtionalAdDescriptions` → `--descriptions`(≥2
|
|
323
|
+
- `headlinePart1~3` + `AddtionalHeadlines` → `--headlines`(≥3条,整表替换)
|
|
324
|
+
- `adDescription`/`adDescription2` + `AddtionalAdDescriptions` → `--descriptions`(≥2条,整表替换)
|
|
321
325
|
- `finalUrl` → `--final-url`;`path1`/`path2` → `--path1`/`--path2`
|
|
322
|
-
- `statusV2` → `--status
|
|
326
|
+
- `statusV2` → `--status`
|
|
323
327
|
|
|
324
328
|
```bash
|
|
325
329
|
siluzan-tso ad ad-edit \
|
|
@@ -346,7 +350,7 @@ siluzan-tso ad keyword-delete -a <accountId> --id <keywordId> --adgroup-id <adGr
|
|
|
346
350
|
|
|
347
351
|
## ad keyword-edit — 搜索关键词编辑
|
|
348
352
|
|
|
349
|
-
|
|
353
|
+
只改 `--final-url` / `--max-cpc` / `--status` 时走字段级 PATCH,原地更新,`id` 与历史数据保留。`--final-url` 传空会清空关键词网址,回到广告自己的网址。改 `--text` / `--match-type`(Google 侧词面与匹配类型不可变)仍走批量 PUT,会新建并删除旧词,`id` 变化时 CLI 会提示。改 `--final-url` / `--max-cpc` / `--status` 后 CLI 会重新拉列表回读实际值,只有回读一致才输出 ✅;未生效或回读失败则报错退出。此时**禁止**自行改用 `keyword-delete` + `keyword-create`,也禁止直调网关删除重建(会丢失 id 与历史数据),哪怕只改一条也不行。应停下来告知用户「平台未生效、需反馈平台」,可另给 Google Ads Editor 导入清单作为替代方案。
|
|
350
354
|
|
|
351
355
|
```bash
|
|
352
356
|
siluzan-tso ad keyword-edit \
|
|
@@ -355,13 +359,13 @@ siluzan-tso ad keyword-edit \
|
|
|
355
359
|
[--max-cpc <n>] [--final-url <url>] [--status Enabled|Paused]
|
|
356
360
|
```
|
|
357
361
|
|
|
358
|
-
传 `--match-type` 时 CLI 自动规范 `keywordText` 括号/引号格式。至少传一项。`--max-cpc` 为主币种元(CLI ×100 写入 `
|
|
362
|
+
传 `--match-type` 时 CLI 自动规范 `keywordText` 括号/引号格式。至少传一项。`--max-cpc` 为主币种元(CLI ×100 写入 `maxCpc`「分」字段,与 `adgroup-edit --max-cpc` 同口径)。`ad keywords --json-out ./snap` 出价见 `maxCPCYuan`。列表里的 `0` 多半是未设置;`--max-cpc 0` 会照写,Google 常回 `Too low`,不能用来清空关键词出价。`--status` 写入 `userStatus`(关键词级开关,非系列的 `status`)。
|
|
359
363
|
|
|
360
364
|
---
|
|
361
365
|
|
|
362
366
|
## ad keyword-status — 搜索关键词状态切换
|
|
363
367
|
|
|
364
|
-
|
|
368
|
+
仅改关键词状态,字段级 PATCH `userStatus`。与 `ad adgroup-status` 用法对称。
|
|
365
369
|
|
|
366
370
|
```bash
|
|
367
371
|
siluzan-tso ad keyword-status -a <accountId> --id <keywordId> --status <Enabled|Paused>
|
|
@@ -462,22 +466,22 @@ siluzan-tso ad extension delete -a <accountId> --id <extensionId>
|
|
|
462
466
|
|
|
463
467
|
**`--bid-modifier` 口径(系列级直接透传 Google 倍率)**
|
|
464
468
|
|
|
465
|
-
| 倍率 | 含义
|
|
466
|
-
| ----- |
|
|
467
|
-
| `1.0` | 不调整
|
|
468
|
-
| `0.8` | 降低 20%
|
|
469
|
-
| `1.2` | 提高 20%
|
|
469
|
+
| 倍率 | 含义 |
|
|
470
|
+
| ----- | ----------------------------------------------------------------------------------------------------------- |
|
|
471
|
+
| `1.0` | 不调整 |
|
|
472
|
+
| `0.8` | 降低 20% |
|
|
473
|
+
| `1.2` | 提高 20% |
|
|
470
474
|
| `0` | **写入**时排除该设备(-100%)。**列表**里网关常把「未设置」也填成 `0`,此时 Google 后台显示 `—`,不是 -100% |
|
|
471
475
|
|
|
472
476
|
**展示口径(与 Google Ads 后台「出价调整」列一致)**:`list` / `google-analysis --sections campaign-device` 已输出 `bidModifierDisplay`。写表格必须用这个字段,禁止把倍率当正百分比(不要把 `0.6` 写成 `40%`)。
|
|
473
477
|
|
|
474
|
-
| `bidModifier`
|
|
475
|
-
|
|
|
476
|
-
| `0`(未设置)
|
|
477
|
-
| `0`(明确排除) | `true`
|
|
478
|
-
| `1.0`
|
|
479
|
-
| `0.6`
|
|
480
|
-
| `1.1`
|
|
478
|
+
| `bidModifier` | `bidModifierSpecified` | `bidModifierDisplay` |
|
|
479
|
+
| --------------- | ---------------------- | -------------------- |
|
|
480
|
+
| `0`(未设置) | 空 / `false` | `—` |
|
|
481
|
+
| `0`(明确排除) | `true` | `-100%` |
|
|
482
|
+
| `1.0` | — | `0%` |
|
|
483
|
+
| `0.6` | — | `-40%` |
|
|
484
|
+
| `1.1` | — | `+10%` |
|
|
481
485
|
|
|
482
486
|
**系列级列表的限制**:系列级接口只返回 `bid_modifier`,不带 `bidModifierSpecified`,所以「未设置」和「已排除(-100%)」都显示为 `0`。CLI 对这类行输出 `bidModifierDisplay = "— 或 -100%(网关不区分…)"` 和 `unsetOrExcluded: true`。遇到时不要断言是哪一种:对用户说明需要到 Google Ads 后台确认。要在建系列时就确定排除某设备,在 `campaign-create` JSON 的 `targetedPlatforms` 里写 `{ "id": 30002, "bidModifier": -100 }`(见 `campaign-create-template.md`)。
|
|
483
487
|
|
|
@@ -30,6 +30,7 @@ Meta / Facebook 线索广告走 **`meta-ad`(W13)**,**禁止**用本命令
|
|
|
30
30
|
## Gotchas
|
|
31
31
|
|
|
32
32
|
- **广告类型**:仅 Search + PMax;**不支持 Display**(见上文「支持的广告类型」)。
|
|
33
|
+
- **Search 改字段**:`campaign-status`、`campaign-edit` 的名称/预算/出价、`adgroup-status` / `adgroup-rename` / `adgroup-edit`、`keyword-status`、`keyword-edit` 的状态/出价/网址、`ad-status` / `ad-edit` 都是字段级 PATCH,只写本次参数。词面和匹配类型仍是批量 PUT。`--search-network` / `--content-network` 仍是整包 PUT。PMax 用 `pmax-edit`,不要用 `campaign-edit`。
|
|
33
34
|
- **命令名**:系列列表是 `ad campaigns`(别名 `campaign-list`),**禁止** `ad-campaigns` / `campaign list`。`balance`/`stats` 须 `-m Google`。
|
|
34
35
|
- **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`。
|
|
35
36
|
- **禁止臆测授权过期**:`ad *` / geo / 拉数出现 403/500 时,**禁止**对用户说「授权可能过期」。须核验 ID 后执行 `account check-access -a <mediaCustomerId>`,仅当 `reauth_required`(或与 `invalidOAuthToken=true` 交叉确认)才走重授权;无激活字段时禁止谈套餐。见 [`accounts-permissions.md`](../accounts/accounts-permissions.md)。
|
|
@@ -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.58-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.58-beta.2'
|
|
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.58-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.58-beta.2"
|
|
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"
|