siluzan-tso-cli 1.1.53-beta.3 → 1.1.53-beta.5

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 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.53-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.53-beta.5),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -2467,7 +2467,13 @@ function rawRequest(url, options) {
2467
2467
  }
2468
2468
  reject(err);
2469
2469
  });
2470
- if (options.body) req.write(options.body);
2470
+ if (options.body !== void 0) {
2471
+ const payload = typeof options.body === "string" ? Buffer.from(options.body) : options.body;
2472
+ if (!headers["Content-Length"] && !headers["content-length"]) {
2473
+ req.setHeader("Content-Length", payload.length);
2474
+ }
2475
+ req.write(payload);
2476
+ }
2471
2477
  req.end();
2472
2478
  });
2473
2479
  }
@@ -2562,6 +2568,20 @@ function formatHttpError(status, rawBody, opts) {
2562
2568
  function buildAuthHeaders(config) {
2563
2569
  return buildSiluzanAuthHeaders(config);
2564
2570
  }
2571
+ async function parseApiResponse(status, text2, verbose) {
2572
+ if (status < 200 || status >= 300) {
2573
+ throw new Error(formatHttpError(status, text2, { verbose }));
2574
+ }
2575
+ if (!text2.trim()) return null;
2576
+ try {
2577
+ return parseJsonWithStringIds(text2);
2578
+ } catch {
2579
+ if (verbose) {
2580
+ console.error(` \u26A0\uFE0F \u54CD\u5E94\u975E JSON\uFF0C\u539F\u59CB\u5185\u5BB9\uFF1A${redactSensitive(text2).slice(0, 200)}`);
2581
+ }
2582
+ return text2;
2583
+ }
2584
+ }
2565
2585
  async function apiFetch(url, config, options = {}, verbose = false) {
2566
2586
  const method = options.method ?? "GET";
2567
2587
  const reqHeaders = {
@@ -2580,19 +2600,7 @@ async function apiFetch(url, config, options = {}, verbose = false) {
2580
2600
  headers: reqHeaders,
2581
2601
  body
2582
2602
  });
2583
- const text2 = res.text;
2584
- if (res.status < 200 || res.status >= 300) {
2585
- throw new Error(formatHttpError(res.status, text2, { verbose }));
2586
- }
2587
- if (!text2.trim()) return null;
2588
- try {
2589
- return parseJsonWithStringIds(text2);
2590
- } catch {
2591
- if (verbose) {
2592
- console.error(` \u26A0\uFE0F \u54CD\u5E94\u975E JSON\uFF0C\u539F\u59CB\u5185\u5BB9\uFF1A${redactSensitive(text2).slice(0, 200)}`);
2593
- }
2594
- return text2;
2595
- }
2603
+ return parseApiResponse(res.status, res.text, verbose);
2596
2604
  }
2597
2605
  async function apiFetchWithHeaders(url, config, options = {}, verbose = false) {
2598
2606
  const method = options.method ?? "GET";
@@ -134432,16 +134440,23 @@ function parseInquiryRecords(raw) {
134432
134440
  for (const item of list) {
134433
134441
  if (!item || typeof item !== "object") continue;
134434
134442
  const row = item;
134435
- const countryZh = pick(row, ["countryZh", "country", "\u56FD\u5BB6", "\u56FD\u5BB6(\u4E2D\u6587)"]);
134436
- const date = pick(row, ["date", "\u65F6\u95F4", "time"]);
134443
+ const countryZh = pick(row, [
134444
+ "\u76EE\u6807\u56FD\u5BB6/\u5730\u533A",
134445
+ "countryZh",
134446
+ "country",
134447
+ "\u56FD\u5BB6",
134448
+ "\u56FD\u5BB6(\u4E2D\u6587)"
134449
+ ]);
134450
+ const date = pick(row, ["\u8BE2\u76D8\u65E5\u671F", "date", "\u65F6\u95F4", "time"]);
134437
134451
  const month = normalizeMonth(pick(row, ["month", "\u6708\u4EFD", "ym"]), date || void 0);
134438
134452
  if (!countryZh && !month) continue;
134439
134453
  out.push({
134440
134454
  month,
134441
134455
  countryZh,
134442
134456
  date: date || void 0,
134443
- product: pick(row, ["product", "\u4EA7\u54C1"]) || void 0,
134444
- source: pick(row, ["source", "\u5BA2\u6237\u6765\u6E90", "\u6765\u6E90"]) || void 0
134457
+ product: pick(row, ["\u4EA7\u54C1/\u54C1\u7C7B", "product", "\u4EA7\u54C1"]) || void 0,
134458
+ source: pick(row, ["\u8BE2\u76D8\u6765\u6E90\u5173\u952E\u8BCD", "source", "\u5BA2\u6237\u6765\u6E90", "\u6765\u6E90"]) || void 0,
134459
+ quality: pick(row, ["\u8BE2\u76D8\u8D28\u91CF\u5206\u7EA7", "qualityGrade", "quality"]) || void 0
134445
134460
  });
134446
134461
  }
134447
134462
  return out;
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.53-beta.3",
4
- "publishedAt": 1789698851730
3
+ "version": "1.1.53-beta.5",
4
+ "publishedAt": 1789876126053
5
5
  }
@@ -323,9 +323,9 @@
323
323
  | P1 / P4 / P10 | 账户 ID | 「请提供 Google 广告账户 ID(mediaCustomerId)。」 |
324
324
  | P1 / P4 | 时间 | 按 `agent-conventions.md` §五 反问区间 |
325
325
  | P10 | 时间 | 季度话术可省略(上一完整季);月度报告 / 下月规划用上一完整月或用户指定月 |
326
- | P10 | 询盘附件 | **未带表先问「有没有本期询盘表」并等待**;有则转 `inquiries.json` `--inquiries`;用户说没有再 collect。**禁止**不问就出报告;**禁止**把 xlsx/csv 原文件传给 collect |
326
+ | P10 | 询盘附件 | **未带表先问「有没有本期询盘表」并等待**;有则先看一眼表头再转 `inquiries.json` `--inquiries`;用户说没有再 collect。**禁止**不问就出报告;**禁止**把 xlsx/csv 原文件传给 collect |
327
327
  | P6 | 非 OKKI 话术 | 若用户实际要普通周期报告 → 改 P4 并说明 |
328
- | P7 | 无询盘文件 | 贴字段清单,**禁止编造询盘**;若用户只要账户级零转化 → 改 **P3** |
328
+ | P7 | 无询盘文件 | 贴字段清单,**禁止编造询盘**;用表前先看一眼表头;若用户只要账户级零转化 → 改 **P3** |
329
329
  | P3(§零·D) | 时间区间 | 按 `agent-conventions.md` §五 反问;巡检默认近 7 天须写明区间 |
330
330
  | W3 | 预算/地域 | 先列待确认项,再进入 campaign-plan 流水线 |
331
331
 
@@ -154,7 +154,7 @@
154
154
  - **时间窗口强约束**:**严格 3 个月** = 分析月份 + 向前 2 个完整自然月,**禁止**扩展到 7 个月。
155
155
  - **步骤**:
156
156
  1. (可选)Read `subagent-orchestration.md` § P7。
157
- 2. 询盘资料入场:用户附文件 → 解析落盘 `./snap-inquiry/inquiries.json`;仅给账户 ID → 反问账户 + 分析月份并贴字段清单,**禁止编造询盘数据**。
157
+ 2. 询盘资料入场:用户附文件 → 先看一眼表头再落盘 `./snap-inquiry/inquiries.json`(列名接近即可);仅给账户 ID → 反问账户 + 分析月份并贴字段清单,**禁止编造询盘数据**。
158
158
  3. CLI 拉数(同一目录):`list-accounts`、`google-analysis --sections campaigns,keywords,search-terms,campaign-geo`,并按月拉 `m1/m2/m3` 的 `campaigns,geographic`。
159
159
  4. 脚本聚合计算 8 Sheet 数据;国家→大洲映射读 `geo-continents.json`,**禁止**硬编码国家名。
160
160
  - **产物**:必产 8 Sheet xlsx(版式见模板),**只能**由 Agent 脚本生成,**禁止**假设 `… excel` 子命令存在。
@@ -44,9 +44,9 @@
44
44
  2. 同时贴出**询盘字段清单 + 1 行示例**,让用户用任意方式补齐再回传:
45
45
 
46
46
  ```
47
- 请把近 3 个月({S} ~ {E})的询盘记录回贴或上传(Excel / CSV / 截图 / 文本表格均可),字段如下:
48
- 月份 | 序号(可空) | 国家(中文) | 时间(YYYY-MM-DD Excel 序列) | 产品 | 客户来源 | IP(可空)
49
- 示例:4月份 | 1 | 印度 | 2026-04-12 | 滴灌带 | WhatsApp(www.extruder.cn)
47
+ 请把近 3 个月({S} ~ {E})的询盘记录回贴或上传(Excel / CSV / 截图 / 文本表格均可),字段如下(* 为必填):
48
+ 序号 | 询盘日期* | 询盘来源关键词 | 目标国家/地区* | 产品/品类* | 客户名称/公司 | 联系方式 | 询盘内容摘要 | 询盘质量分级* | 跟进状态 | 是否成交 | 成交金额(USD)
49
+ 示例:1 | 2026-04-12 | WhatsApp | 印度 | 滴灌带 | | | | 合格 | | |
50
50
  ```
51
51
 
52
52
  3. 拿到资料后由宿主 Agent 解析、落盘 `inquiries.json`,再进入「拉数与写 Excel」节。
@@ -58,8 +58,8 @@
58
58
  1. **账户**:`mediaCustomerId` 来自 `list-accounts -m Google -k <id>`,**禁止猜 ID**。
59
59
  2. **分析月份**:默认**上一个完整自然月**(例:今天 5/13 → 默认 4 月)。用户未明确时**反问**(A) 上月 B) 本月 1 号到昨天 C) 自定义 YYYY-MM)。
60
60
  3. **时间窗口(硬约束)**:**严格 3 个月** = 分析月份 + 向前 2 个完整自然月。例:分析月 = 2026-04 → `--start 2026-02-01 --end 2026-04-30`。**所有月度表只展示 3 行**(不延伸到 7 个月)。
61
- 4. **询盘字段最小集**:`月份 / 国家(中文) / 时间 / 产品 / 客户来源`;可选 `序号 / IP`。
62
- 5. **日期规范化**:`2026.4.1` / `2026/4/1` 等先规范成 `YYYY-MM-DD` 再传 CLI
61
+ 4. **用询盘数据前先看一眼表头**。常见列见上;* 表示通常要有对应含义的列(`询盘日期`≈时间/date,`目标国家/地区`≈国家,`产品/品类`≈产品,`询盘质量分级`有则用)。列名接近即可,不必逐格校验。
62
+ 5. **日期规范化**:`2026.4.1` / `2026/4/1` 等先规范成 `YYYY-MM-DD` 再传 CLI。`月份` 可由询盘日期推导。
63
63
 
64
64
  ---
65
65
 
@@ -155,23 +155,30 @@ Sheet 5 整段系列明细读:`./snap-inquiry/campaigns-<accountId>_<S>-<E>.js
155
155
  ```json
156
156
  [
157
157
  {
158
- "month": "4月份",
159
158
  "seq": 1,
159
+ "date": "2026-04-12",
160
+ "sourceKeyword": "WhatsApp",
160
161
  "country": "印度",
161
- "time": "2026-04-12",
162
162
  "product": "滴灌带",
163
- "source": "WhatsApp(www.extruder.cn)",
164
- "ip": null
163
+ "company": null,
164
+ "contact": null,
165
+ "summary": null,
166
+ "quality": "合格",
167
+ "followUp": null,
168
+ "won": null,
169
+ "amountUsd": null
165
170
  }
166
171
  ]
167
172
  ```
168
173
 
169
- - `month`:保留运营原文(`1月份` / `4月份` …)。
170
- - `country`:**中文名**,与 `countryOrRegionZh` 字典对齐(脚本会自动 lookup 大洲)。Agent 解析时遇到英文/缩写应先翻译为中文,未知国家保留原文并在脚本里标 `未知大洲`。
171
- - `time`:`YYYY-MM-DD`;Excel 日期序列号也接受,由解析方转换。
172
- - `product`:自由文本(`滴灌带` / `打包带` / `片材` / `滴头` / `模具` / `贴条` / `涂布水带` …),脚本以此作 Sheet 3 产品维度透视分桶 key。
173
- - `source`:自由文本(`WhatsApp` / `tawk.to` / `info邮箱` / `网站留言` / `微信(xxxx)` / `阿里巴巴` / `made-in-china` / `Youtube` / `TikTok` …),脚本以此作 Sheet 3 客户来源透视分桶 key。
174
- - `ip`:可空。
174
+ 落盘前先看一眼表头,按含义对上即可(`时间`=`询盘日期`,`国家(中文)`=`目标国家/地区`,`产品`=`产品/品类`)。
175
+
176
+ - `date`:询盘日期,`YYYY-MM-DD`。
177
+ - `country`:目标国家/地区,中文名,与 `countryOrRegionZh` 字典对齐。
178
+ - `product`:产品/品类,Sheet 3 产品分桶。
179
+ - `sourceKeyword` / `source`:来源,Sheet 3 来源分桶(可空)。
180
+ - `quality` / 其余可选列:有则写入,没有就空着。
181
+ - `month`:可由 `date` 推导。**禁止编造**缺失值。
175
182
 
176
183
  ---
177
184
 
@@ -33,7 +33,7 @@
33
33
  | 5 | `keywords` | 关键词与搜索主题 | Search 分组 + 匹配 + 否词方向;有 PMax 则 Search Themes |
34
34
  | 6 | `audience` | 受众规划 | Audience Signal / 名单 / 再营销;没有的标「需创建或导入」 |
35
35
  | 7 | `creative` | 素材与落地页 | 主题、资产缺口、测试假设(每轮 1~2 变量)、广告↔页面映射 |
36
- | 8 | `roadmap` | 季度执行路线图 | 1 / 2 / 3 月:任务、交付物、完成条件、依赖 |
36
+ | 8 | `roadmap` | 季度执行路线图 | 第一个月 / 第二个月 / 第三个月:任务、交付物、完成条件、依赖 |
37
37
  | 9 | `risks` | 风险与待确认 | 预算、数据、素材、页面、销售承接、合规;假设清单 |
38
38
 
39
39
  每条结论前加口径标签之一:`已确认` / `基于历史数据` / `建议验证` / `待业务确认`。
@@ -48,7 +48,7 @@
48
48
  "keywords": { "analysis": ["产品词精确/词组…", "否词方向:求职/教程/免费"] },
49
49
  "audience": { "analysis": ["待业务确认:客户名单未导入"] },
50
50
  "creative": { "analysis": ["主题:工业场景…", "缺口:PMax 视频"] },
51
- "roadmap": { "analysis": ["第1月:目标与基础…", "第2月:核心建设…", "第3月:已验证方向扩"] },
51
+ "roadmap": { "analysis": ["第一个月:目标与基础…", "第二个月:核心建设…", "第三个月:已验证方向扩"] },
52
52
  "risks": { "analysis": ["销售回传缺失 → 主目标暂用表单并标注"] }
53
53
  }
54
54
  }
@@ -104,7 +104,7 @@ CPA、询盘目标**不**从历史外推。要写数字须用户给,或标「
104
104
  | Q23 | 广告↔页 | 每个核心主题至少一个明确页面方向。禁止广告讲 A、页面展 B |
105
105
  | Q24 | 转化路径 | CTA、表单字段、感谢页、电话/WhatsApp/邮件、CRM 回传、销售跟进 |
106
106
  | Q25 | 销售承接 | 接收人、响应时效、分配、无效反馈、结果回传。无承接则降低扩量优先级 |
107
- | Q26 | 三月节奏 | 见下表。月度报告把「三月」收成「本月 / 次月 / 再次月」 |
107
+ | Q26 | 三月节奏 | 见下表。写「第一个月 / 第二个月 / 第三个月」,禁止写「第1月」(会被读成 1 月 / 2 月 / 3 月)。能从本期结束日推下期日历月时写成「第一个月(10月)」。月度报告改成「本月 / 次月 / 再次月」 |
108
108
  | Q27 | 依赖 | 每项关键规划标依赖、负责人或待确认 |
109
109
  | Q28 | 风险 | 至少覆盖:预算不足、转化量不足、数据质量、落地页未完成、素材不足、销售承接、市场合规 |
110
110
  | Q29 | 假设 | 假设不得写成事实;用四类口径标签 |
@@ -114,9 +114,9 @@ CPA、询盘目标**不**从历史外推。要写数字须用户给,或标「
114
114
 
115
115
  | 阶段 | 核心任务 | 交付 | 完成条件 |
116
116
  | ---- | -------- | ---- | -------- |
117
- | 1 月 · 基础与验证 | 目标、预算、市场优先级;补转化追踪、落地页、素材、受众 | 目标表、预算表、Campaign 结构、词/Themes、素材与页面清单 | 核心投放基础具备,关键依赖写清 |
118
- | 2 月 · 重点建设 | 核心市场与核心类型;有限变量测试 | 核心 Campaign、素材测试批次、受众组合、页面优化版 | 核心方向验证完,形成可续投项目 |
119
- | 3 月 · 扩展与沉淀 | 只围已验证方向扩市场/预算/词/素材/受众 | 扩展计划、下季建议、有效资产、未完成项 | 可扩展方向和下季待解决问题都明确 |
117
+ | 第一个月 · 基础与验证 | 目标、预算、市场优先级;补转化追踪、落地页、素材、受众 | 目标表、预算表、Campaign 结构、词/Themes、素材与页面清单 | 核心投放基础具备,关键依赖写清 |
118
+ | 第二个月 · 重点建设 | 核心市场与核心类型;有限变量测试 | 核心 Campaign、素材测试批次、受众组合、页面优化版 | 核心方向验证完,形成可续投项目 |
119
+ | 第三个月 · 扩展与沉淀 | 只围已验证方向扩市场/预算/词/素材/受众 | 扩展计划、下季建议、有效资产、未完成项 | 可扩展方向和下季待解决问题都明确 |
120
120
 
121
121
  ## 硬约束
122
122
 
@@ -24,21 +24,22 @@ CLI **不读** xlsx / csv / 截图。
24
24
 
25
25
  **禁止**把原始 `.xlsx` / `.csv` 传给 `--inquiries`(会 `JSON.parse` 失败)。用户说没有:可 `--inquiry-conversion <转化动作名>`(须用户点名);再没有则询盘位留空并脚注。**禁止编造询盘行。**
26
26
 
27
- 落盘 JSON:根为数组或 `{ "items": [...] }`。每行至少有国家 +(月份或日期):
27
+ 落盘 JSON:根为数组或 `{ "items": [...] }`。**用之前先看一眼表头**,有日期/国家/产品这类列即可(列名接近即可,如 `时间`=`询盘日期`)。**禁止编造**。
28
28
 
29
- | 用途 | 接受的键 | 归一 |
29
+ | 用途 | 常见键 | 归一 |
30
30
  | ---- | -------- | ---- |
31
- | 国家 | `国家` / `国家(中文)` / `country` / `countryZh` | 英文/缩写先译中文,与 geographic `countryNameZh` 对齐 |
32
- | 月份 | `月份` / `month` / `ym` | `2026-04` `4月份`(有日期则用日期年) |
33
- | 日期 | `时间` / `date` / `time` | `YYYY-MM-DD`;Excel 序列号由 Agent 转好 |
34
- | 产品 | `产品` / `product` | 可选 |
35
- | 来源 | `客户来源` / `来源` / `source` | 可选 |
31
+ | 询盘日期 | `询盘日期` / `date` / `时间` / `time` | `YYYY-MM-DD`;Excel 序列号由 Agent 转好 |
32
+ | 目标国家/地区 | `目标国家/地区` / `国家(中文)` / `国家` / `country` / `countryZh` | 英文/缩写先译中文,与 geographic `countryNameZh` 对齐 |
33
+ | 产品/品类 | `产品/品类` / `产品` / `product` | 自由文本 |
34
+ | 询盘来源关键词 | `询盘来源关键词` / `客户来源` / `来源` / `source` | 可选 |
35
+ | 询盘质量分级 | `询盘质量分级` / `quality` / `qualityGrade` | 有则用 |
36
+ | 月份 | `月份` / `month` / `ym` | 可由询盘日期推导 |
36
37
 
37
- 样例(字段名任选上表别名):
38
+ 样例:
38
39
 
39
40
  ```json
40
41
  [
41
- { "月份": "4月份", "国家(中文)": "印度", "时间": "2026-04-12", "产品": "协作机器人", "客户来源": "WhatsApp" }
42
+ { "询盘日期": "2026-04-12", "目标国家/地区": "印度", "产品/品类": "协作机器人", "询盘来源关键词": "WhatsApp" }
42
43
  ]
43
44
  ```
44
45
 
@@ -111,7 +112,7 @@ Agent JSON Schema:`assets/google-quarterly-report.schema.json`。
111
112
  "keywords": { "analysis": ["产品词精确/词组;否词:求职/教程"] },
112
113
  "audience": { "analysis": ["待业务确认:客户名单未导入"] },
113
114
  "creative": { "analysis": ["主题:工业场景;缺口:PMax 视频"] },
114
- "roadmap": { "analysis": ["第1月基础验证;第2月核心建设;第3月已验证方向扩"] },
115
+ "roadmap": { "analysis": ["第一个月基础验证;第二个月核心建设;第三个月已验证方向扩"] },
115
116
  "risks": { "analysis": ["销售回传缺失"] }
116
117
  }
117
118
  }
@@ -44,9 +44,9 @@
44
44
  2. 同时贴出**询盘字段清单 + 1 行示例**,让用户用任意方式补齐再回传:
45
45
 
46
46
  ```
47
- 请把近 3 个月({S} ~ {E})的询盘记录回贴或上传(Excel / CSV / 截图 / 文本表格均可),字段如下:
48
- 月份 | 序号(可空) | 国家(中文) | 时间(YYYY-MM-DD Excel 序列) | 产品 | 客户来源 | IP(可空)
49
- 示例:4月份 | 1 | 印度 | 2026-04-12 | 滴灌带 | WhatsApp(www.extruder.cn)
47
+ 请把近 3 个月({S} ~ {E})的询盘记录回贴或上传(Excel / CSV / 截图 / 文本表格均可),字段如下(* 为必填):
48
+ 序号 | 询盘日期* | 询盘来源关键词 | 目标国家/地区* | 产品/品类* | 客户名称/公司 | 联系方式 | 询盘内容摘要 | 询盘质量分级* | 跟进状态 | 是否成交 | 成交金额(USD)
49
+ 示例:1 | 2026-04-12 | WhatsApp | 印度 | 滴灌带 | | | | 合格 | | |
50
50
  ```
51
51
 
52
52
  3. 拿到资料后由宿主 Agent 解析、落盘 `inquiries.json`,再进入「拉数与写 Excel」节。
@@ -58,8 +58,8 @@
58
58
  1. **账户**:`mediaCustomerId` 来自 `list-accounts -m Google -k <id>`,**禁止猜 ID**。
59
59
  2. **分析月份**:默认**上一个完整自然月**(例:今天 5/13 → 默认 4 月)。用户未明确时**反问**(A) 上月 B) 本月 1 号到昨天 C) 自定义 YYYY-MM)。
60
60
  3. **时间窗口(硬约束)**:**严格 3 个月** = 分析月份 + 向前 2 个完整自然月。例:分析月 = 2026-04 → `--start 2026-02-01 --end 2026-04-30`。**所有月度表只展示 3 行**(不延伸到 7 个月)。
61
- 4. **询盘字段最小集**:`月份 / 国家(中文) / 时间 / 产品 / 客户来源`;可选 `序号 / IP`。
62
- 5. **日期规范化**:`2026.4.1` / `2026/4/1` 等先规范成 `YYYY-MM-DD` 再传 CLI
61
+ 4. **用询盘数据前先看一眼表头**。常见列见上;* 表示通常要有对应含义的列(`询盘日期`≈时间/date,`目标国家/地区`≈国家,`产品/品类`≈产品,`询盘质量分级`有则用)。列名接近即可,不必逐格校验。
62
+ 5. **日期规范化**:`2026.4.1` / `2026/4/1` 等先规范成 `YYYY-MM-DD` 再传 CLI。`月份` 可由询盘日期推导。
63
63
 
64
64
  ---
65
65
 
@@ -155,23 +155,30 @@ Sheet 5 整段系列明细读:`./snap-inquiry/campaigns-<accountId>_<S>-<E>.js
155
155
  ```json
156
156
  [
157
157
  {
158
- "month": "4月份",
159
158
  "seq": 1,
159
+ "date": "2026-04-12",
160
+ "sourceKeyword": "WhatsApp",
160
161
  "country": "印度",
161
- "time": "2026-04-12",
162
162
  "product": "滴灌带",
163
- "source": "WhatsApp(www.extruder.cn)",
164
- "ip": null
163
+ "company": null,
164
+ "contact": null,
165
+ "summary": null,
166
+ "quality": "合格",
167
+ "followUp": null,
168
+ "won": null,
169
+ "amountUsd": null
165
170
  }
166
171
  ]
167
172
  ```
168
173
 
169
- - `month`:保留运营原文(`1月份` / `4月份` …)。
170
- - `country`:**中文名**,与 `countryOrRegionZh` 字典对齐(脚本会自动 lookup 大洲)。Agent 解析时遇到英文/缩写应先翻译为中文,未知国家保留原文并在脚本里标 `未知大洲`。
171
- - `time`:`YYYY-MM-DD`;Excel 日期序列号也接受,由解析方转换。
172
- - `product`:自由文本(`滴灌带` / `打包带` / `片材` / `滴头` / `模具` / `贴条` / `涂布水带` …),脚本以此作 Sheet 3 产品维度透视分桶 key。
173
- - `source`:自由文本(`WhatsApp` / `tawk.to` / `info邮箱` / `网站留言` / `微信(xxxx)` / `阿里巴巴` / `made-in-china` / `Youtube` / `TikTok` …),脚本以此作 Sheet 3 客户来源透视分桶 key。
174
- - `ip`:可空。
174
+ 落盘前先看一眼表头,按含义对上即可(`时间`=`询盘日期`,`国家(中文)`=`目标国家/地区`,`产品`=`产品/品类`)。
175
+
176
+ - `date`:询盘日期,`YYYY-MM-DD`。
177
+ - `country`:目标国家/地区,中文名,与 `countryOrRegionZh` 字典对齐。
178
+ - `product`:产品/品类,Sheet 3 产品分桶。
179
+ - `sourceKeyword` / `source`:来源,Sheet 3 来源分桶(可空)。
180
+ - `quality` / 其余可选列:有则写入,没有就空着。
181
+ - `month`:可由 `date` 推导。**禁止编造**缺失值。
175
182
 
176
183
  ---
177
184
 
@@ -33,7 +33,7 @@
33
33
  | 5 | `keywords` | 关键词与搜索主题 | Search 分组 + 匹配 + 否词方向;有 PMax 则 Search Themes |
34
34
  | 6 | `audience` | 受众规划 | Audience Signal / 名单 / 再营销;没有的标「需创建或导入」 |
35
35
  | 7 | `creative` | 素材与落地页 | 主题、资产缺口、测试假设(每轮 1~2 变量)、广告↔页面映射 |
36
- | 8 | `roadmap` | 季度执行路线图 | 1 / 2 / 3 月:任务、交付物、完成条件、依赖 |
36
+ | 8 | `roadmap` | 季度执行路线图 | 第一个月 / 第二个月 / 第三个月:任务、交付物、完成条件、依赖 |
37
37
  | 9 | `risks` | 风险与待确认 | 预算、数据、素材、页面、销售承接、合规;假设清单 |
38
38
 
39
39
  每条结论前加口径标签之一:`已确认` / `基于历史数据` / `建议验证` / `待业务确认`。
@@ -48,7 +48,7 @@
48
48
  "keywords": { "analysis": ["产品词精确/词组…", "否词方向:求职/教程/免费"] },
49
49
  "audience": { "analysis": ["待业务确认:客户名单未导入"] },
50
50
  "creative": { "analysis": ["主题:工业场景…", "缺口:PMax 视频"] },
51
- "roadmap": { "analysis": ["第1月:目标与基础…", "第2月:核心建设…", "第3月:已验证方向扩"] },
51
+ "roadmap": { "analysis": ["第一个月:目标与基础…", "第二个月:核心建设…", "第三个月:已验证方向扩"] },
52
52
  "risks": { "analysis": ["销售回传缺失 → 主目标暂用表单并标注"] }
53
53
  }
54
54
  }
@@ -104,7 +104,7 @@ CPA、询盘目标**不**从历史外推。要写数字须用户给,或标「
104
104
  | Q23 | 广告↔页 | 每个核心主题至少一个明确页面方向。禁止广告讲 A、页面展 B |
105
105
  | Q24 | 转化路径 | CTA、表单字段、感谢页、电话/WhatsApp/邮件、CRM 回传、销售跟进 |
106
106
  | Q25 | 销售承接 | 接收人、响应时效、分配、无效反馈、结果回传。无承接则降低扩量优先级 |
107
- | Q26 | 三月节奏 | 见下表。月度报告把「三月」收成「本月 / 次月 / 再次月」 |
107
+ | Q26 | 三月节奏 | 见下表。写「第一个月 / 第二个月 / 第三个月」,禁止写「第1月」(会被读成 1 月 / 2 月 / 3 月)。能从本期结束日推下期日历月时写成「第一个月(10月)」。月度报告改成「本月 / 次月 / 再次月」 |
108
108
  | Q27 | 依赖 | 每项关键规划标依赖、负责人或待确认 |
109
109
  | Q28 | 风险 | 至少覆盖:预算不足、转化量不足、数据质量、落地页未完成、素材不足、销售承接、市场合规 |
110
110
  | Q29 | 假设 | 假设不得写成事实;用四类口径标签 |
@@ -114,9 +114,9 @@ CPA、询盘目标**不**从历史外推。要写数字须用户给,或标「
114
114
 
115
115
  | 阶段 | 核心任务 | 交付 | 完成条件 |
116
116
  | ---- | -------- | ---- | -------- |
117
- | 1 月 · 基础与验证 | 目标、预算、市场优先级;补转化追踪、落地页、素材、受众 | 目标表、预算表、Campaign 结构、词/Themes、素材与页面清单 | 核心投放基础具备,关键依赖写清 |
118
- | 2 月 · 重点建设 | 核心市场与核心类型;有限变量测试 | 核心 Campaign、素材测试批次、受众组合、页面优化版 | 核心方向验证完,形成可续投项目 |
119
- | 3 月 · 扩展与沉淀 | 只围已验证方向扩市场/预算/词/素材/受众 | 扩展计划、下季建议、有效资产、未完成项 | 可扩展方向和下季待解决问题都明确 |
117
+ | 第一个月 · 基础与验证 | 目标、预算、市场优先级;补转化追踪、落地页、素材、受众 | 目标表、预算表、Campaign 结构、词/Themes、素材与页面清单 | 核心投放基础具备,关键依赖写清 |
118
+ | 第二个月 · 重点建设 | 核心市场与核心类型;有限变量测试 | 核心 Campaign、素材测试批次、受众组合、页面优化版 | 核心方向验证完,形成可续投项目 |
119
+ | 第三个月 · 扩展与沉淀 | 只围已验证方向扩市场/预算/词/素材/受众 | 扩展计划、下季建议、有效资产、未完成项 | 可扩展方向和下季待解决问题都明确 |
120
120
 
121
121
  ## 硬约束
122
122
 
@@ -247,11 +247,10 @@
247
247
  .bcell .n { font-size: 13px; color: var(--text-2); margin-top: 4px; }
248
248
  .closing {
249
249
  text-align: center; background: #fff; border: 1px solid var(--line-light);
250
- border-radius: var(--radius-lg); padding: 56px 24px; margin: 20px 0 8px; box-shadow: var(--shadow);
250
+ border-radius: var(--radius-lg); padding: 56px 24px; margin: 20px 0 24px; box-shadow: var(--shadow);
251
251
  }
252
252
  .closing h2 { margin: 0 0 10px; font-size: 36px; font-weight: 600; }
253
253
  .closing p { margin: 0; color: var(--text-3); font-size: 13px; }
254
- .footnote { text-align: center; color: var(--text-3); font-size: 12px; padding: 12px 0 24px; }
255
254
  .hidden { display: none !important; }
256
255
  @media print { .topnav { display: none; } .page, .chapter, .cover { break-inside: avoid; box-shadow: none; } }
257
256
  </style>
@@ -332,6 +331,20 @@
332
331
  return `${String(range.start).slice(0, 10)} ~ ${String(range.end).slice(0, 10)}`;
333
332
  }
334
333
 
334
+ /** 下期三个月:写「第一个月」而不是「第1月」,避免被读成 1 月 / 2 月 / 3 月。 */
335
+ function nextPeriodMonthLabels(current) {
336
+ const base = ["第一个月", "第二个月", "第三个月"];
337
+ const end = current && current.end;
338
+ const match = typeof end === "string" ? /^(\d{4})-(\d{2})-(\d{2})$/.exec(end.trim()) : null;
339
+ if (!match) return base;
340
+ let month = Number(match[2]);
341
+ return base.map((label) => {
342
+ month += 1;
343
+ if (month > 12) month = 1;
344
+ return `${label}(${month}月)`;
345
+ });
346
+ }
347
+
335
348
  function ellipsis(text, max) {
336
349
  const s = String(text ?? "").trim();
337
350
  return s.length > max ? s.slice(0, max) + "…" : s;
@@ -492,7 +505,7 @@
492
505
  <div class="bcell"><div class="p">${money(planDefaults.spendLow, currency)}</div><div class="n">建议档 +30%</div></div>
493
506
  <div class="bcell"><div class="p">${money(planDefaults.spendHigh, currency)}</div><div class="n">扩量档 +50%</div></div>
494
507
  </div>
495
- ${monthSplitTable(planDefaults, currency)}
508
+ ${monthSplitTable(planDefaults, currency, meta.current)}
496
509
  </div>
497
510
  <div class="plan-stack">${planChapters(plan)}</div>
498
511
  </section>
@@ -500,7 +513,6 @@
500
513
  <h2>谢谢</h2>
501
514
  <p>${esc(name)} · ${esc(periodLabel(meta.current))}</p>
502
515
  </section>
503
- <p class="footnote">${esc(name)} · ${esc(periodLabel(meta.current))}</p>
504
516
  `;
505
517
 
506
518
  const months = data.monthly || [];
@@ -858,17 +870,18 @@
858
870
  return `<div class="plan-card"><div class="tag">${esc(title)}</div><ul>${items.map((x) => `<li>${esc(x)}</li>`).join("")}</ul></div>`;
859
871
  }
860
872
 
861
- function monthSplitTable(defaults, currency) {
873
+ function monthSplitTable(defaults, currency, current) {
862
874
  const months = defaults && defaults.monthSuggested;
863
875
  if (!Array.isArray(months) || months.length < 3) return "";
864
876
  const shares = (defaults.monthShares || [0.3, 0.35, 0.35]).map((n) => Math.round(Number(n) * 100) + "%");
877
+ const labels = nextPeriodMonthLabels(current);
865
878
  return `<h3 class="st">建议档月度拆分</h3>
866
879
  <div class="table-wrap"><table class="data compact">
867
- <thead><tr><th>月份</th><th class="num">占比</th><th class="num">建议花费</th></tr></thead>
880
+ <thead><tr><th>下期月份</th><th class="num">占比</th><th class="num">建议花费</th></tr></thead>
868
881
  <tbody>
869
- <tr><td>第 1 月</td><td class="num">${shares[0] || "30%"}</td><td class="num">${money(months[0], currency)}</td></tr>
870
- <tr><td>第 2 月</td><td class="num">${shares[1] || "35%"}</td><td class="num">${money(months[1], currency)}</td></tr>
871
- <tr><td>第 3 月</td><td class="num">${shares[2] || "35%"}</td><td class="num">${money(months[2], currency)}</td></tr>
882
+ <tr><td>${esc(labels[0])}</td><td class="num">${shares[0] || "30%"}</td><td class="num">${money(months[0], currency)}</td></tr>
883
+ <tr><td>${esc(labels[1])}</td><td class="num">${shares[1] || "35%"}</td><td class="num">${money(months[1], currency)}</td></tr>
884
+ <tr><td>${esc(labels[2])}</td><td class="num">${shares[2] || "35%"}</td><td class="num">${money(months[2], currency)}</td></tr>
872
885
  </tbody>
873
886
  </table></div>`;
874
887
  }
@@ -24,21 +24,22 @@ CLI **不读** xlsx / csv / 截图。
24
24
 
25
25
  **禁止**把原始 `.xlsx` / `.csv` 传给 `--inquiries`(会 `JSON.parse` 失败)。用户说没有:可 `--inquiry-conversion <转化动作名>`(须用户点名);再没有则询盘位留空并脚注。**禁止编造询盘行。**
26
26
 
27
- 落盘 JSON:根为数组或 `{ "items": [...] }`。每行至少有国家 +(月份或日期):
27
+ 落盘 JSON:根为数组或 `{ "items": [...] }`。**用之前先看一眼表头**,有日期/国家/产品这类列即可(列名接近即可,如 `时间`=`询盘日期`)。**禁止编造**。
28
28
 
29
- | 用途 | 接受的键 | 归一 |
29
+ | 用途 | 常见键 | 归一 |
30
30
  | ---- | -------- | ---- |
31
- | 国家 | `国家` / `国家(中文)` / `country` / `countryZh` | 英文/缩写先译中文,与 geographic `countryNameZh` 对齐 |
32
- | 月份 | `月份` / `month` / `ym` | `2026-04` `4月份`(有日期则用日期年) |
33
- | 日期 | `时间` / `date` / `time` | `YYYY-MM-DD`;Excel 序列号由 Agent 转好 |
34
- | 产品 | `产品` / `product` | 可选 |
35
- | 来源 | `客户来源` / `来源` / `source` | 可选 |
31
+ | 询盘日期 | `询盘日期` / `date` / `时间` / `time` | `YYYY-MM-DD`;Excel 序列号由 Agent 转好 |
32
+ | 目标国家/地区 | `目标国家/地区` / `国家(中文)` / `国家` / `country` / `countryZh` | 英文/缩写先译中文,与 geographic `countryNameZh` 对齐 |
33
+ | 产品/品类 | `产品/品类` / `产品` / `product` | 自由文本 |
34
+ | 询盘来源关键词 | `询盘来源关键词` / `客户来源` / `来源` / `source` | 可选 |
35
+ | 询盘质量分级 | `询盘质量分级` / `quality` / `qualityGrade` | 有则用 |
36
+ | 月份 | `月份` / `month` / `ym` | 可由询盘日期推导 |
36
37
 
37
- 样例(字段名任选上表别名):
38
+ 样例:
38
39
 
39
40
  ```json
40
41
  [
41
- { "月份": "4月份", "国家(中文)": "印度", "时间": "2026-04-12", "产品": "协作机器人", "客户来源": "WhatsApp" }
42
+ { "询盘日期": "2026-04-12", "目标国家/地区": "印度", "产品/品类": "协作机器人", "询盘来源关键词": "WhatsApp" }
42
43
  ]
43
44
  ```
44
45
 
@@ -111,7 +112,7 @@ Agent JSON Schema:`assets/google-quarterly-report.schema.json`。
111
112
  "keywords": { "analysis": ["产品词精确/词组;否词:求职/教程"] },
112
113
  "audience": { "analysis": ["待业务确认:客户名单未导入"] },
113
114
  "creative": { "analysis": ["主题:工业场景;缺口:PMax 视频"] },
114
- "roadmap": { "analysis": ["第1月基础验证;第2月核心建设;第3月已验证方向扩"] },
115
+ "roadmap": { "analysis": ["第一个月基础验证;第二个月核心建设;第三个月已验证方向扩"] },
115
116
  "risks": { "analysis": ["销售回传缺失"] }
116
117
  }
117
118
  }
@@ -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.53-beta.3'
12
+ $PKG_VERSION = '1.1.53-beta.5'
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.53-beta.3"
12
+ readonly PKG_VERSION="1.1.53-beta.5"
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"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.53-beta.3",
3
+ "version": "1.1.53-beta.5",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",