@investoday/investoday-api 1.8.26 → 1.9.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
@@ -33,6 +33,27 @@ Get an API key from:
33
33
 
34
34
  - https://data-api.investoday.net/user/api-key
35
35
 
36
+ ## API base URL
37
+
38
+ The default API base URL is:
39
+
40
+ ```text
41
+ https://data-api.investoday.net/data
42
+ ```
43
+
44
+ Temporarily override it with an environment variable:
45
+
46
+ ```bash
47
+ INVESTODAY_API_BASE_URL="https://data-api.investoday.net/data" investoday-api list
48
+ ```
49
+
50
+ Persist a custom value in the local config:
51
+
52
+ ```bash
53
+ investoday-api config set-base-url "https://data-api.investoday.net/data"
54
+ investoday-api config reset-base-url
55
+ ```
56
+
36
57
  ## Usage
37
58
 
38
59
  ```bash
@@ -41,6 +62,8 @@ investoday-api init
41
62
  investoday-api config status
42
63
  investoday-api config path
43
64
  investoday-api config remove
65
+ investoday-api config set-base-url "https://data-api.investoday.net/data"
66
+ investoday-api config reset-base-url
44
67
  investoday-api update status
45
68
  investoday-api update run
46
69
  investoday-api update enable
package/data/openapi.json CHANGED
@@ -2029,7 +2029,7 @@
2029
2029
  "行业及关联成分股的最新实时日行情"
2030
2030
  ],
2031
2031
  "summary": "行业及关联成分股的最新实时日行情",
2032
- "description": "输入行业代码,获取该行业的最新整体行情(包括指数、涨跌幅、成交量、总市值、成分股数量、涨跌幅排名、领涨股等)以及所有关联成分股的实时行情明细(包括股票代码、名称、开盘价、当前价、涨跌幅、最高/最低价等),用于全面分析行业及其成分股的最新市场表现。",
2032
+ "description": "通过行业代码(必填)查询对应行业及相关成分股的最新实时日行情数据,支持按响应字段指定排序规则、分页查询,返回行业整体行情信息(含价格、涨跌幅、成交量、近一周涨跌幅、总市值、行业内涨跌排名、涨跌家数统计、领涨股信息等)、成分股实时行情详情(含股票代码、名称、最新价、涨跌幅、开盘价、最高价、最低价等)及分页相关数据,可用于行业行情监测、成分股分析、市场热点追踪等场景。",
2033
2033
  "operationId": "get_industry_stock_realtime_quote",
2034
2034
  "parameters": [
2035
2035
  {
@@ -2039,7 +2039,59 @@
2039
2039
  "required": true,
2040
2040
  "schema": {
2041
2041
  "type": "string",
2042
- "example": "330000"
2042
+ "example": "740000"
2043
+ }
2044
+ },
2045
+ {
2046
+ "name": "sortColumn",
2047
+ "in": "query",
2048
+ "description": "排序字段(响应字段)",
2049
+ "required": false,
2050
+ "schema": {
2051
+ "type": "string",
2052
+ "example": "changeRatio"
2053
+ }
2054
+ },
2055
+ {
2056
+ "name": "order",
2057
+ "in": "query",
2058
+ "description": "排序方向",
2059
+ "required": false,
2060
+ "schema": {
2061
+ "type": "string",
2062
+ "allOf": [
2063
+ {
2064
+ "type": "string",
2065
+ "description": "asc代表正序;\ndesc 代表倒序",
2066
+ "enum": [
2067
+ "asc",
2068
+ "desc"
2069
+ ]
2070
+ }
2071
+ ],
2072
+ "example": "desc"
2073
+ }
2074
+ },
2075
+ {
2076
+ "name": "page",
2077
+ "in": "query",
2078
+ "description": "页码",
2079
+ "required": false,
2080
+ "schema": {
2081
+ "type": "integer",
2082
+ "format": "int32",
2083
+ "example": 1
2084
+ }
2085
+ },
2086
+ {
2087
+ "name": "pageSize",
2088
+ "in": "query",
2089
+ "description": "页长",
2090
+ "required": false,
2091
+ "schema": {
2092
+ "type": "integer",
2093
+ "format": "int32",
2094
+ "example": 10
2043
2095
  }
2044
2096
  }
2045
2097
  ],
@@ -2072,9 +2124,10 @@
2072
2124
  "example": -0.0121602
2073
2125
  },
2074
2126
  "volume": {
2075
- "type": "string",
2127
+ "type": "integer",
2128
+ "format": "int32",
2076
2129
  "description": "行业成交量",
2077
- "example": "9547379"
2130
+ "example": 9547379
2078
2131
  },
2079
2132
  "changeRatio1W": {
2080
2133
  "type": "number",
@@ -2381,10 +2434,20 @@
2381
2434
  {
2382
2435
  "name": "conceptType",
2383
2436
  "in": "query",
2384
- "description": "概念类型(jy-聚源、cls-财联社)\"",
2437
+ "description": "概念类型",
2385
2438
  "required": true,
2386
2439
  "schema": {
2387
2440
  "type": "string",
2441
+ "allOf": [
2442
+ {
2443
+ "type": "string",
2444
+ "description": "jy代表聚源;\ncls代表财联社",
2445
+ "enum": [
2446
+ "jy",
2447
+ "cls"
2448
+ ]
2449
+ }
2450
+ ],
2388
2451
  "example": "jy"
2389
2452
  }
2390
2453
  },
@@ -2397,6 +2460,58 @@
2397
2460
  "type": "string",
2398
2461
  "example": "14060061"
2399
2462
  }
2463
+ },
2464
+ {
2465
+ "name": "sortColumn",
2466
+ "in": "query",
2467
+ "description": "排序字段(响应字段)",
2468
+ "required": false,
2469
+ "schema": {
2470
+ "type": "string",
2471
+ "example": "changeRatio"
2472
+ }
2473
+ },
2474
+ {
2475
+ "name": "order",
2476
+ "in": "query",
2477
+ "description": "排序方向",
2478
+ "required": false,
2479
+ "schema": {
2480
+ "type": "string",
2481
+ "allOf": [
2482
+ {
2483
+ "type": "string",
2484
+ "description": "asc代表正序;\ndesc 代表倒序",
2485
+ "enum": [
2486
+ "asc",
2487
+ "desc"
2488
+ ]
2489
+ }
2490
+ ],
2491
+ "example": "desc"
2492
+ }
2493
+ },
2494
+ {
2495
+ "name": "page",
2496
+ "in": "query",
2497
+ "description": "页码",
2498
+ "required": false,
2499
+ "schema": {
2500
+ "type": "integer",
2501
+ "format": "int32",
2502
+ "example": 1
2503
+ }
2504
+ },
2505
+ {
2506
+ "name": "pageSize",
2507
+ "in": "query",
2508
+ "description": "页长",
2509
+ "required": false,
2510
+ "schema": {
2511
+ "type": "integer",
2512
+ "format": "int32",
2513
+ "example": 10
2514
+ }
2400
2515
  }
2401
2516
  ],
2402
2517
  "responses": {
package/data/tree.json CHANGED
@@ -3409,7 +3409,7 @@
3409
3409
  "apiStatus": "release",
3410
3410
  "groupId": 1031,
3411
3411
  "remarks": null,
3412
- "toolName": "get_industry_stock_realtime_quote",
3412
+ "toolName": "get_ind_stk_realtime_qt",
3413
3413
  "toolId": "get_industry_stock_realtime_quote",
3414
3414
  "apiPath": "industry-quote/stock-realtime",
3415
3415
  "enabled": 1,
package/lib/call-api.js CHANGED
@@ -6,11 +6,17 @@ const {
6
6
  } = require("./skill-installer");
7
7
  const {
8
8
  CONFIG_DIR_ENV,
9
+ API_BASE_URL_ENV,
10
+ DEFAULT_API_BASE_URL,
9
11
  getConfigDir,
10
12
  getCredentialsPath,
13
+ readConfig,
11
14
  readCredentials,
15
+ removeApiBaseUrl,
12
16
  removeCredentials,
17
+ resolveApiBaseUrl,
13
18
  resolveApiKey,
19
+ saveApiBaseUrl,
14
20
  saveCredentials,
15
21
  } = require("./config");
16
22
  const { version: PACKAGE_VERSION } = require("../package.json");
@@ -23,7 +29,7 @@ const {
23
29
  runUpdateCommand,
24
30
  } = require("./update");
25
31
 
26
- const BASE_URL = "https://data-api.investoday.net/data";
32
+ const BASE_URL = DEFAULT_API_BASE_URL;
27
33
  const REQUEST_TIMEOUT = 30_000;
28
34
  const API_KEY_MANAGE_URL = "https://data-api.investoday.net/user/api-key";
29
35
 
@@ -68,7 +74,7 @@ function printHelp() {
68
74
  "investoday-api\n\n" +
69
75
  "用法:\n" +
70
76
  " investoday-api init\n" +
71
- " investoday-api config status|path|remove\n" +
77
+ " investoday-api config status|path|remove|set-base-url|reset-base-url\n" +
72
78
  " investoday-api update run|status|enable|disable|register|unregister\n" +
73
79
  " investoday-api skill list [--page <n>] [--page-size <n>] [--json]\n" +
74
80
  " investoday-api skill search <keyword> [--page <n>] [--page-size <n>] [--json]\n" +
@@ -368,7 +374,11 @@ function runConfigCommand(args) {
368
374
  "用法:\n" +
369
375
  " investoday-api config status\n" +
370
376
  " investoday-api config path\n" +
371
- " investoday-api config remove\n\n" +
377
+ " investoday-api config remove\n" +
378
+ " investoday-api config set-base-url <url>\n" +
379
+ " investoday-api config reset-base-url\n\n" +
380
+ `Base URL environment override: ${API_BASE_URL_ENV}\n` +
381
+ `Default Base URL: ${BASE_URL}\n\n` +
372
382
  `配置目录环境变量覆盖: \n`
373
383
  );
374
384
  return;
@@ -385,9 +395,34 @@ function runConfigCommand(args) {
385
395
  return;
386
396
  }
387
397
 
398
+ if (action === "set-base-url") {
399
+ const baseUrl = String(args[1] || "").trim();
400
+ if (!baseUrl) {
401
+ exitWithError("Error: config set-base-url requires a URL.");
402
+ }
403
+ if (args.length > 2) {
404
+ exitWithError("Error: config set-base-url accepts exactly one URL.");
405
+ }
406
+ try {
407
+ saveApiBaseUrl(baseUrl);
408
+ } catch (error) {
409
+ exitWithError(`Error: ${error.message}`);
410
+ }
411
+ process.stdout.write(`${resolveApiBaseUrl().baseUrl}\n`);
412
+ return;
413
+ }
414
+
415
+ if (action === "reset-base-url") {
416
+ removeApiBaseUrl();
417
+ process.stdout.write(`${resolveApiBaseUrl().baseUrl}\n`);
418
+ return;
419
+ }
420
+
388
421
  if (action === "status") {
389
422
  const localCredentials = readCredentials();
390
423
  const resourceApiKey = resolveApiKey();
424
+ const localConfig = readConfig() || {};
425
+ const apiBaseUrl = resolveApiBaseUrl();
391
426
  const localConfigured = Boolean(localCredentials);
392
427
  process.stdout.write(
393
428
  JSON.stringify({
@@ -399,6 +434,12 @@ function runConfigCommand(args) {
399
434
  source: resourceApiKey.source,
400
435
  localConfig: localCredentials && localCredentials.apiKey ? "configured" : "missing",
401
436
  },
437
+ baseUrl: {
438
+ value: apiBaseUrl.baseUrl,
439
+ source: apiBaseUrl.source,
440
+ env: API_BASE_URL_ENV,
441
+ localConfig: localConfig[API_BASE_URL_ENV] ? "configured" : "missing",
442
+ },
402
443
  configDir: getConfigDir(),
403
444
  configFile: getCredentialsPath(),
404
445
  }, null, 2) + "\n"
@@ -925,8 +966,9 @@ function resolveRequestEndpoint(apiPath, method = "") {
925
966
  return null;
926
967
  }
927
968
 
928
- function buildUrl(apiPath, params) {
929
- let url = `${BASE_URL}/${apiPath}`;
969
+ function buildUrl(apiPath, params, options = {}) {
970
+ const baseUrl = String(options.baseUrl || resolveApiBaseUrl().baseUrl).replace(/\/+$/g, "");
971
+ let url = `${baseUrl}/${apiPath}`;
930
972
  if (!Object.keys(params).length) {
931
973
  return url;
932
974
  }
@@ -1547,7 +1589,7 @@ async function callApi(apiPath, method, params, apiKey, options = {}) {
1547
1589
  signal: AbortSignal.timeout(REQUEST_TIMEOUT),
1548
1590
  };
1549
1591
 
1550
- let url = `${BASE_URL}/${apiPath}`;
1592
+ let url = buildUrl(apiPath, {});
1551
1593
  if (method === "POST") {
1552
1594
  const { queryParams, bodyParams } = splitPostParams(params, options.endpoint, options.bodyJson);
1553
1595
  url = buildUrl(apiPath, queryParams);
package/lib/config.js CHANGED
@@ -4,6 +4,8 @@ const path = require("node:path");
4
4
 
5
5
  const CONFIG_DIR_ENV = "INVESTODAY_API_CONFIG_DIR";
6
6
  const API_KEY_ENV = "INVESTODAY_API_KEY";
7
+ const API_BASE_URL_ENV = "INVESTODAY_API_BASE_URL";
8
+ const DEFAULT_API_BASE_URL = "https://data-api.investoday.net/data";
7
9
  const LEGACY_SUB_API_KEY_ENV = "SUB_INVESTODAY_API_KEY";
8
10
  const CONFIG_FILE = "investoday-api.config.json";
9
11
  const LEGACY_CREDENTIALS_FILE = "credentials.enc";
@@ -52,6 +54,31 @@ function saveConfig(config, env = process.env) {
52
54
  atomicWriteFile(getCredentialsPath(env), `${JSON.stringify(config || {}, null, 2)}\n`);
53
55
  }
54
56
 
57
+ function normalizeApiBaseUrl(value) {
58
+ const trimmed = String(value || "").trim();
59
+ if (!trimmed) {
60
+ throw new Error(`${API_BASE_URL_ENV} cannot be empty`);
61
+ }
62
+
63
+ let parsed;
64
+ const normalized = trimmed.replace(/\/+$/g, "");
65
+ try {
66
+ parsed = new URL(normalized);
67
+ } catch {
68
+ throw new Error(`${API_BASE_URL_ENV} must be a valid URL`);
69
+ }
70
+
71
+ if (!["http:", "https:"].includes(parsed.protocol)) {
72
+ throw new Error(`${API_BASE_URL_ENV} must use http or https`);
73
+ }
74
+
75
+ if (parsed.search || parsed.hash) {
76
+ throw new Error(`${API_BASE_URL_ENV} must not include query or hash`);
77
+ }
78
+
79
+ return normalized;
80
+ }
81
+
55
82
  function removeLegacyCredentials(env = process.env) {
56
83
  for (const filePath of [getLegacyCredentialsPath(env), getLegacyKeyPath(env)]) {
57
84
  if (fs.existsSync(filePath)) {
@@ -76,6 +103,14 @@ function saveCredentials(apiKey, env = process.env) {
76
103
  removeLegacyCredentials(env);
77
104
  }
78
105
 
106
+ function saveApiBaseUrl(baseUrl, env = process.env) {
107
+ const existing = readConfig(env) || {};
108
+ saveConfig({
109
+ ...existing,
110
+ [API_BASE_URL_ENV]: normalizeApiBaseUrl(baseUrl),
111
+ }, env);
112
+ }
113
+
79
114
  function readConfig(env = process.env) {
80
115
  const configPath = getCredentialsPath(env);
81
116
  if (!fs.existsSync(configPath)) {
@@ -111,6 +146,15 @@ function removeCredentials(env = process.env) {
111
146
  removeLegacyCredentials(env);
112
147
  }
113
148
 
149
+ function removeApiBaseUrl(env = process.env) {
150
+ const existing = readConfig(env);
151
+ if (!existing || !Object.prototype.hasOwnProperty.call(existing, API_BASE_URL_ENV)) {
152
+ return;
153
+ }
154
+ delete existing[API_BASE_URL_ENV];
155
+ saveConfig(existing, env);
156
+ }
157
+
114
158
  function resolveApiKey(env = process.env) {
115
159
  const envKey = String(env[API_KEY_ENV] || "").trim();
116
160
  if (envKey) {
@@ -125,11 +169,28 @@ function resolveApiKey(env = process.env) {
125
169
  return { apiKey: "", source: "missing" };
126
170
  }
127
171
 
172
+ function resolveApiBaseUrl(env = process.env) {
173
+ const envBaseUrl = String(env[API_BASE_URL_ENV] || "").trim();
174
+ if (envBaseUrl) {
175
+ return { baseUrl: normalizeApiBaseUrl(envBaseUrl), source: "env" };
176
+ }
177
+
178
+ const config = readConfig(env);
179
+ const configBaseUrl = String(config && config[API_BASE_URL_ENV] || "").trim();
180
+ if (configBaseUrl) {
181
+ return { baseUrl: normalizeApiBaseUrl(configBaseUrl), source: "config" };
182
+ }
183
+
184
+ return { baseUrl: DEFAULT_API_BASE_URL, source: "default" };
185
+ }
186
+
128
187
  module.exports = {
129
188
  API_KEY_ENV,
189
+ API_BASE_URL_ENV,
130
190
  CONFIG_FILE,
131
191
  CONFIG_DIR_ENV,
132
192
  CREDENTIALS_FILE: CONFIG_FILE,
193
+ DEFAULT_API_BASE_URL,
133
194
  LEGACY_CREDENTIALS_FILE,
134
195
  LEGACY_KEY_FILE,
135
196
  getConfigDir,
@@ -139,8 +200,12 @@ module.exports = {
139
200
  removeLegacyCredentials,
140
201
  readConfig,
141
202
  readCredentials,
203
+ normalizeApiBaseUrl,
204
+ removeApiBaseUrl,
142
205
  removeCredentials,
143
206
  resolveApiKey,
207
+ resolveApiBaseUrl,
208
+ saveApiBaseUrl,
144
209
  saveConfig,
145
210
  saveCredentials,
146
211
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@investoday/investoday-api",
3
- "version": "1.8.26",
3
+ "version": "1.9.0",
4
4
  "description": "CLI for accessing InvestToday China market financial data via investoday-api.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/investoday-data/investoday-finance-data",