siluzan-tso-cli 1.1.13 → 1.1.14-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 +2 -1
- package/dist/index.js +201 -43
- package/dist/skill/SKILL.md +32 -8
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/account-analytics.md +22 -1
- package/dist/skill/references/accounts.md +5 -2
- package/dist/skill/references/clue.md +5 -0
- package/dist/skill/references/finance.md +9 -5
- package/dist/skill/references/forewarning.md +6 -0
- package/dist/skill/references/google-ads.md +2 -1
- package/dist/skill/references/reporting.md +4 -2
- package/dist/skill/references/setup.md +5 -5
- package/dist/skill/references/tips.md +6 -0
- package/dist/skill/references/tso-home.md +1 -1
- package/dist/skill/scripts/install.ps1 +2 -2
- package/dist/skill/scripts/install.sh +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ HTML 报告模板引用以下 CDN:`cdn.tailwindcss.com`、`cdnjs.cloudflare.co
|
|
|
45
45
|
在**用户的目标项目根目录**执行(根据用户使用的助手选择 `--ai`):
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
npm install -g siluzan-tso-cli
|
|
48
|
+
npm install -g siluzan-tso-cli@beta
|
|
49
49
|
siluzan-tso init --ai cursor # 写入 Cursor(默认)
|
|
50
50
|
siluzan-tso init --ai cursor,claude # 同时写入多个平台
|
|
51
51
|
siluzan-tso init --ai all # 写入所有支持的平台
|
|
@@ -53,6 +53,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
|
|
|
53
53
|
siluzan-tso init --force # 强制覆盖已存在文件
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
> **注意**:当前为测试版(1.1.14-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
56
57
|
|
|
57
58
|
| 助手 | 建议 `--ai` |
|
|
58
59
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -1962,7 +1962,7 @@ import { fileURLToPath as fileURLToPath4 } from "url";
|
|
|
1962
1962
|
import { Command } from "commander";
|
|
1963
1963
|
|
|
1964
1964
|
// src/config/defaults.ts
|
|
1965
|
-
var DEFAULT_API_BASE = "https://tso-api.siluzan.com";
|
|
1965
|
+
var DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
|
|
1966
1966
|
|
|
1967
1967
|
// ../common/dist/index.js
|
|
1968
1968
|
import * as fs from "fs";
|
|
@@ -2441,19 +2441,19 @@ import * as fs3 from "fs/promises";
|
|
|
2441
2441
|
import * as path3 from "path";
|
|
2442
2442
|
async function getSkillFiles(skillDir) {
|
|
2443
2443
|
const out = {};
|
|
2444
|
-
async function
|
|
2444
|
+
async function walk2(dir, prefix) {
|
|
2445
2445
|
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
2446
2446
|
for (const ent of entries) {
|
|
2447
2447
|
const rel = prefix ? `${prefix}/${ent.name}` : ent.name;
|
|
2448
2448
|
const full = path3.join(dir, ent.name);
|
|
2449
2449
|
if (ent.isDirectory()) {
|
|
2450
|
-
await
|
|
2450
|
+
await walk2(full, rel);
|
|
2451
2451
|
} else {
|
|
2452
2452
|
out[rel] = await fs3.readFile(full, "utf8");
|
|
2453
2453
|
}
|
|
2454
2454
|
}
|
|
2455
2455
|
}
|
|
2456
|
-
await
|
|
2456
|
+
await walk2(skillDir, "");
|
|
2457
2457
|
return out;
|
|
2458
2458
|
}
|
|
2459
2459
|
|
|
@@ -3676,18 +3676,25 @@ var VALID_MEDIA_TYPES2 = ["Google", "TikTok", "Yandex", "MetaAd", "BingV2", "Kwa
|
|
|
3676
3676
|
async function runBalance(opts) {
|
|
3677
3677
|
const config = loadConfig(opts.token);
|
|
3678
3678
|
if (!VALID_MEDIA_TYPES2.includes(opts.media)) {
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3679
|
+
const msg = `\u4E0D\u652F\u6301\u7684\u5A92\u4F53\u7C7B\u578B\uFF1A${opts.media}\uFF08\u53EF\u9009\uFF1A${VALID_MEDIA_TYPES2.join(" | ")}\uFF09`;
|
|
3680
|
+
if (opts.json) {
|
|
3681
|
+
console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
3682
|
+
process.exit(1);
|
|
3683
|
+
}
|
|
3684
|
+
console.error(`
|
|
3685
|
+
\u274C ${msg}
|
|
3686
|
+
`);
|
|
3685
3687
|
process.exit(1);
|
|
3686
3688
|
}
|
|
3687
3689
|
const media = opts.media;
|
|
3688
3690
|
if (!BALANCE_SUPPORTED_MEDIA.includes(media)) {
|
|
3691
|
+
const msg = `${media} \u6682\u4E0D\u652F\u6301\u4F59\u989D\u67E5\u8BE2`;
|
|
3692
|
+
if (opts.json) {
|
|
3693
|
+
console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
3694
|
+
process.exit(1);
|
|
3695
|
+
}
|
|
3689
3696
|
console.error(`
|
|
3690
|
-
\u26A0\uFE0F ${
|
|
3697
|
+
\u26A0\uFE0F ${msg}
|
|
3691
3698
|
`);
|
|
3692
3699
|
process.exit(1);
|
|
3693
3700
|
}
|
|
@@ -3707,8 +3714,13 @@ async function runBalance(opts) {
|
|
|
3707
3714
|
opts.verbose
|
|
3708
3715
|
);
|
|
3709
3716
|
} catch (err) {
|
|
3717
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3718
|
+
if (opts.json) {
|
|
3719
|
+
console.log(JSON.stringify({ ok: false, error: message }, null, 2));
|
|
3720
|
+
process.exit(1);
|
|
3721
|
+
}
|
|
3710
3722
|
console.error(`
|
|
3711
|
-
\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${
|
|
3723
|
+
\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${message}
|
|
3712
3724
|
`);
|
|
3713
3725
|
process.exit(1);
|
|
3714
3726
|
}
|
|
@@ -3948,18 +3960,54 @@ async function runBalanceScan(opts) {
|
|
|
3948
3960
|
const CHUNK = 100;
|
|
3949
3961
|
const chunks = [];
|
|
3950
3962
|
for (let i = 0; i < validIds.length; i += CHUNK) chunks.push(validIds.slice(i, i + CHUNK));
|
|
3963
|
+
process.stderr.write(
|
|
3964
|
+
`\u23F3 [balance-scan] \u6709\u6548\u8D26\u6237 ${validIds.length} \u4E2A\uFF0C\u5206 ${chunks.length} \u6279\uFF1B\u4F59\u989D\u4E0E\u8FD1 7 \u65E5\u6D88\u8017\u5E76\u884C\u8BF7\u6C42\uFF08\u5355\u8BF7\u6C42\u6700\u957F\u7EA6 10 \u5206\u949F\uFF09\u3002
|
|
3965
|
+
`
|
|
3966
|
+
);
|
|
3967
|
+
const logBalanceChunk = (idx, ids, m) => {
|
|
3968
|
+
process.stderr.write(
|
|
3969
|
+
` \u2713 [\u4F59\u989D] \u7B2C ${idx + 1}/${chunks.length} \u6279\u5B8C\u6210\uFF08${ids.length} \u6237 \u2192 ${m.size} \u6761\uFF09
|
|
3970
|
+
`
|
|
3971
|
+
);
|
|
3972
|
+
};
|
|
3973
|
+
const logOverviewChunk = (idx, ids, m) => {
|
|
3974
|
+
process.stderr.write(
|
|
3975
|
+
` \u2713 [\u8FD17\u65E5\u6D88\u8017] \u7B2C ${idx + 1}/${chunks.length} \u6279\u5B8C\u6210\uFF08${ids.length} \u6237 \u2192 ${m.size} \u6761\uFF09
|
|
3976
|
+
`
|
|
3977
|
+
);
|
|
3978
|
+
};
|
|
3951
3979
|
const [bMaps, oMaps] = await Promise.all([
|
|
3952
3980
|
Promise.all(
|
|
3953
|
-
chunks.map(
|
|
3954
|
-
(
|
|
3955
|
-
|
|
3981
|
+
chunks.map((ids, chunkIdx) => {
|
|
3982
|
+
process.stderr.write(
|
|
3983
|
+
` \u2192 [\u4F59\u989D] \u7B2C ${chunkIdx + 1}/${chunks.length} \u6279\u8BF7\u6C42\u4E2D\uFF08${ids.length} \u6237\uFF09\u2026
|
|
3984
|
+
`
|
|
3985
|
+
);
|
|
3986
|
+
return fetchBalanceMap(media, ids, config, void 0, void 0, opts.verbose).then(
|
|
3987
|
+
(m) => {
|
|
3988
|
+
logBalanceChunk(chunkIdx, ids, m);
|
|
3989
|
+
return m;
|
|
3990
|
+
}
|
|
3991
|
+
);
|
|
3992
|
+
})
|
|
3956
3993
|
),
|
|
3957
3994
|
Promise.all(
|
|
3958
|
-
chunks.map(
|
|
3959
|
-
(
|
|
3960
|
-
|
|
3995
|
+
chunks.map((ids, chunkIdx) => {
|
|
3996
|
+
process.stderr.write(
|
|
3997
|
+
` \u2192 [\u8FD17\u65E5\u6D88\u8017] \u7B2C ${chunkIdx + 1}/${chunks.length} \u6279\u8BF7\u6C42\u4E2D\uFF08${ids.length} \u6237\uFF09\u2026
|
|
3998
|
+
`
|
|
3999
|
+
);
|
|
4000
|
+
return fetchOverviewMap(media, ids, config, void 0, void 0, opts.verbose).then(
|
|
4001
|
+
(m) => {
|
|
4002
|
+
logOverviewChunk(chunkIdx, ids, m);
|
|
4003
|
+
return m;
|
|
4004
|
+
}
|
|
4005
|
+
);
|
|
4006
|
+
})
|
|
3961
4007
|
)
|
|
3962
4008
|
]);
|
|
4009
|
+
process.stderr.write(`\u23F3 [balance-scan] \u4F59\u989D\u4E0E\u6D88\u8017\u5DF2\u9F50\uFF0C\u6B63\u5728\u6309\u9608\u503C\u7B5B\u9009\u2026
|
|
4010
|
+
`);
|
|
3963
4011
|
for (const m of bMaps) for (const [k, v] of m) balanceMap.set(k, v);
|
|
3964
4012
|
for (const m of oMaps) for (const [k, v] of m) overviewMap.set(k, v);
|
|
3965
4013
|
}
|
|
@@ -4382,8 +4430,13 @@ async function runStats(opts) {
|
|
|
4382
4430
|
try {
|
|
4383
4431
|
raw = await apiFetch2(url, config, {}, opts.verbose);
|
|
4384
4432
|
} catch (err) {
|
|
4433
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4434
|
+
if (opts.json) {
|
|
4435
|
+
console.log(JSON.stringify({ ok: false, error: message }, null, 2));
|
|
4436
|
+
process.exit(1);
|
|
4437
|
+
}
|
|
4385
4438
|
console.error(`
|
|
4386
|
-
\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${
|
|
4439
|
+
\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${message}
|
|
4387
4440
|
`);
|
|
4388
4441
|
process.exit(1);
|
|
4389
4442
|
}
|
|
@@ -5070,6 +5123,37 @@ async function runReportPushReceiveEmails(opts) {
|
|
|
5070
5123
|
console.log();
|
|
5071
5124
|
}
|
|
5072
5125
|
|
|
5126
|
+
// src/utils/strip-legacy-google-fields.ts
|
|
5127
|
+
var LEGACY_WHEN_V2_PRESENT = [
|
|
5128
|
+
["status", "statusV2"],
|
|
5129
|
+
["channelType", "channelTypeV2"],
|
|
5130
|
+
["subChannelType", "subChannelTypeV2"],
|
|
5131
|
+
["biddingStrategyType", "biddingStrategyTypeV2"],
|
|
5132
|
+
["campaignStatus", "campaignStatusV2"],
|
|
5133
|
+
["adGroupStatus", "adGroupStatusV2"],
|
|
5134
|
+
["matchType", "matchTypeV2"],
|
|
5135
|
+
["type", "typeV2"]
|
|
5136
|
+
];
|
|
5137
|
+
function stripLegacyGoogleFieldsIfV2Present(value) {
|
|
5138
|
+
return walk(value);
|
|
5139
|
+
}
|
|
5140
|
+
function walk(obj) {
|
|
5141
|
+
if (Array.isArray(obj)) return obj.map(walk);
|
|
5142
|
+
if (obj !== null && typeof obj === "object") {
|
|
5143
|
+
const o = { ...obj };
|
|
5144
|
+
for (const [legacy, modern] of LEGACY_WHEN_V2_PRESENT) {
|
|
5145
|
+
if (modern in o && legacy in o) {
|
|
5146
|
+
delete o[legacy];
|
|
5147
|
+
}
|
|
5148
|
+
}
|
|
5149
|
+
for (const k of Object.keys(o)) {
|
|
5150
|
+
o[k] = walk(o[k]);
|
|
5151
|
+
}
|
|
5152
|
+
return o;
|
|
5153
|
+
}
|
|
5154
|
+
return obj;
|
|
5155
|
+
}
|
|
5156
|
+
|
|
5073
5157
|
// src/commands/google-analysis.ts
|
|
5074
5158
|
var SECTIONS = [
|
|
5075
5159
|
{
|
|
@@ -5292,7 +5376,7 @@ async function runOneSection(def, opts) {
|
|
|
5292
5376
|
]);
|
|
5293
5377
|
const merged = { images, videos };
|
|
5294
5378
|
if (opts.json) {
|
|
5295
|
-
console.log(JSON.stringify(merged, null, 2));
|
|
5379
|
+
console.log(JSON.stringify(stripLegacyGoogleFieldsIfV2Present(merged), null, 2));
|
|
5296
5380
|
return;
|
|
5297
5381
|
}
|
|
5298
5382
|
const iLen = Array.isArray(images) ? images.length : 0;
|
|
@@ -5307,7 +5391,7 @@ async function runOneSection(def, opts) {
|
|
|
5307
5391
|
}
|
|
5308
5392
|
const data = await fetchJson(config, fullPath, !!opts.verbose);
|
|
5309
5393
|
if (opts.json) {
|
|
5310
|
-
console.log(JSON.stringify(data, null, 2));
|
|
5394
|
+
console.log(JSON.stringify(stripLegacyGoogleFieldsIfV2Present(data), null, 2));
|
|
5311
5395
|
return;
|
|
5312
5396
|
}
|
|
5313
5397
|
console.log(
|
|
@@ -6806,6 +6890,39 @@ function requireGoogleApi(config) {
|
|
|
6806
6890
|
}
|
|
6807
6891
|
return config.googleApiUrl;
|
|
6808
6892
|
}
|
|
6893
|
+
function formatGoogleCampaignListStatus(row) {
|
|
6894
|
+
let result = "-";
|
|
6895
|
+
const raw = row.statusV2;
|
|
6896
|
+
if (raw == null || String(raw).trim() === "") return result;
|
|
6897
|
+
const statusV2 = String(raw).toUpperCase();
|
|
6898
|
+
const start = parseCampaignTimeMs(row.startTime);
|
|
6899
|
+
const end = parseCampaignTimeMs(row.endTime);
|
|
6900
|
+
if (start == null || end == null) return result;
|
|
6901
|
+
const now = Date.now();
|
|
6902
|
+
if (statusV2 === "PAUSED") {
|
|
6903
|
+
if (now > start && now < end) {
|
|
6904
|
+
result = "\u5DF2\u6682\u505C";
|
|
6905
|
+
} else if (now > end) {
|
|
6906
|
+
result = "\u5DF2\u7ED3\u675F\u4F7F\u7528";
|
|
6907
|
+
} else if (now < start) {
|
|
6908
|
+
result = "\u672A\u6295\u653E";
|
|
6909
|
+
}
|
|
6910
|
+
} else if (statusV2 === "ENABLED") {
|
|
6911
|
+
if (now > start && now < end) {
|
|
6912
|
+
result = "\u6709\u6548";
|
|
6913
|
+
} else if (now > end) {
|
|
6914
|
+
result = "\u5DF2\u7ED3\u675F\u4F7F\u7528";
|
|
6915
|
+
} else if (now < start) {
|
|
6916
|
+
result = "\u672A\u6295\u653E";
|
|
6917
|
+
}
|
|
6918
|
+
}
|
|
6919
|
+
return result;
|
|
6920
|
+
}
|
|
6921
|
+
function parseCampaignTimeMs(v) {
|
|
6922
|
+
if (v == null || v === "") return null;
|
|
6923
|
+
const t = new Date(v).getTime();
|
|
6924
|
+
return Number.isFinite(t) ? t : null;
|
|
6925
|
+
}
|
|
6809
6926
|
async function runAdCampaigns(opts) {
|
|
6810
6927
|
const config = loadConfig(opts.token);
|
|
6811
6928
|
const googleApiUrl = requireGoogleApi(config);
|
|
@@ -6828,18 +6945,19 @@ async function runAdCampaigns(opts) {
|
|
|
6828
6945
|
return {
|
|
6829
6946
|
...item,
|
|
6830
6947
|
budgetDisplay,
|
|
6831
|
-
budgetUnit: "display"
|
|
6948
|
+
budgetUnit: "display",
|
|
6949
|
+
statusDisplay: formatGoogleCampaignListStatus(item)
|
|
6832
6950
|
};
|
|
6833
6951
|
});
|
|
6834
6952
|
const n = items.length;
|
|
6835
6953
|
if (opts.json) {
|
|
6836
6954
|
console.log(
|
|
6837
6955
|
JSON.stringify(
|
|
6838
|
-
{
|
|
6956
|
+
stripLegacyGoogleFieldsIfV2Present({
|
|
6839
6957
|
...wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items }),
|
|
6840
6958
|
code: data.code ?? null,
|
|
6841
6959
|
message: data.message ?? null
|
|
6842
|
-
},
|
|
6960
|
+
}),
|
|
6843
6961
|
null,
|
|
6844
6962
|
2
|
|
6845
6963
|
)
|
|
@@ -6874,7 +6992,7 @@ async function runAdCampaigns(opts) {
|
|
|
6874
6992
|
const budget = item.budgetDisplay != null ? item.budgetDisplay.toFixed(2) : "\u2014";
|
|
6875
6993
|
return {
|
|
6876
6994
|
name: (item.name ?? "").slice(0, nameW),
|
|
6877
|
-
status: item.
|
|
6995
|
+
status: item.statusDisplay ?? formatGoogleCampaignListStatus(item),
|
|
6878
6996
|
channelType: item.channelTypeV2 ?? "",
|
|
6879
6997
|
bidding: String(item.biddingStrategyTypeV2 ?? ""),
|
|
6880
6998
|
budget,
|
|
@@ -6917,10 +7035,10 @@ async function runAdGroups(opts) {
|
|
|
6917
7035
|
if (opts.json) {
|
|
6918
7036
|
console.log(
|
|
6919
7037
|
JSON.stringify(
|
|
6920
|
-
{
|
|
7038
|
+
stripLegacyGoogleFieldsIfV2Present({
|
|
6921
7039
|
...wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items }),
|
|
6922
7040
|
code: data.code ?? null
|
|
6923
|
-
},
|
|
7041
|
+
}),
|
|
6924
7042
|
null,
|
|
6925
7043
|
2
|
|
6926
7044
|
)
|
|
@@ -6990,7 +7108,13 @@ async function runAdList(opts) {
|
|
|
6990
7108
|
const n = items.length;
|
|
6991
7109
|
if (opts.json) {
|
|
6992
7110
|
console.log(
|
|
6993
|
-
JSON.stringify(
|
|
7111
|
+
JSON.stringify(
|
|
7112
|
+
stripLegacyGoogleFieldsIfV2Present(
|
|
7113
|
+
wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
|
|
7114
|
+
),
|
|
7115
|
+
null,
|
|
7116
|
+
2
|
|
7117
|
+
)
|
|
6994
7118
|
);
|
|
6995
7119
|
return;
|
|
6996
7120
|
}
|
|
@@ -7027,7 +7151,7 @@ async function runAdList(opts) {
|
|
|
7027
7151
|
campaign: String(item["campaign"] ?? "").slice(0, campW),
|
|
7028
7152
|
adGroup: String(item["adGroup"] ?? "").slice(0, grpW),
|
|
7029
7153
|
status: String(item["statusV2"]),
|
|
7030
|
-
type: String(item["typeV2"] ??
|
|
7154
|
+
type: String(item["typeV2"] ?? ""),
|
|
7031
7155
|
impressions: String(item["impressions"] ?? 0),
|
|
7032
7156
|
clicks: String(item["clicks"] ?? 0),
|
|
7033
7157
|
ctr,
|
|
@@ -7066,7 +7190,13 @@ async function runAdKeywords(opts) {
|
|
|
7066
7190
|
const n = items.length;
|
|
7067
7191
|
if (opts.json) {
|
|
7068
7192
|
console.log(
|
|
7069
|
-
JSON.stringify(
|
|
7193
|
+
JSON.stringify(
|
|
7194
|
+
stripLegacyGoogleFieldsIfV2Present(
|
|
7195
|
+
wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
|
|
7196
|
+
),
|
|
7197
|
+
null,
|
|
7198
|
+
2
|
|
7199
|
+
)
|
|
7070
7200
|
);
|
|
7071
7201
|
return;
|
|
7072
7202
|
}
|
|
@@ -7082,7 +7212,7 @@ ${label}\uFF08\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672C
|
|
|
7082
7212
|
if (opts.negative) {
|
|
7083
7213
|
items.forEach((item) => {
|
|
7084
7214
|
const kwText = Array.isArray(item["keywordText"]) ? item["keywordText"].join(", ") : String(item["text"] ?? item["keywordText"] ?? item["id"] ?? "\u2014");
|
|
7085
|
-
const matchType = item["matchTypeV2"] ??
|
|
7215
|
+
const matchType = item["matchTypeV2"] ?? "\u2014";
|
|
7086
7216
|
console.log(` [${matchType}] ${kwText} id: ${String(item["id"] ?? "")}`);
|
|
7087
7217
|
});
|
|
7088
7218
|
} else {
|
|
@@ -7113,8 +7243,8 @@ ${label}\uFF08\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672C
|
|
|
7113
7243
|
const kwText = String(item["text"] ?? item["keywordText"] ?? "\u2014");
|
|
7114
7244
|
const campaign = String(item["campaignName"] ?? item["campaign"] ?? "");
|
|
7115
7245
|
const adGroup = String(item["adGroupName"] ?? item["adGroup"] ?? "");
|
|
7116
|
-
const status = String(item["
|
|
7117
|
-
const matchType = String(item["matchTypeV2"] ??
|
|
7246
|
+
const status = String(item["userStatus"] ?? "");
|
|
7247
|
+
const matchType = String(item["matchTypeV2"] ?? "");
|
|
7118
7248
|
const ctr = item["ctr"] != null ? (Number(item["ctr"]) * 100).toFixed(2) + "%" : "\u2014";
|
|
7119
7249
|
const spend = item["spend"] != null ? Number(item["spend"]).toFixed(2) : "\u2014";
|
|
7120
7250
|
return {
|
|
@@ -8145,7 +8275,13 @@ async function runAdExtensionList(opts) {
|
|
|
8145
8275
|
const n = items.length;
|
|
8146
8276
|
if (opts.json) {
|
|
8147
8277
|
console.log(
|
|
8148
|
-
JSON.stringify(
|
|
8278
|
+
JSON.stringify(
|
|
8279
|
+
stripLegacyGoogleFieldsIfV2Present(
|
|
8280
|
+
wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
|
|
8281
|
+
),
|
|
8282
|
+
null,
|
|
8283
|
+
2
|
|
8284
|
+
)
|
|
8149
8285
|
);
|
|
8150
8286
|
return;
|
|
8151
8287
|
}
|
|
@@ -8278,7 +8414,13 @@ async function runAdSearchTerms(opts) {
|
|
|
8278
8414
|
const n = items.length;
|
|
8279
8415
|
if (opts.json) {
|
|
8280
8416
|
console.log(
|
|
8281
|
-
JSON.stringify(
|
|
8417
|
+
JSON.stringify(
|
|
8418
|
+
stripLegacyGoogleFieldsIfV2Present(
|
|
8419
|
+
wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
|
|
8420
|
+
),
|
|
8421
|
+
null,
|
|
8422
|
+
2
|
|
8423
|
+
)
|
|
8282
8424
|
);
|
|
8283
8425
|
return;
|
|
8284
8426
|
}
|
|
@@ -8321,7 +8463,7 @@ async function runAdSearchTerms(opts) {
|
|
|
8321
8463
|
term: term.slice(0, termW),
|
|
8322
8464
|
campaign: String(item["campaignName"] ?? "").slice(0, campW),
|
|
8323
8465
|
adGroup: String(item["adGroupName"] ?? "").slice(0, grpW),
|
|
8324
|
-
matchType: String(item["
|
|
8466
|
+
matchType: String(item["matchTypeV2"] ?? ""),
|
|
8325
8467
|
impressions: String(item["impressions"] ?? 0),
|
|
8326
8468
|
clicks: String(item["clicks"] ?? 0),
|
|
8327
8469
|
ctr,
|
|
@@ -8355,7 +8497,7 @@ async function runAdGeoSearch(opts) {
|
|
|
8355
8497
|
for (const item of items) {
|
|
8356
8498
|
const id = String(item["id"] ?? "");
|
|
8357
8499
|
const name = String(item["locationName"] ?? item["canonicalName"] ?? item["name"] ?? "");
|
|
8358
|
-
const type = String(item["targetType"] ?? item["typeV2"] ??
|
|
8500
|
+
const type = String(item["targetType"] ?? item["typeV2"] ?? "");
|
|
8359
8501
|
console.log(` id:${id} ${name} [${type}]`);
|
|
8360
8502
|
}
|
|
8361
8503
|
console.log();
|
|
@@ -8391,7 +8533,13 @@ async function runAdGeoList(opts) {
|
|
|
8391
8533
|
const n = items.length;
|
|
8392
8534
|
if (opts.json) {
|
|
8393
8535
|
console.log(
|
|
8394
|
-
JSON.stringify(
|
|
8536
|
+
JSON.stringify(
|
|
8537
|
+
stripLegacyGoogleFieldsIfV2Present(
|
|
8538
|
+
wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
|
|
8539
|
+
),
|
|
8540
|
+
null,
|
|
8541
|
+
2
|
|
8542
|
+
)
|
|
8395
8543
|
);
|
|
8396
8544
|
return;
|
|
8397
8545
|
}
|
|
@@ -9415,8 +9563,13 @@ async function fetchTikTokClues(opts, config) {
|
|
|
9415
9563
|
);
|
|
9416
9564
|
leads = Array.isArray(res) ? res : res.data ?? [];
|
|
9417
9565
|
} catch (err) {
|
|
9566
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9567
|
+
if (opts.json) {
|
|
9568
|
+
console.log(JSON.stringify({ ok: false, error: message, items: [] }, null, 2));
|
|
9569
|
+
process.exit(1);
|
|
9570
|
+
}
|
|
9418
9571
|
console.error(`
|
|
9419
|
-
\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${
|
|
9572
|
+
\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${message}
|
|
9420
9573
|
`);
|
|
9421
9574
|
process.exit(1);
|
|
9422
9575
|
}
|
|
@@ -9463,8 +9616,13 @@ async function fetchMetaClues(opts, config) {
|
|
|
9463
9616
|
try {
|
|
9464
9617
|
raw = await apiFetch2(url, config, {}, opts.verbose);
|
|
9465
9618
|
} catch (err) {
|
|
9619
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9620
|
+
if (opts.json) {
|
|
9621
|
+
console.log(JSON.stringify({ ok: false, error: message, items: [] }, null, 2));
|
|
9622
|
+
process.exit(1);
|
|
9623
|
+
}
|
|
9466
9624
|
console.error(`
|
|
9467
|
-
\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${
|
|
9625
|
+
\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${message}
|
|
9468
9626
|
`);
|
|
9469
9627
|
process.exit(1);
|
|
9470
9628
|
}
|
|
@@ -12121,14 +12279,14 @@ program.command("accounts-digest").description(
|
|
|
12121
12279
|
program.command("stats").description("\u67E5\u8BE2\u5E7F\u544A\u6D88\u8017\u3001\u70B9\u51FB\u3001\u8F6C\u5316\u7B49\u6295\u653E\u6570\u636E\uFF08\u9ED8\u8BA4\u8FD1 7 \u5929\uFF0C\u4E0D\u542B\u4ECA\u5929\uFF09").requiredOption(
|
|
12122
12280
|
"-m, --media <type>",
|
|
12123
12281
|
"\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2 | Kwai"
|
|
12124
|
-
).option("-a, --accounts <ids>", "\u8D26\u6237 ID\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF08\u7559\u7A7A\u5219\u67E5\u5168\u90E8\u8D26\u6237\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4 7 \u5929\u524D\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u6628\u5929\uFF09").option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u539F\u59CB\u54CD\u5E94", false).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
12282
|
+
).option("-a, --accounts <ids>", "\u8D26\u6237 ID\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF08\u7559\u7A7A\u5219\u67E5\u5168\u90E8\u8D26\u6237\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4 7 \u5929\u524D\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u6628\u5929\uFF09").option("--start-date <date>", "\u540C --start\uFF08\u6587\u6863/Playbook \u517C\u5BB9\u522B\u540D\uFF09").option("--end-date <date>", "\u540C --end\uFF08\u6587\u6863/Playbook \u517C\u5BB9\u522B\u540D\uFF09").option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u539F\u59CB\u54CD\u5E94", false).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
12125
12283
|
async (opts) => {
|
|
12126
12284
|
await runStats({
|
|
12127
12285
|
token: opts.token,
|
|
12128
12286
|
media: opts.media,
|
|
12129
12287
|
accounts: opts.accounts,
|
|
12130
|
-
startDate: opts.start,
|
|
12131
|
-
endDate: opts.end,
|
|
12288
|
+
startDate: opts.start ?? opts.startDate,
|
|
12289
|
+
endDate: opts.end ?? opts.endDate,
|
|
12132
12290
|
json: opts.json,
|
|
12133
12291
|
verbose: opts.verbose
|
|
12134
12292
|
});
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -75,6 +75,8 @@ allowed-tools: Bash(siluzan-tso:*) Read
|
|
|
75
75
|
- 这种报告你无法用它来做数据分析除非用户明确要求(Siluzan平台的优化报告)
|
|
76
76
|
- (推荐,默认生成这种报告)由你主动拉取数据,并按照skill给出的格式,输出给用户:详情请查看(`references/account-analytics.md`)
|
|
77
77
|
|
|
78
|
+
**写报告前必读(账户 vs 系列)**:`stats` / `balance` / `list-accounts` 里的账户 `status` 只表示**广告账户**是否可用,**不能**当作**广告系列**是否启用;系列是否投放须用 `ad campaigns`(及 CLI 派生的 `statusDisplay` 等)。详见 `references/account-analytics.md`「账户状态 ≠ 广告系列状态」。
|
|
79
|
+
|
|
78
80
|
### 广告账户相关
|
|
79
81
|
|
|
80
82
|
- 广告账户开户请阅读: `references/open-account-by-media.md`
|
|
@@ -121,9 +123,12 @@ allowed-tools: Bash(siluzan-tso:*) Read
|
|
|
121
123
|
|
|
122
124
|
### 硬规范
|
|
123
125
|
|
|
126
|
+
- **出报告时账户状态 ≠ 广告系列状态**:`stats` / `balance` / `list-accounts` 中的 `status`(如 Enabled)只表示**广告账户**关联/可用,**绝不能**据此填写或推断**各广告系列**是否「启用/在投」。已暂停或移除的系列若被写成启用,属于严重错误。系列是否启用**必须**来自 `ad campaigns`(或系列维报表中的系列状态)。详见 `references/account-analytics.md`「账户状态 ≠ 广告系列状态」。
|
|
124
127
|
- **不确定时读文档**:遇到不熟悉的命令,先读对应 references 文件或使用-h查看命令帮助,不要猜参数。
|
|
125
128
|
- **先查账户再操作**:对具体账户做操作前,先通过 `list-accounts -m [mediaType] -k [mediaCustomerId]` 确认。特别是不确定是Google/Bing/TikTok这些媒体平台中的哪一个的时候
|
|
126
|
-
- **使用 --json 处理数据**:需对返回数据做计算或筛选时,加 `--json`,再用 `node -e` 过滤提取(见 `references/tips.md
|
|
129
|
+
- **使用 --json 处理数据**:需对返回数据做计算或筛选时,加 `--json`,再用 `node -e` 过滤提取(见 `references/tips.md`)。若用户**已有一份 JSON**(文件或剪贴板),只问如何筛选时:优先给「stdin / 读本地文件 + `node -e`」的通用写法,不必默认再跑一遍业务命令。
|
|
130
|
+
- **CLI 输出忠实**:向用户引用 JSON 时,账户 ID、金额等须与**本次命令 stdout 一致**,不得换成别的示例 ID。禁止编造「stub/示例环境/登录异常」等**未在 CLI 输出或 stderr 中出现**的解释;`data` 为空时只说明「当前返回无记录」并附上原始 JSON。
|
|
131
|
+
- **用户明确要求原始 JSON**:须在回复中给出 CLI 返回的 JSON(或完整代码块),不得以长篇推测替代交付。
|
|
127
132
|
- **不要猜测账户 ID**:`entityId` ≠ `mediaCustomerId`,两者均来自 `list-accounts`。
|
|
128
133
|
- **媒体类型区分大小写**:`Google`、`TikTok`、`MetaAd`、`BingV2`、`Kwai`。
|
|
129
134
|
- **命令透明性**:以简洁的方式向用户说明即将执行的操作意图(如「正在查询您的 Google 账户列表」「正在为账户 xxx 创建预警规则」),让用户了解操作进度。用户主动要求查看执行细节时,应如实提供完整命令。
|
|
@@ -139,9 +144,22 @@ allowed-tools: Bash(siluzan-tso:*) Read
|
|
|
139
144
|
> (A)最近完整自然周(周一到周日)
|
|
140
145
|
> (B)本月 1 号到昨天
|
|
141
146
|
> (C)自定义起止日(请告诉我 `YYYY-MM-DD` 起止)
|
|
142
|
-
2.
|
|
147
|
+
2. 用户给出范围后,**在报告首行显式标注** `统计区间:YYYY-MM-DD ~ YYYY-MM-DD(货币:XXX)`(与 `references/account-analytics.md` 一致),与调用参数保持完全一致。
|
|
143
148
|
3. **只有在用户明确说"按你默认来 / 你决定"**时,才使用下方默认值白名单。
|
|
144
149
|
|
|
150
|
+
### 时间范围反问的例外(不要机械套用到下列任务)
|
|
151
|
+
|
|
152
|
+
- **`list-accounts` 全量**:用户要「所有 Google 账户」且 JSON 时,看返回里的 `total` / `page` / `pageSize`;若 `itemCount < total`,须翻页(增大 `--page-size` 或循环 `--page`)或说明**当前仅为第 N 页**,不得把单页当成全量结论。
|
|
153
|
+
- **「昨天」单日 stats**:用户已给出 Google `mediaCustomerId` 且只说「昨天」时:默认按 **`Asia/Shanghai` 日历日**换算昨日起止;**先** `list-accounts -m Google -k <id> --quick --json` **再** `stats -m Google -a <id> --start … --end … --json`,不要为时区再打断一轮(除非用户明确要求 UTC/其他时区)。
|
|
154
|
+
- **仅持有 `entityId`**:**禁止**把 `entityId` 传给 `stats -a` / `balance -a`。应先 `list-accounts`(必要时 `--json` + 本地筛选或 `-k` 缩小范围)解析出 `mediaCustomerId` 后再调 `stats`。
|
|
155
|
+
- **`forewarning records`**:见 `references/forewarning.md`,不要求先做投放类日期反问。
|
|
156
|
+
- **`invoice list` /「本月」**:见 `references/finance.md`,可用当月 1 日~昨天直接查。
|
|
157
|
+
- **TikTok `clue`「最近一周」**:见 `references/clue.md`,可直接 `--json` 查询,不要先做 A/B/C 日期口径反问。
|
|
158
|
+
|
|
159
|
+
### 仅输出 JSON 的交付
|
|
160
|
+
|
|
161
|
+
用户明确要求「**只输出**一个 JSON / 除 JSON 外不要文字」时:回复中**仅含一个** JSON 代码块,内容与本次 CLI **stdout 完全一致**(分页、失败体均如此);失败时 CLI 已保证 `--json` 下 stdout 仍为 JSON 对象。
|
|
162
|
+
|
|
145
163
|
### 默认值白名单(仅在用户明确授权"你决定"时才能使用)
|
|
146
164
|
|
|
147
165
|
| 场景 | 允许的默认窗口 |
|
|
@@ -166,6 +184,7 @@ allowed-tools: Bash(siluzan-tso:*) Read
|
|
|
166
184
|
2. `list-accounts` 返回的 `mag.advertiserName`
|
|
167
185
|
3. 用户提供的网址 → 明确告诉用户"使用域名作为占位"(例如 `hy-steelpipe.com`)并在交付物里标注 `[待确认品牌名]`
|
|
168
186
|
- **严禁**"hy-steelpipe.com"这样的英文域名被输出成类似"海悦钢管"这种虚构中文品牌。
|
|
187
|
+
- 正文里写出品牌名时,应能回溯到上述来源(可写「来自 list-accounts 的 `mag.advertiserName`:…」);**禁止**写看似真实但未在上述来源或 CLI 输出中出现的名称(包括常见 Demo 风格占位名)。
|
|
169
188
|
|
|
170
189
|
### 批量任务硬约束(≥ 5 个账户或系列)
|
|
171
190
|
|
|
@@ -206,6 +225,8 @@ allowed-tools: Bash(siluzan-tso:*) Read
|
|
|
206
225
|
- 发票申请(`invoice apply`)— 涉及财务
|
|
207
226
|
- 广告发布(`ad batch publish` / `ad campaign-create`)— 涉及预算消耗
|
|
208
227
|
- **只读操作可自主执行**:查询类命令(`list-accounts`、`balance`、`stats`、`report list`、`config show` 等)可直接执行,无需额外确认
|
|
228
|
+
- **Google 广告结构性改动(否定词/系列/关键词等)**:须遵守 `references/google-ads.md` 开篇流程——先阅读规则文档、再列计划与将参考的文档清单;**仅在用户明确确认方案后**才可执行写命令。用户未确认前,不得使用「拿到参数就立刻执行」等措辞暗示可跳过确认。
|
|
229
|
+
- **Google 开户(CLI 指引)**:`open-account google-wizard` 仅适用于真实 TTY;**Agent/自动化环境一律用非交互** `open-account google ...`,审核进度用 `account-history`(详见 `references/workflows.md` 流程一)。向用户写步骤时,把上述禁令放在指引**最前**,再给可复制命令。
|
|
209
230
|
|
|
210
231
|
---
|
|
211
232
|
|
|
@@ -217,15 +238,16 @@ allowed-tools: Bash(siluzan-tso:*) Read
|
|
|
217
238
|
|
|
218
239
|
> 触发关键词:某账户数据 / 投放情况 / 整理账户 / 看下某个账户的表现
|
|
219
240
|
|
|
220
|
-
1. **反问时间范围**(参见"时间范围强制反问"
|
|
241
|
+
1. **反问时间范围**(参见"时间范围强制反问";用户已授权「按默认」时用默认值白名单并写明区间)。
|
|
221
242
|
2. `list-accounts -m Google -k <mediaCustomerId> --quick --json`
|
|
222
243
|
- 一次拿齐:账户基础信息、创建日期、当前状态、公司名(`mag.advertiserName` 作为品牌名)
|
|
223
|
-
3. `stats
|
|
224
|
-
- 拿该区间消耗、点击、转化等;**直接读响应中的主币种数值**,不要再 ×100。
|
|
225
|
-
4. `ad campaigns
|
|
244
|
+
3. `siluzan-tso stats -m Google -a <mediaCustomerId> --start <S> --end <D> --json`(`--start-date` / `--end-date` 与 `--start` / `--end` 等价)
|
|
245
|
+
- 拿该区间消耗、点击、转化等;**直接读响应中的主币种数值**,不要再 ×100。若返回 `{"ok":false,"error":...}`,须原样展示并**不得**用系列数据编造账户级 stats。
|
|
246
|
+
4. `siluzan-tso ad campaigns -a <mediaCustomerId> --start <S> --end <D> --json`
|
|
226
247
|
- 拿广告系列类型(Search / PMax / Display 等)、日预算(**用 `budgetDisplay`**)、优化得分相关字段。
|
|
227
248
|
5. `stats` 结合 `accountsoverview` 字段派生"开始投放时间 / 有效投放天数 / 地区消耗分布"(如接口暂未直出,在 node 里聚合)。
|
|
228
249
|
6. 用 `report-templates/google-account-diagnosis-report.md` 模板输出,**首行标注统计区间和货币**。
|
|
250
|
+
7. 用户要求「先拉 JSON 再文字总结」时:回复中须包含实际 `--json` 输出(完整或明确声明截断),再写总结;**禁止**用未出现在这些 JSON / `list-accounts` 中的字段写品牌名或接口状态。
|
|
229
251
|
|
|
230
252
|
### P2 · 多账户余额扫描 / 预算预警(典型指令:"117 个 Bing 账户不足 7 天的挑出来")
|
|
231
253
|
|
|
@@ -276,8 +298,9 @@ siluzan-tso accounts-digest -m Google \
|
|
|
276
298
|
|
|
277
299
|
1. **反问时间范围**(P 级硬约束),拿到用户回复后再执行 `accounts-digest`。
|
|
278
300
|
2. 如命令已返回 `--json`,直接基于其中 `data.items` 与 `meta.totals` 生成报告;**不要**再逐账户 `stats`。
|
|
279
|
-
3.
|
|
280
|
-
4.
|
|
301
|
+
3. **多账户 `-a id1,id2,...` 时**:表格须覆盖用户请求的**每一个** ID;若某 ID 在 `data.items` 中未出现,仍占一行并标注「未返回/无数据」,并说明与 `meta.totals` 的关系;**禁止**只展示子集却声称已完成多账户汇总。
|
|
302
|
+
4. 跨币种账户:按 `item.currencyCode` 分表或在 meta.currencyNote 提示的前提下分币种小计。
|
|
303
|
+
5. 金额字段严格按"金额与货币单位硬约束"处理。
|
|
281
304
|
|
|
282
305
|
### P4 · Google 账户周期报告(典型指令:"生成 2026.1.1-2026.4.15 的报告")
|
|
283
306
|
|
|
@@ -286,6 +309,7 @@ siluzan-tso accounts-digest -m Google \
|
|
|
286
309
|
3. 使用 `report-templates/google-period-report.md` 模板输出。
|
|
287
310
|
4. 首行必须有:`统计区间:2026-01-01 ~ 2026-04-15` + `货币:XXX`。
|
|
288
311
|
5. 报告必须包含:账户概览、投放趋势、Top 关键词/系列 / 地区分布 / 优化建议;不得编造未拉取到的指标(例如没拉取到的关键词就写"未提供"而不是估算)。
|
|
312
|
+
6. 品牌名遵守上文「品牌名 / 公司名来源硬约束」;描述系列启停时只引用 `ad campaigns`(或系列维报表)中的系列级状态字段,勿与账户 `status` 混用。
|
|
289
313
|
|
|
290
314
|
---
|
|
291
315
|
|
package/dist/skill/_meta.json
CHANGED
|
@@ -16,12 +16,29 @@
|
|
|
16
16
|
5. 输出 HTML 时:**默认**以 `report-templates/report-template.html` 为样式基准(适用于一切总结性、报告性、汇总性成稿);若场景更适合作正式件、深色、单页等,再从 `report-templates/report-template*.html` 中选或询问用户,并对照 `report-templates/README.md`
|
|
17
17
|
6. 注意最终交付的是用html生成的PDF
|
|
18
18
|
7. 使用浏览器或能够打开html/pdf的插件帮用户打开报告
|
|
19
|
-
8. **报告首行**必须标注:`统计区间:YYYY-MM-DD ~ YYYY-MM-DD(货币:XXX)`,与实际调用 `--start` / `--end`
|
|
19
|
+
8. **报告首行**必须标注:`统计区间:YYYY-MM-DD ~ YYYY-MM-DD(货币:XXX)`,与实际调用 `--start` / `--end` 一致。**禁止**用「上周 / 本月 / 近 7 天」等口语替代首行日期;若用户口语指区间,先在首行写换算后的起止日。
|
|
20
20
|
|
|
21
21
|
用于按账户维度拉取 Google Ads 报表、结构类数据。
|
|
22
22
|
|
|
23
23
|
## 报告中的硬约束(必须遵守)
|
|
24
24
|
|
|
25
|
+
### 账户状态 ≠ 广告系列状态(出报告最高优先级)
|
|
26
|
+
|
|
27
|
+
**常见错误**:Agent 看到 `stats`、`balance` 或账户列表里 `status: Enabled`(或「账户正常」),就在报告中把**广告系列**写成「启用」「在投」。这会导致**已暂停、已结束排期或已从投放层面移除**的系列被误标为启用。
|
|
28
|
+
|
|
29
|
+
| 含义 | 数据来源(示例) | `status` / 状态字段表示什么 |
|
|
30
|
+
|------|------------------|-----------------------------|
|
|
31
|
+
| **广告账户**是否在媒体侧关联可用、能否拉数 | `stats`、`balance`、`list-accounts` | 账户级启用/可用,**不描述单个系列是否在投** |
|
|
32
|
+
| **广告系列**是否暂停、是否在排期内可投放 | **`ad campaigns`**(含 `statusDisplay` 等)、系列维报表 | 系列级启停;**唯一**用于写「某系列启用/暂停」 |
|
|
33
|
+
|
|
34
|
+
**写报告时的硬性规则**:
|
|
35
|
+
|
|
36
|
+
1. **禁止**用 `stats` / `balance` / `list-accounts` 返回的账户 `status` 推断或概括**广告系列**是否启用;不得在报告正文写「根据账户状态,各系列均为启用」这类表述(除非已逐条用 `ad campaigns` 核对)。
|
|
37
|
+
2. **凡是**描述「某广告系列是否投放 / 启用 / 暂停 / 移除」,**必须**基于 `siluzan-tso ad campaigns -a <mediaCustomerId> --json`(或 `google-analysis` 中带系列粒度、含系列状态的数据)。账户总览里的消耗/点击可以与系列表并列,但**系列状态列不得来自账户接口**。
|
|
38
|
+
3. 若报告中有「账户概况」与「广告系列明细」两块:前者可用账户级接口;后者**系列状态列只能**来自系列级接口(如 `statusDisplay`),与账户 `status` **不得混为一谈**。
|
|
39
|
+
|
|
40
|
+
**一句话**:账户「能用 / Enabled」≠ 系列「在投」;系列是否启用只看系列级数据(首推 `ad campaigns`)。
|
|
41
|
+
|
|
25
42
|
### 品牌名 / 公司名来源
|
|
26
43
|
|
|
27
44
|
生成带品牌名、方案、邮件、广告文案的报告时,**严禁自行生成品牌名(包括中文译名、拼音、意译)**。品牌名必须来自以下来源之一,按优先级:
|
|
@@ -69,6 +86,8 @@
|
|
|
69
86
|
|
|
70
87
|
使用已配置的 `googleApiUrl` 与 Token,无需手写 curl。
|
|
71
88
|
|
|
89
|
+
> **路由(重要)**:用户要「关键词花费 / 点击 / 转化 / CPA」等**搜索关键词维度**表现时,须用 **`siluzan-tso google-analysis keywords -a <mediaCustomerId> [--start/--end] --json`**。不要用 `ad keywords` 代替(后者偏结构/出价与列表字段,与命题中的「账户内关键词报表」不是同一路径)。
|
|
90
|
+
|
|
72
91
|
```text
|
|
73
92
|
siluzan-tso google-analysis <子命令> -a <mediaCustomerId> [选项]
|
|
74
93
|
```
|
|
@@ -116,6 +135,8 @@ siluzan-tso google-analysis keywords -a 6326027735 --limit 50
|
|
|
116
135
|
siluzan-tso google-analysis final-urls -a 6326027735 --json
|
|
117
136
|
```
|
|
118
137
|
|
|
138
|
+
**CPC / 花费异常巡检(先查后停)**:用户要先定位异常再考虑暂停时,在确认统计区间后,优先用 `google-analysis` 拉**带花费/CPC 粒度**的数据(如 `campaigns`、`daily-metrics`、`keywords` 等,按 CLI `siluzan-tso google-analysis --help` 选择子命令),**不要**在未拉数前就把暂停阈值与写操作一并推进;暂停类命令须遵守 `references/google-ads.md` 的确认流程。
|
|
139
|
+
|
|
119
140
|
---
|
|
120
141
|
|
|
121
142
|
## Meta 账户分析总览(TSO)
|
|
@@ -108,7 +108,7 @@ siluzan-tso balance -m <媒体类型> -a <账户ID列表>
|
|
|
108
108
|
| ---------------------- | ------------------------------------------------------------------------------------ |
|
|
109
109
|
| `-m, --media <type>` | 媒体类型(必填) |
|
|
110
110
|
| `-a, --accounts <ids>` | 账户 `mediaCustomerId`(数字 ID),多个用逗号分隔(必填)。**注意:不是 `entityId`** |
|
|
111
|
-
| `--json` | 输出原始 JSON
|
|
111
|
+
| `--json` | 输出原始 JSON;不支持或查询失败时 stdout 为 `{"ok":false,"error":"..."}` |
|
|
112
112
|
|
|
113
113
|
**示例:**
|
|
114
114
|
|
|
@@ -123,6 +123,8 @@ siluzan-tso balance -m TikTok -a 1234567890,9876543210
|
|
|
123
123
|
siluzan-tso balance -m Google -a 6326027735 --json
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
+
**单户余额与续航**:`balance` 只反映当前余额;判断「还能跑几天 / 是否够花」需结合 `stats`(或业务侧日均消耗)等数据。向用户展示 JSON 时,`mediaCustomerId` 须与本次 `-a` 查询的 ID 及命令输出一致。
|
|
127
|
+
|
|
126
128
|
---
|
|
127
129
|
|
|
128
130
|
## stats — 查询投放消耗数据
|
|
@@ -137,7 +139,8 @@ siluzan-tso stats -m <媒体类型> [选项]
|
|
|
137
139
|
| `-a, --accounts <ids>` | 账户 `mediaCustomerId`(数字 ID),逗号分隔(**必填**,接口不支持查全部账户) | — |
|
|
138
140
|
| `--start <YYYY-MM-DD>` | 开始日期 | 7 天前 |
|
|
139
141
|
| `--end <YYYY-MM-DD>` | 结束日期 | 昨天 |
|
|
140
|
-
| `--
|
|
142
|
+
| `--start-date` / `--end-date` | 与 `--start` / `--end` 同义(CLI 别名,与 SKILL Playbook 一致) | — |
|
|
143
|
+
| `--json` | 输出原始 JSON;**失败时 stdout 仍为 JSON**(`{"ok":false,"error":"..."}`) | — |
|
|
141
144
|
|
|
142
145
|
**示例:**
|
|
143
146
|
|
|
@@ -19,6 +19,11 @@ siluzan-tso clue -m <媒体> -a <账户ID> [选项]
|
|
|
19
19
|
| `--end <date>` | Meta 专用:结束日期(YYYY-MM-DD) |
|
|
20
20
|
| `--json` | 输出原始 JSON |
|
|
21
21
|
|
|
22
|
+
**AI 交付**:用户要求「原始 JSON / 自己筛」时,回复中须包含 **`--json` 命令打印的完整 JSON**(或等价完整代码块),并可按上表说明 `custom_fields` / `system_fields`(TikTok)或 `field_data`(Meta)。**禁止**用未出现在本次 CLI 输出中的账户 ID、媒体或「环境异常」类推测替代 JSON 交付。
|
|
23
|
+
若本次查询失败:CLI 在 `--json` 下会输出 **`{"ok":false,"error":"...","items":[]}`**(stdout),请**原样**贴出该 JSON,不要改成纯文字描述。
|
|
24
|
+
|
|
25
|
+
**时间范围(TikTok)**:用户说「最近一周」且要拉线索、未给起止日时,**不要**再按投放报表类任务做 A/B/C 反问;直接按 CLI 默认窗口执行 `clue -m TikTok -a <advertiserId> --json`(需自定义区间时再用 Meta 同款 `--start/--end` 仅适用于 Meta,TikTok 以接口返回为准)。
|
|
26
|
+
|
|
22
27
|
**TikTok 示例:**
|
|
23
28
|
|
|
24
29
|
```bash
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
## invoice-info — 发票抬头管理
|
|
9
9
|
|
|
10
|
-
对应页面:`https://www.siluzan.com/v3/foreign_trade/settings/invoiceInformation`
|
|
10
|
+
对应页面:`https://www-ci.siluzan.com/v3/foreign_trade/settings/invoiceInformation`
|
|
11
11
|
|
|
12
12
|
发票抬头是开票申请时使用的公司/企业信息模板,支持三种类型:
|
|
13
13
|
|
|
@@ -24,6 +24,8 @@ siluzan-tso invoice-info list -k "公司名关键字" # 按公司名搜
|
|
|
24
24
|
siluzan-tso invoice-info list --json # 原始 JSON(含 id 字段)
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
查询结果若 `data` 为空数组或无可核对字段,表示当前账号下**尚无已保存抬头**:向用户展示**原始 JSON** 并如实说明即可,勿推测「测试桩」等未经 CLI 报错佐证的原因。
|
|
28
|
+
|
|
27
29
|
### 新增发票抬头
|
|
28
30
|
|
|
29
31
|
**PI(形式发票,境外英文):**
|
|
@@ -131,10 +133,10 @@ siluzan-tso config show
|
|
|
131
133
|
**示例:**
|
|
132
134
|
|
|
133
135
|
```
|
|
134
|
-
- 现金充值(单笔):https://www.siluzan.com/recharge/pay
|
|
135
|
-
- 现金充值(批量):https://www.siluzan.com/recharge/pay_batch
|
|
136
|
-
- 月结充值: https://www.siluzan.com/recharge/accountBillingQuota
|
|
137
|
-
- 丝路赞钱包: https://www.siluzan.com/recharge/siluzanWallet
|
|
136
|
+
- 现金充值(单笔):https://www-ci.siluzan.com/recharge/pay
|
|
137
|
+
- 现金充值(批量):https://www-ci.siluzan.com/recharge/pay_batch
|
|
138
|
+
- 月结充值: https://www-ci.siluzan.com/recharge/accountBillingQuota
|
|
139
|
+
- 丝路赞钱包: https://www-ci.siluzan.com/recharge/siluzanWallet
|
|
138
140
|
```
|
|
139
141
|
|
|
140
142
|
---
|
|
@@ -208,6 +210,8 @@ siluzan-tso transfer create -m Google --out 1234567890 --in 9876543210 --amount
|
|
|
208
210
|
siluzan-tso invoice list [选项]
|
|
209
211
|
```
|
|
210
212
|
|
|
213
|
+
> **与投放报表类任务不同**:用户只说「本月开票记录」而未写起止日时,可用**当月 1 日 ~ 昨天**(`Asia/Shanghai` 日历)直接执行 `invoice list --start … --end … --json`,**不必**先做 SKILL「投放数据」类的时间范围 A/B/C 反问;若用户随后纠正区间再重查即可。
|
|
214
|
+
|
|
211
215
|
| 选项 | 说明 |
|
|
212
216
|
| ------------------------ | ---------------------- |
|
|
213
217
|
| `-k, --keyword <text>` | 发票号/关键字 |
|
|
@@ -135,6 +135,8 @@ siluzan-tso forewarning list -m Google
|
|
|
135
135
|
# JSON 格式(含完整配置)
|
|
136
136
|
siluzan-tso forewarning list -m Google --json
|
|
137
137
|
|
|
138
|
+
若用户要求「仅 JSON / 只输出一个 JSON 对象」,回复中只贴本次命令 stdout(含 `total` > `itemCount` 时的分页说明亦在 JSON 内,勿在 JSON 外再写中文解释)。
|
|
139
|
+
|
|
138
140
|
# 按账户筛选
|
|
139
141
|
siluzan-tso forewarning list -m Google --account <mediaCustomerId>
|
|
140
142
|
|
|
@@ -146,6 +148,8 @@ siluzan-tso forewarning list -m Google --keyword "消耗"
|
|
|
146
148
|
|
|
147
149
|
## 查询触发记录
|
|
148
150
|
|
|
151
|
+
> **不属于「投放数据时间范围强制反问」**:用户要查「最近有没有触发记录」并要 JSON 时,**直接**执行 `forewarning records -m Google --json`(可加 `--rule-id` / `--status`);不要先套用 SKILL 里针对消耗/报表的日期反问,也不要强行索要 Google `mediaCustomerId`(本命令不按媒体账户筛)。
|
|
152
|
+
|
|
149
153
|
```bash
|
|
150
154
|
# 查询所有规则的触发记录
|
|
151
155
|
siluzan-tso forewarning records -m Google
|
|
@@ -158,6 +162,8 @@ siluzan-tso forewarning records -m Google --status Success
|
|
|
158
162
|
siluzan-tso forewarning records -m Google --status Failed
|
|
159
163
|
```
|
|
160
164
|
|
|
165
|
+
**创建规则(AI)**:用户已给出阈值、频率、媒体并确认要创建时:① `list-accounts -m Google -k <mediaCustomerId> --json` 取账户 **`entityId`**(`forewarning create --accounts` 用);② `forewarning notify-accounts` 取 **`--notify`** 的微信对象 `entityId`;③ 再执行 `forewarning create ...`。创建属写操作,须用户明确确认后再跑命令。
|
|
166
|
+
|
|
161
167
|
---
|
|
162
168
|
|
|
163
169
|
## 更新已有规则
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
> - 若当前会话中尚未阅读上述任一文件,AI 必须先主动阅读,再继续下一步流程,而不是直接生成广告计划或文案。
|
|
33
33
|
> - 在首次阅读后,AI 需用自己的话向用户**简要复述**上述文档中与本次任务强相关的 3~5 条关键合规/策略要点,并询问用户是否有本地特殊限制需要补充。
|
|
34
34
|
> - 后续生成的关键词、文案、出价与结构,必须**显式遵守这些规则**;一旦与规则冲突,应以规则优先,并向用户说明原因(例如:某些词因合规或商标问题被自动剔除)。
|
|
35
|
+
> - **方案阶段措辞**:输出结构/投放方案时,用「方案草案 / 结构蓝图 / 待你确认后再执行」等表述;**避免**「可立即执行 / 拿到参数就立刻跑命令」等暗示可跳过用户确认的说法。回复开头宜有一行**已读规则文档清单**(本 `google-ads.md` + 上表 `references/google-ads-rules/` 中实际打开的文件名)。
|
|
35
36
|
|
|
36
37
|
### 第二步:向用户补齐关键信息
|
|
37
38
|
|
|
@@ -124,7 +125,7 @@ siluzan-tso ad campaigns -a 6326027735 --start 2026-03-01 --end 2026-03-31
|
|
|
124
125
|
siluzan-tso ad campaigns -a 6326027735 --json
|
|
125
126
|
```
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
输出字段:名称、状态、类型、预算、点击数、展示数(具体字段名以 `--json` 为准;另有 CLI 派生的 `statusDisplay`、`budgetDisplay` 等便于阅读)。
|
|
128
129
|
|
|
129
130
|
---
|
|
130
131
|
|
|
@@ -27,6 +27,8 @@ siluzan-tso report list -m <媒体> [选项]
|
|
|
27
27
|
| `--start / --end <date>` | 日期范围(YYYY-MM-DD) |
|
|
28
28
|
| `--json` | 输出原始 JSON |
|
|
29
29
|
|
|
30
|
+
**仅 JSON 交付**:若用户明确要求「只输出一个 JSON / 不要解释」,回复中**只放一个** JSON 代码块(与本次 CLI stdout 一致),前后不加说明文字;分页结果以 JSON 内 `page` / `pageSize` / `total` / `itemCount` 为准,需全量时再翻页执行。
|
|
31
|
+
|
|
30
32
|
**示例:**
|
|
31
33
|
|
|
32
34
|
```bash
|
|
@@ -212,8 +214,8 @@ siluzan-tso report list -m Google --json
|
|
|
212
214
|
|
|
213
215
|
# 第二步:查看 webUrl
|
|
214
216
|
siluzan-tso config show
|
|
215
|
-
# webUrl: https://www.siluzan.com
|
|
217
|
+
# webUrl: https://www-ci.siluzan.com
|
|
216
218
|
|
|
217
219
|
# 第三步:拼接链接(Google 日报)
|
|
218
|
-
# https://www.siluzan.com/media-report/publish/rpt_abc123?culture=zh-CN
|
|
220
|
+
# https://www-ci.siluzan.com/media-report/publish/rpt_abc123?culture=zh-CN
|
|
219
221
|
```
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
## 安装 CLI
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
npm install -g siluzan-tso-cli
|
|
13
|
+
npm install -g siluzan-tso-cli@beta
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
---
|
|
@@ -47,7 +47,7 @@ siluzan-tso config set --api-key <Key> # 或通过 config set 直接写入
|
|
|
47
47
|
siluzan-tso config set --token <Token> # 备用:设置 JWT Token
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
API Key 获取入口:`https://www.siluzan.com/v3/foreign_trade/settings/apiKeyManagement`
|
|
50
|
+
API Key 获取入口:`https://www-ci.siluzan.com/v3/foreign_trade/settings/apiKeyManagement`
|
|
51
51
|
|
|
52
52
|
### 通过环境变量传入凭据(CI/CD 推荐)
|
|
53
53
|
|
|
@@ -82,9 +82,9 @@ siluzan-tso config show
|
|
|
82
82
|
|
|
83
83
|
```
|
|
84
84
|
构建环境 : production
|
|
85
|
-
apiBaseUrl : https://tso-api.siluzan.com
|
|
86
|
-
googleApiUrl : https://googleapi.mysiluzan.com
|
|
87
|
-
webUrl : https://www.siluzan.com
|
|
85
|
+
apiBaseUrl : https://tso-api-ci.siluzan.com
|
|
86
|
+
googleApiUrl : https://googleapi-ci.mysiluzan.com
|
|
87
|
+
webUrl : https://www-ci.siluzan.com
|
|
88
88
|
apiKey : abcd****1234
|
|
89
89
|
```
|
|
90
90
|
|
|
@@ -10,8 +10,8 @@ $ErrorActionPreference = 'Stop'
|
|
|
10
10
|
$PKG_NAME = 'siluzan-tso-cli'
|
|
11
11
|
$CLI_BIN = 'siluzan-tso'
|
|
12
12
|
$SKILL_LABEL = 'Siluzan TSO'
|
|
13
|
-
$INSTALL_CMD = 'npm install -g siluzan-tso-cli'
|
|
14
|
-
$WEB_BASE = 'https://www.siluzan.com'
|
|
13
|
+
$INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
|
|
14
|
+
$WEB_BASE = 'https://www-ci.siluzan.com'
|
|
15
15
|
|
|
16
16
|
# -- Constants ----------------------------------------------------------------
|
|
17
17
|
$NODE_MAJOR_MIN = 18
|
|
@@ -10,8 +10,8 @@ set -euo pipefail
|
|
|
10
10
|
readonly PKG_NAME="siluzan-tso-cli"
|
|
11
11
|
readonly CLI_BIN="siluzan-tso"
|
|
12
12
|
readonly SKILL_LABEL="Siluzan TSO"
|
|
13
|
-
readonly INSTALL_CMD="npm install -g siluzan-tso-cli"
|
|
14
|
-
readonly WEB_BASE="https://www.siluzan.com"
|
|
13
|
+
readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
|
|
14
|
+
readonly WEB_BASE="https://www-ci.siluzan.com"
|
|
15
15
|
|
|
16
16
|
# -- Constants ----------------------------------------------------------------
|
|
17
17
|
readonly NODE_MAJOR_MIN=18
|