gangtise-openapi-cli 0.50.0 → 0.52.0

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.
Files changed (43) hide show
  1. package/README.md +94 -49
  2. package/dist/src/cli.js +29 -8
  3. package/dist/src/commands/ai.js +27 -12
  4. package/dist/src/commands/alternative.js +2 -2
  5. package/dist/src/commands/bond.js +4 -4
  6. package/dist/src/commands/fund.js +1 -1
  7. package/dist/src/commands/fundamental.js +11 -11
  8. package/dist/src/commands/indicator.js +5 -5
  9. package/dist/src/commands/insight.js +38 -17
  10. package/dist/src/commands/quote.js +6 -6
  11. package/dist/src/commands/raw.js +3 -3
  12. package/dist/src/commands/reference.js +35 -3
  13. package/dist/src/commands/shared.js +20 -8
  14. package/dist/src/commands/tool.js +9 -4
  15. package/dist/src/commands/update.js +11 -0
  16. package/dist/src/commands/vault.js +17 -16
  17. package/dist/src/core/client.js +21 -4
  18. package/dist/src/core/download.js +12 -3
  19. package/dist/src/core/driveUpload.js +6 -2
  20. package/dist/src/core/dryRun.js +68 -0
  21. package/dist/src/core/endpoints.js +87 -6
  22. package/dist/src/core/errors.js +2 -2
  23. package/dist/src/core/etfComponents.js +65 -8
  24. package/dist/src/core/exitStatus.js +4 -0
  25. package/dist/src/core/fileParse.js +10 -3
  26. package/dist/src/core/printer.js +39 -7
  27. package/dist/src/core/statePaths.js +41 -0
  28. package/dist/src/core/transport.js +7 -0
  29. package/gangtise-openapi/SKILL.md +43 -156
  30. package/gangtise-openapi/references/billing.md +142 -0
  31. package/gangtise-openapi/references/commands/ai.md +26 -0
  32. package/gangtise-openapi/references/commands/fund.md +3 -3
  33. package/gangtise-openapi/references/commands/fundamental.md +2 -1
  34. package/gangtise-openapi/references/commands/indicator.md +106 -15
  35. package/gangtise-openapi/references/commands/insight.md +31 -6
  36. package/gangtise-openapi/references/commands/reference-and-lookup.md +42 -4
  37. package/gangtise-openapi/references/commands/tool.md +2 -2
  38. package/gangtise-openapi/references/commands/vault.md +26 -4
  39. package/gangtise-openapi/references/errors.md +3 -3
  40. package/gangtise-openapi/references/examples.md +3 -3
  41. package/gangtise-openapi/references/lookup-ids.md +13 -0
  42. package/gangtise-openapi/references/response-schema.md +8 -2
  43. package/package.json +2 -2
package/README.md CHANGED
@@ -2,41 +2,6 @@
2
2
 
3
3
  一个可直接调用 Gangtise OpenAPI 获取全量金融信息的命令行工具,同时提供Agent Skill。
4
4
 
5
- ## Changelog
6
-
7
- README 仅列最近 5 个版本摘要:
8
-
9
- - **v0.50.0 — 2026-11-01**:① **新增个股笔记**:`vault stock-note-list` 查询(全部 / 按证券 / 按笔记 ID)、`stock-note-add` 新增(一条关联 1~50 只证券)、`stock-note-update` 改写正文、`stock-note-delete` 删除(须 `--yes`),均免费。② **新增 `quote sector-fund-flow` 板块日资金流向**(免费;按当日成分股加总、只计沪深京;撞 `100006` 要缩区间或减板块 / 字段,调小 `--limit` 无效)。③ `vault stock-pool-list` / `stock-pool-stocks` 加 `--space-type 2` 查所属租户的股票池。④ 路演 / 调研 / 策略会 / 论坛加 `--publish-start-time` / `--publish-end-time`,按收录时间筛选,可与举办时间同时用。⑤ `indicator time-series` 不传 `--calendar-type` 时由服务端按指标定日期轴(不再先查 `indicator search`);区间里没有报告期末时报告期类指标返回空结果(此前是逐日 `null` 行);⚠️ 一致预期 `frcst_*` 要逐日序列须显式传 `ND`。⑥ ETF 成分补代码在名称查询失败、A / H 定不了等情形下留 `null`,不再按其余行推断;A / H 价格都对得上时按其余行所在的单一市场取。⑦ 指标参数报错的改法提示按服务端新写法修正。⑧ 文档:量比、自由流通、流通股本与市值、AH 溢价率、涨跌停、TTM 利润类等 EDE 指标;⚠️ 基金收益率同类排名的分母不一定等于所属分类的基金数,同类比较前先核对。
10
-
11
- - **v0.49.0 — 2026-10-28**:① **新增 `gangtise update`**:一条命令更新到最新版(从 npmjs.org `npm install -g`),并替换 `~/.claude` / `~/.codex` / `~/.openclaw/workspace` / `~/.hermes` 下版本较旧的 Agent Skill 副本;`--check` 只查不装,`--no-skill` 不动 skill 副本。② **新版本提示**:在终端里运行任何命令时,若已有新版本,结束后 stderr 提示一行(同一新版本约一天一次);版本信息在后台刷新、命令不等网络,脚本 / Agent / CI 中不提示,`GANGTISE_NO_UPDATE_CHECK=1` 关闭。③ **管理层情绪的 `publishTime` 现为北京时间**,与 `--start-time` / `--end-time` 同一口径;按 v0.48.0 说明「先加 8 小时」处理过的脚本要去掉这一步。④ **路演 / 调研 / 策略会 / 论坛列表的翻页去重改按各自的 ID**(`roadshowId` / `siteVisitId` / `strategyMeetingId` / `forumId`):跨页重复的行会被去掉、结果标为不完整(退出码 3),提示缩短时间范围重拉;`response-schema.md` 同步更正这四个列表的字段名。⑤ **公募基金**:`fund holder-structure` 的 `holderCount` 现为整数,按 v0.44.0 说明「先去千分位逗号再转数字」的处理要去掉(对整数做字符串替换会报错);`fund etf-pcf-components` 的接口只返回成分名称,CLI 按名称匹配补上 `componentCode`(带市场后缀,如 `600519.SH` / `00941.HK`;用该行收盘价核对,A+H 同名两行分别匹配;现金行等匹配不到的为 `null`,stderr 列出),`--no-component-code` 关闭。⑥ `ai security-clue` 的 `--query-mode` / `--gts-code` 补充帮助说明。
12
-
13
- - **v0.48.0 — 2026-10-24**:① **新增管理层情绪三个命令**:`ai management-sentiment-indicator`(每场业绩会的正面回应程度 / 回复乐观度 / 回复确定度评分与按问题类型拆分)、`ai management-sentiment-conclusion`(情绪结论与较上一场的问答对照),各 5 积分/条、自动翻页;`ai management-sentiment-detail` 按列表返回的 `source.sourceId` 取逐条问答评分与依据,10 积分/次。⚠️ 返回的 `publishTime` 比北京时间早 8 小时(时间筛选按北京时间),按时刻展示或按日期归档前先加 8 小时。② **新增 `bond default-list` 违约债券**(0.4 积分/次):按违约日期区间(不超过 1 年)/ `--security` / `--issuer` 三选一;`--issuer` 一次一个、用发行人全称,简称只返回最匹配的一家主体,核对 `issuerName`。③ 文档:EDE 基金指标(分类、复权净值、区间收益与风险指标、同类排名),区间指标的参数须显式传。④ 依赖 `undici` 升级到 7.30.1(含上游安全修复)。
14
-
15
- - **v0.47.0 — 2026-10-17**:① **`insight opinion list` 新增 `--category`(观点类别:业绩点评 / 异动点评 / 调研访谈更新 / 交易台 / 行业分析)与 `--broker-tag`(头部券商 / 新财富团队)**,返回行带 `categoryList` / `brokerTagList`。② **`--llm-tag` 改为 28 项投研业务标签**(强烈推荐、超预期、涨价、龙头等,取值见 `--help`);⚠️ **存量脚本要改**:业绩点评、头部券商、新财富团队改用 `--category earningsReview` / `--broker-tag topBroker` / `--broker-tag newFortune`,传给 `--llm-tag` 报 `100005`。同一选项传多个值取并集。③ `--with-content` 不支持 `--category` / `--broker-tag`(本地报错、不发请求),其 `--llm-tag` 仍只收旧的四个值;要按新标签筛又要正文,用 `list` + `detail`。④ **`bond daily-quote` / `bond valuation` 首行比起始日晚超过 14 天时 stderr 提示**(上市较晚或数据起点较晚;多只同查只看整批最早的首行,逐只起点按 `securityCode` 核对)。⑤ 文档:股东结构类 EDE 指标(`shr_*`),大股东系列须传 `rank`、户均持股系列须传 `shareType`。
16
-
17
- - **v0.46.0 — 2026-10-14**:① **`reference securities-search --category bond`**:按代码或简称搜债券,代码用于 `bond` 命令组。⚠️ **可转债可能出现在任何 `--category` 的结果里**(按简称查股票时常排第一),**只取返回行 `category` 等于所查类别的那一行**;债券代码传给 `fund` 命令会返回空结果、不报错。② **场外基金可按名称搜到**(`--category fund` 同时返回 `.OF` 与场内代码)。③ **计费单价按平台最新价目更新**:一页通等 AI 生成 10/次、业绩点评 / 观点 PK 20/次、热点话题 10/篇、个股看点 1/条、路演四件套 10/条、独立观点列表 1/条、纪要 / 外资研报 / 我的会议下载 30/篇、公告下载 A 股 1 / 港美股 2、`edb-data` 30/指标;额度保护随之调整。④ **数据范围**:`SKILL.md` 按官方权限表逐命令列出正式 / 试用两档,规划查询区间按表来。⑤ **报错口径**:EDE 漏传必填的 `fiscalYear` 报 `100001`;`main-business` / `valuation-analysis` 的 `--field` 写错名字报 `100005`;`screener` 计费按筛选前范围保守估算。完整列表见 CHANGELOG。
18
-
19
- ### 历史里程碑
20
-
21
- - **v0.44.0**:新增公募基金 `fund` 命令组 18 个命令(0.4 积分/次),额度保护覆盖显式 `--size`。
22
- - **v0.43.0**:按条计费列表的额度保护(省略 `--size` 时估算超过 1000 积分须 `--yes`);翻页结果的跨页重复、版本变化与 `total` 封顶检测。
23
- - **v0.42.0**:数值参数只收十进制写法(`0x10` / `1e3` 这类写法发请求前报错);接管道时保留退出码 `3` / `4`。
24
- - **v0.41.0**:新增债券 12 个命令与云盘管理 9 个命令;观点列表改走 v2(列表只含摘要 1 积分/条,正文按 ID 另取);新增会议线索与联网搜索。
25
- - **v0.40.0**:自选股股票池可增删改(首批写操作,删池须显式 `--yes`);`indicator time-series` 自动判定日期轴;Token 缓存绑定签发它的凭证。
26
- - **v0.39.0**:`csv` / `jsonl` 导出的元信息加上字节数与 `sha256`,转交前可核验;并发导出到同一 `--output` 不再互相掺混,文件被另一次导出替换时退出码 4。
27
- - **v0.38.0**:`quote realtime` / `day-kline` / `minute-kline` 支持沪深 ETF 与 20 个全球指数,代码直接传即可。
28
- - **v0.37.0**:下载的「智能文件命名」改为默认只读缓存,缓存未命中不再自动回查 list 接口(省下按条计费的开销),要中文文件名加 `--resolve-title`。
29
- - **v0.36.0**:日期写法放宽为三种「年在前」格式(「年在后」仍拒收,接口会按美式解析导致差半年),`indicator screener` 支持无日期指标。
30
- - **v0.29.0**:新增财报日历与 PDF 解析工具,群消息补 `quoteMsg`,并加强大整数 ID 与高积分调用防护。
31
- - **v0.26.0–v0.27.0**:建立高积分端点 `no-replay`、原子下载与容错分页机制,并补齐 Skill 分发和发布质量门禁。
32
- - **v0.22.0–v0.23.0**:统一“省略 `--size` 即拉全量”的分页语义,引入机器可识别的部分结果、Token 自愈,并完成 API 域名迁移与资金流向、机构搜索支持。
33
- - **v0.19.0–v0.20.0**:上线 EDE 证券指标接口,扩展美股公告与财务报表,同时加强凭证脱敏、CSV 正确性和分页容错。
34
- - **v0.16.0–v0.18.0**:以服务端参考数据替代多数本地静态表,收紧端点参数,并加入产业公众号资讯。
35
- - **v0.14.0–v0.15.0**:新增跨市场实时行情、美股日 K 与题材数据,完善全市场 K 线分片和部分失败容错。
36
- - **v0.12.0–v0.13.0**:奠定并发翻页、连接复用、流式输出与 K 线分片架构,并扩展港股财报、EDB 和股票池。
37
-
38
- > 完整更新明细及更早版本见 [CHANGELOG.md](CHANGELOG.md)。
39
-
40
5
  ## 首次安装
41
6
 
42
7
  ```bash
@@ -93,6 +58,41 @@ export GANGTISE_NO_UPDATE_CHECK=1 # 关闭新版本提示与后台版本查
93
58
  如果没有 `GANGTISE_TOKEN`,CLI 会自动调用 token 接口并缓存到本地(`~/.config/gangtise/token.json`,权限 0600)。服务端判定 Token 失效时,先用其他进程已刷新并写入缓存的 token,不行再重新登录,然后重发请求;凭证本身错(AK/SK 不匹配)不重试,直接报错让你查环境变量。
94
59
 
95
60
 
61
+ ## Changelog
62
+
63
+ README 仅列最近 5 个版本摘要:
64
+
65
+ - **v0.52.0 — 2026-11-14**:① **新增 `insight overseas-opinion list` / `detail` 海外观点**:海外媒体、投资人、分析师等账号的帖子,附中文翻译;列表摘要 **0.1 积分/条**、自动翻页,可按账号、证券、类别、GICS 行业、地区、原创 / 转发、关键词与发布时间筛选;一次查询最多取到前 10000 条,超出时 stderr 提示并以退出码 3 标为不完整,按时间段分批拉;`detail` 取全文与译文 **10 积分/条**。② **新增 `reference overseas-account-search`**(免费):按账号名片段查海外观点账号 ID。③ `--output` 传目录时在发请求前报错(所有命令;此前数据取回后才在保存时失败,按篇 / 按条计费的已计费)。④ 帕米尔纪要:`categoryList` / `marketList` 只在按 `--category` / `--market` 筛选时完整返回,其余查法下 `categoryList` 为空、`marketList` 不含 `aShares` / `usChinaConcept`;⚠️ 拉全量后按这两个字段本地分组的脚本,要改为按类别、市场分别查询。⑤ `fund etf-pcf-components` 补代码更保守:同名 A / H 股中,另一只要有日 K 证据才算排除(日 K 要覆盖到相应那天;行情比日 K 快一天、或日 K 起点更晚时不算),否则这一行留 `null`,此前可能被填成另一市场那只。⑥ `fund top10-holders`:2026 年中报起,除另有披露规则的产品(如 REITs)外不再披露前十大持有人,查不到这一期属正常。 ⑦ **发请求前就被拒时退出码改为 `2`**(未知选项、取值不合法、缺 `--yes`、`--output` 是目录、未配凭证),这类情况没有计费;`1` 仍是请求发出之后的失败。按 `== 1` 判断失败的脚本要把 `2` 也算进去。⑧ **新增全局 `--dry-run`**:打印将发出的请求与单价、不发送;**全局 `--columns a,b,c`**:只保留指定列。⑨ 单证券命令的 `--security-code` 也可写成 `--security`;`--help` 里每个选项都有说明。⑩ 三大报表、`reference` 搜索等免费查询收到空数据体(`data: null`)时报错退出 1,此前打印 `null` 并退出 0。⑪ 三大报表的 `--fiscal-year` 要和 `--period` 一起传,否则返回最新一期。 ⑫ 行业组合指标 `scr_indu_citic` / `_sw` / `_gics` 现在必须传 `industryLevel`,不传报 `100001`。⑬ EDE 新增指标文档:单季度 / MRQ / 年化财务指标、区间估值、A / B 股与证监会口径市值、IPO 首发;`finc_pe` 等报告期年化估值要同时给 `tradeDate` 与 `reportDate`,`ipo_*` 要写 `--indicator-param "<code>:"`。⑭ `--output` 与上传的 `--file` 不能指向 CLI 自己的配置目录(存 token 缓存);`tool file-parse` 另查 PDF 文件头。
66
+
67
+ - **v0.51.0 — 2026-11-07**:① **新增 `ai expert-intel` AI 专家情报**:从专家会议、会议纪要提炼的个股情报(标题 + 内容标签 + 正文,附关联证券 / 研究方向 / 题材),可按证券、研究方向、题材、内容标签、关键词与发布时间筛选;**20 积分/条**、自动翻页,省略 `--size` 时估算超过 1000 积分须加 `--yes`。② **新增 `reference index-weight` 指数成分股权重**(免费):按指数代码与交易日返回全部成分股及权重(%),可一次查多个指数。权重按**每月最后一个交易日**提供,其他日期、非交易日与写错的指数代码都返回空结果、不报错——CLI 在 stderr 点名没取到数据的指数。③ **新增 `vault wechat-message-download` 群消息文件下载**(免费):用 `wechat-message-list` 返回的 `msgId` 下载文档 / 图片 / 音频 / 视频消息的文件,按服务端给的文件名保存、内容原样落盘(`.txt` / `.json` 等文本类文件同样不改动);文本 / 链接消息返回 `130005`,消息不存在或无权访问返回 `130003`。④ `vault wechat-message-list --category` 新增 `audio` / `video`;⚠️ 文档类消息的取值是 `document`,写成 `documents` 会报 `100005`,脚本里用了后者的要改。⑤ 文档:EDE 新增银行 / 保险 / 券商专项财务指标、估值分位数等;⚠️ `scr_indu`(所属行业)、`cdr_conv_ratio`(CDR 转换比例)与半年期行情、周 / 月 / 季 / 年均成交额与成交量共 23 项指标预计下线,`indicator search` 已搜不到;行业分类改用 `scr_indu_citic` / `scr_indu_sw` / `scr_indu_gics`。⑥ `fund etf-pcf-components` 补代码:同名 A / H 股价格碰巧相同、或另一市场那只排除不了时,改用该行的 `pctChange` 核对(收盘价与涨跌幅都相同的那只),此前这类行要么按其余各行的市场补(可能补错),要么留 `null`;涨跌幅也定不了时留 `null`,不再按其余各行的市场补。⑦ `fund_ret_rank` / `fund_ret_rank_pct` 的同类基金数量现与 `industryLevel` 对应的分类一致,同类比较可直接使用,不必再自行核对分母。
68
+
69
+ - **v0.50.0 — 2026-11-01**:① **新增个股笔记**:`vault stock-note-list` 查询(全部 / 按证券 / 按笔记 ID)、`stock-note-add` 新增(一条关联 1~50 只证券)、`stock-note-update` 改写正文、`stock-note-delete` 删除(须 `--yes`),均免费。② **新增 `quote sector-fund-flow` 板块日资金流向**(免费;按当日成分股加总、只计沪深京;撞 `100006` 要缩区间或减板块 / 字段,调小 `--limit` 无效)。③ `vault stock-pool-list` / `stock-pool-stocks` 加 `--space-type 2` 查所属租户的股票池。④ 路演 / 调研 / 策略会 / 论坛加 `--publish-start-time` / `--publish-end-time`,按收录时间筛选,可与举办时间同时用。⑤ `indicator time-series` 不传 `--calendar-type` 时由服务端按指标定日期轴(不再先查 `indicator search`);区间里没有报告期末时报告期类指标返回空结果(此前是逐日 `null` 行);⚠️ 一致预期 `frcst_*` 要逐日序列须显式传 `ND`。⑥ ETF 成分补代码在名称查询失败、A / H 定不了等情形下留 `null`,不再按其余行推断;A / H 价格都对得上时按其余行所在的单一市场取。⑦ 指标参数报错的改法提示按服务端新写法修正。⑧ 文档:量比、自由流通、流通股本与市值、AH 溢价率、涨跌停、TTM 利润类等 EDE 指标;⚠️ 基金收益率同类排名的分母不一定等于所属分类的基金数,同类比较前先核对。
70
+
71
+ - **v0.49.0 — 2026-10-28**:① **新增 `gangtise update`**:一条命令更新到最新版(从 npmjs.org `npm install -g`),并替换 `~/.claude` / `~/.codex` / `~/.openclaw/workspace` / `~/.hermes` 下版本较旧的 Agent Skill 副本;`--check` 只查不装,`--no-skill` 不动 skill 副本。② **新版本提示**:在终端里运行任何命令时,若已有新版本,结束后 stderr 提示一行(同一新版本约一天一次);版本信息在后台刷新、命令不等网络,脚本 / Agent / CI 中不提示,`GANGTISE_NO_UPDATE_CHECK=1` 关闭。③ **管理层情绪的 `publishTime` 现为北京时间**,与 `--start-time` / `--end-time` 同一口径;按 v0.48.0 说明「先加 8 小时」处理过的脚本要去掉这一步。④ **路演 / 调研 / 策略会 / 论坛列表的翻页去重改按各自的 ID**(`roadshowId` / `siteVisitId` / `strategyMeetingId` / `forumId`):跨页重复的行会被去掉、结果标为不完整(退出码 3),提示缩短时间范围重拉;`response-schema.md` 同步更正这四个列表的字段名。⑤ **公募基金**:`fund holder-structure` 的 `holderCount` 现为整数,按 v0.44.0 说明「先去千分位逗号再转数字」的处理要去掉(对整数做字符串替换会报错);`fund etf-pcf-components` 的接口只返回成分名称,CLI 按名称匹配补上 `componentCode`(带市场后缀,如 `600519.SH` / `00941.HK`;用该行收盘价核对,A+H 同名两行分别匹配;现金行等匹配不到的为 `null`,stderr 列出),`--no-component-code` 关闭。⑥ `ai security-clue` 的 `--query-mode` / `--gts-code` 补充帮助说明。
72
+
73
+ - **v0.48.0 — 2026-10-24**:① **新增管理层情绪三个命令**:`ai management-sentiment-indicator`(每场业绩会的正面回应程度 / 回复乐观度 / 回复确定度评分与按问题类型拆分)、`ai management-sentiment-conclusion`(情绪结论与较上一场的问答对照),各 5 积分/条、自动翻页;`ai management-sentiment-detail` 按列表返回的 `source.sourceId` 取逐条问答评分与依据,10 积分/次。⚠️ 返回的 `publishTime` 比北京时间早 8 小时(时间筛选按北京时间),按时刻展示或按日期归档前先加 8 小时。② **新增 `bond default-list` 违约债券**(0.4 积分/次):按违约日期区间(不超过 1 年)/ `--security` / `--issuer` 三选一;`--issuer` 一次一个、用发行人全称,简称只返回最匹配的一家主体,核对 `issuerName`。③ 文档:EDE 基金指标(分类、复权净值、区间收益与风险指标、同类排名),区间指标的参数须显式传。④ 依赖 `undici` 升级到 7.30.1(含上游安全修复)。
74
+
75
+ ### 历史里程碑
76
+
77
+ - **v0.44.0**:新增公募基金 `fund` 命令组 18 个命令(0.4 积分/次),额度保护覆盖显式 `--size`。
78
+ - **v0.43.0**:按条计费列表的额度保护(省略 `--size` 时估算超过 1000 积分须 `--yes`);翻页结果的跨页重复、版本变化与 `total` 封顶检测。
79
+ - **v0.42.0**:数值参数只收十进制写法(`0x10` / `1e3` 这类写法发请求前报错);接管道时保留退出码 `3` / `4`。
80
+ - **v0.41.0**:新增债券 12 个命令与云盘管理 9 个命令;观点列表改走 v2(列表只含摘要 1 积分/条,正文按 ID 另取);新增会议线索与联网搜索。
81
+ - **v0.40.0**:自选股股票池可增删改(首批写操作,删池须显式 `--yes`);`indicator time-series` 自动判定日期轴;Token 缓存绑定签发它的凭证。
82
+ - **v0.39.0**:`csv` / `jsonl` 导出的元信息加上字节数与 `sha256`,转交前可核验;并发导出到同一 `--output` 不再互相掺混,文件被另一次导出替换时退出码 4。
83
+ - **v0.38.0**:`quote realtime` / `day-kline` / `minute-kline` 支持沪深 ETF 与 20 个全球指数,代码直接传即可。
84
+ - **v0.37.0**:下载的「智能文件命名」改为默认只读缓存,缓存未命中不再自动回查 list 接口(省下按条计费的开销),要中文文件名加 `--resolve-title`。
85
+ - **v0.36.0**:日期写法放宽为三种「年在前」格式(「年在后」仍拒收,接口会按美式解析导致差半年),`indicator screener` 支持无日期指标。
86
+ - **v0.29.0**:新增财报日历与 PDF 解析工具,群消息补 `quoteMsg`,并加强大整数 ID 与高积分调用防护。
87
+ - **v0.26.0–v0.27.0**:建立高积分端点 `no-replay`、原子下载与容错分页机制,并补齐 Skill 分发和发布质量门禁。
88
+ - **v0.22.0–v0.23.0**:统一“省略 `--size` 即拉全量”的分页语义,引入机器可识别的部分结果、Token 自愈,并完成 API 域名迁移与资金流向、机构搜索支持。
89
+ - **v0.19.0–v0.20.0**:上线 EDE 证券指标接口,扩展美股公告与财务报表,同时加强凭证脱敏、CSV 正确性和分页容错。
90
+ - **v0.16.0–v0.18.0**:以服务端参考数据替代多数本地静态表,收紧端点参数,并加入产业公众号资讯。
91
+ - **v0.14.0–v0.15.0**:新增跨市场实时行情、美股日 K 与题材数据,完善全市场 K 线分片和部分失败容错。
92
+ - **v0.12.0–v0.13.0**:奠定并发翻页、连接复用、流式输出与 K 线分片架构,并扩展港股财报、EDB 和股票池。
93
+
94
+ > 完整更新明细及更早版本见 [CHANGELOG.md](CHANGELOG.md)。
95
+
96
96
  ## AI Agent Skill
97
97
 
98
98
  本项目包含 Skill 定义(`gangtise-openapi/SKILL.md`),可让 AI agent 自动调用 `gangtise` CLI 完成投研数据查询。支持以下 AI 编程助手:
@@ -109,18 +109,19 @@ gangtise-openapi/
109
109
  ├── SKILL.md # 主 skill 文件(必备规则、速查表、按需引用 references)
110
110
  └── references/
111
111
  ├── commands/ # 按命令组拆分的详细参数文档(agent 按需 Read)
112
- │ ├── ai.md # AI 能力(知识库 / 个股线索与看点 / 一页通等生成类 / 业绩点评与观点 PK 异步任务 / 热点 / 管理层讨论 / 管理层情绪)
112
+ │ ├── ai.md # AI 能力(知识库 / 个股线索与看点 / 一页通等生成类 / 业绩点评与观点 PK 异步任务 / 主题跟踪与热点 / AI 专家情报 / 管理层讨论 / 管理层情绪)
113
113
  │ ├── alternative.md # 行业指标数据库 EDB(search / data)+ 题材指数画像与成分股
114
114
  │ ├── bond.md # 债券(基本资料 / 发行人 / 行情与估值 / 兑付 / 公告 / 发行 / 评级 / 行权 / 违约)
115
115
  │ ├── fund.md # 公募基金(基本信息 / 净值 / 费率 / 经理 / 规模与持有人 / 资产配置 / 持仓与分布 / ETF 申赎与份额)
116
116
  │ ├── fundamental.md # 财务数据(A股/港股/美股三大报表 / 主营 / 估值 / 盈利预测 / 股东)
117
117
  │ ├── indicator.md # 证券级数据指标 EDE(search / 截面 / 时序 / 条件选股)
118
- │ ├── insight.md # 投研内容(研报 / 观点 / 纪要 / 公告 / 外资 / 财报日历 / 会议线索 / 公众号 / QA / 研报图表)
118
+ │ ├── insight.md # 投研内容(研报 / 观点 / 纪要 / 帕米尔纪要 / 路演调研 / 公告 / 外资 / 海外观点 / 财报日历 / 会议线索 / 公众号 / QA / 研报图表)
119
119
  │ ├── quote.md # 行情(A股/港股/美股/ETF/各类指数的日 K、分钟 K、实时行情、资金流向)
120
- │ ├── reference-and-lookup.md # 证券 / 首席 / 机构 / 公众号 / 题材 / 板块 ID 搜索与常量速查
120
+ │ ├── reference-and-lookup.md # 证券 / 首席 / 机构 / 公众号 / 海外观点账号 / 题材 / 板块 ID 搜索、板块成分股与指数权重、常量速查
121
121
  │ ├── tool.md # PDF 解析(file-parse)/ 联网搜索(web-search)
122
- │ └── vault.md # 云盘(含目录与文件管理)/ 录音 / 会议 / 群消息 / 股票池
123
- ├── errors.md # 错误码全表、不报错的坑、退出码 3 / 4 的判读、Troubleshooting
122
+ │ └── vault.md # 云盘(含目录与文件管理)/ 录音 / 会议 / 群消息与群文件 / 股票池 / 个股笔记
123
+ ├── billing.md # 积分单价全表、不自动重放的端点、各命令可查的数据范围
124
+ ├── errors.md # 错误码全表、不报错的坑、退出码 2 / 3 / 4 的判读、Troubleshooting
124
125
  ├── examples.md # 典型场景的端到端示例
125
126
  ├── fields.md # 行情 / 财务 / 估值等字段中英文对照速查表
126
127
  ├── lookup-ids.md # 常用 ID 速查表(行业/券商/机构/公告分类等)
@@ -171,19 +172,22 @@ install_skill ~/.hermes/skills # Hermes
171
172
  | | `announcement-us list` / `download` | 美股公告(含 PDF/Markdown 下载) |
172
173
  | | `foreign-opinion list` / `detail` | 外资机构观点(同上:列表摘要 + `detail` 取原文与译文) |
173
174
  | | `independent-opinion list` / `download` | 外资独立分析师观点(含原文/翻译HTML下载) |
175
+ | | `overseas-opinion list` / `detail` | 海外观点(海外媒体 / 投资人 / 分析师账号的帖子,附中文翻译;列表摘要 0.1 积分/条,`detail` 取全文与译文 10 积分/条) |
174
176
  | | `official-account list` / `download` | 产业公众号资讯(含 txt/HTML 下载) |
175
177
  | | `qa list` | 投资者问答 QA(互动平台/电话会议/调研纪要,按证券) |
176
178
  | | `report-image list` / `download` | 研报图表搜索(按关键词,含原图 JPEG 下载) |
177
179
  | | `highlight list` | 会议线索(会议核心要点信息流,按时间倒序;5 积分/条) |
178
- | **Reference** | `securities-search` | GTS Code 搜索(按名称/代码/拼音匹配;`--category` 可选 stock / dr / index / fund / bond,按返回行的 `category` 取) |
180
+ | **Reference** | `securities-search` | GTS Code 搜索(按名称/代码/拼音匹配;`--category` 可选 stock / dr / index / fund / bond,带上则只返回该类别) |
179
181
  | | `chiefs-search` | 首席分析师 ID 搜索(按姓名/机构/团队匹配) |
180
182
  | | `institution-search` | 机构 ID 搜索(内资券商/外资/牵头/观点机构,按名称匹配) |
181
- | | `official-account-search` | 公众号 ID 搜索(按公众号名/机构/分类匹配,返回 accountId) |
183
+ | | `official-account-search` | 公众号 ID 搜索(按公众号名称匹配,名称用简称的按机构全称查不到;返回 accountId) |
184
+ | | `overseas-account-search` | 海外观点账号 ID 搜索(按账号名片段匹配,返回 accountId;免费) |
182
185
  | | `constant-category` | 常量分类列表(含各分类适用的接口与参数) |
183
186
  | | `constant-list` | 按分类导出常量值全量列表(行业/城市/公告分类/区域等) |
184
187
  | | `concept-search` | 题材 ID 搜索(名称/拼音/分组名匹配) |
185
188
  | | `sector-search` | 板块 ID 搜索(返回层级路径) |
186
189
  | | `sector-constituents` | 板块成分股查询 |
190
+ | | `index-weight` | 指数成分股及权重(%)(按指数代码 + 月末交易日;可一次查多个指数;免费) |
187
191
  | **Quote** | `day-kline` | 历史日K线——A股/港股/美股个股 + 沪深 ETF + 交易所/概念/行业指数 + 全球指数,可混查 |
188
192
  | | `day-kline-hk` / `day-kline-us` | ⚠️ 已弃用,能力并入 `day-kline`(接口仍可调,但不校验证券代码) |
189
193
  | | `index-day-kline` | ⚠️ 已弃用,能力并入 `day-kline`(返回字段相同);只收明确的指数代码,`--security all` 会被 CLI 拒绝 |
@@ -232,6 +236,7 @@ install_skill ~/.hermes/skills # Hermes
232
236
  | | `earnings-review` / `earnings-review-check` | 业绩回顾 |
233
237
  | | `theme-tracking` | 主题跟踪 |
234
238
  | | `hot-topic` | 热点话题 |
239
+ | | `expert-intel` | AI 专家情报(从专家会议与纪要提炼的个股情报,按证券 / 研究方向 / 题材 / 内容标签筛;20 积分/条) |
235
240
  | | `research-outline` | 研究提纲 |
236
241
  | | `management-discuss-announcement` | 管理层讨论-财报 |
237
242
  | | `management-discuss-earnings-call` | 管理层讨论-业绩会 |
@@ -247,6 +252,7 @@ install_skill ~/.hermes/skills # Hermes
247
252
  | | `record-list` / `record-download` | 录音速记列表与下载 |
248
253
  | | `my-conference-list` / `my-conference-download` | 我的会议列表与下载 |
249
254
  | | `wechat-message-list` / `wechat-chatroom-list` | 群消息列表与群ID查询 |
255
+ | | `wechat-message-download` | 群消息文件下载(文档 / 图片 / 音频 / 视频消息,按 `msgId`) |
250
256
  | | `stock-pool-list` / `stock-pool-stocks` | 自选股股票池列表与证券明细(`--space-type 2` 查所属租户的股票池) |
251
257
  | | `stock-pool-create` / `stock-pool-rename` / `stock-pool-delete` | 建池 / 改名 / 删池(删池须加 `--yes`) |
252
258
  | | `stock-pool-add-stock` / `stock-pool-remove-stock` | 批量关注 / 批量取消关注个股 |
@@ -319,11 +325,12 @@ gangtise ai knowledge-batch --query 比亚迪 --query 最近热门概念
319
325
  - **流式下载**:二进制响应(PDF 等)直接 `pipeline` 到磁盘,不经过内存缓冲;不带 `--output` 时先写入临时文件,定好文件名后再改名。50MB PDF 内存占用近乎为零。
320
326
  - **流式输出**:`--format jsonl` 或 `csv` 加 `--output <file>` 时,翻页 / 全市场分片 / 多证券分批请求的行**按到达顺序逐批写盘**,取数阶段不持有整份结果,内存不随行数增长;csv 先落临时行文件、收尾时按列并集写表头再转成 csv(磁盘两遍、内存不变)。jsonl / csv 无论写文件还是输出到终端都按批写出,每行以换行结尾。任一只 / 页 / 片失败,命令退出时后台已无取数与写盘,不留 `.part`。
321
327
  - **导出元信息**:`csv` / `jsonl` 落盘时旁边生成 `<文件>.meta.json`——命令行、数据行数、列名、`complete`(写元信息时的判定:退出 3 即 `false`)、`total` / `partial` / `failedPages` / `failedShards` / `truncatedShards` / `droppedColumns` / `missingFields` / `duplicateRows` / `changedRows` / `totalCapped` 等全部完整性标记(以及不影响完整性判定的 `outOfWindowShards`)、抓取时间与时区、CLI 版本,以及数据文件的字节数 `bytes` 与内容哈希 `sha256`。命令行与结果里的 key / secret / token 类字段写成 `[redacted]`。元信息在数据文件发布之后才就位,导出失败不会留下新数据配旧元信息。**转交或归档前建议核一次**:`shasum -a 256 <文件>` 与元信息里的 `sha256` 相等,才说明这份元信息描述的就是旁边这份数据——两个进程同时导出到**同一个 `--output`** 时,最终文件一定是其中某一次的完整产物,但旁边的元信息有可能来自另一次,核哈希就能发现。**输的那一次自己也会报**:收尾时核对 `--output` 是否仍是本次写出的文件(仍是同一个文件就不必回读,否则回读比对哈希),不符就在 stderr 说明「这个文件不是本次命令的产物」并**退出码 4**。这两种格式的文件本身只有数据行,转交之后靠它核验是否完整;`json` 自带标记,不生成。
322
- - **自动重试**:5xx / 429 / `ECONNREFUSED` / `ECONNRESET` / `ETIMEDOUT` / `ENOTFOUND` / `EAI_AGAIN` / `UND_ERR_*`(undici 连接/超时类)/ `999999` 系统错误自动指数退避重试 2 次。**不重放的端点例外**(贵档:one-pager 等生成/提交类 + `tool file-parse` 提交 + 30/篇 的 summary / foreign-report / my-conference 下载 + 单价未公布但保守同档的 pamirs-summary 下载 + 题材 `concept-info` / `concept-securities`(含 `--full`)+ `ai management-sentiment-detail`;`bond` 全部 13 个命令(多数按次,三个评级命令按条 / 按债券 / 按发行人)、`fund` 全部 18 个命令(按次)与按次计费的 `tool web-search`;按条计费的 `insight highlight list`、观点 `detail` 与 `list --with-content`,以及单次最多 6000 只、按只计费的 `ai stock-summary` 与行数随日期区间增长的 `fundamental earning-forecast`;另加不计分但不可重复执行的 `vault stock-pool-create`、`vault stock-note-add` 与云盘的上传 / 新建文件夹 / 复制 / 两个删除,共 67 个):5xx/超时不重放,仅连接失败、429 与 token 自愈重试。**两类端点各有各的理由**:贵档是**重放会重复扣分**——服务端可能已经执行并计费,重发按次计费的再扣一次,重发按篇/按条计费的会把已交付的行再计一次;不计分的那几个是**重放会造成副作用或把成功报成失败**——股票池名不允许重复,重发一个其实已经建成的请求,回来的是 `230006 股票池名称重复`;笔记没有防重机制,重发一个其实已经写入的新增会多出一条笔记;云盘允许同名,重发上传 / 新建 / 复制会多出一份;重发一个其实已经删掉的删除,回来的是「文件不存在」或 `130002`。**`indicator`(EDE)端点对 `999999` 不重试**——重放一次已计费的查询没有意义(EDE 无数据不用此码,而是保留行列的占位单元格 `null`;代码或参数名写错报 `100003`、缺必填参数报 `100001`)。**终态码 `999011`(凭证无效)/ `140002`(终态失败:异步生成失败、参数错误或业务处理失败)在任何 HTTP 状态下都不重试**——凭证错不会因重试而变,`140002` 立即重发得到的还是同一结果。
328
+ - **自动重试**:5xx / 429 / `ECONNREFUSED` / `ECONNRESET` / `ETIMEDOUT` / `ENOTFOUND` / `EAI_AGAIN` / `UND_ERR_*`(undici 连接/超时类)/ `999999` 系统错误自动指数退避重试 2 次。**不重放的端点例外**(贵档:one-pager 等生成/提交类 + `tool file-parse` 提交 + 30/篇 的 summary / foreign-report / my-conference 下载 + 单价未公布但保守同档的 pamirs-summary 下载 + 题材 `concept-info` / `concept-securities`(含 `--full`)+ `ai management-sentiment-detail`;`bond` 全部 13 个命令(多数按次,三个评级命令按条 / 按债券 / 按发行人)、`fund` 全部 18 个命令(按次)与按次计费的 `tool web-search`;按条计费的 `insight highlight list`、`ai expert-intel`、观点 `detail` 与 `list --with-content`,以及单次最多 6000 只、按只计费的 `ai stock-summary` 与行数随日期区间增长的 `fundamental earning-forecast`;另加不计分但不可重复执行的 `vault stock-pool-create`、`vault stock-note-add` 与云盘的上传 / 新建文件夹 / 复制 / 两个删除,共 69 个):5xx/超时不重放,仅连接失败、429 与 token 自愈重试。**两类端点各有各的理由**:贵档是**重放会重复扣分**——服务端可能已经执行并计费,重发按次计费的再扣一次,重发按篇/按条计费的会把已交付的行再计一次;不计分的那几个是**重放会造成副作用或把成功报成失败**——股票池名不允许重复,重发一个其实已经建成的请求,回来的是 `230006 股票池名称重复`;笔记没有防重机制,重发一个其实已经写入的新增会多出一条笔记;云盘允许同名,重发上传 / 新建 / 复制会多出一份;重发一个其实已经删掉的删除,回来的是「文件不存在」或 `130002`。**`indicator`(EDE)端点对 `999999` 不重试**——重放一次已计费的查询没有意义(EDE 无数据不用此码,而是保留行列的占位单元格 `null`;代码或参数名写错报 `100003`、缺必填参数报 `100001`)。**终态码 `999011`(凭证无效)/ `140002`(终态失败:异步生成失败、参数错误或业务处理失败)在任何 HTTP 状态下都不重试**——凭证错不会因重试而变,`140002` 立即重发得到的还是同一结果。
323
329
 
324
330
  <!-- no-replay-endpoints
325
331
  上面那句点名的「不重放」端点,完整清单如下(endpoint key,与 `gangtise raw list` 一致):
326
332
  ai.earnings-review.get-id
333
+ ai.expert-intel.list
327
334
  ai.hot-topic
328
335
  ai.investment-logic
329
336
  ai.knowledge-batch
@@ -378,6 +385,7 @@ insight.foreign-report.download
378
385
  insight.highlight.list
379
386
  insight.opinion.detail
380
387
  insight.opinion.list-with-content
388
+ insight.overseas-opinion.detail
381
389
  insight.pamirs-summary.download
382
390
  insight.summary.download
383
391
  tool.file-parse.submit
@@ -397,6 +405,10 @@ vault.stock-pool.create
397
405
  - **K线/资金流向自动分片**:`quote day-kline --security aShares|hkStocks|usStocks`、`quote fund-flow --security aShares` 等全市场查询自动按日期切分(按工作日计:A股 K线/资金流向 1 个/片、美股 1 个/片、港股 2 个/片;已弃用的 `day-kline-hk`/`day-kline-us` 用 `all`,分别 2/1 个/片),并发执行后合并结果;周六日不单独发请求。分片时如果用户未传 `--limit`,自动注入 `limit: 10000`(API 上限)避免默认 6000 截断。**显式多证券**的日 K 在「证券数 × 交易日数」达到 `--limit` 时自动分批请求并按传入顺序合并(每批按行数上限装入尽量多的证券,未传 `--limit` 时上限按 10000 算;撞上限的证券标 `partial` + `truncatedSecurities`);`minute-kline` 的 `--security` 可重复,逐只并发请求后合并。
398
406
  - **Token 内存缓存**:Token 在进程内存中缓存,避免每次请求读盘。
399
407
  - **`--verbose`**:打印每个请求的方法、路径、状态码、耗时和响应大小到 stderr,方便定位慢查询。
408
+ - **`--dry-run`**:打印命令将发出的第一个请求(端点、URL、请求体、平台公布的单价、可确定时的最多扣费 `maxCredits`)后退出 0,不发送、不需要凭证、不写 `--output`;上传显示表单字段与文件名、大小;需要先看第一次返回才能决定的后续请求(翻页、取详情、补代码)不在其中。删除类命令预览时不需要 `--yes`,输出里 `needsYes: true` 表示真正执行要加 `--yes`。
409
+ - **`--columns a,b,c`**:每行只保留这几列,按给定顺序,table / json / jsonl / csv / markdown 都生效;某列在所有行里都不存在时 stderr 列出实际有的列名。导出 csv / jsonl 时会先在内存里收齐再写盘。
410
+ - **单证券命令的 `--security`**:`fundamental`、AI 单证券生成类与 `insight qa list` 的 `--security-code` 也可以写成 `--security`(只收一个代码)。
411
+ - **退出码**:`0` 完整成功(含合法空结果);`1` 失败,请求可能已发出;`2` 发请求前就被拒(选项或取值不对、缺 `--yes`、`--output` 是目录、未配凭证),没有计费,改了重跑即可;`3` 结果不完整;`4` 导出完整但 `--output` 被另一次导出替换。
400
412
 
401
413
  ## 自动翻页
402
414
 
@@ -416,6 +428,7 @@ vault.stock-pool.create
416
428
  - `insight announcement-us list`
417
429
  - `insight foreign-opinion list`
418
430
  - `insight independent-opinion list`
431
+ - `insight overseas-opinion list`
419
432
  - `insight official-account list`
420
433
  - `insight qa list`
421
434
  - `insight highlight list`
@@ -429,17 +442,19 @@ vault.stock-pool.create
429
442
  - `ai hot-topic`
430
443
  - `ai management-sentiment-indicator`
431
444
  - `ai management-sentiment-conclusion`
445
+ - `ai expert-intel`
446
+ - `reference overseas-account-search`
432
447
 
433
448
  规则:
434
449
  - **省略 `--size` 一律拉全量**(无论是否传时间范围),CLI 自动翻页查完
435
- - **按条计费的列表有额度保护**:省略 `--size` 时,CLI 先拿到 `total`,按 `total × 单价` 估算全量要花的积分,超过 1000 积分就报错、退出码 1,不再往下拉,报错写明估算值。整页不超过 50 积分的列表用第一页拿 `total`;整页更贵的(路演 / 调研 / 策略会 / 论坛、会议线索、个股线索、热点话题、管理层情绪指标 / 结论、`--with-content`)先只取 1 条:结果最多 1 条时它就是全部结果;否则放行后从头翻页,这 1 条会多计一次费(知道要多少条就直接传 `--size N`,不走这一步)。确认要全量就加 `--yes`,只要一部分就传 `--size N`——显式 `--size` 按 `min(--size, total) × 单价` 估算仍超过 1000 积分时同样报错(`--size 100000` 不能当「全量」绕过),要 `--yes`。`fundamental earning-forecast` 不分页,按日期区间估算(工作日数 × 3 条 × 0.5),同一条线。免费列表不受影响
450
+ - **按条计费的列表有额度保护**:省略 `--size` 时,CLI 先拿到 `total`,按 `total × 单价` 估算全量要花的积分,超过 1000 积分就报错、退出码 1(已发过取 total 的请求),不再往下拉,报错写明估算值。整页不超过 50 积分的列表用第一页拿 `total`;整页更贵的(路演 / 调研 / 策略会 / 论坛、会议线索、个股线索、热点话题、管理层情绪指标 / 结论、AI 专家情报、`--with-content`)先只取 1 条:结果最多 1 条时它就是全部结果;否则放行后从头翻页,这 1 条会多计一次费(知道要多少条就直接传 `--size N`,不走这一步)。确认要全量就加 `--yes`,只要一部分就传 `--size N`——显式 `--size` 按 `min(--size, total) × 单价` 估算仍超过 1000 积分时同样报错(`--size 100000` 不能当「全量」绕过),要 `--yes`。`fundamental earning-forecast` 不分页,按日期区间估算(工作日数 × 3 条 × 0.5),同一条线,在发请求前拦下、退出码 2。免费列表不受影响
436
451
  - 数据量未知时,可先 `--size 1` 从 stderr 的 `Total: N` 探明量级,再决定是否全量
437
452
  - 如果显式传了 `--size`,则按指定值翻页,直到达到 `size` 或数据取完
438
453
  - `--from` 必须是非负整数,`--size` 必须是正整数;非法数字会在本地直接报 `ValidationError`,不会继续请求 API
439
454
  - 安全上限:自动翻页最多 1000 页,防止异常循环
440
- - 部分页失败、或服务端实际返回行数与 `total` 矛盾(提前短页)时,不丢弃已取到的数据:结果带 `partial: true`(页失败时另有 `failedPages`;K线分片为 `failedShards`——早于账号可回溯窗口而跳过的分片不算失败、也不标 `partial`,另列在 `outOfWindowShards`;只有部分分片返回、合并时放不下的列为 `droppedColumns`;`quote` 系带 `--field` 而服务端没回的列为 `missingFields`;翻页时同一行在相邻两页各出现一次为 `duplicateRows`,按时间排序的列表在同一时刻的一组数据跨页时会发生,重复了几行也就漏了几行,重复行已去掉,缩短时间范围重拉可补齐;同一 ID 在后面的页以不同内容再次出现为 `changedRows`,说明翻页期间列表有变动、也可能漏了行,两版都保留(按 ID 去重只留一版),重拉即可——重拉后仍在同一处出现,说明这个列表里同一 ID 对应多条记录,按 ID 去重前先核对;`--format json` 可见),stderr 输出警告,**进程退出码为 3**(完整成功为 0)
455
+ - 部分页失败、或服务端实际返回行数与 `total` 矛盾(提前短页)时,不丢弃已取到的数据:结果带 `partial: true`(页失败时另有 `failedPages`;K线分片为 `failedShards`——早于账号可回溯窗口而跳过的分片不算失败、也不标 `partial`,另列在 `outOfWindowShards`;只有部分分片返回、合并时放不下的列为 `droppedColumns`;`quote` 系带 `--field` 而服务端没回的列为 `missingFields`;翻页时同一行在相邻两页各出现一次为 `duplicateRows`,按时间这类可重复的字段排序、同一时刻的一组数据跨在页边界上时可能出现,重复了几行也就漏了几行,重复行已去掉,缩短时间范围重拉可补齐;同一 ID 在后面的页以不同内容再次出现为 `changedRows`,说明翻页期间列表有变动、也可能漏了行,两版都保留(按 ID 去重只留一版),重拉即可——重拉后仍在同一处出现,说明这个列表里同一 ID 对应多条记录,按 ID 去重前先核对;`--format json` 可见),stderr 输出警告,**进程退出码为 3**(完整成功为 0)
441
456
  - **`indicator` 命令的退出码 3**(脚本按 `!= 0` 判失败的需留意):服务端整指标/整证券没返回时标 `partial` + `omittedIndicators` / `omittedSecurities` 并退出 3。这个分支很少触发——服务端对解析不了的代码直接报 `100003` 并点名是哪个(指标码拼错 →「指标 xxx 不存在」;证券后缀错,如美股写成 `AAPL.US` 而非 `AAPL.O` →「xxx 不是有效证券或者板块ID」),**无论同批有没有正确的代码都会报**,CLI 相应退出 1。真实的无数据/无覆盖仍是占位单元格 + 退出码 0。占位值统一是 `null`。⚠️ **报告期类指标(`is_*`)与交易日类指标混查、或显式传 `--calendar-type ND` / `TD` / `WD` 时,时序上大部分行都是占位**(只有报告期末那几行是真值,`TD` 下落在非交易日的报告期末那几期整期缺失;单独查报告期类指标且不传 `--calendar-type` 时只返回报告期末),`null` 虽被 Excel / pandas / SQL 的聚合跳过,**但行数不变**,手工「总和 ÷ 行数」仍会差几十倍;详见 skill 的 `references/commands/indicator.md`。**条件选股的缺列另有更严的一档**:把缺列的变量当作无法求值,若表达式(按 `&&`/`||` 的布尔结构)再无任何可成立的分支,则**退出码 1 且不输出**——那些行以「通过了该条件」的名义呈现,而条件根本无法证明被执行过。⚠️ 这一档以「服务端返回了命中行」为前提;**零命中时一律退出码 0**(没有行需要被质疑),所以空集不能直接当成「无标的符合条件」——另有两种成因产生**逐字相同**的输出:**日期没落在报告期末**(报告期类指标此时整批 `null`),或**该指标不覆盖这批证券**(如拿 A 股专属指标查港美股)。语义约定:`0` 完整成功(含合法空结果)/`3` 有数据但不完整/`4` 数据写出完整、但 `--output` 指向的文件在收尾期间被另一次写向同一路径的导出替换掉了(不是本次命令的产物,stderr 会说明并给出两边的 sha256 前缀;与 `3` 同时发生时退出 `3`)/`1` 硬失败/`130` / `143` / `129` 被 Ctrl-C / `kill` / 终端断开中断(进程以该信号结束,shell 里的 `$?` 即此值,脚本循环会随之停下;本次导出的暂存文件已删除;自动命名的下载被中断时可能留下一个 0 字节的同名文件,删掉即可;中断前已发出的请求服务端照常处理并计费)。接 `| head` 等管道提前关掉读端时,退出码同样保留,脚本需 `set -o pipefail` 才看得到
442
- - **分页端点返回 `null` 也退出 3**:分页端点的正常响应是 `{total, list}`,真实的空结果是 `{total: 0, list: []}`。若响应体是 `null`,CLI 在 stderr 告警并**退出码 3**——只给告警的话,脚本无法区分「这个筛选确实没命中」和「这个筛选没生效」。机器格式(jsonl/csv)此时 **stdout 不输出任何字节**(不是空行),`--format json` 仍忠实打印 `null`。⚠️ 带 `--output` 时文件仍会被创建,为 0 字节(旁边的 `.meta.json` 标 `complete: false`)。
457
+ - **分页端点返回 `null` 也退出 3**:分页端点的正常响应是 `{total, list}`,真实的空结果是 `{total: 0, list: []}`。若响应体是 `null`,CLI 在 stderr 告警并**退出码 3**——只给告警的话,脚本无法区分「这个筛选确实没命中」和「这个筛选没生效」。机器格式(jsonl/csv)此时 **stdout 不输出任何字节**(不是空行),`--format json` 仍忠实打印 `null`。⚠️ 带 `--output` 时文件仍会被创建,为 0 字节(旁边的 `.meta.json` 标 `complete: false`)。例外:`insight highlight list`、`insight overseas-opinion list`、`reference overseas-account-search`、`ai expert-intel`、`ai management-sentiment-indicator`、`ai management-sentiment-conclusion` 收到 `null` 时直接报错、退出 1(stderr 带 trace),不输出数据。
443
458
  - 🔴 **`total` 被服务端封顶时会标 `totalCapped` 并退出 3**:分页端点的 `total` 若被服务端封顶(返回一个固定上限而非真实条数),省略 `--size` 的全量拉取会**正好取满那个上限就停、且不报任何异常**——导出的文件是截断的却看不出来。全量拉取结束后会**多探一行**(`from = total`):探到数据就标 `partial` + `totalCapped` 并退出 3;探针被服务端以偏移窗口类错误拒绝、或 `total` 正好等于端点声明的偏移窗口(`vault wechat-message-list` / `insight highlight list` 为 10000,窗口外的行取不到也数不到)时,同样按封顶处理。判据不写死 10000,服务端改配置仍然有效;`total` 诚实时探针返回空、不产生计费。传了 `--size` 的有界请求不做此探测。
444
459
  - 分页结果中 `total` 字段会被保留(json 格式输出 `{total, list}`);其他格式下 stderr 输出 `Total: N, showing: M`(json 格式不输出该行)
445
460
 
@@ -454,6 +469,8 @@ vault.stock-pool.create
454
469
 
455
470
  推荐工作流:先 `list` 再 `download`,文件名自动正确且零额外成本。
456
471
 
472
+ `vault wechat-message-download` 不走标题缓存:直接用服务端返回的文件名(一般就是群里发送时的文件名;图片、视频多为一串随机字符),没有时用 `wechat-file-{msgId}`,同名同样自动加 `-1`、`-2`。文件内容原样保存,不做转码。
473
+
457
474
  🔴 **缓存未命中时不会自动回查 list 接口**。回查要拉最近 200 条记录(4 次请求),而这些 list 多数按 **0.1 积分/条**计费,约 20 积分——这笔开销只用于取一个更易读的文件名(下载本身 1–30 积分),所以默认不回查。需要时加 `--resolve-title`:
458
475
 
459
476
  ```bash
@@ -462,6 +479,8 @@ gangtise insight research download --report-id 432092410345574400 --resolve-titl
462
479
 
463
480
  `--resolve-title` 取回的 200 条标题会一并写入缓存,所以同一批后续的下载不再重复回查。带 `--output` 时该参数无效(文件名已由你指定)。
464
481
 
482
+ `--output` 是**文件路径**(含文件名)。传目录——已存在的目录,或以 `/` 结尾的路径——会在发请求前报错、不扣费(所有命令都一样,包括列表导出);要存进某个目录又想自动命名,先 `cd` 到该目录再省略 `--output`。CLI 自己的配置目录(`~/.config/gangtise/`,存 token 缓存)与 `GANGTISE_TOKEN_CACHE_PATH` 指向的文件同样不能作 `--output`,也不能作 `--file` 上传。
483
+
465
484
  ## 常用示例
466
485
 
467
486
  ### 认证
@@ -471,6 +490,15 @@ gangtise auth login
471
490
  gangtise auth status
472
491
  ```
473
492
 
493
+ ### 发请求前先看一眼 / 只要几列
494
+
495
+ ```bash
496
+ # 打印将发出的请求、单价与最多扣多少积分,不发送、不需要凭证
497
+ gangtise insight research list --keyword 光伏 --size 30 --dry-run
498
+ # 每行只留这几列(所有格式都生效)
499
+ gangtise insight overseas-opinion list --size 20 --columns publishTime,accountName,briefTranslate
500
+ ```
501
+
474
502
  ### Insight
475
503
 
476
504
  ```bash
@@ -536,6 +564,13 @@ gangtise insight foreign-opinion list --security APP.O --rating buy --format jso
536
564
  # 外资观点原文 + 中文译文(content / contentTranslate)
537
565
  gangtise insight foreign-opinion detail --foreign-opinion-id 10911654
538
566
 
567
+ # 海外观点:先按账号名找 accountId,再按账号 / 证券 / 时间筛(--industry 用 GICS 一级行业 ID,见 lookup-ids.md)
568
+ gangtise reference overseas-account-search --keyword bloomberg --format json
569
+ gangtise insight overseas-opinion list --account 34713362 --start-time 2026-11-10 --end-time 2026-11-11 --size 50 --format json
570
+ gangtise insight overseas-opinion list --security NVDA.O --original-flag 1 --rank-type 2 --size 20 --format json
571
+ # 海外观点全文 + 中文译文(10 积分/条)
572
+ gangtise insight overseas-opinion detail --overseas-opinion-id 2120462799755149526
573
+
539
574
  # 外资独立观点
540
575
  gangtise insight independent-opinion list --keyword "肿瘤" --industry 100800118 --format json
541
576
  gangtise insight independent-opinion download --independent-opinion-id 207051900018372 --file-type 2
@@ -576,7 +611,7 @@ gangtise reference securities-search --keyword "贵州茅台" --category stock
576
611
  gangtise reference securities-search --keyword "600519" --category stock
577
612
  gangtise reference securities-search --keyword gzmt --top 5
578
613
  gangtise reference securities-search --keyword "银行" --category stock --category index
579
- # 债券:按代码或简称查,代码用于 bond 命令组;按简称查股票时同名可转债也会出现,按返回的 category 取
614
+ # 债券:按代码或简称查,代码用于 bond 命令组;查股票时带上 --category stock,否则同名可转债可能排在正股前面
580
615
  gangtise reference securities-search --keyword 甬金转债 --category bond
581
616
 
582
617
  # 首席分析师 ID 搜索(按姓名/机构/团队;拿 chiefId 供 insight opinion list --chief 使用)
@@ -584,7 +619,7 @@ gangtise reference chiefs-search --keyword 东吴证券 --top 3 --format json
584
619
  gangtise reference chiefs-search --keyword 芦哲 --format json
585
620
  # 机构 ID 搜索(--category: domesticBroker/foreignInstitution/leadInstitution/opinionInstitution/foreignOpinionInstitution)
586
621
  gangtise reference institution-search --keyword 招商证券 --category domesticBroker --top 3 --format json
587
- # 公众号 ID 搜索(按名称/机构/分类;拿 accountId 供 insight official-account list --account-id)
622
+ # 公众号 ID 搜索(按公众号名称;拿 accountId 供 insight official-account list --account-id)
588
623
  gangtise reference official-account-search --keyword 中信证券 --top 3 --format json
589
624
 
590
625
  # 常量查询:先看分类,再按分类导出全量常量值
@@ -600,6 +635,10 @@ gangtise reference concept-search --keyword jqr # 拼音首字母
600
635
  # 板块:先搜板块 ID,再查成分股(sectorId 必须来自 sector-search)
601
636
  gangtise reference sector-search --keyword 半导体设备 --format json
602
637
  gangtise reference sector-constituents --sector-id 1000001005 --format json
638
+
639
+ # 指数成分股权重(%):日期取月末交易日,可一次查多个指数
640
+ gangtise reference index-weight --index 000300.SH --date 2026-10-30 --format csv --output hs300-weight.csv
641
+ gangtise reference index-weight --index 000300.SH,000905.SH,000852.SH --date 2026-09-30 --format json
603
642
  ```
604
643
 
605
644
  ### Quote
@@ -771,6 +810,9 @@ gangtise ai theme-tracking --theme-id 121000131 --date 2026-03-01 --type morning
771
810
  gangtise ai hot-topic --start-date 2026-03-22 --end-date 2026-03-27 --category morningBriefing --category noonBriefing --with-related-securities --with-close-reading
772
811
  # 不传 --category 默认查全部类型(早报+午报+盘中快报+晚报),--with-related-securities 和 --with-close-reading 默认开启,可用 --no-with-related-securities / --no-with-close-reading 关闭
773
812
  gangtise ai hot-topic --start-date 2026-04-15 --end-date 2026-04-17
813
+ # AI 专家情报:20 积分/条,记得带 --size;--tag 是纯文本内容标签,多个为「任一命中」
814
+ gangtise ai expert-intel --security 300750.SZ --start-time 2026-10-01 --size 10
815
+ gangtise ai expert-intel --keyword 特斯拉 --concept 121000745 --tag 景气上行 --tag 超预期 --size 20
774
816
  gangtise ai research-outline --security-code 600519.SH
775
817
  # 管理层讨论-财报(三个细分维度)
776
818
  gangtise ai management-discuss-announcement --report-date 2025-06-30 --security-code 000001.SZ --dimension businessOperation
@@ -836,6 +878,9 @@ gangtise vault wechat-chatroom-list --room-name "AI学习群,投研分享群" --
836
878
  gangtise vault wechat-message-list --keyword AI应用 --wechat-group-id ueKEGyhdjFGkjyebh --category text --category url --tag roadShow --tag meetingSummary --size 50
837
879
  # 按证券代码过滤群消息
838
880
  gangtise vault wechat-message-list --security 000001.SZ --security 300750.SZ --size 50
881
+ # 群里的文件:先筛文档 / 图片 / 音频 / 视频消息拿 msgId,再按 msgId 下载(text / url 消息没有文件)
882
+ gangtise vault wechat-message-list --wechat-group-id ueKEGyhdjFGkjyebh --category document --category image --category audio --category video --size 50 --format json
883
+ gangtise vault wechat-message-download --msg-id 4784255865508301077
839
884
 
840
885
  # 自选股股票池
841
886
  gangtise vault stock-pool-list
@@ -900,7 +945,7 @@ gangtise indicator screener --indicator F1:mgn_flag \
900
945
  --security 1000000287 --expression "F1 contains '是'" \
901
946
  --date 2026-08-13 --format table # 白酒板块里的融资融券标的
902
947
 
903
- # 公司/证券静态属性(pty_* 经营范围·注册地 / scr_* 上市板块·ISIN 等)的 parameterList
948
+ # 公司/证券静态属性(pty_* 经营范围·注册地 / scr_* 上市板块·ISIN 等;行业分类 scr_indu* 除外,它要 --date)的 parameterList
904
949
  # 里没有日期参数,要加一条冒号后留空的绑定声明「该指标不要 --date 注入的 tradeDate」;
905
950
  # 截面与选股写法一致,选股上按变量名(F1:)、截面上按指标 code(code:):
906
951
  gangtise indicator screener --indicator F1:pty_op_scope --indicator-param "F1:" \
package/dist/src/cli.js CHANGED
@@ -1,13 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command, InvalidArgumentError } from "commander";
3
- import { ACCUMULATING_PARSERS, unknownChoiceMessage } from "./core/args.js";
4
- import { ApiError, ValidationError } from "./core/errors.js";
3
+ import { ACCUMULATING_PARSERS, collectList, unknownChoiceMessage } from "./core/args.js";
4
+ import { ApiError, ConfigError, ValidationError } from "./core/errors.js";
5
5
  import { removeStagingFiles } from "./core/output.js";
6
- import { isVerbose, setVerbose } from "./core/transport.js";
6
+ import { anyRequestSent, isVerbose, setVerbose } from "./core/transport.js";
7
+ import { isDryRun, setDryRun } from "./core/dryRun.js";
8
+ import { assertOutputFilePath } from "./core/download.js";
9
+ import { setColumns } from "./core/printer.js";
7
10
  import { checkForUpdate, noticeUpdate } from "./core/updateCheck.js";
8
11
  import { CLI_VERSION } from "./version.js";
9
12
  import { COMMAND_GROUPS } from "./commands/groups.js";
10
- import { decidedExitCode, markFailed } from "./core/exitStatus.js";
13
+ import { decidedExitCode, EXIT_REFUSED, markFailed, markRefused } from "./core/exitStatus.js";
11
14
  const program = new Command();
12
15
  let ranCommand;
13
16
  program
@@ -15,10 +18,18 @@ program
15
18
  .description("Gangtise OpenAPI CLI")
16
19
  .version(CLI_VERSION)
17
20
  .option("--verbose", "Print per-request timings to stderr (also: GANGTISE_VERBOSE=1)")
18
- .hook("preAction", (thisCommand, actionCommand) => {
21
+ .option("--dry-run", "Print the command's first request (endpoint, body, published price) as JSON and exit 0 without sending it; needs no credentials")
22
+ .option("--columns <names>", "Keep only these columns, in this order, in every format (comma-separate or repeat), e.g. --columns title,publishTime", collectList, [])
23
+ .hook("preAction", async (thisCommand, actionCommand) => {
19
24
  if (thisCommand.opts().verbose)
20
25
  setVerbose(true);
26
+ if (thisCommand.opts().dryRun)
27
+ setDryRun(true);
28
+ setColumns(thisCommand.opts().columns);
21
29
  ranCommand = actionCommand;
30
+ const output = actionCommand.opts().output;
31
+ if (typeof output === "string")
32
+ await assertOutputFilePath(output);
22
33
  });
23
34
  for (const group of COMMAND_GROUPS) {
24
35
  program.addCommand(group);
@@ -45,15 +56,22 @@ function refuseRepeatedSingleValues(command) {
45
56
  if (!takesValue || option.variadic || (original && ACCUMULATING_PARSERS.has(original)))
46
57
  continue;
47
58
  let given = 0;
59
+ const names = option.flags.replace(/\s*[<[].*$/, "");
48
60
  option.argParser((value, previous) => {
49
61
  if (++given > 1)
50
- throw new ValidationError(`${option.long} was given more than once; it takes a single value. Pass it once — options that take several values say "repeatable" in --help.`);
62
+ throw new ValidationError(`${names} was given more than once; it takes a single value. Pass it once — options that take several values say "repeatable" in --help.`);
51
63
  return original ? original(value, previous) : value;
52
64
  });
53
65
  }
54
66
  command.commands.forEach(refuseRepeatedSingleValues);
55
67
  }
56
68
  refuseRepeatedSingleValues(program);
69
+ function exitTwoOnUsageErrors(command) {
70
+ command.exitOverride((error) => process.exit(error.exitCode === 0 ? 0 : EXIT_REFUSED));
71
+ command.configureHelp({ showGlobalOptions: true });
72
+ command.commands.forEach(exitTwoOnUsageErrors);
73
+ }
74
+ exitTwoOnUsageErrors(program);
57
75
  const FATAL_FLUSH_MS = 200;
58
76
  let leaving = false;
59
77
  function reportFatal(error) {
@@ -121,14 +139,17 @@ async function main() {
121
139
  }
122
140
  if (error instanceof Error) {
123
141
  process.stderr.write(`${error.name}: ${error.message}\n`);
124
- markFailed();
142
+ if ((error instanceof ValidationError || error instanceof ConfigError) && !anyRequestSent())
143
+ markRefused();
144
+ else
145
+ markFailed();
125
146
  return;
126
147
  }
127
148
  process.stderr.write("Unknown error\n");
128
149
  markFailed();
129
150
  }
130
151
  finally {
131
- if (process.stderr.isTTY && !process.env.CI && !process.env.GANGTISE_NO_UPDATE_CHECK && ranCommand?.name() !== "update")
152
+ if (process.stderr.isTTY && !process.env.CI && !process.env.GANGTISE_NO_UPDATE_CHECK && ranCommand?.name() !== "update" && !isDryRun())
132
153
  noticeUpdate(CLI_VERSION);
133
154
  }
134
155
  }
@@ -6,7 +6,7 @@ import { ValidationError } from "../core/errors.js";
6
6
  import { markFailed } from "../core/exitStatus.js";
7
7
  import { parseOutputFormat } from "../core/output.js";
8
8
  import { printData } from "../core/printer.js";
9
- import { emit, withClient, runDownload, multiChoice, field, required, date, list, choiceList, flag, format, output, from, size, startTime, endTime, requestBody, query } from "./shared.js";
9
+ import { emit, withClient, runDownload, multiChoice, field, value, required, date, list, choiceList, flag, format, output, from, size, startTime, endTime, requestBody, query, OUTPUT_HELP, DOWNLOAD_OUTPUT_HELP, securityCode } from "./shared.js";
10
10
  import { checkMarketKeywords } from "./quote.js";
11
11
  export const ai = new Command("ai").description("AI APIs");
12
12
  function addGenerationTask(name, spec) {
@@ -36,7 +36,7 @@ function addGenerationTask(name, spec) {
36
36
  markFailed();
37
37
  }
38
38
  }));
39
- ai.command(`${name}-check`).requiredOption("--data-id <id>", `dataId from ${name}`).option("--format <format>", "Output format", "json").option("--output <path>").action((options) => withClient((client) => checkAsyncContent(client, spec.content, options.dataId, parseOutputFormat(options.format), options.output)));
39
+ ai.command(`${name}-check`).requiredOption("--data-id <id>", `dataId from ${name}`).option("--format <format>", "Output format", "json").option("--output <path>", OUTPUT_HELP).action((options) => withClient((client) => checkAsyncContent(client, spec.content, options.dataId, parseOutputFormat(options.format), options.output)));
40
40
  }
41
41
  ai.command("knowledge-batch")
42
42
  .option("--query <text>", "Query text; repeat for up to 5. Each --query is taken whole — commas inside it are part of the question, not separators", collectText, [])
@@ -46,13 +46,13 @@ ai.command("knowledge-batch")
46
46
  .option("--start-time <datetime>", "13/10-digit epoch or YYYY-MM-DD[ HH:mm[:ss]] (space or T)")
47
47
  .option("--end-time <datetime>", "13/10-digit epoch or YYYY-MM-DD[ HH:mm[:ss]] (space or T)")
48
48
  .option("--format <format>", "Output format", "json")
49
- .option("--output <path>")
49
+ .option("--output <path>", OUTPUT_HELP)
50
50
  .action((options) => {
51
51
  if (!options.query.length)
52
52
  throw new ValidationError("--query is required: pass at least one --query");
53
53
  return emit(options, (client) => client.call("ai.knowledge-batch", { queries: options.query, top: parseNumberOption(options.top, "--top", { integer: true, min: 1, max: 20 }), resourceTypes: options.resourceType.length ? options.resourceType : undefined, knowledgeNames: maybeArray(options.knowledgeName), startTime: parseTimestamp13(options.startTime, "--start-time"), endTime: parseEndTimestamp13(options.endTime, "--end-time") }));
54
54
  });
55
- ai.command("knowledge-resource-download").requiredOption("--resource-type <number>").requiredOption("--source-id <id>").option("--output <path>").action((options) => withClient(async (client) => {
55
+ ai.command("knowledge-resource-download").requiredOption("--resource-type <number>", "resourceType of the hit, from knowledge-batch").requiredOption("--source-id <id>", "sourceId of the hit, from knowledge-batch").option("--output <path>", DOWNLOAD_OUTPUT_HELP).action((options) => withClient(async (client) => {
56
56
  await runDownload(client, "ai.knowledge-resource.download", { resourceType: parseNumberOption(options.resourceType, "--resource-type", { integer: true, min: 0 }), sourceId: options.sourceId }, {
57
57
  output: options.output,
58
58
  fallbackName: `resource-${options.sourceId}`,
@@ -70,15 +70,15 @@ query(ai, "security-clue", {
70
70
  format(), output(),
71
71
  ],
72
72
  });
73
- query(ai, "one-pager", { endpoint: "ai.one-pager", fields: [required("--security-code <code>", undefined, "securityCode"), format("json"), output()] });
74
- query(ai, "investment-logic", { endpoint: "ai.investment-logic", fields: [required("--security-code <code>", undefined, "securityCode"), format("json"), output()] });
75
- query(ai, "peer-comparison", { endpoint: "ai.peer-comparison", fields: [required("--security-code <code>", undefined, "securityCode"), format("json"), output()] });
73
+ query(ai, "one-pager", { endpoint: "ai.one-pager", fields: [securityCode(), format("json"), output()] });
74
+ query(ai, "investment-logic", { endpoint: "ai.investment-logic", fields: [securityCode(), format("json"), output()] });
75
+ query(ai, "peer-comparison", { endpoint: "ai.peer-comparison", fields: [securityCode(), format("json"), output()] });
76
76
  addGenerationTask("earnings-review", {
77
77
  submit: "ai.earnings-review.get-id",
78
78
  content: "ai.earnings-review.get-content",
79
79
  label: "Earnings review",
80
80
  idFailure: "Failed to get earnings review ID. The report may not be available yet.",
81
- fields: [required("--security-code <code>", undefined, "securityCode"), required("--period <period>", "Report period (e.g. 2025q3, 2025interim, 2025annual)", "period")],
81
+ fields: [securityCode(), required("--period <period>", "Report period (e.g. 2025q3, 2025interim, 2025annual)", "period")],
82
82
  });
83
83
  query(ai, "theme-tracking", {
84
84
  endpoint: "ai.theme-tracking",
@@ -89,7 +89,7 @@ query(ai, "theme-tracking", {
89
89
  format("json"), output(),
90
90
  ],
91
91
  });
92
- query(ai, "research-outline", { endpoint: "ai.research-outline", fields: [required("--security-code <code>", undefined, "securityCode"), format("json"), output()] });
92
+ query(ai, "research-outline", { endpoint: "ai.research-outline", fields: [securityCode(), format("json"), output()] });
93
93
  const HOT_TOPIC_CATEGORIES = ["morningBriefing", "noonBriefing", "afternoonFlash", "eveningBriefing"];
94
94
  query(ai, "hot-topic", {
95
95
  endpoint: "ai.hot-topic",
@@ -105,11 +105,26 @@ query(ai, "hot-topic", {
105
105
  format("json"), output(),
106
106
  ],
107
107
  });
108
+ query(ai, "expert-intel", {
109
+ description: "AI expert intel: company takeaways distilled from expert meetings and minutes, newest first (20 credits/row)",
110
+ endpoint: "ai.expert-intel.list",
111
+ fields: [
112
+ from(), size("Total rows to return; omit to fetch all (max page 50). Billed per row — bound this on wide ranges"),
113
+ startTime("Publish time from (yyyy-MM-dd or yyyy-MM-dd HH:mm:ss)"), endTime("Publish time to (yyyy-MM-dd or yyyy-MM-dd HH:mm:ss; a bare date covers the whole day)"),
114
+ value("--keyword <text>", "Keyword", "keyword"),
115
+ field(new Option("--search-type <number>", "Where --keyword matches: 1=title 2=title + body (the API default). Titles carry no company name — filter companies with --security").choices(["1", "2"]), (v) => ({ searchType: v === undefined ? undefined : Number(v) })),
116
+ list("--security <code>", "Security code with market suffix, e.g. 300750.SZ (repeatable); intel linked to ANY of them matches. A missing or unknown suffix rejects the call (120001); a real suffix of the wrong market (600519.SZ) is not rejected and matches nothing", "securityList"),
117
+ list("--research-area <id>", "Research area ID: citicIndustry code (1008001xx, reference constant-list). swIndustry (104xx0000), gangtiseIndustry direction (122000xxx) and unknown codes return 0 here", "researchAreaList"),
118
+ list("--concept <id>", "Concept ID (reference concept-search); an unknown ID matches nothing", "conceptList"),
119
+ list("--tag <text>", "Content tag, plain text, e.g. 景气上行 / 超预期 / 重大催化 (repeatable; any of them matches)", "tagList"),
120
+ format("json"), output(),
121
+ ],
122
+ });
108
123
  query(ai, "management-discuss-announcement", {
109
124
  endpoint: "ai.management-discuss-announcement",
110
125
  fields: [
111
126
  date("--report-date <date>", "Report date (yyyy-MM-dd, e.g. 2025-06-30)", "reportDate", { required: true }),
112
- required("--security-code <code>", "Security code (e.g. 000001.SZ)", "securityCode"),
127
+ securityCode("Security code, e.g. 000001.SZ"),
113
128
  field(new Option("--dimension <name>", "Discussion dimension: businessOperation/financialPerformance/developmentAndRisk/all").choices(["businessOperation", "financialPerformance", "developmentAndRisk", "all"]).makeOptionMandatory(), (v) => ({ discussionDimension: v })),
114
129
  format("json"), output(),
115
130
  ],
@@ -118,7 +133,7 @@ query(ai, "management-discuss-earnings-call", {
118
133
  endpoint: "ai.management-discuss-earnings-call",
119
134
  fields: [
120
135
  date("--report-date <date>", "Report date (yyyy-MM-dd, e.g. 2025-06-30)", "reportDate", { required: true }),
121
- required("--security-code <code>", "Security code (e.g. 000001.SZ)", "securityCode"),
136
+ securityCode("Security code, e.g. 000001.SZ"),
122
137
  field(new Option("--dimension <name>", "Discussion dimension").choices(["businessOperation", "financialPerformance", "developmentAndRisk"]).makeOptionMandatory(), (v) => ({ discussionDimension: v })),
123
138
  format("json"), output(),
124
139
  ],
@@ -159,7 +174,7 @@ ai.command("stock-summary")
159
174
  .description("Stock highlights: refined research summary per security (A-share / HK)")
160
175
  .option("--security <code>", `Security code (e.g. 600519.SH / 00700.HK), up to ${STOCK_SUMMARY_MAX_SECURITIES} per call; market keywords are NOT supported by this endpoint`, collectList, [])
161
176
  .option("--format <format>", "Output format", "table")
162
- .option("--output <path>")
177
+ .option("--output <path>", OUTPUT_HELP)
163
178
  .action((options) => {
164
179
  if (!options.security.length)
165
180
  throw new ValidationError(`--security is required: pass one or more security codes (A-share / HK), up to ${STOCK_SUMMARY_MAX_SECURITIES} per call`);
@@ -3,7 +3,7 @@ import { collectList, dateArg, parseNumberOption } from "../core/args.js";
3
3
  import { assertColumnarHeader, zipFieldRow } from "../core/normalize.js";
4
4
  import { parseOutputFormat } from "../core/output.js";
5
5
  import { printData } from "../core/printer.js";
6
- import { withClient, field, required, flag, format, output, query } from "./shared.js";
6
+ import { withClient, field, required, flag, format, output, query, OUTPUT_HELP } from "./shared.js";
7
7
  export const alternative = new Command("alternative").description("Alternative data APIs");
8
8
  query(alternative, "edb-search", {
9
9
  endpoint: "alternative.edb-search",
@@ -18,7 +18,7 @@ alternative.command("edb-data")
18
18
  .requiredOption("--start-date <date>", "Start date (yyyy-MM-dd)", dateArg("--start-date"))
19
19
  .requiredOption("--end-date <date>", "End date (yyyy-MM-dd)", dateArg("--end-date"))
20
20
  .option("--format <format>", "Output format", "table")
21
- .option("--output <path>")
21
+ .option("--output <path>", OUTPUT_HELP)
22
22
  .action((options) => withClient(async (client) => {
23
23
  const format = parseOutputFormat(options.format);
24
24
  const raw = await client.call("alternative.edb-data", {