daxiapi-cli 1.1.0 → 2.0.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/lib/error.js ADDED
@@ -0,0 +1,80 @@
1
+ const chalk = require('chalk');
2
+
3
+ const errorMessages = {
4
+ 401: {
5
+ title: 'API Token 无效或已过期',
6
+ solutions: [
7
+ '检查 Token 是否正确配置:\n daxiapi config set token YOUR_TOKEN',
8
+ '或设置环境变量:\n export DAXIAPI_TOKEN=YOUR_TOKEN',
9
+ 'Token 可在 daxiapi.com 用户中心获取'
10
+ ]
11
+ },
12
+ 403: {
13
+ title: '无访问权限',
14
+ solutions: [
15
+ '检查您的账户是否有访问该接口的权限',
16
+ '联系管理员开通相关权限',
17
+ '访问 daxiapi.com 了解权限说明'
18
+ ]
19
+ },
20
+ 429: {
21
+ title: '请求频率超限',
22
+ solutions: [
23
+ '请稍后重试',
24
+ '检查您的请求频率是否过高',
25
+ '升级账户获取更高配额'
26
+ ]
27
+ },
28
+ 500: {
29
+ title: '服务器内部错误',
30
+ solutions: [
31
+ '请稍后重试',
32
+ '如果问题持续,请联系技术支持',
33
+ '访问 daxiapi.com 查看服务状态'
34
+ ]
35
+ }
36
+ };
37
+
38
+ function handleError(error) {
39
+ console.log(chalk.red.bold('\n❌ 错误:'), error.message || error.title);
40
+
41
+ const statusCode = error.response?.status || error.statusCode;
42
+ const errorInfo = errorMessages[statusCode];
43
+
44
+ if (errorInfo) {
45
+ console.log(chalk.bold('\n解决方法:'));
46
+ errorInfo.solutions.forEach((solution, index) => {
47
+ console.log(` ${index + 1}. ${solution}`);
48
+ });
49
+ } else if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
50
+ console.log(chalk.bold('\n解决方法:'));
51
+ console.log(' 1. 检查网络连接是否正常');
52
+ console.log(' 2. 检查 API 地址是否正确:\n daxiapi config set baseUrl https://daxiapi.com');
53
+ console.log(' 3. 如果使用代理,请检查代理配置');
54
+ } else if (error.details) {
55
+ console.log(chalk.bold('\n详细信息:'));
56
+ error.details.forEach(detail => {
57
+ console.log(` - ${detail}`);
58
+ });
59
+ if (error.examples) {
60
+ console.log(chalk.bold('\n使用示例:'));
61
+ error.examples.forEach(example => {
62
+ console.log(` ${example}`);
63
+ });
64
+ }
65
+ }
66
+
67
+ console.log('');
68
+ }
69
+
70
+ function createParameterError(message, details, examples) {
71
+ const error = new Error(message);
72
+ error.details = details;
73
+ error.examples = examples;
74
+ return error;
75
+ }
76
+
77
+ module.exports = {
78
+ handleError,
79
+ createParameterError
80
+ };
package/lib/output.js CHANGED
@@ -1,328 +1,7 @@
1
- const chalk = require('chalk');
2
- const consoleTable = require('console.table');
3
-
4
- /**
5
- * 错误输出
6
- */
7
- function error(err) {
8
- console.log(chalk.red.bold('\n❌ 错误:'), err.message);
9
- if (err.response?.status === 401) {
10
- console.log(chalk.yellow(' Token无效或已过期,请重新配置'));
11
- } else if (err.response?.status === 429) {
12
- console.log(chalk.yellow(' 请求频率超限,请稍后重试'));
13
- }
14
- }
15
-
16
- /**
17
- * 纯文本输出
18
- */
19
- function text(data) {
20
- if (typeof data === 'string') {
21
- console.log(data);
22
- } else {
23
- console.log(JSON.stringify(data, null, 2));
24
- }
25
- }
26
-
27
- /**
28
- * 表格输出
29
- */
30
- function table(data) {
31
- if (typeof data === 'string') {
32
- // Markdown表格直接输出
33
- console.log(data);
34
- } else if (Array.isArray(data)) {
35
- console.table(data);
36
- } else {
37
- console.log(JSON.stringify(data, null, 2));
38
- }
39
- }
40
-
41
- /**
42
- * 市场概览
43
- */
44
- function marketOverview(data) {
45
- console.log(chalk.bold.cyan('📈 市场概览\n'));
46
-
47
- if (data.index && data.index.length > 0) {
48
- console.log(chalk.bold('主要指数:'));
49
- const indexData = data.index.map(item => ({
50
- '名称': item.name,
51
- '涨跌幅': formatPercent(item.zdf),
52
- 'CS': item.cs?.toFixed(2) || '-',
53
- '5日': formatPercent(item.zdf5),
54
- '20日': formatPercent(item.zdf20)
55
- }));
56
- console.table(indexData);
57
- }
58
-
59
- if (data.above_ma200_ratio !== undefined) {
60
- console.log(chalk.bold('\n市场宽度:'));
61
- console.log(` 200日均线上方: ${chalk.cyan((data.above_ma200_ratio * 100).toFixed(1) + '%')}`);
62
- }
63
-
64
- if (data.zdfRange) {
65
- console.log(chalk.bold('\n涨跌分布:'));
66
- const range = data.zdfRange;
67
- console.log(` 涨停(>10%): ${chalk.red(range.zdf_10 || 0)}`);
68
- console.log(` 大涨(7-10%): ${chalk.red(range.zdf_10z7 || 0)}`);
69
- console.log(` 上涨(5-7%): ${chalk.red(range.zdf_7z5 || 0)}`);
70
- console.log(` 微涨(2-5%): ${chalk.red(range.zdf_5z2 || 0)}`);
71
- console.log(` 平盘: ${chalk.gray(range.zdf_0 || 0)}`);
72
- console.log(` 微跌(0-2%): ${chalk.green(range.zdf_2z0 || 0)}`);
73
- console.log(` 下跌(2-5%): ${chalk.green(range.zdf_0z_2 || 0)}`);
74
- console.log(` 大跌(5-7%): ${chalk.green(range.zdf_7z_5 || 0)}`);
75
- console.log(` 跌停(<-10%): ${chalk.green(range.zdf__10 || 0)}`);
76
- }
77
- }
78
-
79
- /**
80
- * 指数估值表格
81
- */
82
- function valueTable(data) {
83
- console.log(chalk.bold.cyan('💰 指数估值\n'));
84
-
85
- if (data.items && data.items.length > 0) {
86
- const tableData = data.items.map(item => ({
87
- 名称: item.name,
88
- PE: item.PE?.toFixed(2) || '-',
89
- PE分位: (item.PEPercentile * 100).toFixed(1) + '%',
90
- PB: item.PB?.toFixed(2) || '-',
91
- PB分位: (item.PBPercentile * 100).toFixed(1) + '%',
92
- 温度: formatTemp(item.wendu)
93
- }));
94
- console.table(tableData);
95
- console.log(chalk.gray(`\n数据日期: ${data.fullDate}`));
96
- }
97
- }
98
-
99
- /**
100
- * 新闻列表
101
- */
102
- function newsList(data) {
103
- console.log(chalk.bold.cyan('📰 收盘新闻\n'));
104
-
105
- if (data.title) {
106
- console.log(chalk.bold(data.title));
107
- if (data.summary) {
108
- console.log(chalk.gray(data.summary));
109
- }
110
- if (data.showTime) {
111
- console.log(chalk.gray(`时间: ${data.showTime}`));
112
- }
113
- if (data.uniqueUrl) {
114
- console.log(chalk.blue(`链接: ${data.uniqueUrl}`));
115
- }
116
- }
117
- }
118
-
119
- /**
120
- * K线数据
121
- */
122
- function klines(data) {
123
- console.log(chalk.bold.cyan(`📊 ${data.name} K线数据\n`));
124
-
125
- if (data.klines && data.klines.length > 0) {
126
- const tableData = data.klines.slice(-10).map(item => ({
127
- 日期: item.date,
128
- 开盘: item.open?.toFixed(2),
129
- 收盘: item.close?.toFixed(2),
130
- 最高: item.high?.toFixed(2),
131
- 最低: item.low?.toFixed(2),
132
- 成交量: formatVolume(item.vol)
133
- }));
134
- console.table(tableData);
135
- }
136
- }
137
-
138
- /**
139
- * 板块热力图
140
- */
141
- function sectorHeatmap(data) {
142
- console.log(chalk.bold.cyan('🔥 板块热力图\n'));
143
-
144
- if (data.csHeatmap) {
145
- console.log(chalk.bold('CS动量热力图:'));
146
- console.log(data.csHeatmap);
147
- }
148
-
149
- if (data.crossover) {
150
- console.log(chalk.bold('\n突破箱体板块:'));
151
- console.log(data.crossover);
152
- }
153
- }
154
-
155
- /**
156
- * 股票列表
157
- */
158
- function stockList(data) {
159
- console.log(chalk.bold.cyan('📋 股票列表\n'));
160
-
161
- if (Array.isArray(data) && data.length > 0) {
162
- const tableData = data.slice(0, 20).map(item => ({
163
- 代码: item.code || item.stockId,
164
- 名称: item.name,
165
- 收盘价: item.close?.toFixed(2) || '-',
166
- 涨跌幅: formatPercent(item.zdf),
167
- CS: item.cs?.toFixed(2) || '-',
168
- RPS: item.rps_score?.toFixed(0) || '-',
169
- 标签: (item.tags || []).join(',')
170
- }));
171
- console.table(tableData);
172
- }
173
- }
174
-
175
- /**
176
- * 股票详情
177
- */
178
- function stockDetail(data) {
179
- console.log(chalk.bold.cyan('📊 个股详情\n'));
180
-
181
- if (Array.isArray(data)) {
182
- data.forEach(stock => {
183
- console.log(chalk.bold.blue(`${stock.name} (${stock.code})`));
184
- console.log(chalk.gray('─'.repeat(40)));
185
-
186
- console.log(`价格: ${stock.close?.toFixed(2)} 涨跌幅: ${formatPercent(stock.zdf)}`);
187
- console.log(`今开: ${stock.open?.toFixed(2)} 最高: ${stock.high?.toFixed(2)} 最低: ${stock.low?.toFixed(2)}`);
188
- console.log('');
189
-
190
- console.log(chalk.bold('动量指标:'));
191
- console.log(` CS(短期): ${stock.cs?.toFixed(2)} SM(中期): ${stock.sm?.toFixed(2)} ML(长期): ${stock.ml?.toFixed(2)}`);
192
- console.log(` RPS: ${stock.rps_score?.toFixed(0)} SCTR: ${stock.sctr?.toFixed(0)}`);
193
- console.log('');
194
-
195
- console.log(chalk.bold('均线系统:'));
196
- console.log(` MA20: ${stock.ma20?.toFixed(2)} MA50: ${stock.ma50?.toFixed(2)}`);
197
- console.log(` MA150: ${stock.ma150?.toFixed(2)} MA200: ${stock.ma200?.toFixed(2)}`);
198
- console.log('');
199
-
200
- if (stock.tags && stock.tags.length > 0) {
201
- console.log(chalk.bold('形态标签:'), stock.tags.join(', '));
202
- }
203
-
204
- const signals = [];
205
- if (stock.isVCP) signals.push('VCP');
206
- if (stock.isSOS) signals.push('SOS');
207
- if (stock.isLPS) signals.push('LPS');
208
- if (stock.isSpring) signals.push('Spring');
209
- if (stock.isCrossoverBox) signals.push('突破箱体');
210
- if (signals.length > 0) {
211
- console.log(chalk.bold('技术形态:'), chalk.green(signals.join(', ')));
212
- }
213
-
214
- console.log('');
215
- });
216
- }
217
- }
218
-
219
- /**
220
- * 搜索结果
221
- */
222
- function searchResult(data) {
223
- console.log(chalk.bold.cyan('🔍 搜索结果\n'));
224
-
225
- if (Array.isArray(data) && data.length > 0) {
226
- const tableData = data.map(item => ({
227
- 代码: item.code,
228
- 名称: item.name,
229
- 拼音: item.pinyin,
230
- 类型: item.type === 'hy' ? '行业' : '个股'
231
- }));
232
- console.table(tableData);
233
- } else {
234
- console.log(chalk.yellow('未找到匹配结果'));
235
- }
236
- }
237
-
238
- /**
239
- * K线数据输出(增强版)
240
- */
241
- function klineData(data, limit = 20) {
242
- console.log(chalk.bold.cyan(`📊 ${data.name} (${data.code}) K线数据\n`));
243
- console.log(chalk.gray(`数据源: ${data.source === 'qq' ? '腾讯财经' : '东方财富'}`));
244
- console.log(chalk.gray(`数据条数: ${data.count}\n`));
245
-
246
- if (data.klines && data.klines.length > 0) {
247
- const displayData = data.klines.slice(-limit);
248
- const tableData = displayData.map(item => ({
249
- '日期': item.date,
250
- '开盘': item.open?.toFixed(2),
251
- '收盘': item.close?.toFixed(2),
252
- '最高': item.high?.toFixed(2),
253
- '最低': item.low?.toFixed(2),
254
- '涨跌幅': formatPercent(item.close && item.open ? ((item.close - item.open) / item.open * 100) : null),
255
- '成交量': formatVolume(item.volume),
256
- '成交额': formatAmount(item.amount)
257
- }));
258
- console.table(tableData);
259
- }
260
- }
261
-
262
- /**
263
- * K线简单输出(最近N天)
264
- */
265
- function klineSimple(data, limit = 10) {
266
- if (data.klines && data.klines.length > 0) {
267
- const displayData = data.klines.slice(-limit);
268
- console.log(chalk.bold(`${data.name} (${data.code}) 最近${displayData.length}日K线 [${data.source}]:`));
269
-
270
- displayData.forEach(item => {
271
- const change = item.close && item.open ? ((item.close - item.open) / item.open * 100) : 0;
272
- const changeStr = change >= 0 ? chalk.red(`+${change.toFixed(2)}%`) : chalk.green(`${change.toFixed(2)}%`);
273
- console.log(` ${item.date} | 开${item.open?.toFixed(2)} 收${item.close?.toFixed(2)} 高${item.high?.toFixed(2)} 低${item.low?.toFixed(2)} | ${changeStr}`);
274
- });
275
- }
276
- }
277
-
278
- // ==================== 辅助函数 ====================
279
-
280
- function formatPercent(value) {
281
- if (value === null || value === undefined) return '-';
282
- const num = typeof value === 'string' ? parseFloat(value) : value;
283
- if (isNaN(num)) return '-';
284
- const str = num.toFixed(2) + '%';
285
- return num >= 0 ? chalk.red(str) : chalk.green(str);
286
- }
287
-
288
- function formatTemp(value) {
289
- if (value === null || value === undefined) return '-';
290
- const num = typeof value === 'string' ? parseFloat(value) : value;
291
- if (isNaN(num)) return '-';
292
-
293
- if (num < 20) return chalk.blue(num.toFixed(1) + '°C');
294
- if (num < 40) return chalk.cyan(num.toFixed(1) + '°C');
295
- if (num < 60) return chalk.yellow(num.toFixed(1) + '°C');
296
- if (num < 80) return chalk.rgb(255, 165, 0)(num.toFixed(1) + '°C');
297
- return chalk.red(num.toFixed(1) + '°C');
298
- }
299
-
300
- function formatVolume(vol) {
301
- if (!vol) return '-';
302
- if (vol >= 100000000) return (vol / 100000000).toFixed(2) + '亿';
303
- if (vol >= 10000) return (vol / 10000).toFixed(2) + '万';
304
- return vol.toString();
305
- }
306
-
307
- function formatAmount(amount) {
308
- if (!amount) return '-';
309
- if (amount >= 100000000) return (amount / 100000000).toFixed(2) + '亿';
310
- if (amount >= 10000) return (amount / 10000).toFixed(2) + '万';
311
- return amount.toFixed(2);
1
+ function output(data) {
2
+ console.log(JSON.stringify(data, null, 2));
312
3
  }
313
4
 
314
5
  module.exports = {
315
- error,
316
- text,
317
- table,
318
- marketOverview,
319
- valueTable,
320
- newsList,
321
- klines,
322
- sectorHeatmap,
323
- stockList,
324
- stockDetail,
325
- searchResult,
326
- klineData,
327
- klineSimple
6
+ output
328
7
  };
package/package.json CHANGED
@@ -1,28 +1,30 @@
1
1
  {
2
- "name": "daxiapi-cli",
3
- "version": "1.1.0",
4
- "description": "大虾皮(daxiapi.com)金融数据API命令行工具",
5
- "bin": {
6
- "daxiapi": "./bin/index.js",
7
- "dxp": "./bin/index.js"
8
- },
9
- "scripts": {
10
- "test": "node bin/index.js"
11
- },
12
- "keywords": [
13
- "stock",
14
- "api",
15
- "daxiapi",
16
- "cli",
17
- "finance"
18
- ],
19
- "author": "daxiapi.com",
20
- "license": "MIT",
21
- "dependencies": {
22
- "axios": "^1.6.0",
23
- "chalk": "^4.1.2",
24
- "commander": "^11.1.0",
25
- "conf": "^10.2.0",
26
- "console.table": "^0.10.0"
27
- }
2
+ "name": "daxiapi-cli",
3
+ "version": "2.0.0",
4
+ "description": "大虾皮金融数据API命令行工具",
5
+ "bin": {
6
+ "daxiapi": "./bin/index.js",
7
+ "dxp": "./bin/index.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node bin/index.js --help"
11
+ },
12
+ "keywords": [
13
+ "stock",
14
+ "api",
15
+ "daxiapi",
16
+ "cli",
17
+ "finance"
18
+ ],
19
+ "author": "daxiapi.com",
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "axios": "^1.6.0",
23
+ "chalk": "^4.1.2",
24
+ "commander": "^11.1.0",
25
+ "conf": "^10.2.0"
26
+ },
27
+ "engines": {
28
+ "node": ">=14.0.0"
29
+ }
28
30
  }