gangtise-openapi-cli 0.5.1 → 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
@@ -152,8 +152,16 @@ gangtise quote day-kline --security 600519.SH --start-date 2026-03-01 --end-date
152
152
 
153
153
  ```bash
154
154
  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
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
157
165
  gangtise fundamental main-business --security-code 600519.SH --breakdown region
158
166
  gangtise fundamental valuation-analysis --security-code 600519.SH --indicator peTtm
159
167
  ```
@@ -168,6 +176,9 @@ gangtise ai security-clue --start-time "2026-04-01 00:00:00" --end-time "2026-04
168
176
  gangtise ai one-pager --security-code 600519.SH
169
177
  gangtise ai investment-logic --security-code 600519.SH
170
178
  gangtise ai peer-comparison --security-code 600519.SH
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
171
182
  gangtise ai cloud-disk-list --keyword 部门文档 --space-type 1 --file-type 1
172
183
 
173
184
  # 云盘下载:自动使用文件标题命名
@@ -194,7 +205,7 @@ gangtise raw call insight.opinion.list --body '{"from":0,"size":120}'
194
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`
195
206
  - quote: `day-kline`
196
207
  - 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`
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`
198
209
 
199
210
  注意:`knowledge-resource-download` 依赖正确的 `resourceType + sourceId` 组合;错误组合会返回 `433007 不支持该数据源`。
200
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,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);
@@ -338,62 +349,133 @@ insight.addCommand(announcement);
338
349
  program.addCommand(insight);
339
350
  const quote = new Command("quote").description("Quote APIs");
340
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) => {
341
- const client = new GangtiseClient(loadConfig());
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
  });
344
355
  program.addCommand(quote);
345
356
  const fundamental = new Command("fundamental").description("Fundamental APIs");
346
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) => {
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);
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);
349
368
  });
350
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) => {
351
- const client = new GangtiseClient(loadConfig());
370
+ const client = await createClient();
352
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);
353
372
  });
354
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) => {
355
- const client = new GangtiseClient(loadConfig());
374
+ const client = await createClient();
356
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);
357
376
  });
358
377
  program.addCommand(fundamental);
359
378
  const ai = new Command("ai").description("AI APIs");
360
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) => {
361
- const client = new GangtiseClient(loadConfig());
380
+ const client = await createClient();
362
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);
363
382
  });
364
383
  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());
384
+ const client = await createClient();
366
385
  await saveDownloadResult(await client.call("ai.knowledge-resource.download", undefined, { resourceType: Number(options.resourceType), sourceId: options.sourceId }), `resource-${options.sourceId}`, options.output);
367
386
  });
368
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) => {
369
- const client = new GangtiseClient(loadConfig());
388
+ const client = await createClient();
370
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);
371
390
  });
372
391
  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());
392
+ const client = await createClient();
374
393
  await printData(await client.call("ai.one-pager", { securityCode: options.securityCode }), parseFormat(options.format), options.output);
375
394
  });
376
395
  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());
396
+ const client = await createClient();
378
397
  await printData(await client.call("ai.investment-logic", { securityCode: options.securityCode }), parseFormat(options.format), options.output);
379
398
  });
380
399
  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());
400
+ const client = await createClient();
382
401
  await printData(await client.call("ai.peer-comparison", { securityCode: options.securityCode }), parseFormat(options.format), options.output);
383
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
+ });
384
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) => {
385
- const client = new GangtiseClient(loadConfig());
467
+ const client = await createClient();
386
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" });
387
469
  });
388
470
  ai.command("cloud-disk-download").requiredOption("--file-id <id>").option("--output <path>").action(async (options) => {
389
- const client = new GangtiseClient(loadConfig());
471
+ const client = await createClient();
390
472
  const result = await client.call("ai.cloud-disk.download", undefined, { fileId: options.fileId });
391
473
  const title = options.output ? undefined : await resolveTitle(client, result, "ai.cloud-disk.list", "fileId", options.fileId);
392
474
  await saveDownloadResult(result, `file-${options.fileId}`, options.output ?? title);
393
475
  });
394
476
  program.addCommand(ai);
395
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) => {
396
- const client = new GangtiseClient(loadConfig());
478
+ const client = await createClient();
397
479
  const body = options.body ? JSON.parse(options.body) : undefined;
398
480
  const data = await client.call(endpointKey, body, options.query);
399
481
  if (data && typeof data === "object" && "data" in data && data.data instanceof Uint8Array) {
@@ -423,4 +505,3 @@ async function main() {
423
505
  }
424
506
  }
425
507
  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
@@ -2,7 +2,7 @@ import { request } from "undici";
2
2
  import { normalizeToken, readTokenCache, requireAccessCredentials, writeTokenCache } from "./auth.js";
3
3
  import { ApiError, ValidationError } from "./errors.js";
4
4
  import { ENDPOINTS, ENDPOINT_REGISTRY } from "./endpoints.js";
5
- import { ANNOUNCEMENT_CATEGORIES, BROKER_ORGS, INDUSTRIES, INDUSTRY_CODES, MEETING_ORGS, REGIONS, RESEARCH_AREAS } from "./lookupData.js";
5
+ import { getLookupData } from "./lookupData/index.js";
6
6
  export class GangtiseClient {
7
7
  config;
8
8
  constructor(config) {
@@ -47,26 +47,19 @@ export class GangtiseClient {
47
47
  return parsed;
48
48
  }
49
49
  async readLocalLookup(endpoint) {
50
- if (endpoint.key === "lookup.research-areas.list") {
51
- return RESEARCH_AREAS;
52
- }
53
- if (endpoint.key === "lookup.broker-orgs.list") {
54
- return BROKER_ORGS;
55
- }
56
- if (endpoint.key === "lookup.meeting-orgs.list") {
57
- return MEETING_ORGS;
58
- }
59
- if (endpoint.key === "lookup.industries.list") {
60
- return INDUSTRIES;
61
- }
62
- if (endpoint.key === "lookup.regions.list") {
63
- return REGIONS;
64
- }
65
- if (endpoint.key === "lookup.announcement-categories.list") {
66
- return ANNOUNCEMENT_CATEGORIES;
67
- }
68
- if (endpoint.key === "lookup.industry-codes.list") {
69
- return INDUSTRY_CODES;
50
+ const keyMapping = {
51
+ "lookup.research-areas.list": "research-areas",
52
+ "lookup.broker-orgs.list": "broker-orgs",
53
+ "lookup.meeting-orgs.list": "meeting-orgs",
54
+ "lookup.industries.list": "industries",
55
+ "lookup.regions.list": "regions",
56
+ "lookup.announcement-categories.list": "announcement-categories",
57
+ "lookup.industry-codes.list": "industry-codes",
58
+ "lookup.theme-ids.list": "theme-ids",
59
+ };
60
+ const lookupKey = keyMapping[endpoint.key];
61
+ if (lookupKey) {
62
+ return getLookupData(lookupKey);
70
63
  }
71
64
  throw new ApiError(`Unsupported local lookup endpoint: ${endpoint.key}`);
72
65
  }
@@ -253,4 +246,3 @@ export class GangtiseClient {
253
246
  return this.requestJson(endpoint, body);
254
247
  }
255
248
  }
256
- //# sourceMappingURL=client.js.map
@@ -15,4 +15,3 @@ export function loadConfig() {
15
15
  tokenCachePath: process.env.GANGTISE_TOKEN_CACHE_PATH ?? DEFAULT_TOKEN_CACHE_PATH,
16
16
  };
17
17
  }
18
- //# sourceMappingURL=config.js.map
@@ -55,6 +55,13 @@ export const ENDPOINTS = {
55
55
  kind: "json",
56
56
  description: "List Shenwan industry codes from local docs",
57
57
  },
58
+ lookupThemeIds: {
59
+ key: "lookup.theme-ids.list",
60
+ method: "GET",
61
+ path: "/guide/theme-ids-local",
62
+ kind: "json",
63
+ description: "List theme IDs from local docs",
64
+ },
58
65
  insightOpinionList: {
59
66
  key: "insight.opinion.list",
60
67
  method: "POST",
@@ -165,9 +172,23 @@ export const ENDPOINTS = {
165
172
  fundamentalIncomeStatement: {
166
173
  key: "fundamental.income-statement",
167
174
  method: "POST",
168
- path: "/application/open-fundamental/financial-report/income-statement",
175
+ path: "/application/open-fundamental/financial-report/income-statement/accumulated",
176
+ kind: "json",
177
+ description: "Query income statement (accumulated)",
178
+ },
179
+ fundamentalBalanceSheet: {
180
+ key: "fundamental.balance-sheet",
181
+ method: "POST",
182
+ path: "/application/open-fundamental/financial-report/balance-sheet/accumulated",
169
183
  kind: "json",
170
- description: "Query income statement",
184
+ description: "Query balance sheet (accumulated)",
185
+ },
186
+ fundamentalCashFlow: {
187
+ key: "fundamental.cash-flow",
188
+ method: "POST",
189
+ path: "/application/open-fundamental/financial-report/cash-flow-statement/accumulated",
190
+ kind: "json",
191
+ description: "Query cash flow statement (accumulated)",
171
192
  },
172
193
  fundamentalMainBusiness: {
173
194
  key: "fundamental.main-business",
@@ -226,6 +247,34 @@ export const ENDPOINTS = {
226
247
  kind: "json",
227
248
  description: "Generate peer comparison",
228
249
  },
250
+ aiEarningsReviewGetId: {
251
+ key: "ai.earnings-review.get-id",
252
+ method: "POST",
253
+ path: "/application/open-ai/agent/earnings-review-getid",
254
+ kind: "json",
255
+ description: "Get earnings review ID",
256
+ },
257
+ aiEarningsReviewGetContent: {
258
+ key: "ai.earnings-review.get-content",
259
+ method: "POST",
260
+ path: "/application/open-ai/agent/earnings-review-getcontent",
261
+ kind: "json",
262
+ description: "Get earnings review content",
263
+ },
264
+ aiThemeTracking: {
265
+ key: "ai.theme-tracking",
266
+ method: "POST",
267
+ path: "/application/open-ai/agent/theme-tracking",
268
+ kind: "json",
269
+ description: "Get theme tracking daily report",
270
+ },
271
+ aiResearchOutline: {
272
+ key: "ai.research-outline",
273
+ method: "POST",
274
+ path: "/application/open-ai/agent/research-outline",
275
+ kind: "json",
276
+ description: "Get company research outline",
277
+ },
229
278
  aiCloudDiskList: {
230
279
  key: "ai.cloud-disk.list",
231
280
  method: "POST",
@@ -246,4 +295,3 @@ export const ENDPOINT_REGISTRY = Object.values(ENDPOINTS).reduce((accumulator, e
246
295
  accumulator[endpoint.key] = endpoint;
247
296
  return accumulator;
248
297
  }, {});
249
- //# sourceMappingURL=endpoints.js.map