gangtise-openapi-cli 0.15.1 → 0.17.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.
package/README.md CHANGED
@@ -4,6 +4,30 @@
4
4
 
5
5
  ## Changelog
6
6
 
7
+ ### v0.17.0 — 2026-06-15
8
+
9
+ **接口变更(Breaking)**
10
+ - 日程类命令(`roadshow` / `site-visit` / `strategy` / `forum` list)改为各自只暴露 API spec 支持的筛选选项,移除原先一刀切多出的无效选项:`strategy` 仅保留 `--institution` / `--location`;`forum` 仅保留 `--research-area` / `--location`;`site-visit` 移除 `--participant-role` / `--broker-type`;`roadshow` 移除 `--object`。传不支持的选项现由 commander 直接报 `unknown option`(此前会静默发送、服务端返回空结果)
11
+ - `insight announcement list` 移除无效的 `--announcement-type`(服务端忽略、恒返全量);A 股公告分类筛选用 `--category`(`aShareAnnouncementCategory` 常量 ID)
12
+
13
+ **说明 / 修正**
14
+ - `--industry` 用 `citicIndustry` 码(`1008001xx`,全命令通用);`--research-area` 用 `gangtiseIndustry` 码(行业 `1008001xx` + 宏观/策略/固收/金工/海外等方向 `122000xxx`)。详见 `gangtise-openapi/references/commands/reference-and-lookup.md`
15
+ - 日程类 `--location`(domesticCity)服务端过滤已生效(v0.16.0 时曾未生效)
16
+
17
+ ### v0.16.0 — 2026-06-12
18
+
19
+ **新增接口(参考数据 · 常量查询,均免积分)**
20
+ - `reference constant-category` — 查询常量分类:全量导出常量分类及各分类适用于哪些接口的哪些参数(7 个分类:中信/申万/Gangtise 行业、国内城市、A股/港股公告分类、区域)
21
+ - `reference constant-list --category <code>` — 查询常量值:按分类导出全量常量(`constantId` / `constantName`,树形分类含 `children` 嵌套)
22
+ - `reference concept-search --keyword <kw>` — 查询题材 ID:按名称/拼音/分组名搜索,返回 `conceptId`(供 `alternative concept-info / concept-securities`、`ai theme-tracking` 使用)
23
+ - `reference sector-search --keyword <kw>` — 查询板块 ID:返回 `sectorId` + `hierarchy` 层级路径
24
+ - `reference sector-constituents --sector-id <id>` — 查询板块成分股:返回该板块全量成分股(`gtsCode` / `gtsName`);注意 sectorId 必须来自 sector-search,题材 conceptId 查不到成分
25
+
26
+ **接口变更(Breaking)**
27
+ - 移除已被新 API 覆盖的 6 个本地 lookup 子命令及静态数据:`lookup research-area / industry / region / announcement-category / theme-id / industry-code list`,请改用 `reference constant-list` / `reference concept-search` / `reference sector-constituents`(申万行业代码 `821xxx.SWI` 全量:`sector-constituents --sector-id 2000000014`,即申万一级行业指数板块)
28
+ - `lookup` 仅保留 2 个 API 未覆盖的本地表:`broker-org` / `meeting-org`
29
+ - 路演/调研/策略会/论坛 list 新增 `--location <id>` 按城市过滤(domesticCity 常量 ID;服务端过滤 v0.17.0 起已生效)
30
+
7
31
  ### v0.15.0 — 2026-05-29
8
32
 
9
33
  **新增接口**
@@ -135,7 +159,7 @@ npm version patch --no-git-tag-version
135
159
  npm run prepare
136
160
  VERSION=$(node -p "require('./package.json').version")
137
161
  git commit -am "chore: release v$VERSION"
138
- git tag "v$VERSION"
162
+ git tag -a "v$VERSION" -m "v$VERSION" # 必须 annotated:--follow-tags 不推 lightweight tag
139
163
  git push --follow-tags
140
164
  ```
141
165
 
@@ -235,7 +259,7 @@ cp -r gangtise-openapi ~/.hermes/skills/gangtise-openapi
235
259
  | 模块 | 子命令 | 说明 |
236
260
  |------|--------|------|
237
261
  | **Auth** | `login` / `status` | 认证登录、状态查询 |
238
- | **Lookup** | `research-area list` / `broker-org list` / `meeting-org list` / `industry list` / `industry-code list` / `region list` / `announcement-category list` / `theme-id list` | 枚举速查(内置,无需额外文档) |
262
+ | **Lookup** | `broker-org list` / `meeting-org list` | 本地枚举表(API 未覆盖的部分;行业/区域/公告分类/题材/申万行业代码改用 Reference 接口) |
239
263
  | **Insight** | `opinion list` | 内资机构观点 |
240
264
  | | `summary list` / `download` | 纪要(含下载,支持 `--file-type` 选原始/HTML) |
241
265
  | | `roadshow list` | 路演 |
@@ -249,6 +273,11 @@ cp -r gangtise-openapi ~/.hermes/skills/gangtise-openapi
249
273
  | | `foreign-opinion list` | 外资机构观点 |
250
274
  | | `independent-opinion list` / `download` | 外资独立分析师观点(含原文/翻译HTML下载) |
251
275
  | **Reference** | `securities-search` | GTS Code 搜索(按名称/代码/拼音匹配) |
276
+ | | `constant-category` | 常量分类列表(含各分类适用的接口与参数) |
277
+ | | `constant-list` | 按分类导出常量值全量列表(行业/城市/公告分类/区域等) |
278
+ | | `concept-search` | 题材 ID 搜索(名称/拼音/分组名匹配) |
279
+ | | `sector-search` | 板块 ID 搜索(返回层级路径) |
280
+ | | `sector-constituents` | 板块成分股查询 |
252
281
  | **Quote** | `day-kline` / `day-kline-hk` / `day-kline-us` | A股/港股/美股历史日K线 |
253
282
  | | `index-day-kline` | 沪深京指数日K线 |
254
283
  | | `minute-kline` | A股分钟K线 |
@@ -302,19 +331,21 @@ cp -r gangtise-openapi ~/.hermes/skills/gangtise-openapi
302
331
  先查枚举/参数:
303
332
 
304
333
  ```bash
305
- gangtise lookup research-area list
306
- gangtise lookup broker-org list
307
- gangtise lookup meeting-org list
308
- gangtise lookup industry list
309
- gangtise lookup region list # 外资研报区域
310
- gangtise lookup announcement-category list # 公告分类
311
- gangtise lookup industry-code list # 申万行业代码(用于 security-clue --gts-code
334
+ gangtise reference constant-category # 有哪些常量分类、各用于哪些参数
335
+ gangtise reference constant-list --category citicIndustry # 中信行业(--industry 通用)
336
+ gangtise reference constant-list --category gangtiseIndustry # Gangtise 行业 + 方向(--research-area 用)
337
+ gangtise reference constant-list --category swIndustry # 申万行业
338
+ gangtise reference constant-list --category regionCategory # 外资研报区域
339
+ gangtise reference constant-list --category aShareAnnouncementCategory # A股公告分类(树形)
340
+ gangtise reference sector-constituents --sector-id 2000000014 # 申万行业代码 821xxx.SWI 全量(security-clue --gts-code 用)
341
+ gangtise lookup broker-org list # 券商机构(本地表)
342
+ gangtise lookup meeting-org list # 会议机构(本地表)
312
343
  ```
313
344
 
314
345
  再调用业务命令:
315
346
 
316
347
  ```bash
317
- gangtise insight opinion list --industry 104710000
348
+ gangtise insight opinion list --industry 100800128
318
349
  gangtise insight summary list --institution C100000017
319
350
  gangtise quote day-kline --security 600519.SH --start-date 2025-03-01 --end-date 2025-03-12
320
351
  gangtise ai knowledge-batch --query 比亚迪 --query 最近热门概念
@@ -388,10 +419,10 @@ gangtise auth status
388
419
  gangtise insight research list --start-time "2026-04-01 00:00:00" --end-time "2026-04-09 23:59:59"
389
420
 
390
421
  # 无时间范围 → 默认 --size 200
391
- gangtise insight research list --industry 104270000 --category company --llm-tag inDepth --rating buy
422
+ gangtise insight research list --industry 100800126 --category company --llm-tag inDepth --rating buy
392
423
 
393
424
  # 多值 List 模式:一次查多家券商 + 多个行业 + 多个评级
394
- gangtise insight research list --broker C100000027 --broker C100000014 --industry 104340000 --industry 104370000 --rating buy --rating overweight --format json
425
+ gangtise insight research list --broker C100000027 --broker C100000014 --industry 100800119 --industry 100800118 --rating buy --rating overweight --format json
395
426
 
396
427
  gangtise insight opinion list --keyword AI
397
428
  gangtise insight summary list --keyword 算力
@@ -421,7 +452,7 @@ gangtise insight foreign-opinion list --keyword "自动驾驶" --region us --ran
421
452
  gangtise insight foreign-opinion list --security APP.O --rating buy --format json
422
453
 
423
454
  # 外资独立观点
424
- gangtise insight independent-opinion list --keyword "肿瘤" --industry 104370000 --format json
455
+ gangtise insight independent-opinion list --keyword "肿瘤" --industry 100800118 --format json
425
456
  gangtise insight independent-opinion download --independent-opinion-id 207051900018372 --file-type 2
426
457
 
427
458
  # 纪要下载(会议平台来源可选 HTML 格式)
@@ -436,6 +467,19 @@ gangtise reference securities-search --keyword "贵州茅台" --category stock
436
467
  gangtise reference securities-search --keyword "600519" --category stock
437
468
  gangtise reference securities-search --keyword gzmt --top 5
438
469
  gangtise reference securities-search --keyword "银行" --category stock --category index
470
+
471
+ # 常量查询:先看分类,再按分类导出全量常量值
472
+ gangtise reference constant-category --format json
473
+ gangtise reference constant-list --category citicIndustry --format json
474
+ gangtise reference constant-list --category aShareAnnouncementCategory --format json # 树形,含 children
475
+
476
+ # 题材 ID 搜索(供 concept-info / concept-securities / theme-tracking 使用)
477
+ gangtise reference concept-search --keyword 机器人 --top 3 --format json
478
+ gangtise reference concept-search --keyword jqr # 拼音首字母
479
+
480
+ # 板块:先搜板块 ID,再查成分股(sectorId 必须来自 sector-search)
481
+ gangtise reference sector-search --keyword 半导体设备 --format json
482
+ gangtise reference sector-constituents --sector-id 1000001005 --format json
439
483
  ```
440
484
 
441
485
  ### Quote
@@ -593,7 +637,7 @@ gangtise alternative edb-data \
593
637
  --output ./indicator.csv
594
638
 
595
639
  # 题材指数:先查 conceptId(与 theme-id 共用 ID 体系),再拉画像 / 成分股
596
- gangtise lookup theme-id list | grep 机器人 # → 121000130
640
+ gangtise reference concept-search --keyword 机器人 --format json # → 121000130
597
641
  gangtise alternative concept-info --concept-id 121000130 --format json
598
642
  # 题材成分股(题材深度 F8,按分组返回,标记重点个股)
599
643
  gangtise alternative concept-securities --concept-id 121000130 --format json
package/dist/src/cli.js CHANGED
@@ -108,21 +108,15 @@ program
108
108
  const cache = await readTokenCache(config.tokenCachePath);
109
109
  await printData({ hasEnvToken: Boolean(config.token), hasCachedToken: Boolean(cache?.accessToken), cache }, parseOutputFormat(options.format));
110
110
  }));
111
- const lookup = new Command("lookup").description("Lookup helper APIs");
111
+ const lookup = new Command("lookup").description("Local lookup tables (IDs not covered by 'reference constant-list')");
112
112
  const addLookupList = (name, endpointKey, description) => {
113
113
  const cmd = new Command(name);
114
114
  if (description)
115
115
  cmd.description(description);
116
116
  lookup.addCommand(cmd.addCommand(new Command("list").option("--format <format>", "Output format", "table").action((options) => emit(options, (client) => client.call(endpointKey)))));
117
117
  };
118
- addLookupList("research-area", "lookup.research-areas.list");
119
118
  addLookupList("broker-org", "lookup.broker-orgs.list");
120
119
  addLookupList("meeting-org", "lookup.meeting-orgs.list");
121
- addLookupList("industry", "lookup.industries.list");
122
- addLookupList("region", "lookup.regions.list", "Foreign report region codes");
123
- addLookupList("announcement-category", "lookup.announcement-categories.list", "Announcement category codes");
124
- addLookupList("industry-code", "lookup.industry-codes.list", "Shenwan industry codes for security-clue --gts-code");
125
- addLookupList("theme-id", "lookup.theme-ids.list", "Theme IDs for theme-tracking --theme-id");
126
120
  program.addCommand(lookup);
127
121
  const insight = new Command("insight").description("Insight APIs");
128
122
  const opinion = new Command("opinion");
@@ -150,16 +144,57 @@ addTimeFilters(summary.command("list").option("--search-type <number>", "Search
150
144
  categoryList: maybeArray(options.category), marketList: maybeArray(options.market), participantRoleList: maybeArray(options.participantRole),
151
145
  }), { endpointKey: "insight.summary.list", idField: "summaryId" }));
152
146
  addDownloadCommand(summary, { endpointKey: "insight.summary.download", idOption: "--summary-id", idField: "summaryId", fallbackPrefix: "summary", fileType: { description: "File type: 1=original(default) 2=HTML; only affects meeting platform summaries" }, titleListEndpoint: "insight.summary.list" });
153
- const addScheduleList = (command, endpointKey) => addTimeFilters(command.command("list").option("--research-area <id>", "Research area", collectList, []).option("--institution <id>", "Institution ID", collectList, []).option("--security <code>", "Security code", collectList, []).option("--category <name>", "Category", collectList, []).option("--market <name>", "Market", collectList, []).option("--participant-role <name>", "Participant role", collectList, []).option("--broker-type <name>", "Broker type", collectList, []).option("--object <type>", "Object type: company/industry", collectList, []).option("--permission <number>", "Permission", collectNumberList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call(endpointKey, {
154
- from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword,
155
- researchAreaList: maybeArray(options.researchArea), institutionList: maybeArray(options.institution), securityList: maybeArray(options.security),
156
- categoryList: maybeArray(options.category), marketList: maybeArray(options.market), participantRoleList: maybeArray(options.participantRole),
157
- brokerTypeList: maybeArray(options.brokerType), objectList: maybeArray(options.object), permission: options.permission.length ? options.permission : undefined,
158
- })));
159
- addScheduleList(roadshow, "insight.roadshow.list");
160
- addScheduleList(siteVisit, "insight.site-visit.list");
161
- addScheduleList(strategy, "insight.strategy.list");
162
- addScheduleList(forum, "insight.forum.list");
147
+ const addScheduleList = (command, endpointKey, fields) => {
148
+ const list = command.command("list");
149
+ if (fields.researchArea)
150
+ list.option("--research-area <id>", "Research area ID (constant-list category gangtiseIndustry: 1008001xx industries + 122000xxx macro/strategy/fixed-income/quant/overseas directions)", collectList, []);
151
+ if (fields.institution)
152
+ list.option("--institution <id>", "Lead institution ID", collectList, []);
153
+ if (fields.security)
154
+ list.option("--security <code>", "Security code", collectList, []);
155
+ if (fields.object)
156
+ list.option("--object <type>", "Object type: company/industry", collectList, []);
157
+ if (fields.category)
158
+ list.option("--category <name>", fields.category, collectList, []);
159
+ if (fields.market)
160
+ list.option("--market <name>", fields.market, collectList, []);
161
+ if (fields.participantRole)
162
+ list.option("--participant-role <name>", "Participant role: management/expert", collectList, []);
163
+ if (fields.brokerType)
164
+ list.option("--broker-type <name>", "Lead broker type: cnBroker/otherBroker", collectList, []);
165
+ if (fields.permission)
166
+ list.option("--permission <number>", "Permission: 1=public 2=private", collectNumberList, []);
167
+ if (fields.location)
168
+ list.option("--location <id>", "Location ID (domesticCity constant, via 'reference constant-list')", collectList, []);
169
+ list.option("--format <format>", "Output format", "table").option("--output <path>", "Output path");
170
+ addTimeFilters(list).action((options) => emit(options, (client) => client.call(endpointKey, {
171
+ from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword,
172
+ researchAreaList: fields.researchArea ? maybeArray(options.researchArea) : undefined,
173
+ institutionList: fields.institution ? maybeArray(options.institution) : undefined,
174
+ securityList: fields.security ? maybeArray(options.security) : undefined,
175
+ objectList: fields.object ? maybeArray(options.object) : undefined,
176
+ categoryList: fields.category ? maybeArray(options.category) : undefined,
177
+ marketList: fields.market ? maybeArray(options.market) : undefined,
178
+ participantRoleList: fields.participantRole ? maybeArray(options.participantRole) : undefined,
179
+ brokerTypeList: fields.brokerType ? maybeArray(options.brokerType) : undefined,
180
+ permission: fields.permission && options.permission?.length ? options.permission : undefined,
181
+ locationList: fields.location ? maybeArray(options.location) : undefined,
182
+ })));
183
+ };
184
+ addScheduleList(roadshow, "insight.roadshow.list", {
185
+ researchArea: true, institution: true, security: true, location: true,
186
+ category: "Roadshow type: earningsCall/strategyMeeting/companyAnalysis/industryAnalysis/fundRoadshow",
187
+ market: "Market: aShares/hkStocks/usChinaConcept/usStocks",
188
+ participantRole: true, brokerType: true, permission: true,
189
+ });
190
+ addScheduleList(siteVisit, "insight.site-visit.list", {
191
+ researchArea: true, institution: true, security: true, location: true, object: true,
192
+ category: "Site-visit form: single/series",
193
+ market: "Market: aShares/hkStocks/usChinaConcept",
194
+ permission: true,
195
+ });
196
+ addScheduleList(strategy, "insight.strategy.list", { institution: true, location: true });
197
+ addScheduleList(forum, "insight.forum.list", { researchArea: true, location: true });
163
198
  addTimeFilters(research.command("list").option("--search-type <number>", "Search type: 1=title 2=fulltext", "1").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--broker <id>", "Broker ID", collectList, []).option("--security <code>", "Security code", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--category <name>", "Report category", collectList, []).option("--llm-tag <tag>", "Semantic tag", collectList, []).option("--rating <name>", "Rating", collectList, []).option("--rating-change <name>", "Rating change", collectList, []).option("--min-pages <number>", "Min report pages").option("--max-pages <number>", "Max report pages").option("--source <type>", "Source type", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call("insight.research.list", {
164
199
  from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword,
165
200
  searchType: parseNumberOption(options.searchType, "--search-type", { integer: true, min: 1 }), rankType: parseNumberOption(options.rankType, "--rank-type", { integer: true, min: 1 }),
@@ -178,11 +213,11 @@ addTimeFilters(foreignReport.command("list").option("--search-type <number>", "S
178
213
  minReportPages: parseOptionalNumberOption(options.minPages, "--min-pages", { integer: true, min: 0 }), maxReportPages: parseOptionalNumberOption(options.maxPages, "--max-pages", { integer: true, min: 0 }),
179
214
  }), { endpointKey: "insight.foreign-report.list", idField: "reportId" }));
180
215
  addDownloadCommand(foreignReport, { endpointKey: "insight.foreign-report.download", idOption: "--report-id", idField: "reportId", fallbackPrefix: "foreign-report", fileType: { description: "File type: 1=PDF 2=Markdown 3=CN-PDF 4=CN-Markdown", default: "1" }, titleListEndpoint: "insight.foreign-report.list" });
181
- addTimeFilters(announcement.command("list").option("--search-type <number>", "Search type: 1=title 2=fulltext", "1").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--security <code>", "Security code", collectList, []).option("--announcement-type <type>", "Announcement type", collectList, []).option("--category <id>", "Category ID", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call("insight.announcement.list", {
216
+ addTimeFilters(announcement.command("list").option("--search-type <number>", "Search type: 1=title 2=fulltext", "1").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--security <code>", "Security code", collectList, []).option("--category <id>", "Category ID", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call("insight.announcement.list", {
182
217
  from: parseFrom(options.from), size: parseSize(options.size),
183
218
  startTime: parseTimestamp13(options.startTime, "--start-time"), endTime: parseTimestamp13(options.endTime, "--end-time"),
184
219
  searchType: parseNumberOption(options.searchType, "--search-type", { integer: true, min: 1 }), rankType: parseNumberOption(options.rankType, "--rank-type", { integer: true, min: 1 }), keyword: options.keyword,
185
- securityList: maybeArray(options.security), announcementTypeList: maybeArray(options.announcementType), categoryList: maybeArray(options.category),
220
+ securityList: maybeArray(options.security), categoryList: maybeArray(options.category),
186
221
  }), { endpointKey: "insight.announcement.list", idField: "announcementId" }));
187
222
  addDownloadCommand(announcement, { endpointKey: "insight.announcement.download", idOption: "--announcement-id", idField: "announcementId", fallbackPrefix: "announcement", fileType: { description: "File type: 1=PDF 2=Markdown", default: "1" }, titleListEndpoint: "insight.announcement.list" });
188
223
  addTimeFilters(announcementHk.command("list").option("--search-type <number>", "Search type: 1=title 2=fulltext", "1").option("--rank-type <number>", "Rank type: 1=composite 2=time desc", "1").option("--security <code>", "Security code (e.g. 01913.HK)", collectList, []).option("--category <id>", "Category ID", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call("insight.announcement-hk.list", {
@@ -302,7 +337,7 @@ ai.command("earnings-review").requiredOption("--security-code <code>").requiredO
302
337
  }
303
338
  }));
304
339
  ai.command("earnings-review-check").requiredOption("--data-id <id>", "dataId from earnings-review").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => withClient((client) => checkAsyncContent(client, "ai.earnings-review.get-content", options.dataId, parseOutputFormat(options.format), options.output)));
305
- ai.command("theme-tracking").requiredOption("--theme-id <id>", "Theme ID (use lookup theme-id list)").requiredOption("--date <date>", "Date (yyyy-MM-dd)").option("--type <name>", "Report type: morning/night", collectList, []).option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => {
340
+ ai.command("theme-tracking").requiredOption("--theme-id <id>", "Theme ID (use 'reference concept-search')").requiredOption("--date <date>", "Date (yyyy-MM-dd)").option("--type <name>", "Report type: morning/night", collectList, []).option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => {
306
341
  const typeList = options.type.length ? options.type : undefined;
307
342
  return client.call("ai.theme-tracking", { themeId: options.themeId, date: options.date, type: typeList });
308
343
  }));
@@ -355,6 +390,17 @@ reference.command("securities-search").requiredOption("--keyword <text>", "Searc
355
390
  category: options.category.length ? options.category : undefined,
356
391
  top: parseNumberOption(options.top, "--top", { integer: true, min: 1 }),
357
392
  })));
393
+ reference.command("constant-category").description("List constant categories and which API params accept them").option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("reference.constant-category")));
394
+ reference.command("constant-list").requiredOption("--category <code>", "Category code from 'reference constant-category' (e.g. citicIndustry/swIndustry/regionCategory)").option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("reference.constant-list", { category: options.category })));
395
+ reference.command("concept-search").requiredOption("--keyword <text>", "Search keyword (name/pinyin/group name)").option("--top <number>", "Max results (default: 10, max: 10)", "10").option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("reference.concept-search", {
396
+ keyword: options.keyword,
397
+ top: parseNumberOption(options.top, "--top", { integer: true, min: 1 }),
398
+ })));
399
+ reference.command("sector-search").option("--keyword <text>", "Search keyword (name/pinyin)").option("--top <number>", "Max results (default: 10, max: 10)", "10").option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("reference.sector-search", {
400
+ keyword: options.keyword,
401
+ top: parseNumberOption(options.top, "--top", { integer: true, min: 1 }),
402
+ })));
403
+ reference.command("sector-constituents").requiredOption("--sector-id <id>", "Sector ID from 'reference sector-search'").option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("reference.sector-constituents", { sectorId: options.sectorId })));
358
404
  program.addCommand(reference);
359
405
  const vault = new Command("vault").description("Vault APIs");
360
406
  vault.command("drive-list").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").option("--start-time <datetime>").option("--end-time <datetime>").option("--keyword <text>").option("--file-type <number>", "File type", collectNumberList, []).option("--space-type <number>", "Space type", collectNumberList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("vault.drive.list", { from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword, fileTypeList: options.fileType.length ? options.fileType : undefined, spaceTypeList: options.spaceType.length ? options.spaceType : undefined }), { endpointKey: "vault.drive.list", idField: "fileId" }));
@@ -390,8 +436,8 @@ alternative.command("edb-data").option("--indicator-id <id>", "Indicator ID (rep
390
436
  }
391
437
  await printData(data, parseOutputFormat(options.format), options.output);
392
438
  }));
393
- alternative.command("concept-info").requiredOption("--concept-id <id>", "Concept (theme index) ID, e.g. 121000130 机器人; discover via 'gangtise lookup theme-id list'").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("alternative.concept-info", { conceptId: options.conceptId })));
394
- alternative.command("concept-securities").requiredOption("--concept-id <id>", "Concept (theme index) ID, e.g. 121000130 机器人; discover via 'gangtise lookup theme-id list'").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("alternative.concept-securities", { conceptId: options.conceptId })));
439
+ alternative.command("concept-info").requiredOption("--concept-id <id>", "Concept (theme index) ID, e.g. 121000130 机器人; discover via 'gangtise reference concept-search'").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("alternative.concept-info", { conceptId: options.conceptId })));
440
+ alternative.command("concept-securities").requiredOption("--concept-id <id>", "Concept (theme index) ID, e.g. 121000130 机器人; discover via 'gangtise reference concept-search'").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("alternative.concept-securities", { conceptId: options.conceptId })));
395
441
  program.addCommand(alternative);
396
442
  program.command("raw").description("Raw API calls").addCommand(new Command("call").argument("<endpointKey>").option("--body <json>").option("--query <key=value>", "Query string pair", collectKeyValue, {}).option("--format <format>", "Output format", "json").option("--output <path>").action(async (endpointKey, options) => {
397
443
  const endpoint = ENDPOINTS[endpointKey];
@@ -101,14 +101,8 @@ export class GangtiseClient {
101
101
  }
102
102
  async readLocalLookup(endpoint) {
103
103
  const keyMapping = {
104
- "lookup.research-areas.list": "research-areas",
105
104
  "lookup.broker-orgs.list": "broker-orgs",
106
105
  "lookup.meeting-orgs.list": "meeting-orgs",
107
- "lookup.industries.list": "industries",
108
- "lookup.regions.list": "regions",
109
- "lookup.announcement-categories.list": "announcement-categories",
110
- "lookup.industry-codes.list": "industry-codes",
111
- "lookup.theme-ids.list": "theme-ids",
112
106
  };
113
107
  const lookupKey = keyMapping[endpoint.key];
114
108
  if (lookupKey) {
@@ -8,13 +8,6 @@ export const ENDPOINTS = {
8
8
  description: "Get access token",
9
9
  },
10
10
  // ─── lookup (served from local data, not HTTP) ───
11
- "lookup.research-areas.list": {
12
- key: "lookup.research-areas.list",
13
- method: "GET",
14
- path: "/guide/research-area-local",
15
- kind: "json",
16
- description: "List research areas from local docs",
17
- },
18
11
  "lookup.broker-orgs.list": {
19
12
  key: "lookup.broker-orgs.list",
20
13
  method: "GET",
@@ -29,41 +22,6 @@ export const ENDPOINTS = {
29
22
  kind: "json",
30
23
  description: "List meeting orgs from local docs",
31
24
  },
32
- "lookup.industries.list": {
33
- key: "lookup.industries.list",
34
- method: "GET",
35
- path: "/guide/industries-local",
36
- kind: "json",
37
- description: "List industries from local docs",
38
- },
39
- "lookup.regions.list": {
40
- key: "lookup.regions.list",
41
- method: "GET",
42
- path: "/guide/regions-local",
43
- kind: "json",
44
- description: "List regions from local docs",
45
- },
46
- "lookup.announcement-categories.list": {
47
- key: "lookup.announcement-categories.list",
48
- method: "GET",
49
- path: "/guide/announcement-categories-local",
50
- kind: "json",
51
- description: "List announcement categories from local docs",
52
- },
53
- "lookup.industry-codes.list": {
54
- key: "lookup.industry-codes.list",
55
- method: "GET",
56
- path: "/guide/industry-codes-local",
57
- kind: "json",
58
- description: "List Shenwan industry codes from local docs",
59
- },
60
- "lookup.theme-ids.list": {
61
- key: "lookup.theme-ids.list",
62
- method: "GET",
63
- path: "/guide/theme-ids-local",
64
- kind: "json",
65
- description: "List theme IDs from local docs",
66
- },
67
25
  // ─── insight ───
68
26
  "insight.opinion.list": {
69
27
  key: "insight.opinion.list",
@@ -211,6 +169,41 @@ export const ENDPOINTS = {
211
169
  kind: "json",
212
170
  description: "Search GTS codes (securities)",
213
171
  },
172
+ "reference.constant-category": {
173
+ key: "reference.constant-category",
174
+ method: "GET",
175
+ path: "/application/open-reference/constants/category",
176
+ kind: "json",
177
+ description: "List constant categories and their API usage scopes",
178
+ },
179
+ "reference.constant-list": {
180
+ key: "reference.constant-list",
181
+ method: "POST",
182
+ path: "/application/open-reference/constants/getList",
183
+ kind: "json",
184
+ description: "List all constant values of a category",
185
+ },
186
+ "reference.concept-search": {
187
+ key: "reference.concept-search",
188
+ method: "POST",
189
+ path: "/application/open-reference/concepts/search",
190
+ kind: "json",
191
+ description: "Search concept (theme) IDs by keyword",
192
+ },
193
+ "reference.sector-search": {
194
+ key: "reference.sector-search",
195
+ method: "POST",
196
+ path: "/application/open-reference/sectors/search",
197
+ kind: "json",
198
+ description: "Search sector IDs by keyword",
199
+ },
200
+ "reference.sector-constituents": {
201
+ key: "reference.sector-constituents",
202
+ method: "POST",
203
+ path: "/application/open-reference/sectors/constituents",
204
+ kind: "json",
205
+ description: "List constituent securities of a sector",
206
+ },
214
207
  // ─── quote ───
215
208
  "quote.day-kline": {
216
209
  key: "quote.day-kline",
@@ -1,13 +1,7 @@
1
1
  const cache = new Map();
2
2
  const loaders = {
3
- "research-areas": () => import("./research-areas.js"),
4
3
  "broker-orgs": () => import("./broker-orgs.js"),
5
4
  "meeting-orgs": () => import("./meeting-orgs.js"),
6
- "industries": () => import("./industries.js"),
7
- "regions": () => import("./regions.js"),
8
- "announcement-categories": () => import("./announcement-categories.js"),
9
- "industry-codes": () => import("./industry-codes.js"),
10
- "theme-ids": () => import("./theme-ids.js"),
11
5
  };
12
6
  export async function getLookupData(key) {
13
7
  if (cache.has(key))
@@ -29,5 +29,10 @@ export function normalizeRows(value) {
29
29
  const hasMeta = Object.keys(meta).length > 0;
30
30
  return hasMeta ? { ...meta, list: chatRoomList } : chatRoomList;
31
31
  }
32
+ if (Array.isArray(record.constants)) {
33
+ const { constants, ...meta } = record;
34
+ const hasMeta = Object.keys(meta).length > 0;
35
+ return hasMeta ? { ...meta, list: constants } : constants;
36
+ }
32
37
  return value;
33
38
  }
@@ -1,2 +1,2 @@
1
1
  // Auto-generated — DO NOT EDIT
2
- export const CLI_VERSION = "0.15.1";
2
+ export const CLI_VERSION = "0.17.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gangtise-openapi-cli",
3
- "version": "0.15.1",
3
+ "version": "0.17.0",
4
4
  "description": "CLI for Gangtise OpenAPI",
5
5
  "license": "MIT",
6
6
  "repository": {