gangtise-openapi-cli 0.5.1 → 0.7.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
@@ -44,6 +44,7 @@ export GANGTISE_TOKEN="Bearer xxx"
44
44
  - `gangtise quote ...`
45
45
  - `gangtise fundamental ...`
46
46
  - `gangtise ai ...`
47
+ - `gangtise vault ...`
47
48
  - `gangtise raw call ...`
48
49
 
49
50
  其中 `lookup` 下的研究方向、机构、行业枚举已内置在项目中,无需额外本地文档文件。
@@ -84,7 +85,7 @@ gangtise ai knowledge-batch --query 比亚迪 --query 最近热门概念
84
85
  - `insight foreign-report list`
85
86
  - `insight announcement list`
86
87
  - `ai security-clue`
87
- - `ai cloud-disk-list`
88
+ - `vault drive-list`
88
89
 
89
90
  规则:
90
91
  - **有时间范围时**(传了 `--start-time/--end-time` 或 `--start-date/--end-date`):**省略 `--size`**,CLI 自动翻页查全
@@ -93,7 +94,7 @@ gangtise ai knowledge-batch --query 比亚迪 --query 最近热门概念
93
94
 
94
95
  ## 智能文件命名
95
96
 
96
- 下载命令(`summary download`、`research download`、`foreign-report download`、`announcement download`、`cloud-disk-download`)省略 `--output` 时,自动使用真实标题作为文件名:
97
+ 下载命令(`summary download`、`research download`、`foreign-report download`、`announcement download`、`vault drive-download`)省略 `--output` 时,自动使用真实标题作为文件名:
97
98
 
98
99
  1. **缓存优先** — 如果之前执行过对应的 `list` 命令,标题已缓存在 `~/.config/gangtise/title-cache.json`,直接使用,无额外 API 调用
99
100
  2. **API 回查** — 缓存未命中时,自动查询最近 200 条记录匹配标题
@@ -146,14 +147,26 @@ gangtise insight roadshow list --institution C100000017
146
147
 
147
148
  ```bash
148
149
  gangtise quote day-kline --security 600519.SH --start-date 2026-03-01 --end-date 2026-03-31
150
+ # 不传 --security 默认返回全市场,不传 --start-date 默认往前一年,不传 --end-date 默认最新
151
+ gangtise quote day-kline --format json
152
+ # 港股日K线
153
+ gangtise quote day-kline-hk --security 00700.HK --start-date 2026-03-01 --end-date 2026-03-31
149
154
  ```
150
155
 
151
156
  ### Fundamental
152
157
 
153
158
  ```bash
154
159
  gangtise fundamental income-statement --security-code 600519.SH --fiscal-year 2025 --period q3 --field netProfit
155
- # 多周期:同时查一季报和中报
156
- gangtise fundamental income-statement --security-code 600519.SH --fiscal-year 2024 --period q1 --period q2 --report-type consolidated
160
+ # 多年度:同时查2023-2025年报净利润
161
+ gangtise fundamental income-statement --security-code 600519.SH --fiscal-year 2023 --fiscal-year 2024 --fiscal-year 2025 --period annual --field netProfit
162
+ # 最新一期完整利润表
163
+ gangtise fundamental income-statement --security-code 600519.SH --format json
164
+ gangtise fundamental balance-sheet --security-code 600519.SH --fiscal-year 2025 --period q3 --field totalCurrAssets --field totalCurrLiab
165
+ # 最新一期完整资产负债表
166
+ gangtise fundamental balance-sheet --security-code 600519.SH --format json
167
+ gangtise fundamental cash-flow --security-code 600519.SH --fiscal-year 2025 --period q3 --field netOpCashFlows --field netInvCashFlows --field netFinCashFlows
168
+ # 最新一期完整现金流量表
169
+ gangtise fundamental cash-flow --security-code 600519.SH --format json
157
170
  gangtise fundamental main-business --security-code 600519.SH --breakdown region
158
171
  gangtise fundamental valuation-analysis --security-code 600519.SH --indicator peTtm
159
172
  ```
@@ -168,14 +181,22 @@ gangtise ai security-clue --start-time "2026-04-01 00:00:00" --end-time "2026-04
168
181
  gangtise ai one-pager --security-code 600519.SH
169
182
  gangtise ai investment-logic --security-code 600519.SH
170
183
  gangtise ai peer-comparison --security-code 600519.SH
171
- gangtise ai cloud-disk-list --keyword 部门文档 --space-type 1 --file-type 1
184
+ gangtise ai earnings-review --security-code 600519.SH --period 2025q3
185
+ gangtise ai theme-tracking --theme-id 121000131 --date 2026-03-01 --type morning
186
+ gangtise ai research-outline --security-code 600519.SH
187
+ ```
188
+
189
+ ### Vault
190
+
191
+ ```bash
192
+ gangtise vault drive-list --keyword 部门文档 --space-type 1 --file-type 1
172
193
 
173
194
  # 云盘下载:自动使用文件标题命名
174
- gangtise ai cloud-disk-download --file-id 62130
195
+ gangtise vault drive-download --file-id 62130
175
196
  # → 2028 全球智能危机 一份来自未来的金融史思想实验 .pdf
197
+ ```
176
198
 
177
199
  gangtise ai knowledge-resource-download --resource-type 60 --source-id 3052524 --output ./resource.txt
178
- # 若接口返回外链 URL,也会直接输出 URL 或按 --output 保存
179
200
  ```
180
201
 
181
202
  ### Raw
@@ -192,9 +213,10 @@ gangtise raw call insight.opinion.list --body '{"from":0,"size":120}'
192
213
  - auth: `login` / `status`
193
214
  - lookup: `research-area list` / `broker-org list` / `meeting-org list` / `industry list` / `industry-code list` / `region list` / `announcement-category list`
194
215
  - insight: `opinion list` / `summary list` / `summary download` / `roadshow list` / `site-visit list` / `strategy list` / `forum list` / `research list` / `research download` / `foreign-report list` / `foreign-report download` / `announcement list` / `announcement download`
195
- - quote: `day-kline`
216
+ - quote: `day-kline` / `day-kline-hk`
196
217
  - fundamental: `income-statement` / `main-business` / `valuation-analysis`
197
- - ai: `knowledge-batch` / `knowledge-resource-download` / `security-clue` / `cloud-disk-list` / `cloud-disk-download` / `one-pager` / `investment-logic` / `peer-comparison`
218
+ - ai: `knowledge-batch` / `knowledge-resource-download` / `security-clue` / `one-pager` / `investment-logic` / `peer-comparison` / `earnings-review` / `theme-tracking` / `research-outline`
219
+ - vault: `drive-list` / `drive-download`
198
220
 
199
221
  注意:`knowledge-resource-download` 依赖正确的 `resourceType + sourceId` 组合;错误组合会返回 `433007 不支持该数据源`。
200
222
 
@@ -210,17 +232,22 @@ gangtise raw call insight.opinion.list --body '{"from":0,"size":120}'
210
232
 
211
233
  ## Claude Code Skill
212
234
 
213
- 本项目包含一个 [Claude Code](https://claude.ai/claude-code) skill 定义(`SKILL.md`),可让 AI agent 自动调用 `gangtise` CLI 完成投研数据查询。
235
+ 本项目包含一个 [Claude Code](https://claude.ai/claude-code) skill 定义(`gangtise-openapi/SKILL.md`),可让 AI agent 自动调用 `gangtise` CLI 完成投研数据查询。
236
+
237
+ Skill 目录结构:
238
+
239
+ ```
240
+ gangtise-openapi/
241
+ ├── SKILL.md # 主 skill 文件(命令参考、参数枚举、使用规则)
242
+ └── references/
243
+ └── lookup-ids.md # 常用 ID 速查表(行业/券商/机构/公告分类等)
244
+ ```
214
245
 
215
246
  安装到 Claude Code:
216
247
 
217
248
  ```bash
218
- # 方式一:从本地项目安装
219
- cp SKILL.md ~/.claude/skills/gangtise-openapi/SKILL.md
220
-
221
- # 方式二:手动创建目录并复制
222
- mkdir -p ~/.claude/skills/gangtise-openapi
223
- cp SKILL.md ~/.claude/skills/gangtise-openapi/
249
+ # 从项目根目录复制整个 skill 目录
250
+ cp -r gangtise-openapi ~/.claude/skills/gangtise-openapi
224
251
  ```
225
252
 
226
253
  安装后,在 Claude Code 中可以用自然语言触发,例如:
package/dist/src/cli.js CHANGED
@@ -1,15 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command, Option } from "commander";
3
- import fs from "node:fs/promises";
4
- import os from "node:os";
5
- import path, { extname } from "node:path";
6
3
  import { collectKeyValue, collectList, collectNumberList, maybeArray, toTimestamp13 } from "./core/args.js";
7
- import { readTokenCache } from "./core/auth.js";
8
- import { GangtiseClient } from "./core/client.js";
9
4
  import { loadConfig } from "./core/config.js";
10
5
  import { ApiError, ConfigError, DownloadError } from "./core/errors.js";
11
- import { renderOutput, saveOutputIfNeeded } from "./core/output.js";
12
- import { normalizeRows } from "./core/normalize.js";
6
+ // --- Lazy-loaded modules (deferred to action handlers) ---
7
+ async function createClient() {
8
+ const { GangtiseClient } = await import("./core/client.js");
9
+ return new GangtiseClient(loadConfig());
10
+ }
11
+ async function readTokenCache(...args) {
12
+ return (await import("./core/auth.js")).readTokenCache(...args);
13
+ }
14
+ async function normalizeRows(...args) {
15
+ return (await import("./core/normalize.js")).normalizeRows(...args);
16
+ }
17
+ async function renderOutput(...args) {
18
+ return (await import("./core/output.js")).renderOutput(...args);
19
+ }
20
+ async function saveOutputIfNeeded(...args) {
21
+ return (await import("./core/output.js")).saveOutputIfNeeded(...args);
22
+ }
13
23
  function parseFormat(value) {
14
24
  const format = value ?? "table";
15
25
  if (["table", "json", "jsonl", "csv", "markdown"].includes(format)) {
@@ -18,21 +28,32 @@ function parseFormat(value) {
18
28
  throw new ConfigError(`Unsupported format: ${format}`);
19
29
  }
20
30
  // --- Title cache: list writes, download reads ---
21
- const TITLE_CACHE_PATH = path.join(os.homedir(), ".config", "gangtise", "title-cache.json");
31
+ let _titleCachePath;
32
+ function getTitleCachePath() {
33
+ if (!_titleCachePath) {
34
+ const path = require("node:path");
35
+ const os = require("node:os");
36
+ _titleCachePath = path.join(os.homedir(), ".config", "gangtise", "title-cache.json");
37
+ }
38
+ return _titleCachePath;
39
+ }
22
40
  const TITLE_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24h
23
41
  async function readTitleCache() {
24
42
  try {
25
- return JSON.parse(await fs.readFile(TITLE_CACHE_PATH, "utf8"));
43
+ const fs = await import("node:fs/promises");
44
+ return JSON.parse(await fs.readFile(getTitleCachePath(), "utf8"));
26
45
  }
27
46
  catch {
28
47
  return {};
29
48
  }
30
49
  }
31
50
  async function writeTitleCache(endpoint, titles) {
51
+ const fs = await import("node:fs/promises");
52
+ const path = await import("node:path");
32
53
  const data = await readTitleCache();
33
54
  data[endpoint] = { titles, ts: Date.now() };
34
- await fs.mkdir(path.dirname(TITLE_CACHE_PATH), { recursive: true });
35
- await fs.writeFile(TITLE_CACHE_PATH, JSON.stringify(data), "utf8");
55
+ await fs.mkdir(path.dirname(getTitleCachePath()), { recursive: true });
56
+ await fs.writeFile(getTitleCachePath(), JSON.stringify(data), "utf8");
36
57
  }
37
58
  function lookupTitleCache(data, endpoint, id) {
38
59
  const entry = data[endpoint];
@@ -41,7 +62,7 @@ function lookupTitleCache(data, endpoint, id) {
41
62
  return entry.titles[id];
42
63
  }
43
64
  async function printData(data, format, output, cache) {
44
- const normalized = normalizeRows(data);
65
+ const normalized = await normalizeRows(data);
45
66
  // Populate title cache from list results
46
67
  if (cache && Array.isArray(normalized)) {
47
68
  const titleField = cache.titleField ?? "title";
@@ -58,7 +79,7 @@ async function printData(data, format, output, cache) {
58
79
  if (Object.keys(titles).length > 0)
59
80
  writeTitleCache(cache.endpointKey, titles).catch(() => { });
60
81
  }
61
- const content = renderOutput(normalized, format);
82
+ const content = await renderOutput(normalized, format);
62
83
  if (output) {
63
84
  await saveOutputIfNeeded(content, output);
64
85
  process.stdout.write(`${output}\n`);
@@ -100,6 +121,7 @@ function extFromContentType(contentType) {
100
121
  }
101
122
  /** Resolve a human-readable filename by looking up the title from cache or list endpoint. */
102
123
  async function resolveTitle(client, result, listEndpoint, idField, idValue, titleField = "title") {
124
+ const { extname } = await import("node:path");
103
125
  const file = result;
104
126
  const serverExt = file.filename ? extname(file.filename) : extFromContentType(file.contentType);
105
127
  function buildFilename(rawTitle) {
@@ -166,30 +188,15 @@ function addTimeFilters(command) {
166
188
  .option("--keyword <keyword>", "Keyword");
167
189
  }
168
190
  const program = new Command();
169
- import { createRequire } from "node:module";
170
- import { fileURLToPath } from "node:url";
171
- function loadPackageVersion() {
172
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
173
- const req = createRequire(import.meta.url);
174
- // Works from both src/ (dev) and dist/src/ (built)
175
- try {
176
- return req(path.resolve(__dirname, "../package.json")).version;
177
- }
178
- catch { }
179
- try {
180
- return req(path.resolve(__dirname, "../../package.json")).version;
181
- }
182
- catch { }
183
- return "0.0.0";
184
- }
185
- program.name("gangtise").description("Gangtise OpenAPI CLI").version(loadPackageVersion());
191
+ const CLI_VERSION = "0.6.0";
192
+ program.name("gangtise").description("Gangtise OpenAPI CLI").version(CLI_VERSION);
186
193
  program
187
194
  .command("auth")
188
195
  .description("Authentication commands")
189
196
  .addCommand(new Command("login")
190
197
  .option("--format <format>", "Output format", "json")
191
198
  .action(async (options) => {
192
- const client = new GangtiseClient(loadConfig());
199
+ const client = await createClient();
193
200
  await printData(await client.login(), parseFormat(options.format));
194
201
  }))
195
202
  .addCommand(new Command("status")
@@ -202,32 +209,36 @@ program
202
209
  const lookup = new Command("lookup").description("Lookup helper APIs");
203
210
  lookup
204
211
  .addCommand(new Command("research-area").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(async (options) => {
205
- const client = new GangtiseClient(loadConfig());
212
+ const client = await createClient();
206
213
  await printData(await client.call("lookup.research-areas.list"), parseFormat(options.format));
207
214
  })))
208
215
  .addCommand(new Command("broker-org").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(async (options) => {
209
- const client = new GangtiseClient(loadConfig());
216
+ const client = await createClient();
210
217
  await printData(await client.call("lookup.broker-orgs.list"), parseFormat(options.format));
211
218
  })))
212
219
  .addCommand(new Command("meeting-org").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(async (options) => {
213
- const client = new GangtiseClient(loadConfig());
220
+ const client = await createClient();
214
221
  await printData(await client.call("lookup.meeting-orgs.list"), parseFormat(options.format));
215
222
  })))
216
223
  .addCommand(new Command("industry").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(async (options) => {
217
- const client = new GangtiseClient(loadConfig());
224
+ const client = await createClient();
218
225
  await printData(await client.call("lookup.industries.list"), parseFormat(options.format));
219
226
  })))
220
227
  .addCommand(new Command("region").description("Foreign report region codes").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(async (options) => {
221
- const client = new GangtiseClient(loadConfig());
228
+ const client = await createClient();
222
229
  await printData(await client.call("lookup.regions.list"), parseFormat(options.format));
223
230
  })))
224
231
  .addCommand(new Command("announcement-category").description("Announcement category codes").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(async (options) => {
225
- const client = new GangtiseClient(loadConfig());
232
+ const client = await createClient();
226
233
  await printData(await client.call("lookup.announcement-categories.list"), parseFormat(options.format));
227
234
  })))
228
235
  .addCommand(new Command("industry-code").description("Shenwan industry codes for security-clue --gts-code").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(async (options) => {
229
- const client = new GangtiseClient(loadConfig());
236
+ const client = await createClient();
230
237
  await printData(await client.call("lookup.industry-codes.list"), parseFormat(options.format));
238
+ })))
239
+ .addCommand(new Command("theme-id").description("Theme IDs for theme-tracking --theme-id").addCommand(new Command("list").option("--format <format>", "Output format", "table").action(async (options) => {
240
+ const client = await createClient();
241
+ await printData(await client.call("lookup.theme-ids.list"), parseFormat(options.format));
231
242
  })));
232
243
  program.addCommand(lookup);
233
244
  const insight = new Command("insight").description("Insight APIs");
@@ -241,7 +252,7 @@ const research = new Command("research");
241
252
  const foreignReport = new Command("foreign-report");
242
253
  const announcement = new Command("announcement");
243
254
  addTimeFilters(opinion.command("list").option("--rank-type <number>", "Rank type", "1").option("--research-area <id>", "Research area ID", collectList, []).option("--chief <id>", "Chief ID", collectList, []).option("--security <code>", "Security code", collectList, []).option("--broker <id>", "Broker ID", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--concept <id>", "Concept ID", collectList, []).option("--llm-tag <tag>", "Semantic tag", collectList, []).option("--source <source>", "Source", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action(async (options) => {
244
- const client = new GangtiseClient(loadConfig());
255
+ const client = await createClient();
245
256
  await printData(await client.call("insight.opinion.list", {
246
257
  from: Number(options.from), size: options.size === undefined ? undefined : Number(options.size), startTime: options.startTime, endTime: options.endTime,
247
258
  rankType: Number(options.rankType), keyword: options.keyword, researchAreaList: maybeArray(options.researchArea), chiefList: maybeArray(options.chief),
@@ -250,7 +261,7 @@ addTimeFilters(opinion.command("list").option("--rank-type <number>", "Rank type
250
261
  }), parseFormat(options.format), options.output);
251
262
  });
252
263
  addTimeFilters(summary.command("list").option("--search-type <number>", "Search type", "1").option("--rank-type <number>", "Rank type", "1").option("--source <number>", "Source type", collectNumberList, []).option("--research-area <id>", "Research area", collectList, []).option("--security <code>", "Security code", collectList, []).option("--institution <id>", "Institution ID", collectList, []).option("--category <name>", "Category", collectList, []).option("--market <name>", "Market", collectList, []).option("--participant-role <name>", "Participant role", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action(async (options) => {
253
- const client = new GangtiseClient(loadConfig());
264
+ const client = await createClient();
254
265
  await printData(await client.call("insight.summary.list", {
255
266
  from: Number(options.from), size: options.size === undefined ? undefined : Number(options.size), startTime: options.startTime, endTime: options.endTime,
256
267
  searchType: Number(options.searchType), rankType: Number(options.rankType), keyword: options.keyword, sourceList: options.source.length ? options.source : undefined,
@@ -259,13 +270,13 @@ addTimeFilters(summary.command("list").option("--search-type <number>", "Search
259
270
  }), parseFormat(options.format), options.output, { endpointKey: "insight.summary.list", idField: "summaryId" });
260
271
  });
261
272
  summary.command("download").requiredOption("--summary-id <id>").option("--output <path>").action(async (options) => {
262
- const client = new GangtiseClient(loadConfig());
273
+ const client = await createClient();
263
274
  const result = await client.call("insight.summary.download", undefined, { summaryId: options.summaryId });
264
275
  const title = options.output ? undefined : await resolveTitle(client, result, "insight.summary.list", "summaryId", options.summaryId);
265
276
  await saveDownloadResult(result, `summary-${options.summaryId}`, options.output ?? title);
266
277
  });
267
278
  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(async (options) => {
268
- const client = new GangtiseClient(loadConfig());
279
+ const client = await createClient();
269
280
  await printData(await client.call(endpointKey, {
270
281
  from: Number(options.from), size: options.size === undefined ? undefined : Number(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword,
271
282
  researchAreaList: maybeArray(options.researchArea), institutionList: maybeArray(options.institution), securityList: maybeArray(options.security),
@@ -278,7 +289,7 @@ addScheduleList(siteVisit, "insight.site-visit.list");
278
289
  addScheduleList(strategy, "insight.strategy.list");
279
290
  addScheduleList(forum, "insight.forum.list");
280
291
  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(async (options) => {
281
- const client = new GangtiseClient(loadConfig());
292
+ const client = await createClient();
282
293
  await printData(await client.call("insight.research.list", {
283
294
  from: Number(options.from), size: options.size === undefined ? undefined : Number(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword,
284
295
  searchType: Number(options.searchType), rankType: Number(options.rankType),
@@ -289,13 +300,13 @@ addTimeFilters(research.command("list").option("--search-type <number>", "Search
289
300
  }), parseFormat(options.format), options.output, { endpointKey: "insight.research.list", idField: "reportId" });
290
301
  });
291
302
  research.command("download").requiredOption("--report-id <id>").option("--file-type <number>", "File type: 1=PDF 2=Markdown", "1").option("--output <path>").action(async (options) => {
292
- const client = new GangtiseClient(loadConfig());
303
+ const client = await createClient();
293
304
  const result = await client.call("insight.research.download", undefined, { reportId: options.reportId, fileType: Number(options.fileType) });
294
305
  const title = options.output ? undefined : await resolveTitle(client, result, "insight.research.list", "reportId", options.reportId);
295
306
  await saveDownloadResult(result, `research-${options.reportId}`, options.output ?? title);
296
307
  });
297
308
  addTimeFilters(foreignReport.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("--region <id>", "Region ID", collectList, []).option("--category <name>", "Report category", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--broker <id>", "Broker ID", 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("--format <format>", "Output format", "table").option("--output <path>", "Output path")).action(async (options) => {
298
- const client = new GangtiseClient(loadConfig());
309
+ const client = await createClient();
299
310
  await printData(await client.call("insight.foreign-report.list", {
300
311
  from: Number(options.from), size: options.size === undefined ? undefined : Number(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword,
301
312
  searchType: Number(options.searchType), rankType: Number(options.rankType),
@@ -306,13 +317,13 @@ addTimeFilters(foreignReport.command("list").option("--search-type <number>", "S
306
317
  }), parseFormat(options.format), options.output, { endpointKey: "insight.foreign-report.list", idField: "reportId" });
307
318
  });
308
319
  foreignReport.command("download").requiredOption("--report-id <id>").option("--file-type <number>", "File type: 1=PDF 2=Markdown 3=CN-PDF 4=CN-Markdown", "1").option("--output <path>").action(async (options) => {
309
- const client = new GangtiseClient(loadConfig());
320
+ const client = await createClient();
310
321
  const result = await client.call("insight.foreign-report.download", undefined, { reportId: options.reportId, fileType: Number(options.fileType) });
311
322
  const title = options.output ? undefined : await resolveTitle(client, result, "insight.foreign-report.list", "reportId", options.reportId);
312
323
  await saveDownloadResult(result, `foreign-report-${options.reportId}`, options.output ?? title);
313
324
  });
314
325
  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(async (options) => {
315
- const client = new GangtiseClient(loadConfig());
326
+ const client = await createClient();
316
327
  await printData(await client.call("insight.announcement.list", {
317
328
  from: Number(options.from), size: options.size === undefined ? undefined : Number(options.size),
318
329
  startTime: toTimestamp13(options.startTime), endTime: toTimestamp13(options.endTime),
@@ -321,7 +332,7 @@ addTimeFilters(announcement.command("list").option("--search-type <number>", "Se
321
332
  }), parseFormat(options.format), options.output, { endpointKey: "insight.announcement.list", idField: "announcementId" });
322
333
  });
323
334
  announcement.command("download").requiredOption("--announcement-id <id>").option("--file-type <number>", "File type: 1=PDF 2=Markdown", "1").option("--output <path>").action(async (options) => {
324
- const client = new GangtiseClient(loadConfig());
335
+ const client = await createClient();
325
336
  const result = await client.call("insight.announcement.download", undefined, { announcementId: options.announcementId, fileType: Number(options.fileType) });
326
337
  const title = options.output ? undefined : await resolveTitle(client, result, "insight.announcement.list", "announcementId", options.announcementId);
327
338
  await saveDownloadResult(result, `announcement-${options.announcementId}`, options.output ?? title);
@@ -337,63 +348,140 @@ insight.addCommand(foreignReport);
337
348
  insight.addCommand(announcement);
338
349
  program.addCommand(insight);
339
350
  const quote = new Command("quote").description("Quote APIs");
340
- quote.command("day-kline").option("--security <code>", "Security code", collectList, []).option("--start-date <date>").option("--end-date <date>").option("--limit <number>").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
341
- const client = new GangtiseClient(loadConfig());
351
+ quote.command("day-kline").option("--security <code>", "Security code (A-share only: .SH/.SZ/.BJ)", 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: 5000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
352
+ const client = await createClient();
342
353
  await printData(await client.call("quote.day-kline", { securityList: maybeArray(options.security), startDate: options.startDate, endDate: options.endDate, limit: options.limit ? Number(options.limit) : undefined, fieldList: maybeArray(options.field) }), parseFormat(options.format), options.output);
343
354
  });
355
+ quote.command("day-kline-hk").option("--security <code>", "Security code (HK stock only: .HK)", 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: 5000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
356
+ const client = await createClient();
357
+ await printData(await client.call("quote.day-kline-hk", { securityList: maybeArray(options.security), startDate: options.startDate, endDate: options.endDate, limit: options.limit ? Number(options.limit) : undefined, fieldList: maybeArray(options.field) }), parseFormat(options.format), options.output);
358
+ });
344
359
  program.addCommand(quote);
345
360
  const fundamental = new Command("fundamental").description("Fundamental APIs");
346
361
  fundamental.command("income-statement").requiredOption("--security-code <code>").option("--start-date <date>").option("--end-date <date>").option("--fiscal-year <year>", "Fiscal year", collectList, []).option("--period <period>", "Period", collectList, []).option("--report-type <type>", "Report type", collectList, []).option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
347
- const client = new GangtiseClient(loadConfig());
348
- await printData(await client.call("fundamental.income-statement", { securityCode: options.securityCode, startDate: options.startDate, endDate: options.endDate, fiscalYear: maybeArray(options.fiscalYear), period: options.period.length ? options.period : ["latest"], reportType: options.reportType.length ? options.reportType : ["consolidated"], fieldList: maybeArray(options.field) }), parseFormat(options.format), options.output);
362
+ const client = await createClient();
363
+ await printData(await client.call("fundamental.income-statement", { securityCode: options.securityCode, startDate: options.startDate, endDate: options.endDate, fiscalYear: maybeArray(options.fiscalYear), period: options.period.length ? options.period : undefined, reportType: options.reportType.length ? options.reportType : undefined, fieldList: maybeArray(options.field) }), parseFormat(options.format), options.output);
364
+ });
365
+ fundamental.command("balance-sheet").requiredOption("--security-code <code>").option("--start-date <date>").option("--end-date <date>").option("--fiscal-year <year>", "Fiscal year", collectList, []).option("--period <period>", "Period", collectList, []).option("--report-type <type>", "Report type", collectList, []).option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
366
+ const client = await createClient();
367
+ await printData(await client.call("fundamental.balance-sheet", { securityCode: options.securityCode, startDate: options.startDate, endDate: options.endDate, fiscalYear: maybeArray(options.fiscalYear), period: options.period.length ? options.period : undefined, reportType: options.reportType.length ? options.reportType : undefined, fieldList: maybeArray(options.field) }), parseFormat(options.format), options.output);
368
+ });
369
+ fundamental.command("cash-flow").requiredOption("--security-code <code>").option("--start-date <date>").option("--end-date <date>").option("--fiscal-year <year>", "Fiscal year", collectList, []).option("--period <period>", "Period", collectList, []).option("--report-type <type>", "Report type", collectList, []).option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
370
+ const client = await createClient();
371
+ await printData(await client.call("fundamental.cash-flow", { securityCode: options.securityCode, startDate: options.startDate, endDate: options.endDate, fiscalYear: maybeArray(options.fiscalYear), period: options.period.length ? options.period : undefined, reportType: options.reportType.length ? options.reportType : undefined, fieldList: maybeArray(options.field) }), parseFormat(options.format), options.output);
349
372
  });
350
373
  fundamental.command("main-business").requiredOption("--security-code <code>").option("--start-date <date>").option("--end-date <date>").option("--fiscal-year <year>", "Fiscal year", collectList, []).addOption(new Option("--breakdown <type>", "Breakdown: product/industry/region").choices(["product", "industry", "region"]).default("product")).addOption(new Option("--period <type>", "Period: interim/annual").choices(["interim", "annual"])).option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
351
- const client = new GangtiseClient(loadConfig());
374
+ const client = await createClient();
352
375
  await printData(await client.call("fundamental.main-business", { securityCode: options.securityCode, startDate: options.startDate, endDate: options.endDate, fiscalYear: maybeArray(options.fiscalYear), breakdown: options.breakdown, period: options.period, fieldList: maybeArray(options.field) }), parseFormat(options.format), options.output);
353
376
  });
354
377
  fundamental.command("valuation-analysis").requiredOption("--security-code <code>").addOption(new Option("--indicator <name>", "Indicator").choices(["peTtm", "pbMrq", "peg", "psTtm", "pcfTtm", "em"]).makeOptionMandatory()).option("--start-date <date>").option("--end-date <date>").option("--limit <number>").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
355
- const client = new GangtiseClient(loadConfig());
378
+ const client = await createClient();
356
379
  await printData(await client.call("fundamental.valuation-analysis", { securityCode: options.securityCode, indicator: options.indicator, startDate: options.startDate, endDate: options.endDate, limit: options.limit ? Number(options.limit) : undefined, fieldList: maybeArray(options.field) }), parseFormat(options.format), options.output);
357
380
  });
358
381
  program.addCommand(fundamental);
359
382
  const ai = new Command("ai").description("AI APIs");
360
383
  ai.command("knowledge-batch").requiredOption("--query <text>", "Query", collectList, []).option("--top <number>", "Top", "10").option("--resource-type <number>", "Resource type", collectNumberList, []).option("--knowledge-name <name>", "Knowledge name", collectList, []).option("--start-time <ms>").option("--end-time <ms>").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
361
- const client = new GangtiseClient(loadConfig());
384
+ const client = await createClient();
362
385
  await printData(await client.call("ai.knowledge-batch", { queries: options.query, top: Number(options.top), resourceTypes: options.resourceType.length ? options.resourceType : undefined, knowledgeNames: maybeArray(options.knowledgeName), startTime: options.startTime ? Number(options.startTime) : undefined, endTime: options.endTime ? Number(options.endTime) : undefined }), parseFormat(options.format), options.output);
363
386
  });
364
387
  ai.command("knowledge-resource-download").requiredOption("--resource-type <number>").requiredOption("--source-id <id>").option("--output <path>").action(async (options) => {
365
- const client = new GangtiseClient(loadConfig());
388
+ const client = await createClient();
366
389
  await saveDownloadResult(await client.call("ai.knowledge-resource.download", undefined, { resourceType: Number(options.resourceType), sourceId: options.sourceId }), `resource-${options.sourceId}`, options.output);
367
390
  });
368
391
  ai.command("security-clue").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").requiredOption("--start-time <datetime>").requiredOption("--end-time <datetime>").addOption(new Option("--query-mode <mode>").choices(["bySecurity", "byIndustry"]).makeOptionMandatory()).option("--gts-code <code>", "GTS code", collectList, []).option("--source <name>", "Source", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action(async (options) => {
369
- const client = new GangtiseClient(loadConfig());
392
+ const client = await createClient();
370
393
  await printData(await client.call("ai.security-clue.list", { from: Number(options.from), size: options.size === undefined ? undefined : Number(options.size), startTime: options.startTime, endTime: options.endTime, queryMode: options.queryMode, gtsCodeList: maybeArray(options.gtsCode), source: maybeArray(options.source) }), parseFormat(options.format), options.output);
371
394
  });
372
395
  ai.command("one-pager").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
373
- const client = new GangtiseClient(loadConfig());
396
+ const client = await createClient();
374
397
  await printData(await client.call("ai.one-pager", { securityCode: options.securityCode }), parseFormat(options.format), options.output);
375
398
  });
376
399
  ai.command("investment-logic").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
377
- const client = new GangtiseClient(loadConfig());
400
+ const client = await createClient();
378
401
  await printData(await client.call("ai.investment-logic", { securityCode: options.securityCode }), parseFormat(options.format), options.output);
379
402
  });
380
403
  ai.command("peer-comparison").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
381
- const client = new GangtiseClient(loadConfig());
404
+ const client = await createClient();
382
405
  await printData(await client.call("ai.peer-comparison", { securityCode: options.securityCode }), parseFormat(options.format), options.output);
383
406
  });
384
- ai.command("cloud-disk-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(async (options) => {
385
- const client = new GangtiseClient(loadConfig());
386
- await printData(await client.call("ai.cloud-disk.list", { from: Number(options.from), size: options.size === undefined ? undefined : Number(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 }), parseFormat(options.format), options.output, { endpointKey: "ai.cloud-disk.list", idField: "fileId" });
407
+ ai.command("earnings-review").requiredOption("--security-code <code>").requiredOption("--period <period>", "Report period (e.g. 2025q3, 2025interim, 2025annual)").option("--wait", "Wait for content generation (blocking, up to 3 min)").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
408
+ const client = await createClient();
409
+ // Step 1: get dataId
410
+ const idResult = await client.call("ai.earnings-review.get-id", { securityCode: options.securityCode, period: options.period });
411
+ const dataId = idResult?.dataId;
412
+ if (!dataId) {
413
+ process.stderr.write("Failed to get earnings review ID. The report may not be available yet.\n");
414
+ process.exitCode = 1;
415
+ return;
416
+ }
417
+ // Non-blocking: return dataId immediately
418
+ if (!options.wait) {
419
+ process.stderr.write(`Earnings review task submitted. dataId: ${dataId}\n`);
420
+ process.stdout.write(`${JSON.stringify({ dataId, status: "pending", hint: `Run 'gangtise ai earnings-review-check --data-id ${dataId}' in ~2 minutes to get results` })}\n`);
421
+ return;
422
+ }
423
+ // Blocking (--wait): poll for content
424
+ process.stderr.write(`Got dataId: ${dataId}, waiting for content generation...\n`);
425
+ let attempts = 0;
426
+ const maxAttempts = 12;
427
+ const delayMs = 15_000;
428
+ while (attempts < maxAttempts) {
429
+ attempts++;
430
+ const contentResult = await client.call("ai.earnings-review.get-content", { dataId });
431
+ if (contentResult?.data?.content) {
432
+ await printData(contentResult.data, parseFormat(options.format), options.output);
433
+ return;
434
+ }
435
+ if (attempts < maxAttempts) {
436
+ process.stderr.write(`Attempt ${attempts}/${maxAttempts}: content not ready, retrying in 15s...\n`);
437
+ await new Promise(resolve => setTimeout(resolve, delayMs));
438
+ }
439
+ }
440
+ process.stderr.write(`Content not available after ${maxAttempts} attempts. Try again later with: gangtise ai earnings-review-check --data-id ${dataId}\n`);
441
+ process.exitCode = 1;
442
+ });
443
+ ai.command("earnings-review-check").requiredOption("--data-id <id>", "dataId from earnings-review").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
444
+ const client = await createClient();
445
+ try {
446
+ const contentResult = await client.call("ai.earnings-review.get-content", { dataId: options.dataId });
447
+ if (contentResult?.data?.content) {
448
+ await printData(contentResult.data, parseFormat(options.format), options.output);
449
+ return;
450
+ }
451
+ process.stdout.write(`${JSON.stringify({ dataId: options.dataId, status: "pending", hint: "Content not ready yet, retry in ~2 minutes" })}\n`);
452
+ }
453
+ catch (error) {
454
+ if (error instanceof ApiError && (error.code === "410110" || error.message?.includes("生成中"))) {
455
+ process.stdout.write(`${JSON.stringify({ dataId: options.dataId, status: "pending", hint: "Content not ready yet, retry in ~2 minutes" })}\n`);
456
+ return;
457
+ }
458
+ throw error;
459
+ }
460
+ });
461
+ 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(async (options) => {
462
+ const client = await createClient();
463
+ const typeList = options.type.length ? options.type : undefined;
464
+ await printData(await client.call("ai.theme-tracking", { themeId: options.themeId, date: options.date, type: typeList }), parseFormat(options.format), options.output);
465
+ });
466
+ ai.command("research-outline").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
467
+ const client = await createClient();
468
+ await printData(await client.call("ai.research-outline", { securityCode: options.securityCode }), parseFormat(options.format), options.output);
469
+ });
470
+ const vault = new Command("vault").description("Vault APIs");
471
+ 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(async (options) => {
472
+ const client = await createClient();
473
+ await printData(await client.call("vault.drive.list", { from: Number(options.from), size: options.size === undefined ? undefined : Number(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 }), parseFormat(options.format), options.output, { endpointKey: "vault.drive.list", idField: "fileId" });
387
474
  });
388
- ai.command("cloud-disk-download").requiredOption("--file-id <id>").option("--output <path>").action(async (options) => {
389
- const client = new GangtiseClient(loadConfig());
390
- const result = await client.call("ai.cloud-disk.download", undefined, { fileId: options.fileId });
391
- const title = options.output ? undefined : await resolveTitle(client, result, "ai.cloud-disk.list", "fileId", options.fileId);
475
+ vault.command("drive-download").requiredOption("--file-id <id>").option("--output <path>").action(async (options) => {
476
+ const client = await createClient();
477
+ const result = await client.call("vault.drive.download", undefined, { fileId: options.fileId });
478
+ const title = options.output ? undefined : await resolveTitle(client, result, "vault.drive.list", "fileId", options.fileId);
392
479
  await saveDownloadResult(result, `file-${options.fileId}`, options.output ?? title);
393
480
  });
481
+ program.addCommand(vault);
394
482
  program.addCommand(ai);
395
483
  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) => {
396
- const client = new GangtiseClient(loadConfig());
484
+ const client = await createClient();
397
485
  const body = options.body ? JSON.parse(options.body) : undefined;
398
486
  const data = await client.call(endpointKey, body, options.query);
399
487
  if (data && typeof data === "object" && "data" in data && data.data instanceof Uint8Array) {
@@ -423,4 +511,3 @@ async function main() {
423
511
  }
424
512
  }
425
513
  void main();
426
- //# sourceMappingURL=cli.js.map
@@ -44,4 +44,3 @@ export function toTimestamp13(value) {
44
44
  return undefined;
45
45
  return ms;
46
46
  }
47
- //# sourceMappingURL=args.js.map
@@ -33,4 +33,3 @@ export function requireAccessCredentials(accessKey, secretKey) {
33
33
  }
34
34
  return { accessKey, secretKey };
35
35
  }
36
- //# sourceMappingURL=auth.js.map