gangtise-openapi-cli 0.16.0 → 0.17.1

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,16 @@
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
+
7
17
  ### v0.16.0 — 2026-06-12
8
18
 
9
19
  **新增接口(参考数据 · 常量查询,均免积分)**
@@ -16,7 +26,7 @@
16
26
  **接口变更(Breaking)**
17
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`,即申万一级行业指数板块)
18
28
  - `lookup` 仅保留 2 个 API 未覆盖的本地表:`broker-org` / `meeting-org`
19
- - 路演/调研/策略会/论坛 list 新增 `--location <id>` 按城市过滤(domesticCity 常量 ID;实测 2026-06-12 服务端过滤暂未生效)
29
+ - 路演/调研/策略会/论坛 list 新增 `--location <id>` 按城市过滤(domesticCity 常量 ID;服务端过滤 v0.17.0 起已生效)
20
30
 
21
31
  ### v0.15.0 — 2026-05-29
22
32
 
@@ -322,7 +332,8 @@ cp -r gangtise-openapi ~/.hermes/skills/gangtise-openapi
322
332
 
323
333
  ```bash
324
334
  gangtise reference constant-category # 有哪些常量分类、各用于哪些参数
325
- gangtise reference constant-list --category citicIndustry # 中信行业(--industry / --research-area)
335
+ gangtise reference constant-list --category citicIndustry # 中信行业(--industry 通用)
336
+ gangtise reference constant-list --category gangtiseIndustry # Gangtise 行业 + 方向(--research-area 用)
326
337
  gangtise reference constant-list --category swIndustry # 申万行业
327
338
  gangtise reference constant-list --category regionCategory # 外资研报区域
328
339
  gangtise reference constant-list --category aShareAnnouncementCategory # A股公告分类(树形)
@@ -334,7 +345,7 @@ gangtise lookup meeting-org list # 会议机构(本地表)
334
345
  再调用业务命令:
335
346
 
336
347
  ```bash
337
- gangtise insight opinion list --industry 104710000
348
+ gangtise insight opinion list --industry 100800128
338
349
  gangtise insight summary list --institution C100000017
339
350
  gangtise quote day-kline --security 600519.SH --start-date 2025-03-01 --end-date 2025-03-12
340
351
  gangtise ai knowledge-batch --query 比亚迪 --query 最近热门概念
@@ -408,10 +419,10 @@ gangtise auth status
408
419
  gangtise insight research list --start-time "2026-04-01 00:00:00" --end-time "2026-04-09 23:59:59"
409
420
 
410
421
  # 无时间范围 → 默认 --size 200
411
- 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
412
423
 
413
424
  # 多值 List 模式:一次查多家券商 + 多个行业 + 多个评级
414
- 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
415
426
 
416
427
  gangtise insight opinion list --keyword AI
417
428
  gangtise insight summary list --keyword 算力
@@ -441,7 +452,7 @@ gangtise insight foreign-opinion list --keyword "自动驾驶" --region us --ran
441
452
  gangtise insight foreign-opinion list --security APP.O --rating buy --format json
442
453
 
443
454
  # 外资独立观点
444
- gangtise insight independent-opinion list --keyword "肿瘤" --industry 104370000 --format json
455
+ gangtise insight independent-opinion list --keyword "肿瘤" --industry 100800118 --format json
445
456
  gangtise insight independent-opinion download --independent-opinion-id 207051900018372 --file-type 2
446
457
 
447
458
  # 纪要下载(会议平台来源可选 HTML 格式)
package/dist/src/cli.js CHANGED
@@ -144,17 +144,57 @@ addTimeFilters(summary.command("list").option("--search-type <number>", "Search
144
144
  categoryList: maybeArray(options.category), marketList: maybeArray(options.market), participantRoleList: maybeArray(options.participantRole),
145
145
  }), { endpointKey: "insight.summary.list", idField: "summaryId" }));
146
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" });
147
- 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("--location <id>", "Location ID (domesticCity constant, via 'reference constant-list')", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action((options) => emit(options, (client) => client.call(endpointKey, {
148
- from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword,
149
- researchAreaList: maybeArray(options.researchArea), institutionList: maybeArray(options.institution), securityList: maybeArray(options.security),
150
- categoryList: maybeArray(options.category), marketList: maybeArray(options.market), participantRoleList: maybeArray(options.participantRole),
151
- brokerTypeList: maybeArray(options.brokerType), objectList: maybeArray(options.object), permission: options.permission.length ? options.permission : undefined,
152
- locationList: maybeArray(options.location),
153
- })));
154
- addScheduleList(roadshow, "insight.roadshow.list");
155
- addScheduleList(siteVisit, "insight.site-visit.list");
156
- addScheduleList(strategy, "insight.strategy.list");
157
- 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 });
158
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", {
159
199
  from: parseFrom(options.from), size: parseSize(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword,
160
200
  searchType: parseNumberOption(options.searchType, "--search-type", { integer: true, min: 1 }), rankType: parseNumberOption(options.rankType, "--rank-type", { integer: true, min: 1 }),
@@ -173,11 +213,11 @@ addTimeFilters(foreignReport.command("list").option("--search-type <number>", "S
173
213
  minReportPages: parseOptionalNumberOption(options.minPages, "--min-pages", { integer: true, min: 0 }), maxReportPages: parseOptionalNumberOption(options.maxPages, "--max-pages", { integer: true, min: 0 }),
174
214
  }), { endpointKey: "insight.foreign-report.list", idField: "reportId" }));
175
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" });
176
- 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", {
177
217
  from: parseFrom(options.from), size: parseSize(options.size),
178
218
  startTime: parseTimestamp13(options.startTime, "--start-time"), endTime: parseTimestamp13(options.endTime, "--end-time"),
179
219
  searchType: parseNumberOption(options.searchType, "--search-type", { integer: true, min: 1 }), rankType: parseNumberOption(options.rankType, "--rank-type", { integer: true, min: 1 }), keyword: options.keyword,
180
- securityList: maybeArray(options.security), announcementTypeList: maybeArray(options.announcementType), categoryList: maybeArray(options.category),
220
+ securityList: maybeArray(options.security), categoryList: maybeArray(options.category),
181
221
  }), { endpointKey: "insight.announcement.list", idField: "announcementId" }));
182
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" });
183
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", {
@@ -221,10 +261,19 @@ insight.addCommand(foreignOpinion);
221
261
  insight.addCommand(independentOpinion);
222
262
  program.addCommand(insight);
223
263
  const quote = new Command("quote").description("Quote APIs");
224
- quote.command("day-kline").option("--security <code>", "Security code (A-share: .SH/.SZ/.BJ, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => callKlineWithSharding(client, "quote.day-kline", buildQuoteKlineBody(options), { shardDays: 1 })));
225
- quote.command("day-kline-hk").option("--security <code>", "Security code (HK stock: .HK, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => callKlineWithSharding(client, "quote.day-kline-hk", buildQuoteKlineBody(options), { shardDays: 2 })));
226
- quote.command("day-kline-us").option("--security <code>", "Security code (US stock: e.g. AAPL.O, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => callKlineWithSharding(client, "quote.day-kline-us", buildQuoteKlineBody(options), { shardDays: 1 })));
227
- quote.command("index-day-kline").option("--security <code>", "Index code (.SH/.SZ/.BJ, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => callKlineWithSharding(client, "quote.index-day-kline", buildQuoteKlineBody(options), { shardDays: 30 })));
264
+ const addKlineCommand = (name, endpointKey, securityHelp, shardDays) => quote.command(name)
265
+ .option("--security <code>", securityHelp, collectList, [])
266
+ .option("--start-date <date>", "Start date (default: 1 year before end-date)")
267
+ .option("--end-date <date>", "End date (default: latest)")
268
+ .option("--limit <number>", "Max rows per request (default: 6000, max: 10000)")
269
+ .option("--field <field>", "Field", collectList, [])
270
+ .option("--format <format>", "Output format", "table")
271
+ .option("--output <path>")
272
+ .action((options) => emit(options, (client) => callKlineWithSharding(client, endpointKey, buildQuoteKlineBody(options), { shardDays })));
273
+ addKlineCommand("day-kline", "quote.day-kline", "Security code (A-share: .SH/.SZ/.BJ, or 'all' for full market)", 1);
274
+ addKlineCommand("day-kline-hk", "quote.day-kline-hk", "Security code (HK stock: .HK, or 'all' for full market)", 2);
275
+ addKlineCommand("day-kline-us", "quote.day-kline-us", "Security code (US stock: e.g. AAPL.O, or 'all' for full market)", 1);
276
+ addKlineCommand("index-day-kline", "quote.index-day-kline", "Index code (.SH/.SZ/.BJ, or 'all' for full market)", 30);
228
277
  quote.command("minute-kline").option("--security <code>", "Security code (A-share only: .SH/.SZ/.BJ)").option("--start-time <datetime>", "Start time (yyyy-MM-dd HH:mm:ss)").option("--end-time <datetime>", "End time (yyyy-MM-dd HH:mm:ss)").option("--limit <number>", "Max rows per request (default: 5000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("quote.minute-kline", { securityCode: options.security, startTime: options.startTime, endTime: options.endTime, limit: parseOptionalNumberOption(options.limit, "--limit", { integer: true, min: 1 }), fieldList: maybeArray(options.field) })));
229
278
  quote.command("realtime").description("Realtime quote snapshot (A-share / HK / US)").option("--security <code>", "Security code (e.g. 600519.SH / 00700.HK / AAPL.O), or market keyword: aShares / hkStocks / usStocks", collectList, []).option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("quote.realtime", { securityList: maybeArray(options.security), fieldList: maybeArray(options.field) })));
230
279
  program.addCommand(quote);
@@ -165,8 +165,10 @@ export class GangtiseClient {
165
165
  nextFrom += size;
166
166
  }
167
167
  const MAX_PAGES = 1000;
168
+ let truncatedByPageCap = false;
168
169
  if (pageRequests.length + 1 > MAX_PAGES) {
169
170
  pageRequests.length = MAX_PAGES - 1;
171
+ truncatedByPageCap = true;
170
172
  }
171
173
  let unexpectedShape = false;
172
174
  let totalDrift = false;
@@ -195,6 +197,12 @@ export class GangtiseClient {
195
197
  if (totalDrift && isVerbose()) {
196
198
  process.stderr.write(`[gangtise] warning: 'total' changed across pages (data shifted during fetch)\n`);
197
199
  }
200
+ // Always surface a cap-induced truncation (not gated on verbose): the user
201
+ // asked for everything and is silently getting a subset, mirroring the
202
+ // partial-result warning in quoteSharding.
203
+ if (truncatedByPageCap) {
204
+ process.stderr.write(`[gangtise] warning: hit the ${MAX_PAGES}-page safety cap; fetched ${collected.length} of ${total} rows. Narrow the query (e.g. a shorter date range) or pass --size to fetch a bounded subset.\n`);
205
+ }
198
206
  return {
199
207
  ...firstPage,
200
208
  total,
@@ -330,7 +338,16 @@ export class GangtiseClient {
330
338
  // Stream directly to disk when caller already knows the destination
331
339
  if (options?.streamTo) {
332
340
  await fs.mkdir(path.dirname(options.streamTo), { recursive: true });
333
- await pipeline(response.body, createWriteStream(options.streamTo));
341
+ try {
342
+ await pipeline(response.body, createWriteStream(options.streamTo));
343
+ }
344
+ catch (error) {
345
+ // A mid-stream failure leaves a truncated file on disk; remove it so a
346
+ // failed download never looks like a complete one. withRetry may still
347
+ // replay the request (the next attempt re-creates the file).
348
+ await fs.unlink(options.streamTo).catch(() => { });
349
+ throw error;
350
+ }
334
351
  logTiming(`GET ${endpoint.path} (stream)`, Date.now() - startedAt, `${response.statusCode}`);
335
352
  return { contentType, filename, savedPath: options.streamTo };
336
353
  }
@@ -1,2 +1,2 @@
1
1
  // Auto-generated — DO NOT EDIT
2
- export const CLI_VERSION = "0.16.0";
2
+ export const CLI_VERSION = "0.17.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gangtise-openapi-cli",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "description": "CLI for Gangtise OpenAPI",
5
5
  "license": "MIT",
6
6
  "repository": {