gangtise-openapi-cli 0.5.0 → 0.6.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
@@ -57,6 +57,8 @@ gangtise lookup research-area list
57
57
  gangtise lookup broker-org list
58
58
  gangtise lookup meeting-org list
59
59
  gangtise lookup industry list
60
+ gangtise lookup region list # 外资研报区域
61
+ gangtise lookup announcement-category list # 公告分类
60
62
  gangtise lookup industry-code list # 申万行业代码(用于 security-clue --gts-code)
61
63
  ```
62
64
 
@@ -85,10 +87,9 @@ gangtise ai knowledge-batch --query 比亚迪 --query 最近热门概念
85
87
  - `ai cloud-disk-list`
86
88
 
87
89
  规则:
88
- - `--from` 表示起始偏移量
89
- - `--size` 表示最终最多返回多少条记录
90
- - 如果未传 `--size`,CLI 会根据返回里的 `total` 自动翻页,把从 `from` 开始的全部数据查全
91
- - 如果传了 `--size`,即使超过接口单次上限,CLI 也会自动翻页,直到累计记录数达到 `size` 或数据取完
90
+ - **有时间范围时**(传了 `--start-time/--end-time` 或 `--start-date/--end-date`):**省略 `--size`**,CLI 自动翻页查全
91
+ - **无时间范围时**(未传时间参数):默认 `--size 200`,防止一次查询数据量过大
92
+ - 如果显式传了 `--size`,则按指定值翻页,直到达到 `size` 或数据取完
92
93
 
93
94
  ## 智能文件命名
94
95
 
@@ -112,11 +113,14 @@ gangtise auth status
112
113
  ### Insight
113
114
 
114
115
  ```bash
115
- # 只取前 120 条,CLI 内部自动翻页
116
- gangtise insight research list --start-time "2026-04-04 00:00:00" --end-time "2026-04-04 23:59:59" --size 120
116
+ # 有时间范围 省略 --size,自动查全
117
+ gangtise insight research list --start-time "2026-04-01 00:00:00" --end-time "2026-04-09 23:59:59"
117
118
 
118
- # 不传 size,自动查全
119
- gangtise insight research list --start-time "2026-04-04 00:00:00" --end-time "2026-04-04 23:59:59"
119
+ # 无时间范围 → 默认 --size 200
120
+ gangtise insight research list --industry 104270000 --category company --llm-tag inDepth --rating buy
121
+
122
+ # 多值 List 模式:一次查多家券商 + 多个行业 + 多个评级
123
+ gangtise insight research list --broker C100000027 --broker C100000014 --industry 104340000 --industry 104370000 --rating buy --rating overweight --format json
120
124
 
121
125
  gangtise insight opinion list --keyword AI
122
126
  gangtise insight summary list --keyword 算力
@@ -125,8 +129,12 @@ gangtise insight summary list --keyword 算力
125
129
  gangtise insight summary download --summary-id 4902586
126
130
  # → 超颖电子:2026年4月7日投资者关系活动记录表.txt
127
131
 
128
- gangtise insight research download --report-id 432092410345574400
129
- # 建筑材料行业投资策略周报:战争对预期的影响仍大 中长期关注传统建材底部机会.pdf
132
+ # 下载 Markdown 版本
133
+ gangtise insight research download --report-id 432092410345574400 --file-type 2
134
+ # 下载外资研报中文翻译版
135
+ gangtise insight foreign-report download --report-id RPT20260401001 --file-type 4
136
+ # 下载公告 Markdown 版本
137
+ gangtise insight announcement download --announcement-id 123456 --file-type 2
130
138
 
131
139
  # 也可手动指定文件名
132
140
  gangtise insight research download --report-id 12345 --output ./report.pdf
@@ -144,7 +152,17 @@ gangtise quote day-kline --security 600519.SH --start-date 2026-03-01 --end-date
144
152
 
145
153
  ```bash
146
154
  gangtise fundamental income-statement --security-code 600519.SH --fiscal-year 2025 --period q3 --field netProfit
147
- gangtise fundamental main-business --security-code 600519.SH
155
+ # 多年度:同时查2023-2025年报净利润
156
+ gangtise fundamental income-statement --security-code 600519.SH --fiscal-year 2023 --fiscal-year 2024 --fiscal-year 2025 --period annual --field netProfit
157
+ # 最新一期完整利润表
158
+ gangtise fundamental income-statement --security-code 600519.SH --format json
159
+ gangtise fundamental balance-sheet --security-code 600519.SH --fiscal-year 2025 --period q3 --field totalCurrAssets --field totalCurrLiab
160
+ # 最新一期完整资产负债表
161
+ gangtise fundamental balance-sheet --security-code 600519.SH --format json
162
+ gangtise fundamental cash-flow --security-code 600519.SH --fiscal-year 2025 --period q3 --field netOpCashFlows --field netInvCashFlows --field netFinCashFlows
163
+ # 最新一期完整现金流量表
164
+ gangtise fundamental cash-flow --security-code 600519.SH --format json
165
+ gangtise fundamental main-business --security-code 600519.SH --breakdown region
148
166
  gangtise fundamental valuation-analysis --security-code 600519.SH --indicator peTtm
149
167
  ```
150
168
 
@@ -152,12 +170,16 @@ gangtise fundamental valuation-analysis --security-code 600519.SH --indicator pe
152
170
 
153
171
  ```bash
154
172
  gangtise ai knowledge-batch --query 比亚迪 --query 最近热门概念
155
- gangtise ai security-clue --start-time "2026-03-01 00:00:00" --end-time "2026-03-23 23:59:59" --query-mode bySecurity --gts-code 000001.SZ --size 800
156
- gangtise ai security-clue --start-time "2026-04-01 00:00:00" --end-time "2026-04-07 23:59:59" --query-mode byIndustry --gts-code 821055.SWI
173
+ # resource-type:同时搜索券商研报和外资研报
174
+ gangtise ai knowledge-batch --query 新能源汽车 --resource-type 10 --resource-type 11 --top 10
175
+ gangtise ai security-clue --start-time "2026-04-01 00:00:00" --end-time "2026-04-09 23:59:59" --query-mode byIndustry --gts-code 821035.SWI --source researchReport --source announcement
157
176
  gangtise ai one-pager --security-code 600519.SH
158
177
  gangtise ai investment-logic --security-code 600519.SH
159
178
  gangtise ai peer-comparison --security-code 600519.SH
160
- gangtise ai cloud-disk-list --keyword 部门文档
179
+ gangtise ai earnings-review --security-code 600519.SH --period 2025q3
180
+ gangtise ai theme-tracking --theme-id 121000131 --date 2026-03-01 --type morning
181
+ gangtise ai research-outline --security-code 600519.SH
182
+ gangtise ai cloud-disk-list --keyword 部门文档 --space-type 1 --file-type 1
161
183
 
162
184
  # 云盘下载:自动使用文件标题命名
163
185
  gangtise ai cloud-disk-download --file-id 62130
@@ -178,12 +200,12 @@ gangtise raw call insight.opinion.list --body '{"from":0,"size":120}'
178
200
  ## 已验证的真实联调
179
201
 
180
202
  已真实跑通:
181
- - auth: `login`
182
- - lookup: `research-area list` / `broker-org list` / `meeting-org list` / `industry list` / `industry-code list`
203
+ - auth: `login` / `status`
204
+ - lookup: `research-area list` / `broker-org list` / `meeting-org list` / `industry list` / `industry-code list` / `region list` / `announcement-category list`
183
205
  - 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`
184
206
  - quote: `day-kline`
185
207
  - fundamental: `income-statement` / `main-business` / `valuation-analysis`
186
- - ai: `knowledge-batch` / `knowledge-resource-download` / `security-clue` / `cloud-disk-list` / `cloud-disk-download` / `one-pager` / `investment-logic` / `peer-comparison`
208
+ - ai: `knowledge-batch` / `knowledge-resource-download` / `security-clue` / `cloud-disk-list` / `cloud-disk-download` / `one-pager` / `investment-logic` / `peer-comparison` / `earnings-review` / `theme-tracking` / `research-outline`
187
209
 
188
210
  注意:`knowledge-resource-download` 依赖正确的 `resourceType + sourceId` 组合;错误组合会返回 `433007 不支持该数据源`。
189
211
 
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,64 +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 data = [
230
- { name: "申万农林牧渔", code: "821031.SWI" },
231
- { name: "申万基础化工", code: "821032.SWI" },
232
- { name: "申万钢铁", code: "821033.SWI" },
233
- { name: "申万有色金属", code: "821034.SWI" },
234
- { name: "申万电子", code: "821035.SWI" },
235
- { name: "申万汽车", code: "821036.SWI" },
236
- { name: "申万家用电器", code: "821037.SWI" },
237
- { name: "申万食品饮料", code: "821038.SWI" },
238
- { name: "申万纺织服饰", code: "821039.SWI" },
239
- { name: "申万轻工制造", code: "821040.SWI" },
240
- { name: "申万医药生物", code: "821041.SWI" },
241
- { name: "申万公用事业", code: "821042.SWI" },
242
- { name: "申万交通运输", code: "821043.SWI" },
243
- { name: "申万房地产", code: "821044.SWI" },
244
- { name: "申万商贸零售", code: "821045.SWI" },
245
- { name: "申万社会服务", code: "821046.SWI" },
246
- { name: "申万银行", code: "821047.SWI" },
247
- { name: "申万非银金融", code: "821048.SWI" },
248
- { name: "申万综合", code: "821049.SWI" },
249
- { name: "申万建筑材料", code: "821050.SWI" },
250
- { name: "申万建筑装饰", code: "821051.SWI" },
251
- { name: "申万电力设备", code: "821052.SWI" },
252
- { name: "申万机械设备", code: "821053.SWI" },
253
- { name: "申万国防军工", code: "821054.SWI" },
254
- { name: "申万计算机", code: "821055.SWI" },
255
- { name: "申万传媒", code: "821056.SWI" },
256
- { name: "申万通信", code: "821057.SWI" },
257
- { name: "申万煤炭", code: "821058.SWI" },
258
- { name: "申万石油石化", code: "821059.SWI" },
259
- { name: "申万环保", code: "821060.SWI" },
260
- { name: "申万美容护理", code: "821061.SWI" },
261
- ];
262
- await printData(data, parseFormat(options.format));
236
+ const client = await createClient();
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));
263
242
  })));
264
243
  program.addCommand(lookup);
265
244
  const insight = new Command("insight").description("Insight APIs");
@@ -273,7 +252,7 @@ const research = new Command("research");
273
252
  const foreignReport = new Command("foreign-report");
274
253
  const announcement = new Command("announcement");
275
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) => {
276
- const client = new GangtiseClient(loadConfig());
255
+ const client = await createClient();
277
256
  await printData(await client.call("insight.opinion.list", {
278
257
  from: Number(options.from), size: options.size === undefined ? undefined : Number(options.size), startTime: options.startTime, endTime: options.endTime,
279
258
  rankType: Number(options.rankType), keyword: options.keyword, researchAreaList: maybeArray(options.researchArea), chiefList: maybeArray(options.chief),
@@ -282,7 +261,7 @@ addTimeFilters(opinion.command("list").option("--rank-type <number>", "Rank type
282
261
  }), parseFormat(options.format), options.output);
283
262
  });
284
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) => {
285
- const client = new GangtiseClient(loadConfig());
264
+ const client = await createClient();
286
265
  await printData(await client.call("insight.summary.list", {
287
266
  from: Number(options.from), size: options.size === undefined ? undefined : Number(options.size), startTime: options.startTime, endTime: options.endTime,
288
267
  searchType: Number(options.searchType), rankType: Number(options.rankType), keyword: options.keyword, sourceList: options.source.length ? options.source : undefined,
@@ -291,13 +270,13 @@ addTimeFilters(summary.command("list").option("--search-type <number>", "Search
291
270
  }), parseFormat(options.format), options.output, { endpointKey: "insight.summary.list", idField: "summaryId" });
292
271
  });
293
272
  summary.command("download").requiredOption("--summary-id <id>").option("--output <path>").action(async (options) => {
294
- const client = new GangtiseClient(loadConfig());
273
+ const client = await createClient();
295
274
  const result = await client.call("insight.summary.download", undefined, { summaryId: options.summaryId });
296
275
  const title = options.output ? undefined : await resolveTitle(client, result, "insight.summary.list", "summaryId", options.summaryId);
297
276
  await saveDownloadResult(result, `summary-${options.summaryId}`, options.output ?? title);
298
277
  });
299
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) => {
300
- const client = new GangtiseClient(loadConfig());
279
+ const client = await createClient();
301
280
  await printData(await client.call(endpointKey, {
302
281
  from: Number(options.from), size: options.size === undefined ? undefined : Number(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword,
303
282
  researchAreaList: maybeArray(options.researchArea), institutionList: maybeArray(options.institution), securityList: maybeArray(options.security),
@@ -310,7 +289,7 @@ addScheduleList(siteVisit, "insight.site-visit.list");
310
289
  addScheduleList(strategy, "insight.strategy.list");
311
290
  addScheduleList(forum, "insight.forum.list");
312
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) => {
313
- const client = new GangtiseClient(loadConfig());
292
+ const client = await createClient();
314
293
  await printData(await client.call("insight.research.list", {
315
294
  from: Number(options.from), size: options.size === undefined ? undefined : Number(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword,
316
295
  searchType: Number(options.searchType), rankType: Number(options.rankType),
@@ -321,13 +300,13 @@ addTimeFilters(research.command("list").option("--search-type <number>", "Search
321
300
  }), parseFormat(options.format), options.output, { endpointKey: "insight.research.list", idField: "reportId" });
322
301
  });
323
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) => {
324
- const client = new GangtiseClient(loadConfig());
303
+ const client = await createClient();
325
304
  const result = await client.call("insight.research.download", undefined, { reportId: options.reportId, fileType: Number(options.fileType) });
326
305
  const title = options.output ? undefined : await resolveTitle(client, result, "insight.research.list", "reportId", options.reportId);
327
306
  await saveDownloadResult(result, `research-${options.reportId}`, options.output ?? title);
328
307
  });
329
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) => {
330
- const client = new GangtiseClient(loadConfig());
309
+ const client = await createClient();
331
310
  await printData(await client.call("insight.foreign-report.list", {
332
311
  from: Number(options.from), size: options.size === undefined ? undefined : Number(options.size), startTime: options.startTime, endTime: options.endTime, keyword: options.keyword,
333
312
  searchType: Number(options.searchType), rankType: Number(options.rankType),
@@ -338,13 +317,13 @@ addTimeFilters(foreignReport.command("list").option("--search-type <number>", "S
338
317
  }), parseFormat(options.format), options.output, { endpointKey: "insight.foreign-report.list", idField: "reportId" });
339
318
  });
340
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) => {
341
- const client = new GangtiseClient(loadConfig());
320
+ const client = await createClient();
342
321
  const result = await client.call("insight.foreign-report.download", undefined, { reportId: options.reportId, fileType: Number(options.fileType) });
343
322
  const title = options.output ? undefined : await resolveTitle(client, result, "insight.foreign-report.list", "reportId", options.reportId);
344
323
  await saveDownloadResult(result, `foreign-report-${options.reportId}`, options.output ?? title);
345
324
  });
346
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) => {
347
- const client = new GangtiseClient(loadConfig());
326
+ const client = await createClient();
348
327
  await printData(await client.call("insight.announcement.list", {
349
328
  from: Number(options.from), size: options.size === undefined ? undefined : Number(options.size),
350
329
  startTime: toTimestamp13(options.startTime), endTime: toTimestamp13(options.endTime),
@@ -353,7 +332,7 @@ addTimeFilters(announcement.command("list").option("--search-type <number>", "Se
353
332
  }), parseFormat(options.format), options.output, { endpointKey: "insight.announcement.list", idField: "announcementId" });
354
333
  });
355
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) => {
356
- const client = new GangtiseClient(loadConfig());
335
+ const client = await createClient();
357
336
  const result = await client.call("insight.announcement.download", undefined, { announcementId: options.announcementId, fileType: Number(options.fileType) });
358
337
  const title = options.output ? undefined : await resolveTitle(client, result, "insight.announcement.list", "announcementId", options.announcementId);
359
338
  await saveDownloadResult(result, `announcement-${options.announcementId}`, options.output ?? title);
@@ -370,62 +349,133 @@ insight.addCommand(announcement);
370
349
  program.addCommand(insight);
371
350
  const quote = new Command("quote").description("Quote APIs");
372
351
  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) => {
373
- const client = new GangtiseClient(loadConfig());
352
+ const client = await createClient();
374
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);
375
354
  });
376
355
  program.addCommand(quote);
377
356
  const fundamental = new Command("fundamental").description("Fundamental APIs");
378
357
  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) => {
379
- const client = new GangtiseClient(loadConfig());
380
- 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);
358
+ const client = await createClient();
359
+ 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);
360
+ });
361
+ 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) => {
362
+ const client = await createClient();
363
+ 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);
364
+ });
365
+ 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) => {
366
+ const client = await createClient();
367
+ 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);
381
368
  });
382
369
  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) => {
383
- const client = new GangtiseClient(loadConfig());
370
+ const client = await createClient();
384
371
  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);
385
372
  });
386
373
  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) => {
387
- const client = new GangtiseClient(loadConfig());
374
+ const client = await createClient();
388
375
  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);
389
376
  });
390
377
  program.addCommand(fundamental);
391
378
  const ai = new Command("ai").description("AI APIs");
392
379
  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) => {
393
- const client = new GangtiseClient(loadConfig());
380
+ const client = await createClient();
394
381
  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);
395
382
  });
396
383
  ai.command("knowledge-resource-download").requiredOption("--resource-type <number>").requiredOption("--source-id <id>").option("--output <path>").action(async (options) => {
397
- const client = new GangtiseClient(loadConfig());
384
+ const client = await createClient();
398
385
  await saveDownloadResult(await client.call("ai.knowledge-resource.download", undefined, { resourceType: Number(options.resourceType), sourceId: options.sourceId }), `resource-${options.sourceId}`, options.output);
399
386
  });
400
387
  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) => {
401
- const client = new GangtiseClient(loadConfig());
388
+ const client = await createClient();
402
389
  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);
403
390
  });
404
391
  ai.command("one-pager").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
405
- const client = new GangtiseClient(loadConfig());
392
+ const client = await createClient();
406
393
  await printData(await client.call("ai.one-pager", { securityCode: options.securityCode }), parseFormat(options.format), options.output);
407
394
  });
408
395
  ai.command("investment-logic").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
409
- const client = new GangtiseClient(loadConfig());
396
+ const client = await createClient();
410
397
  await printData(await client.call("ai.investment-logic", { securityCode: options.securityCode }), parseFormat(options.format), options.output);
411
398
  });
412
399
  ai.command("peer-comparison").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
413
- const client = new GangtiseClient(loadConfig());
400
+ const client = await createClient();
414
401
  await printData(await client.call("ai.peer-comparison", { securityCode: options.securityCode }), parseFormat(options.format), options.output);
415
402
  });
403
+ 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) => {
404
+ const client = await createClient();
405
+ // Step 1: get dataId
406
+ const idResult = await client.call("ai.earnings-review.get-id", { securityCode: options.securityCode, period: options.period });
407
+ const dataId = idResult?.dataId;
408
+ if (!dataId) {
409
+ process.stderr.write("Failed to get earnings review ID. The report may not be available yet.\n");
410
+ process.exitCode = 1;
411
+ return;
412
+ }
413
+ // Non-blocking: return dataId immediately
414
+ if (!options.wait) {
415
+ process.stderr.write(`Earnings review task submitted. dataId: ${dataId}\n`);
416
+ 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`);
417
+ return;
418
+ }
419
+ // Blocking (--wait): poll for content
420
+ process.stderr.write(`Got dataId: ${dataId}, waiting for content generation...\n`);
421
+ let attempts = 0;
422
+ const maxAttempts = 12;
423
+ const delayMs = 15_000;
424
+ while (attempts < maxAttempts) {
425
+ attempts++;
426
+ const contentResult = await client.call("ai.earnings-review.get-content", { dataId });
427
+ if (contentResult?.data?.content) {
428
+ await printData(contentResult.data, parseFormat(options.format), options.output);
429
+ return;
430
+ }
431
+ if (attempts < maxAttempts) {
432
+ process.stderr.write(`Attempt ${attempts}/${maxAttempts}: content not ready, retrying in 15s...\n`);
433
+ await new Promise(resolve => setTimeout(resolve, delayMs));
434
+ }
435
+ }
436
+ process.stderr.write(`Content not available after ${maxAttempts} attempts. Try again later with: gangtise ai earnings-review-check --data-id ${dataId}\n`);
437
+ process.exitCode = 1;
438
+ });
439
+ 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) => {
440
+ const client = await createClient();
441
+ try {
442
+ const contentResult = await client.call("ai.earnings-review.get-content", { dataId: options.dataId });
443
+ if (contentResult?.data?.content) {
444
+ await printData(contentResult.data, parseFormat(options.format), options.output);
445
+ return;
446
+ }
447
+ process.stdout.write(`${JSON.stringify({ dataId: options.dataId, status: "pending", hint: "Content not ready yet, retry in ~2 minutes" })}\n`);
448
+ }
449
+ catch (error) {
450
+ if (error instanceof ApiError && (error.code === "410110" || error.message?.includes("生成中"))) {
451
+ process.stdout.write(`${JSON.stringify({ dataId: options.dataId, status: "pending", hint: "Content not ready yet, retry in ~2 minutes" })}\n`);
452
+ return;
453
+ }
454
+ throw error;
455
+ }
456
+ });
457
+ 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) => {
458
+ const client = await createClient();
459
+ const typeList = options.type.length ? options.type : undefined;
460
+ await printData(await client.call("ai.theme-tracking", { themeId: options.themeId, date: options.date, type: typeList }), parseFormat(options.format), options.output);
461
+ });
462
+ ai.command("research-outline").requiredOption("--security-code <code>").option("--format <format>", "Output format", "json").option("--output <path>").action(async (options) => {
463
+ const client = await createClient();
464
+ await printData(await client.call("ai.research-outline", { securityCode: options.securityCode }), parseFormat(options.format), options.output);
465
+ });
416
466
  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) => {
417
- const client = new GangtiseClient(loadConfig());
467
+ const client = await createClient();
418
468
  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" });
419
469
  });
420
470
  ai.command("cloud-disk-download").requiredOption("--file-id <id>").option("--output <path>").action(async (options) => {
421
- const client = new GangtiseClient(loadConfig());
471
+ const client = await createClient();
422
472
  const result = await client.call("ai.cloud-disk.download", undefined, { fileId: options.fileId });
423
473
  const title = options.output ? undefined : await resolveTitle(client, result, "ai.cloud-disk.list", "fileId", options.fileId);
424
474
  await saveDownloadResult(result, `file-${options.fileId}`, options.output ?? title);
425
475
  });
426
476
  program.addCommand(ai);
427
477
  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) => {
428
- const client = new GangtiseClient(loadConfig());
478
+ const client = await createClient();
429
479
  const body = options.body ? JSON.parse(options.body) : undefined;
430
480
  const data = await client.call(endpointKey, body, options.query);
431
481
  if (data && typeof data === "object" && "data" in data && data.data instanceof Uint8Array) {
@@ -455,4 +505,3 @@ async function main() {
455
505
  }
456
506
  }
457
507
  void main();
458
- //# 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