daxiapi-cli 2.3.3 → 2.4.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/README.md CHANGED
@@ -220,6 +220,19 @@ daxiapi report finance 600036
220
220
  daxiapi report finance 000001
221
221
  ```
222
222
 
223
+ ### 新闻数据
224
+
225
+ ```bash
226
+ # 获取个股舆情(内部自动将 code 转为 secid)
227
+ daxiapi news sentiment -c 600031 -p 20
228
+
229
+ # 获取个股公告
230
+ daxiapi news notice -c 600031 -p 20 -i 1
231
+
232
+ # 获取个股研报(支持时间区间)
233
+ daxiapi news report -c 600031 -p 25 -i 1 -b 2026-01-01 -e 2026-04-08
234
+ ```
235
+
223
236
  ### 工具
224
237
 
225
238
  ```bash
package/bin/index.js CHANGED
@@ -21,5 +21,6 @@ require('../commands/dividend')(program);
21
21
  require('../commands/hotrank')(program);
22
22
  require('../commands/turnover')(program);
23
23
  require('../commands/report')(program);
24
+ require('../commands/news')(program);
24
25
 
25
26
  program.parse(process.argv);
@@ -31,7 +31,24 @@ module.exports = function (program) {
31
31
  process.exit(1);
32
32
  }
33
33
  });
34
+ marketCmd.command('compass')
35
+ .description('获取市场整合趋势结构、估值、情绪三层指标,用于A股市场三维结构分析系统,给出市场当前状态的综合判断与操作建议')
36
+ .action(async () => {
37
+ try {
38
+ const token = config.getToken();
39
+ if (!token) {
40
+ const error = new Error('未配置 API Token');
41
+ error.response = {status: 401};
42
+ throw error;
43
+ }
34
44
 
45
+ const data = await api.getCompassData(token);
46
+ output(data);
47
+ } catch (error) {
48
+ handleError(error);
49
+ process.exit(1);
50
+ }
51
+ });
35
52
  marketCmd
36
53
  .command('temp')
37
54
  .description(
@@ -0,0 +1,96 @@
1
+ const api = require('../lib/api');
2
+ const {handleError, createParameterError} = require('../lib/error');
3
+ const {encode} = require('../lib/output');
4
+ const {getSecid} = require('../lib/utils');
5
+
6
+ function toPositiveInt(value, fieldName, examples) {
7
+ const number = Number.parseInt(value, 10);
8
+ if (!Number.isInteger(number) || number <= 0) {
9
+ throw createParameterError('参数无效', [`参数 '${fieldName}' 必须是大于0的整数`], examples);
10
+ }
11
+ return number;
12
+ }
13
+
14
+ function validateDate(value, fieldName, examples) {
15
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
16
+ throw createParameterError('参数无效', [`参数 '${fieldName}' 必须是 YYYY-MM-DD 格式`], examples);
17
+ }
18
+ }
19
+
20
+ module.exports = function (program) {
21
+ const newsCmd = program.command('news').description('获取个股舆情、公告、研报数据,用于跟踪个股信息面变化。');
22
+
23
+ newsCmd
24
+ .command('sentiment')
25
+ .description('获取个股舆情列表。')
26
+ .option('-c, --code <code>', '股票代码(6位),如600031', '600031')
27
+ .option('-p, --page-size <pageSize>', '每页条数,默认20', '20')
28
+ .action(async options => {
29
+ try {
30
+ if (!options.code) {
31
+ throw createParameterError('参数无效', ["参数 'code' 不能为空"], ['daxiapi news sentiment -c600031 -p20']);
32
+ }
33
+
34
+ const secid = getSecid(options.code);
35
+ const pageSize = toPositiveInt(options.pageSize, 'pageSize', ['daxiapi news sentiment -c600031 -p20']);
36
+ const data = await api.getNewsSentiment(secid, pageSize);
37
+ console.log('```toon\n' + encode(data) + '\n```');
38
+ } catch (error) {
39
+ handleError(error);
40
+ process.exit(1);
41
+ }
42
+ });
43
+
44
+ newsCmd
45
+ .command('notice')
46
+ .description('获取个股公告列表。')
47
+ .option('-c, --code <code>', '股票代码(6位),如600031', '600031')
48
+ .option('-p, --page-size <pageSize>', '每页条数,默认20', '20')
49
+ .option('-i, --page-index <pageIndex>', '页码,默认1', '1')
50
+ .action(async options => {
51
+ try {
52
+ if (!options.code) {
53
+ throw createParameterError('参数无效', ["参数 'code' 不能为空"], ['daxiapi news notice -c600031 -p20 -i1']);
54
+ }
55
+
56
+ const pageSize = toPositiveInt(options.pageSize, 'pageSize', ['daxiapi news notice -c600031 -p20 -i1']);
57
+ const pageIndex = toPositiveInt(options.pageIndex, 'pageIndex', ['daxiapi news notice -c600031 -p20 -i1']);
58
+ const data = await api.getNewsNotice(options.code, pageSize, pageIndex);
59
+ console.log('```toon\n' + encode(data) + '\n```');
60
+ } catch (error) {
61
+ handleError(error);
62
+ process.exit(1);
63
+ }
64
+ });
65
+
66
+ newsCmd
67
+ .command('report')
68
+ .description('获取个股研报列表。')
69
+ .option('-c, --code <code>', '股票代码(6位),如600031', '600031')
70
+ .option('-p, --page-size <pageSize>', '每页条数,默认25', '25')
71
+ .option('-i, --page-index <pageIndex>', '页码,默认1', '1')
72
+ .option('-b, --begin-time <beginTime>', '开始日期,格式 YYYY-MM-DD', '2026-01-01')
73
+ .option('-e, --end-time <endTime>', '结束日期,格式 YYYY-MM-DD')
74
+ .action(async options => {
75
+ try {
76
+ if (!options.code) {
77
+ throw createParameterError('参数无效', ["参数 'code' 不能为空"], [
78
+ 'daxiapi news report -c600031 -p25 -i1 -b2026-01-01 -e2026-04-08'
79
+ ]);
80
+ }
81
+
82
+ validateDate(options.beginTime, 'beginTime', ['daxiapi news report -b2026-01-01']);
83
+ const endTime = options.endTime || new Date().toISOString().slice(0, 10);
84
+ validateDate(endTime, 'endTime', ['daxiapi news report -e2026-04-08']);
85
+
86
+ const pageSize = toPositiveInt(options.pageSize, 'pageSize', ['daxiapi news report -p25']);
87
+ const pageIndex = toPositiveInt(options.pageIndex, 'pageIndex', ['daxiapi news report -i1']);
88
+
89
+ const data = await api.getNewsReport(options.code, pageSize, pageIndex, options.beginTime, endTime);
90
+ console.log('```toon\n' + encode(data) + '\n```');
91
+ } catch (error) {
92
+ handleError(error);
93
+ process.exit(1);
94
+ }
95
+ });
96
+ };
package/commands/stock.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const config = require('../lib/config');
2
2
  const api = require('../lib/api');
3
3
  const {handleError, createParameterError} = require('../lib/error');
4
- const {output} = require('../lib/output');
4
+ const {output, encode} = require('../lib/output');
5
5
 
6
6
  const SUPPORTED_PATTERNS = [
7
7
  'gxl',
@@ -142,4 +142,35 @@ module.exports = function (program) {
142
142
  process.exit(1);
143
143
  }
144
144
  });
145
+
146
+ stockCmd
147
+ .command('capital-flow <code>')
148
+ .description(
149
+ '获取个股主力资金流向数据,返回最近N天的资金动向。' +
150
+ '包括主力净流入、主力净流入占比、五日主力净流入、板块资金流向等详细数据。' +
151
+ '支持6位数字代码,自动识别沪深市场。可用于分析主力资金动向和资金面分析。'
152
+ )
153
+ .option('--days <days>', '返回天数(默认5天)', '5')
154
+ .action(async (code, options) => {
155
+ try {
156
+ if (!code) {
157
+ throw createParameterError('参数无效', ["参数 'code' 不能为空"], ['daxiapi stock capital-flow 000001']);
158
+ }
159
+
160
+ const days = parseInt(options.days, 10);
161
+ if (isNaN(days) || days < 1 || days > 30) {
162
+ throw createParameterError(
163
+ '参数无效',
164
+ ["参数 'days' 必须是1-30之间的整数"],
165
+ ['daxiapi stock capital-flow 000001 --days 5', 'daxiapi stock capital-flow 000001 --days 10']
166
+ );
167
+ }
168
+
169
+ const data = await api.getCapitalFlow(code, days);
170
+ console.log('```toon\n' + encode(data) + '\n```');
171
+ } catch (error) {
172
+ handleError(error);
173
+ process.exit(1);
174
+ }
175
+ });
145
176
  };
package/lib/api.js CHANGED
@@ -107,6 +107,10 @@ async function getSecId(token, code) {
107
107
  const client = createClient(token);
108
108
  return post(client, '/get_sec_id', {code});
109
109
  }
110
+ async function getCompassData(token, ) {
111
+ const client = createClient(token);
112
+ return get(client, '/get_market_compass');
113
+ }
110
114
 
111
115
  async function queryStockData(token, q, type = 'stock') {
112
116
  const client = createClient(token);
@@ -146,8 +150,8 @@ async function getDividendScore(token, code) {
146
150
  scores: recentScores.map(item => ({
147
151
  date: item.date,
148
152
  score: item.totalScore,
149
- cs: item.cs.toFixed(2),
150
- rsi: item.rsi.toFixed(2)
153
+ cs: item.cs != null ? item.cs.toFixed(2) : null,
154
+ rsi: item.rsi != null ? item.rsi.toFixed(2) : null
151
155
  }))
152
156
  };
153
157
  }
@@ -156,8 +160,6 @@ async function getStockRank(type = 'hour', listType = 'normal') {
156
160
  try {
157
161
  const params = {stock_type: 'a', list_type: listType};
158
162
 
159
- // 只有 normal 和 skyrocket 才有 type 参数,默认为 hour
160
- // 其他榜单类型固定为 day
161
163
  if (listType === 'normal' || listType === 'skyrocket') {
162
164
  params.type = type;
163
165
  } else {
@@ -204,19 +206,6 @@ async function getPlateRank(type = 'concept') {
204
206
  const data = extractData(response, 'plate_list');
205
207
 
206
208
  return data.map(d => {
207
- // - code: "881160"
208
- // rise_and_fall: 1.4869
209
- // etf_rise_and_fall: -0.1488
210
- // hot_rank_chg: 0
211
- // market_id: 48
212
- // hot_tag: 连续11天上榜
213
- // etf_product_id: "159766"
214
- // rate: "10366.5"
215
- // etf_name: 旅游ETF富国
216
- // name: 旅游及酒店
217
- // tag: 1家涨停
218
- // etf_market_id: 36
219
- // order: 20
220
209
  return {
221
210
  code: d.code,
222
211
  name: d.name,
@@ -270,19 +259,15 @@ async function getTurnoverData(type = 'day') {
270
259
  const currentTurnover = latest[1];
271
260
  const prevTurnover = previous[1];
272
261
  const diff = currentTurnover - prevTurnover;
273
- const currentYi = currentTurnover / 100000000; // 先转为亿
274
- const currentWanYi = (currentYi / 10000).toFixed(2); // 再转为万亿
275
- const diffYi = (Math.abs(diff) / 100000000).toFixed(2); // 转为亿
262
+ const currentYi = currentTurnover / 100000000;
263
+ const currentWanYi = (currentYi / 10000).toFixed(2);
264
+ const diffYi = (Math.abs(diff) / 100000000).toFixed(2);
276
265
  const formattedData = {
277
266
  当前成交额: currentWanYi + '万亿',
278
267
  变化量: (diff > 0 ? '增加' : '减少') + diffYi + '亿',
279
268
  较上日: diff > 0 ? '增加' : '减少'
280
269
  };
281
270
  const isTrading = minuteData.isTrading;
282
- // '0': { val: 1623545300000, name: '当日成交额', key: 'turnover' },
283
- // '1': { val: 1668872000000, name: '昨日成交额', key: 'turnover_pre' },
284
- // '2': { val: -45326700000, name: '较昨日变动', key: 'turnover_change' },
285
- // '3': { val: 1623545300000, name: '预测全天成交额', key: 'predict_turnover' },
286
271
  const rs = {};
287
272
  minuteData.header.forEach((item, index) => {
288
273
  rs[item.name] = item.val;
@@ -339,9 +324,128 @@ async function getTurnoverDataByMinute() {
339
324
  }
340
325
  }
341
326
 
327
+ function normalizeHttps(url) {
328
+ if (!url) {
329
+ return '';
330
+ }
331
+ return String(url).replace(/^http:\/\//, 'https://');
332
+ }
333
+
334
+ async function getNewsSentiment(secid, pageSize = 20) {
335
+ const response = await axios.get('https://np-listapi.eastmoney.com/comm/web/getListInfo', {
336
+ params: {
337
+ client: 'web',
338
+ biz: 'web_voice',
339
+ mTypeAndCode: secid,
340
+ pageSize,
341
+ type: 1,
342
+ req_trace: '3d4e684212bee1464c1b44611236955b',
343
+ },
344
+ });
345
+
346
+ const payload = response.data || {};
347
+ const list = payload.data?.list || [];
348
+ return {
349
+ pageIndex: payload.data?.page_index || 1,
350
+ pageSize: payload.data?.page_size || pageSize,
351
+ total: payload.data?.totle_hits || 0,
352
+ list: list.map((item) => ({
353
+ title: item.Art_Title,
354
+ showTime: item.Art_ShowTime,
355
+ artCode: item.Art_Code,
356
+ url: normalizeHttps(item.Art_Url),
357
+ originUrl: normalizeHttps(item.Art_OriginUrl),
358
+ })),
359
+ };
360
+ }
361
+
362
+ async function getNewsNotice(code, pageSize = 20, pageIndex = 1) {
363
+ const response = await axios.get('https://np-anotice-stock.eastmoney.com/api/security/ann', {
364
+ params: {
365
+ sr: -1,
366
+ page_size: pageSize,
367
+ page_index: pageIndex,
368
+ ann_type: 'A',
369
+ client_source: 'web',
370
+ stock_list: code,
371
+ f_node: 0,
372
+ s_node: 0,
373
+ },
374
+ });
375
+
376
+ const payload = response.data || {};
377
+ const data = payload.data || {};
378
+ const list = data.list || [];
379
+
380
+ return {
381
+ pageIndex: data.page_index || pageIndex,
382
+ pageSize: data.page_size || pageSize,
383
+ total: data.total_hits || 0,
384
+ list: list.map((item) => {
385
+ const stockCode = item.codes?.[0]?.stock_code || code;
386
+ return {
387
+ title: item.title,
388
+ noticeDate: item.notice_date,
389
+ displayTime: item.display_time,
390
+ artCode: item.art_code,
391
+ stockCode,
392
+ url: `https://data.eastmoney.com/notices/detail/${stockCode}/${item.art_code}.html`,
393
+ columns: (item.columns || []).map((column) => column.column_name),
394
+ };
395
+ }),
396
+ };
397
+ }
398
+
399
+ async function getNewsReport(code, pageSize = 25, pageIndex = 1, beginTime = '2026-01-01', endTime) {
400
+ const response = await axios.get('https://reportapi.eastmoney.com/report/list', {
401
+ params: {
402
+ pageNo: pageIndex,
403
+ pageSize,
404
+ code,
405
+ industryCode: '*',
406
+ industry: '*',
407
+ rating: '*',
408
+ ratingchange: '*',
409
+ beginTime,
410
+ endTime,
411
+ fields: '',
412
+ qType: 0,
413
+ sort: 'publishDate,desc',
414
+ },
415
+ });
416
+
417
+ const payload = response.data || {};
418
+ const list = payload.data || [];
419
+
420
+ return {
421
+ pageIndex: payload.pageNo || pageIndex,
422
+ pageSize: payload.size || pageSize,
423
+ total: payload.hits || 0,
424
+ list: list.map((item) => ({
425
+ title: item.title,
426
+ stockCode: item.stockCode,
427
+ stockName: item.stockName,
428
+ publishDate: item.publishDate,
429
+ orgName: item.orgName,
430
+ rating: item.emRatingName,
431
+ infoCode: item.infoCode,
432
+ predictNextTwoYearEps: item.predictNextTwoYearEps,
433
+ predictNextTwoYearPe: item.predictNextTwoYearPe,
434
+ predictNextYearEps: item.predictNextYearEps,
435
+ predictNextYearPe: item.predictNextYearPe,
436
+ predictThisYearEps: item.predictThisYearEps,
437
+ predictThisYearPe: item.predictThisYearPe,
438
+ predictLastYearEps: item.predictLastYearEps,
439
+ predictLastYearPe: item.predictLastYearPe,
440
+ url: `https://data.eastmoney.com/report/info/${item.infoCode}.html`,
441
+ })),
442
+ };
443
+ }
444
+
342
445
  module.exports = {
343
446
  getMarketData,
344
447
  getMarketTemp,
448
+ getCompassData,
345
449
  getMarketStyle,
346
450
  getMarketValueData,
347
451
  getBkData,
@@ -361,5 +465,8 @@ module.exports = {
361
465
  getPlateRank,
362
466
  getTurnoverData,
363
467
  getTurnoverDataByMinute,
364
- getFinanceReportDetail
468
+ getFinanceReportDetail,
469
+ getNewsSentiment,
470
+ getNewsNotice,
471
+ getNewsReport,
365
472
  };
package/lib/caibao.js CHANGED
@@ -2,59 +2,57 @@
2
2
  // const vm = require('vm');
3
3
 
4
4
  // ?ut=&invt=2&fltt=2&fields=&secid=1.600862&cb=&_=1601168994587
5
+ const axios = require('axios');
5
6
  const picker = require('@ksky521/html-picker');
6
7
  const iconv = require('./iconv');
7
8
 
8
9
  const {getHeader} = require('./thsUtils');
9
10
 
10
- const request = require('request-promise-native');
11
-
12
11
  const API_URL = 'http://basic.10jqka.com.cn/';
13
12
 
14
- function getDetail(stockId) {
13
+ async function getDetail(stockId) {
15
14
  const prefix = `${API_URL}${stockId}`;
16
15
  const url = `${prefix}/finance.html`;
17
- // console.log(url);
18
- return request({
19
- uri: url,
20
- encoding: null,
16
+ const response = await axios.get(url, {
17
+ responseType: 'arraybuffer',
18
+ timeout: 30000,
21
19
  headers: getHeader({Referrer: prefix})
22
- }).then(data => {
23
- const html = iconv(data);
24
- let rs = {};
25
- picker(html, {
26
- data: {
27
- selector: 'p#main',
28
- handler($node) {
29
- rs = JSON.parse($node.html());
30
- }
20
+ });
21
+
22
+ const html = iconv(response.data);
23
+ let rs = {};
24
+ picker(html, {
25
+ data: {
26
+ selector: 'p#main',
27
+ handler($node) {
28
+ rs = JSON.parse($node.html());
31
29
  }
32
- });
33
- const titles = rs.title;
34
- const content = rs.report;
35
- const reports = [];
36
- for (let index = 0; index < content[0].length; index++) {
37
- const report = {};
38
- for (let i = 0; i < titles.length; i++) {
39
- let title = titles[i];
40
- if (typeof title === 'string') {
41
- title = title.trim();
42
- } else if (Array.isArray(title)) {
43
- title = title[0];
44
- }
45
- if (content[index]) {
46
- report[title] = content[i][index];
47
- } else {
48
- // console.log(report[title]);
49
- }
30
+ }
31
+ });
32
+ const titles = rs.title;
33
+ const content = rs.report;
34
+ const reports = [];
35
+ for (let index = 0; index < content[0].length; index++) {
36
+ const report = {};
37
+ for (let i = 0; i < titles.length; i++) {
38
+ let title = titles[i];
39
+ if (typeof title === 'string') {
40
+ title = title.trim();
41
+ } else if (Array.isArray(title)) {
42
+ title = title[0];
50
43
  }
51
- if (Object.keys(report).length > 0) {
52
- reports.push(report);
44
+ if (content[index]) {
45
+ report[title] = content[i][index];
46
+ } else {
47
+ // console.log(report[title]);
53
48
  }
54
49
  }
55
- // console.log(titles.length, content.length);
56
- return reports;
57
- });
50
+ if (Object.keys(report).length > 0) {
51
+ reports.push(report);
52
+ }
53
+ }
54
+
55
+ return reports;
58
56
  }
59
57
 
60
58
  module.exports = getDetail;
@@ -1,7 +1,7 @@
1
1
  const DIVIDEND_SCORE_CONSTANTS = {
2
2
  ROLLING_WINDOW: 440,
3
- PERCENTILE_LOW: 5,
4
- PERCENTILE_HIGH: 95,
3
+ PERCENTILE_LOW: 0,
4
+ PERCENTILE_HIGH: 100,
5
5
  SCORE_MA_PERIOD: 5,
6
6
  EMA_PERIOD: 20,
7
7
  MA_PERIOD: 80,
@@ -140,6 +140,7 @@ function calculateScores(data) {
140
140
 
141
141
  const csValues = dataCopy.map(d => d.cs);
142
142
  const ma80BiasValues = dataCopy.map(d => d.ma80Bias);
143
+ const rsiValues = dataCopy.map(d => d.rsi);
143
144
 
144
145
  for (let i = 0; i < dataCopy.length; i++) {
145
146
  const current = dataCopy[i];
@@ -155,6 +156,7 @@ function calculateScores(data) {
155
156
  const startIdx = Math.max(0, i - DIVIDEND_SCORE_CONSTANTS.ROLLING_WINDOW + 1);
156
157
  const csHistory = csValues.slice(startIdx, i + 1);
157
158
  const ma80History = ma80BiasValues.slice(startIdx, i + 1);
159
+ const rsiHistory = rsiValues.slice(startIdx, i + 1);
158
160
 
159
161
  current.csScore = calculateRollingScore(
160
162
  current.cs,
@@ -168,7 +170,12 @@ function calculateScores(data) {
168
170
  DIVIDEND_SCORE_CONSTANTS.PERCENTILE_LOW,
169
171
  DIVIDEND_SCORE_CONSTANTS.PERCENTILE_HIGH
170
172
  );
171
- current.rsiScore = current.rsi;
173
+ current.rsiScore = calculateRollingScore(
174
+ current.rsi,
175
+ rsiHistory,
176
+ DIVIDEND_SCORE_CONSTANTS.PERCENTILE_LOW,
177
+ DIVIDEND_SCORE_CONSTANTS.PERCENTILE_HIGH
178
+ );
172
179
 
173
180
  if (current.csScore !== null && current.ma80Score !== null && current.rsiScore !== null) {
174
181
  current.totalScore = parseFloat(
package/lib/output.js CHANGED
@@ -8,7 +8,10 @@ function output(data) {
8
8
  console.log('[60]{"日期","分数","cs值","rsi值"}:');
9
9
 
10
10
  data.scores.forEach(item => {
11
- console.log(` ${item.date},${item.score || '-'},${item.cs || '-'},${item.rsi || '-'}`);
11
+ const score = item.score != null ? item.score : '-';
12
+ const cs = item.cs != null ? item.cs : '-';
13
+ const rsi = item.rsi != null ? item.rsi : '-';
14
+ console.log(` ${item.date},${score},${cs},${rsi}`);
12
15
  });
13
16
 
14
17
  console.log('```');
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "daxiapi-cli",
3
- "version": "2.3.3",
3
+ "version": "2.4.2",
4
4
  "description": "大虾皮金融数据API命令行工具",
5
5
  "bin": {
6
6
  "daxiapi": "./bin/index.js",
7
7
  "dxp": "./bin/index.js"
8
8
  },
9
9
  "scripts": {
10
- "test": "node bin/index.js --help"
10
+ "test": "node --test test/*.test.js",
11
+ "test:coverage": "node --test --experimental-test-coverage --test-coverage-branches=80 --test-coverage-functions=80 --test-coverage-lines=80 test/*.test.js"
11
12
  },
12
13
  "keywords": [
13
14
  "stock",
@@ -20,8 +21,6 @@
20
21
  "license": "MIT",
21
22
  "dependencies": {
22
23
  "iconv-lite": "^0.5.0",
23
- "request": "^2.88.0",
24
- "request-promise-native": "^1.0.7",
25
24
  "@ksky521/html-picker": "^1.0.0",
26
25
  "@toon-format/toon": "^2.1.0",
27
26
  "axios": "^1.6.0",
@@ -38,4 +37,4 @@
38
37
  "lib",
39
38
  "README.md"
40
39
  ]
41
- }
40
+ }