gangtise-openapi-cli 0.17.0 → 0.17.2

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/dist/src/cli.js CHANGED
@@ -261,10 +261,19 @@ insight.addCommand(foreignOpinion);
261
261
  insight.addCommand(independentOpinion);
262
262
  program.addCommand(insight);
263
263
  const quote = new Command("quote").description("Quote APIs");
264
- quote.command("day-kline").option("--security <code>", "Security code (A-share: .SH/.SZ/.BJ, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => callKlineWithSharding(client, "quote.day-kline", buildQuoteKlineBody(options), { shardDays: 1 })));
265
- quote.command("day-kline-hk").option("--security <code>", "Security code (HK stock: .HK, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => callKlineWithSharding(client, "quote.day-kline-hk", buildQuoteKlineBody(options), { shardDays: 2 })));
266
- quote.command("day-kline-us").option("--security <code>", "Security code (US stock: e.g. AAPL.O, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => callKlineWithSharding(client, "quote.day-kline-us", buildQuoteKlineBody(options), { shardDays: 1 })));
267
- quote.command("index-day-kline").option("--security <code>", "Index code (.SH/.SZ/.BJ, or 'all' for full market)", collectList, []).option("--start-date <date>", "Start date (default: 1 year before end-date)").option("--end-date <date>", "End date (default: latest)").option("--limit <number>", "Max rows per request (default: 6000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => callKlineWithSharding(client, "quote.index-day-kline", buildQuoteKlineBody(options), { shardDays: 30 })));
264
+ const addKlineCommand = (name, endpointKey, securityHelp, shardDays) => quote.command(name)
265
+ .option("--security <code>", securityHelp, collectList, [])
266
+ .option("--start-date <date>", "Start date (default: 1 year before end-date)")
267
+ .option("--end-date <date>", "End date (default: latest)")
268
+ .option("--limit <number>", "Max rows per request (default: 6000, max: 10000)")
269
+ .option("--field <field>", "Field", collectList, [])
270
+ .option("--format <format>", "Output format", "table")
271
+ .option("--output <path>")
272
+ .action((options) => emit(options, (client) => callKlineWithSharding(client, endpointKey, buildQuoteKlineBody(options), { shardDays })));
273
+ addKlineCommand("day-kline", "quote.day-kline", "Security code (A-share: .SH/.SZ/.BJ, or 'all' for full market)", 1);
274
+ addKlineCommand("day-kline-hk", "quote.day-kline-hk", "Security code (HK stock: .HK, or 'all' for full market)", 2);
275
+ addKlineCommand("day-kline-us", "quote.day-kline-us", "Security code (US stock: e.g. AAPL.O, or 'all' for full market)", 1);
276
+ addKlineCommand("index-day-kline", "quote.index-day-kline", "Index code (.SH/.SZ/.BJ, or 'all' for full market)", 30);
268
277
  quote.command("minute-kline").option("--security <code>", "Security code (A-share only: .SH/.SZ/.BJ)").option("--start-time <datetime>", "Start time (yyyy-MM-dd HH:mm:ss)").option("--end-time <datetime>", "End time (yyyy-MM-dd HH:mm:ss)").option("--limit <number>", "Max rows per request (default: 5000, max: 10000)").option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("quote.minute-kline", { securityCode: options.security, startTime: options.startTime, endTime: options.endTime, limit: parseOptionalNumberOption(options.limit, "--limit", { integer: true, min: 1 }), fieldList: maybeArray(options.field) })));
269
278
  quote.command("realtime").description("Realtime quote snapshot (A-share / HK / US)").option("--security <code>", "Security code (e.g. 600519.SH / 00700.HK / AAPL.O), or market keyword: aShares / hkStocks / usStocks", collectList, []).option("--field <field>", "Field", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("quote.realtime", { securityList: maybeArray(options.security), fieldList: maybeArray(options.field) })));
270
279
  program.addCommand(quote);
@@ -9,7 +9,10 @@ import { ENDPOINTS } from "./endpoints.js";
9
9
  import { getLookupData } from "./lookupData/index.js";
10
10
  import { getDispatcher, isVerbose, logTiming, markRetryable, runWithConcurrency, withRetry } from "./transport.js";
11
11
  const PAGINATION_CONCURRENCY = Number(process.env.GANGTISE_PAGE_CONCURRENCY ?? 5) || 5;
12
- const AUTH_RETRY_CODES = new Set(["8000014", "8000015"]);
12
+ // Auth errors that warrant a forced re-login + one replay. 8000014/8000015 are
13
+ // AK/SK errors; 0000001008 is a server-side token invalidation (the token still
14
+ // looks valid by local expiry, so only a forced refresh recovers it).
15
+ const AUTH_RETRY_CODES = new Set(["8000014", "8000015", "0000001008"]);
13
16
  export class GangtiseClient {
14
17
  config;
15
18
  refreshPromise = null;
@@ -165,8 +168,10 @@ export class GangtiseClient {
165
168
  nextFrom += size;
166
169
  }
167
170
  const MAX_PAGES = 1000;
171
+ let truncatedByPageCap = false;
168
172
  if (pageRequests.length + 1 > MAX_PAGES) {
169
173
  pageRequests.length = MAX_PAGES - 1;
174
+ truncatedByPageCap = true;
170
175
  }
171
176
  let unexpectedShape = false;
172
177
  let totalDrift = false;
@@ -195,6 +200,12 @@ export class GangtiseClient {
195
200
  if (totalDrift && isVerbose()) {
196
201
  process.stderr.write(`[gangtise] warning: 'total' changed across pages (data shifted during fetch)\n`);
197
202
  }
203
+ // Always surface a cap-induced truncation (not gated on verbose): the user
204
+ // asked for everything and is silently getting a subset, mirroring the
205
+ // partial-result warning in quoteSharding.
206
+ if (truncatedByPageCap) {
207
+ process.stderr.write(`[gangtise] warning: hit the ${MAX_PAGES}-page safety cap; fetched ${collected.length} of ${total} rows. Narrow the query (e.g. a shorter date range) or pass --size to fetch a bounded subset.\n`);
208
+ }
198
209
  return {
199
210
  ...firstPage,
200
211
  total,
@@ -330,7 +341,16 @@ export class GangtiseClient {
330
341
  // Stream directly to disk when caller already knows the destination
331
342
  if (options?.streamTo) {
332
343
  await fs.mkdir(path.dirname(options.streamTo), { recursive: true });
333
- await pipeline(response.body, createWriteStream(options.streamTo));
344
+ try {
345
+ await pipeline(response.body, createWriteStream(options.streamTo));
346
+ }
347
+ catch (error) {
348
+ // A mid-stream failure leaves a truncated file on disk; remove it so a
349
+ // failed download never looks like a complete one. withRetry may still
350
+ // replay the request (the next attempt re-creates the file).
351
+ await fs.unlink(options.streamTo).catch(() => { });
352
+ throw error;
353
+ }
334
354
  logTiming(`GET ${endpoint.path} (stream)`, Date.now() - startedAt, `${response.statusCode}`);
335
355
  return { contentType, filename, savedPath: options.streamTo };
336
356
  }
@@ -16,6 +16,7 @@ const ERROR_HINTS = {
16
16
  "999995": "当前账号积分不足。",
17
17
  "900002": "请求缺少 uid。",
18
18
  "900001": "请求参数为空或缺少必填项。",
19
+ "0000001008": "Token 已失效(多为他处登录挤掉本会话);有 AK/SK 时 CLI 会自动重新登录重试一次,否则请重新登录。",
19
20
  "8000014": "GANGTISE_ACCESS_KEY 错误。",
20
21
  "8000015": "GANGTISE_SECRET_KEY 错误。",
21
22
  "8000016": "开发账号状态异常。",
@@ -1,2 +1,2 @@
1
1
  // Auto-generated — DO NOT EDIT
2
- export const CLI_VERSION = "0.17.0";
2
+ export const CLI_VERSION = "0.17.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gangtise-openapi-cli",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
4
4
  "description": "CLI for Gangtise OpenAPI",
5
5
  "license": "MIT",
6
6
  "repository": {