dskcode 0.1.4 → 0.1.6
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 +121 -17
- package/dist/index.js +640 -15
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-GMSRSNWA.js +0 -61
- package/dist/chunk-GMSRSNWA.js.map +0 -1
- package/dist/config-42MSCTVE.js +0 -9
- package/dist/config-42MSCTVE.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,21 +1,278 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
loadConfig
|
|
4
|
-
} from "./chunk-GMSRSNWA.js";
|
|
5
2
|
|
|
6
3
|
// src/cli/index.tsx
|
|
7
4
|
import { Command } from "commander";
|
|
8
5
|
|
|
6
|
+
// src/config/loader.ts
|
|
7
|
+
import { existsSync, watch } from "fs";
|
|
8
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
var defaultConfig = {
|
|
11
|
+
defaultProvider: "deepseek",
|
|
12
|
+
maxTokens: 8192,
|
|
13
|
+
temperature: 0.7,
|
|
14
|
+
maxToolRounds: 20,
|
|
15
|
+
providers: [
|
|
16
|
+
{
|
|
17
|
+
name: "deepseek",
|
|
18
|
+
baseUrl: "https://api.deepseek.com",
|
|
19
|
+
model: "deepseek-v4-flash"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
tools: [
|
|
23
|
+
{ name: "read_file", enabled: true },
|
|
24
|
+
{ name: "write_file", enabled: true },
|
|
25
|
+
{ name: "edit_file", enabled: true },
|
|
26
|
+
{ name: "bash", enabled: true },
|
|
27
|
+
{ name: "glob", enabled: true },
|
|
28
|
+
{ name: "grep", enabled: true },
|
|
29
|
+
{ name: "ls", enabled: true },
|
|
30
|
+
{ name: "fetch", enabled: true }
|
|
31
|
+
],
|
|
32
|
+
plugins: [],
|
|
33
|
+
stock: {
|
|
34
|
+
symbols: [
|
|
35
|
+
{ code: "sh000001" },
|
|
36
|
+
{ code: "sz399300" },
|
|
37
|
+
{ code: "sh601899" }
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
function resolveConfigFiles(configPath) {
|
|
42
|
+
if (configPath) {
|
|
43
|
+
return [configPath];
|
|
44
|
+
}
|
|
45
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
|
|
46
|
+
return [
|
|
47
|
+
join(home, ".dskcode", "settings.json"),
|
|
48
|
+
join(process.cwd(), ".dskcode", "settings.json")
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
function mergeConfig(base, overlay) {
|
|
52
|
+
const result = { ...base };
|
|
53
|
+
if (overlay.defaultProvider !== void 0) {
|
|
54
|
+
result.defaultProvider = overlay.defaultProvider;
|
|
55
|
+
}
|
|
56
|
+
if (overlay.verbose !== void 0) {
|
|
57
|
+
result.verbose = overlay.verbose;
|
|
58
|
+
}
|
|
59
|
+
if (overlay.maxTokens !== void 0) {
|
|
60
|
+
result.maxTokens = overlay.maxTokens;
|
|
61
|
+
}
|
|
62
|
+
if (overlay.temperature !== void 0) {
|
|
63
|
+
result.temperature = overlay.temperature;
|
|
64
|
+
}
|
|
65
|
+
if (overlay.maxToolRounds !== void 0) {
|
|
66
|
+
result.maxToolRounds = overlay.maxToolRounds;
|
|
67
|
+
}
|
|
68
|
+
if (overlay.providers !== void 0) {
|
|
69
|
+
result.providers = overlay.providers;
|
|
70
|
+
}
|
|
71
|
+
if (overlay.tools !== void 0) {
|
|
72
|
+
result.tools = overlay.tools;
|
|
73
|
+
}
|
|
74
|
+
if (overlay.plugins !== void 0) {
|
|
75
|
+
result.plugins = overlay.plugins;
|
|
76
|
+
}
|
|
77
|
+
if (overlay.stock !== void 0) {
|
|
78
|
+
result.stock = overlay.stock;
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
var ENV_PREFIX = "DSKCODE_";
|
|
83
|
+
var ENV_MAP = {
|
|
84
|
+
[`${ENV_PREFIX}DEFAULT_PROVIDER`]: "defaultProvider",
|
|
85
|
+
[`${ENV_PREFIX}VERBOSE`]: "verbose",
|
|
86
|
+
[`${ENV_PREFIX}MAX_TOKENS`]: "maxTokens",
|
|
87
|
+
[`${ENV_PREFIX}TEMPERATURE`]: "temperature",
|
|
88
|
+
[`${ENV_PREFIX}MAX_TOOL_ROUNDS`]: "maxToolRounds"
|
|
89
|
+
};
|
|
90
|
+
function applyEnvVars(config) {
|
|
91
|
+
for (const [envKey, configKey] of Object.entries(ENV_MAP)) {
|
|
92
|
+
const raw = process.env[envKey];
|
|
93
|
+
if (raw === void 0) continue;
|
|
94
|
+
const cfg = config;
|
|
95
|
+
switch (configKey) {
|
|
96
|
+
case "verbose":
|
|
97
|
+
case "defaultProvider": {
|
|
98
|
+
cfg[configKey] = raw;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case "maxTokens":
|
|
102
|
+
case "maxToolRounds": {
|
|
103
|
+
const n = Number(raw);
|
|
104
|
+
if (Number.isFinite(n) && n > 0) {
|
|
105
|
+
cfg[configKey] = n;
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case "temperature": {
|
|
110
|
+
const n = Number(raw);
|
|
111
|
+
if (Number.isFinite(n) && n >= 0 && n <= 2) {
|
|
112
|
+
cfg[configKey] = n;
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const apiKey = process.env.DEEPSEEK_API_KEY;
|
|
119
|
+
if (apiKey) {
|
|
120
|
+
const deepseek = config.providers.find((p) => p.name === "deepseek");
|
|
121
|
+
if (deepseek && !deepseek.apiKey) {
|
|
122
|
+
deepseek.apiKey = apiKey;
|
|
123
|
+
}
|
|
124
|
+
if (!deepseek) {
|
|
125
|
+
config.providers.unshift({
|
|
126
|
+
name: "deepseek",
|
|
127
|
+
baseUrl: "https://api.deepseek.com",
|
|
128
|
+
model: "deepseek-v4-flash",
|
|
129
|
+
apiKey
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return config;
|
|
134
|
+
}
|
|
135
|
+
function applyCliOverrides(config, flags) {
|
|
136
|
+
if (flags.verbose !== void 0) {
|
|
137
|
+
config.verbose = flags.verbose;
|
|
138
|
+
}
|
|
139
|
+
if (flags.model !== void 0) {
|
|
140
|
+
const provider = config.providers.find(
|
|
141
|
+
(p) => p.name === config.defaultProvider
|
|
142
|
+
);
|
|
143
|
+
if (provider) {
|
|
144
|
+
provider.model = flags.model;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (flags.maxTokens !== void 0 && flags.maxTokens > 0) {
|
|
148
|
+
config.maxTokens = flags.maxTokens;
|
|
149
|
+
}
|
|
150
|
+
if (flags.temperature !== void 0 && flags.temperature >= 0 && flags.temperature <= 2) {
|
|
151
|
+
config.temperature = flags.temperature;
|
|
152
|
+
}
|
|
153
|
+
return config;
|
|
154
|
+
}
|
|
155
|
+
function validateConfig(config) {
|
|
156
|
+
const errors = [];
|
|
157
|
+
if (!config.providers || config.providers.length === 0) {
|
|
158
|
+
errors.push({
|
|
159
|
+
field: "providers",
|
|
160
|
+
message: "\u81F3\u5C11\u9700\u8981\u914D\u7F6E\u4E00\u4E2A Provider\u3002\u8BF7\u901A\u8FC7\u914D\u7F6E\u6587\u4EF6\u6216 DEEPSEEK_API_KEY \u73AF\u5883\u53D8\u91CF\u8BBE\u7F6E\u3002"
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
for (let i = 0; i < config.providers.length; i++) {
|
|
164
|
+
const p = config.providers[i];
|
|
165
|
+
if (!p.name) {
|
|
166
|
+
errors.push({
|
|
167
|
+
field: `providers[${i}].name`,
|
|
168
|
+
message: `\u7B2C ${i + 1} \u4E2A Provider \u7F3A\u5C11 name \u5B57\u6BB5\u3002`
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (!p.model) {
|
|
172
|
+
errors.push({
|
|
173
|
+
field: `providers[${i}].model`,
|
|
174
|
+
message: `Provider "${p.name || i}" \u7F3A\u5C11 model \u5B57\u6BB5\u3002`
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (config.defaultProvider) {
|
|
179
|
+
const exists = config.providers.some(
|
|
180
|
+
(p) => p.name === config.defaultProvider
|
|
181
|
+
);
|
|
182
|
+
if (!exists) {
|
|
183
|
+
errors.push({
|
|
184
|
+
field: "defaultProvider",
|
|
185
|
+
message: `\u9ED8\u8BA4 Provider "${config.defaultProvider}" \u672A\u5728 providers \u4E2D\u5B9A\u4E49\u3002`
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (config.temperature !== void 0 && (config.temperature < 0 || config.temperature > 2)) {
|
|
190
|
+
errors.push({
|
|
191
|
+
field: "temperature",
|
|
192
|
+
message: "temperature \u5FC5\u987B\u5728 0.0 ~ 2.0 \u4E4B\u95F4\u3002"
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (config.maxToolRounds !== void 0 && config.maxToolRounds < 1) {
|
|
196
|
+
errors.push({
|
|
197
|
+
field: "maxToolRounds",
|
|
198
|
+
message: "maxToolRounds \u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E 1\u3002"
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return errors;
|
|
202
|
+
}
|
|
203
|
+
async function loadConfig(configPath) {
|
|
204
|
+
const filePaths = resolveConfigFiles(configPath);
|
|
205
|
+
let config = structuredClone(defaultConfig);
|
|
206
|
+
for (const filePath of filePaths) {
|
|
207
|
+
try {
|
|
208
|
+
const raw = await readFile(filePath, "utf-8");
|
|
209
|
+
const parsed = JSON.parse(raw);
|
|
210
|
+
config = mergeConfig(config, parsed);
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
config = applyEnvVars(config);
|
|
215
|
+
return config;
|
|
216
|
+
}
|
|
217
|
+
async function loadAndValidate(configPath) {
|
|
218
|
+
const config = await loadConfig(configPath);
|
|
219
|
+
const errors = validateConfig(config);
|
|
220
|
+
return { config, errors };
|
|
221
|
+
}
|
|
222
|
+
async function saveApiKey(apiKey) {
|
|
223
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
|
|
224
|
+
const configDir = join(home, ".dskcode");
|
|
225
|
+
const configFile = join(configDir, "settings.json");
|
|
226
|
+
await mkdir(configDir, { recursive: true });
|
|
227
|
+
let configData;
|
|
228
|
+
try {
|
|
229
|
+
const raw = await readFile(configFile, "utf-8");
|
|
230
|
+
configData = JSON.parse(raw);
|
|
231
|
+
} catch {
|
|
232
|
+
configData = structuredClone(defaultConfig);
|
|
233
|
+
}
|
|
234
|
+
const providers = configData.providers ?? [];
|
|
235
|
+
const existing = providers.find((p) => p.name === "deepseek");
|
|
236
|
+
if (existing) {
|
|
237
|
+
existing.apiKey = apiKey;
|
|
238
|
+
} else {
|
|
239
|
+
providers.push({
|
|
240
|
+
name: "deepseek",
|
|
241
|
+
apiKey,
|
|
242
|
+
baseUrl: "https://api.deepseek.com",
|
|
243
|
+
model: "deepseek-v4-flash"
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
configData.providers = providers;
|
|
247
|
+
await writeFile(configFile, JSON.stringify(configData, null, 2), "utf-8");
|
|
248
|
+
return configFile;
|
|
249
|
+
}
|
|
250
|
+
|
|
9
251
|
// src/cli/middleware.ts
|
|
10
252
|
async function loadConfigMiddleware() {
|
|
11
253
|
const opts = this.optsWithGlobals();
|
|
12
254
|
const verbose = opts.verbose ?? false;
|
|
13
255
|
let config;
|
|
256
|
+
const errors = [];
|
|
14
257
|
try {
|
|
15
|
-
|
|
258
|
+
const result = await loadAndValidate(opts.config);
|
|
259
|
+
config = result.config;
|
|
260
|
+
if (result.errors.length > 0) {
|
|
261
|
+
for (const e of result.errors) {
|
|
262
|
+
errors.push(e.message);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
16
265
|
} catch {
|
|
17
|
-
|
|
18
|
-
|
|
266
|
+
config = structuredClone(defaultConfig);
|
|
267
|
+
}
|
|
268
|
+
config = applyCliOverrides(config, {
|
|
269
|
+
verbose,
|
|
270
|
+
model: opts.model
|
|
271
|
+
});
|
|
272
|
+
if (errors.length > 0 && verbose) {
|
|
273
|
+
for (const msg of errors) {
|
|
274
|
+
console.error(` \u26A0 ${msg}`);
|
|
275
|
+
}
|
|
19
276
|
}
|
|
20
277
|
return { config, verbose };
|
|
21
278
|
}
|
|
@@ -66,10 +323,73 @@ function customHelp(program2) {
|
|
|
66
323
|
lines.push(" dskcode setup");
|
|
67
324
|
lines.push(` ${chalk.dim("# \u751F\u6210 shell \u81EA\u52A8\u8865\u5168")}`);
|
|
68
325
|
lines.push(" dskcode completion");
|
|
326
|
+
lines.push(` ${chalk.dim("# \u67E5\u770B\u81EA\u9009\u80A1\u884C\u60C5")}`);
|
|
327
|
+
lines.push(" dskcode stock");
|
|
328
|
+
lines.push(` ${chalk.dim("# \u67E5\u770B\u6307\u5B9A\u80A1\u7968\u884C\u60C5")}`);
|
|
329
|
+
lines.push(" dskcode stock sh513090 sz000001");
|
|
69
330
|
lines.push("");
|
|
70
331
|
return lines.join("\n");
|
|
71
332
|
}
|
|
72
333
|
|
|
334
|
+
// src/cli/api-key-setup.ts
|
|
335
|
+
import { createInterface } from "readline";
|
|
336
|
+
import chalk2 from "chalk";
|
|
337
|
+
function hasApiKey(providers) {
|
|
338
|
+
if (providers.some((p) => p.apiKey)) return true;
|
|
339
|
+
if (process.env.DEEPSEEK_API_KEY) return true;
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
async function promptForApiKey() {
|
|
343
|
+
console.log(
|
|
344
|
+
chalk2.yellow("\n \u26A0 \u672A\u68C0\u6D4B\u5230 API Key \u914D\u7F6E")
|
|
345
|
+
);
|
|
346
|
+
console.log(
|
|
347
|
+
chalk2.dim(" \u4F60\u53EF\u4EE5\u901A\u8FC7\u4EE5\u4E0B\u4EFB\u4E00\u65B9\u5F0F\u914D\u7F6E\uFF1A")
|
|
348
|
+
);
|
|
349
|
+
console.log(
|
|
350
|
+
chalk2.dim(" \xB7 \u73AF\u5883\u53D8\u91CF: export DEEPSEEK_API_KEY=sk-xxx")
|
|
351
|
+
);
|
|
352
|
+
console.log(
|
|
353
|
+
chalk2.dim(" \xB7 \u914D\u7F6E\u6587\u4EF6: ~/.dskcode/settings.json")
|
|
354
|
+
);
|
|
355
|
+
console.log(
|
|
356
|
+
chalk2.dim(" \xB7 \u4E0B\u9762\u76F4\u63A5\u8F93\u5165\uFF0C\u81EA\u52A8\u4FDD\u5B58\u5230\u5168\u5C40\u914D\u7F6E\n")
|
|
357
|
+
);
|
|
358
|
+
const rl = createInterface({
|
|
359
|
+
input: process.stdin,
|
|
360
|
+
output: process.stdout
|
|
361
|
+
});
|
|
362
|
+
return new Promise((resolve) => {
|
|
363
|
+
const cleanup = () => {
|
|
364
|
+
rl.close();
|
|
365
|
+
};
|
|
366
|
+
process.stdin.on("keypress", (_, key) => {
|
|
367
|
+
if (key.ctrl && key.name === "c") {
|
|
368
|
+
cleanup();
|
|
369
|
+
resolve(null);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
rl.question(
|
|
373
|
+
` ${chalk2.cyan("\u{1F511}")} ${chalk2.bold("\u8BF7\u8F93\u5165\u4F60\u7684 DeepSeek API Key:")} `,
|
|
374
|
+
(answer) => {
|
|
375
|
+
cleanup();
|
|
376
|
+
const trimmed = answer.trim();
|
|
377
|
+
if (!trimmed) {
|
|
378
|
+
console.log(chalk2.red(" \u2716 API Key \u4E0D\u80FD\u4E3A\u7A7A"));
|
|
379
|
+
resolve(null);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (trimmed.length < 10) {
|
|
383
|
+
console.log(chalk2.red(" \u2716 API Key \u683C\u5F0F\u4E0D\u6B63\u786E\uFF0C\u957F\u5EA6\u81F3\u5C11 10 \u4F4D"));
|
|
384
|
+
resolve(null);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
resolve(trimmed);
|
|
388
|
+
}
|
|
389
|
+
);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
73
393
|
// src/ui/RenderScope.tsx
|
|
74
394
|
import { render } from "ink";
|
|
75
395
|
function renderApp(node) {
|
|
@@ -1075,26 +1395,317 @@ function initGames() {
|
|
|
1075
1395
|
|
|
1076
1396
|
// src/cli/index.tsx
|
|
1077
1397
|
import { render as render4 } from "ink";
|
|
1078
|
-
import
|
|
1079
|
-
|
|
1080
|
-
|
|
1398
|
+
import chalk3 from "chalk";
|
|
1399
|
+
|
|
1400
|
+
// src/stock/StockList.tsx
|
|
1401
|
+
import { Box as Box7, Text as Text8, useInput as useInput4 } from "ink";
|
|
1402
|
+
import { useState as useState6, useCallback as useCallback5, useEffect as useEffect5 } from "react";
|
|
1403
|
+
import asciichart from "asciichart";
|
|
1404
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1405
|
+
var MINUTE_API = "https://web.ifzq.gtimg.cn/appstock/app/minute/query?code={code}&r=0.1";
|
|
1406
|
+
function toApiCode(code) {
|
|
1407
|
+
if (/^sh|^sz/.test(code)) return code;
|
|
1408
|
+
if (/^60/.test(code) || /^68/.test(code)) return "sh" + code;
|
|
1409
|
+
if (/^00/.test(code) || /^30/.test(code) || /^39/.test(code)) return "sz" + code;
|
|
1410
|
+
if (/^51/.test(code)) return "sh" + code;
|
|
1411
|
+
return "sh" + code;
|
|
1412
|
+
}
|
|
1413
|
+
async function fetchStockMinute(code) {
|
|
1414
|
+
const url = MINUTE_API.replace("{code}", toApiCode(code));
|
|
1415
|
+
try {
|
|
1416
|
+
const resp = await fetch(url);
|
|
1417
|
+
const json = await resp.json();
|
|
1418
|
+
if (json.code !== 0) return null;
|
|
1419
|
+
const stockKey = toApiCode(code);
|
|
1420
|
+
const stockData = json.data?.[stockKey];
|
|
1421
|
+
if (!stockData) return null;
|
|
1422
|
+
const rawMinutes = stockData.data?.data ?? [];
|
|
1423
|
+
const prices = [];
|
|
1424
|
+
for (const line of rawMinutes) {
|
|
1425
|
+
const parts = line.split(" ");
|
|
1426
|
+
if (parts.length >= 2) {
|
|
1427
|
+
const p = parseFloat(parts[1]);
|
|
1428
|
+
if (!isNaN(p)) prices.push(p);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
const qtKey = stockKey;
|
|
1432
|
+
const qt = stockData.qt?.[qtKey];
|
|
1433
|
+
let quote = null;
|
|
1434
|
+
if (qt && qt.length >= 35) {
|
|
1435
|
+
quote = {
|
|
1436
|
+
code,
|
|
1437
|
+
name: qt[1] ?? "",
|
|
1438
|
+
price: parseFloat(qt[3] ?? "0"),
|
|
1439
|
+
changePercent: parseFloat(qt[32] ?? "0"),
|
|
1440
|
+
changeAmount: parseFloat(qt[31] ?? "0"),
|
|
1441
|
+
high: parseFloat(qt[33] ?? "0"),
|
|
1442
|
+
low: parseFloat(qt[34] ?? "0"),
|
|
1443
|
+
volume: parseInt(qt[6] ?? "0", 10)
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
return {
|
|
1447
|
+
prices,
|
|
1448
|
+
quote,
|
|
1449
|
+
date: stockData.data?.date ?? ""
|
|
1450
|
+
};
|
|
1451
|
+
} catch {
|
|
1452
|
+
return null;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
var minuteCache = /* @__PURE__ */ new Map();
|
|
1456
|
+
function cacheMinute(code, prices) {
|
|
1457
|
+
minuteCache.set(code, prices);
|
|
1458
|
+
}
|
|
1459
|
+
var FALLBACK_STOCKS = [
|
|
1460
|
+
{ code: "000001", name: "\u4E0A\u8BC1\u6307\u6570", price: 3150, changePercent: 0.35, changeAmount: 11.02, high: 3160, low: 3140, volume: 28543e4 },
|
|
1461
|
+
{ code: "399006", name: "\u521B\u4E1A\u677F\u6307", price: 1820, changePercent: -0.52, changeAmount: -9.5, high: 1835, low: 1815, volume: 9865e4 },
|
|
1462
|
+
{ code: "601688", name: "\u534E\u6CF0\u8BC1\u5238", price: 14.25, changePercent: 1.05, changeAmount: 0.15, high: 14.38, low: 14.1, volume: 452100 }
|
|
1463
|
+
];
|
|
1464
|
+
async function fetchStocks(codes) {
|
|
1465
|
+
const results = await Promise.all(
|
|
1466
|
+
codes.map(async (code) => {
|
|
1467
|
+
const data = await fetchStockMinute(code);
|
|
1468
|
+
if (data?.quote) {
|
|
1469
|
+
if (data.prices.length > 0) cacheMinute(code, data.prices);
|
|
1470
|
+
return data.quote;
|
|
1471
|
+
}
|
|
1472
|
+
return null;
|
|
1473
|
+
})
|
|
1474
|
+
);
|
|
1475
|
+
const real = results.filter((r) => r !== null);
|
|
1476
|
+
if (real.length > 0) return real;
|
|
1477
|
+
return FALLBACK_STOCKS;
|
|
1478
|
+
}
|
|
1479
|
+
function formatPrice(p) {
|
|
1480
|
+
return p >= 100 ? p.toFixed(2) : p.toFixed(3);
|
|
1481
|
+
}
|
|
1482
|
+
function formatVolume(v) {
|
|
1483
|
+
if (v >= 1e4) return (v / 1e4).toFixed(1) + "\u4E07";
|
|
1484
|
+
return v.toLocaleString();
|
|
1485
|
+
}
|
|
1486
|
+
function latestPoints(data, maxPoints = 60) {
|
|
1487
|
+
if (data.length <= maxPoints) return data;
|
|
1488
|
+
return data.slice(data.length - maxPoints);
|
|
1489
|
+
}
|
|
1490
|
+
function StockList({ codes, onExit }) {
|
|
1491
|
+
const [stocks, setStocks] = useState6([]);
|
|
1492
|
+
const [selectedIndex, setSelectedIndex] = useState6(0);
|
|
1493
|
+
const [loading, setLoading] = useState6(true);
|
|
1494
|
+
const [lastUpdate, setLastUpdate] = useState6("");
|
|
1495
|
+
const [detailView, setDetailView] = useState6(null);
|
|
1496
|
+
const [detailPrices, setDetailPrices] = useState6(null);
|
|
1497
|
+
const [detailLoading, setDetailLoading] = useState6(false);
|
|
1498
|
+
const [detailCountdown, setDetailCountdown] = useState6(10);
|
|
1499
|
+
const [countdown, setCountdown] = useState6(5);
|
|
1500
|
+
const loadData = useCallback5(async () => {
|
|
1501
|
+
setLoading(true);
|
|
1502
|
+
try {
|
|
1503
|
+
const data = await fetchStocks(codes ?? []);
|
|
1504
|
+
setStocks(data);
|
|
1505
|
+
setLastUpdate((/* @__PURE__ */ new Date()).toLocaleTimeString());
|
|
1506
|
+
} catch {
|
|
1507
|
+
}
|
|
1508
|
+
setLoading(false);
|
|
1509
|
+
}, [codes]);
|
|
1510
|
+
useEffect5(() => {
|
|
1511
|
+
loadData();
|
|
1512
|
+
}, [loadData]);
|
|
1513
|
+
useEffect5(() => {
|
|
1514
|
+
const interval = setInterval(() => {
|
|
1515
|
+
setCountdown((prev) => {
|
|
1516
|
+
if (prev <= 1) {
|
|
1517
|
+
loadData();
|
|
1518
|
+
return 5;
|
|
1519
|
+
}
|
|
1520
|
+
return prev - 1;
|
|
1521
|
+
});
|
|
1522
|
+
}, 1e3);
|
|
1523
|
+
return () => clearInterval(interval);
|
|
1524
|
+
}, [loadData]);
|
|
1525
|
+
useEffect5(() => {
|
|
1526
|
+
if (!detailView) {
|
|
1527
|
+
setDetailPrices(null);
|
|
1528
|
+
setDetailLoading(false);
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
const loadDetail = () => {
|
|
1532
|
+
minuteCache.delete(detailView.code);
|
|
1533
|
+
fetchStockMinute(detailView.code).then((data) => {
|
|
1534
|
+
if (data && data.prices.length > 0) {
|
|
1535
|
+
cacheMinute(detailView.code, data.prices);
|
|
1536
|
+
setDetailPrices(data.prices);
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
};
|
|
1540
|
+
loadDetail();
|
|
1541
|
+
setDetailCountdown(10);
|
|
1542
|
+
const timer = setInterval(loadDetail, 1e4);
|
|
1543
|
+
return () => clearInterval(timer);
|
|
1544
|
+
}, [detailView]);
|
|
1545
|
+
useEffect5(() => {
|
|
1546
|
+
if (!detailView) return;
|
|
1547
|
+
const timer = setInterval(() => {
|
|
1548
|
+
setDetailCountdown((prev) => prev > 0 ? prev - 1 : 10);
|
|
1549
|
+
}, 1e3);
|
|
1550
|
+
return () => clearInterval(timer);
|
|
1551
|
+
}, [detailView]);
|
|
1552
|
+
useInput4(
|
|
1553
|
+
useCallback5(
|
|
1554
|
+
(input, key) => {
|
|
1555
|
+
if (detailView) {
|
|
1556
|
+
if (key.escape || input === "q" || input === " ") {
|
|
1557
|
+
setDetailView(null);
|
|
1558
|
+
}
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
if (stocks.length === 0) return;
|
|
1562
|
+
if (key.upArrow || input === "k") {
|
|
1563
|
+
setSelectedIndex((prev) => prev > 0 ? prev - 1 : stocks.length - 1);
|
|
1564
|
+
} else if (key.downArrow || input === "j") {
|
|
1565
|
+
setSelectedIndex((prev) => prev < stocks.length - 1 ? prev + 1 : 0);
|
|
1566
|
+
} else if (key.return) {
|
|
1567
|
+
const stock = stocks[selectedIndex];
|
|
1568
|
+
if (stock) setDetailView(stock);
|
|
1569
|
+
} else if (key.escape || input === "q") {
|
|
1570
|
+
onExit();
|
|
1571
|
+
} else if (input === "r") {
|
|
1572
|
+
setCountdown(5);
|
|
1573
|
+
loadData();
|
|
1574
|
+
}
|
|
1575
|
+
},
|
|
1576
|
+
[stocks, selectedIndex, detailView, onExit, loadData]
|
|
1577
|
+
)
|
|
1578
|
+
);
|
|
1579
|
+
if (detailView) {
|
|
1580
|
+
if (detailLoading) {
|
|
1581
|
+
return /* @__PURE__ */ jsx7(Box7, { paddingLeft: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " \u27F3 \u52A0\u8F7D\u5206\u65F6\u6570\u636E..." }) });
|
|
1582
|
+
}
|
|
1583
|
+
return renderDetail(detailView, () => setDetailView(null), detailPrices ?? void 0, detailCountdown);
|
|
1584
|
+
}
|
|
1585
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
1586
|
+
/* @__PURE__ */ jsxs7(Box7, { marginBottom: 1, justifyContent: "space-between", children: [
|
|
1587
|
+
/* @__PURE__ */ jsx7(Text8, { bold: true, color: "#00ffff", children: " \u{1F4C8} \u81EA\u9009\u80A1\u76D1\u63A7" }),
|
|
1588
|
+
/* @__PURE__ */ jsx7(Text8, { dimColor: true, children: loading ? " \u27F3 \u5237\u65B0\u4E2D..." : ` \u6BCF ${countdown}s \u81EA\u52A8\u5237\u65B0` })
|
|
1589
|
+
] }),
|
|
1590
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
1591
|
+
/* @__PURE__ */ jsx7(Box7, { width: 3 }),
|
|
1592
|
+
/* @__PURE__ */ jsx7(Box7, { width: 9, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u4EE3\u7801" }) }),
|
|
1593
|
+
/* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u540D\u79F0" }) }),
|
|
1594
|
+
/* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6700\u65B0\u4EF7" }) }),
|
|
1595
|
+
/* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6DA8\u8DCC\u5E45" }) }),
|
|
1596
|
+
/* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6DA8\u8DCC\u989D" }) }),
|
|
1597
|
+
/* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6700\u9AD8" }) }),
|
|
1598
|
+
/* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6700\u4F4E" }) }),
|
|
1599
|
+
/* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6210\u4EA4\u91CF" }) })
|
|
1600
|
+
] }),
|
|
1601
|
+
/* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " " + "\u2500".repeat(100) }) }),
|
|
1602
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: stocks.map((stock, index) => {
|
|
1603
|
+
const isSelected = index === selectedIndex;
|
|
1604
|
+
const isUp = stock.changePercent >= 0;
|
|
1605
|
+
const color = isUp ? "#ff1493" : "#00ff41";
|
|
1606
|
+
return /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
1607
|
+
/* @__PURE__ */ jsx7(Box7, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx7(Text8, { bold: true, color: "#00ffff", children: "\u25B8 " }) : /* @__PURE__ */ jsx7(Text8, { children: " " }) }),
|
|
1608
|
+
/* @__PURE__ */ jsx7(Box7, { width: 9, children: /* @__PURE__ */ jsx7(Text8, { bold: true, color: isSelected ? "#00ffff" : "#ffffff", children: stock.code }) }),
|
|
1609
|
+
/* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { color: isSelected ? "#ffffff" : "#cccccc", children: stock.name }) }),
|
|
1610
|
+
/* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { bold: true, color, children: formatPrice(stock.price) }) }),
|
|
1611
|
+
/* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { color, children: [
|
|
1612
|
+
isUp ? "+" : "",
|
|
1613
|
+
stock.changePercent.toFixed(2),
|
|
1614
|
+
"%"
|
|
1615
|
+
] }) }),
|
|
1616
|
+
/* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { color, children: [
|
|
1617
|
+
isUp ? "+" : "",
|
|
1618
|
+
stock.changeAmount.toFixed(3)
|
|
1619
|
+
] }) }),
|
|
1620
|
+
/* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { color: "#cccccc", children: formatPrice(stock.high) }) }),
|
|
1621
|
+
/* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { color: "#cccccc", children: formatPrice(stock.low) }) }),
|
|
1622
|
+
/* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { color: "#888888", children: formatVolume(stock.volume) }) })
|
|
1623
|
+
] }, stock.code);
|
|
1624
|
+
}) }),
|
|
1625
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: ` \u2191/\u2193 \u9009\u62E9 Enter \u8BE6\u60C5 r \u624B\u52A8\u5237\u65B0 q \u8FD4\u56DE` }) }),
|
|
1626
|
+
/* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: ` \u6700\u540E\u66F4\u65B0: ${lastUpdate}` }) })
|
|
1627
|
+
] });
|
|
1628
|
+
}
|
|
1629
|
+
function renderDetail(stock, _onBack, prices, countdown = 10) {
|
|
1630
|
+
const isUp = stock.changePercent >= 0;
|
|
1631
|
+
const colorCode = isUp ? "#ff1493" : "#00ff41";
|
|
1632
|
+
const arrow = isUp ? "\u25B2" : "\u25BC";
|
|
1633
|
+
let chartLines = [];
|
|
1634
|
+
if (prices && prices.length > 0) {
|
|
1635
|
+
const chartColor = isUp ? asciichart.red : asciichart.green;
|
|
1636
|
+
const latest = latestPoints(prices, 60);
|
|
1637
|
+
let raw = asciichart.plot(latest, {
|
|
1638
|
+
height: 10,
|
|
1639
|
+
colors: [chartColor]
|
|
1640
|
+
});
|
|
1641
|
+
raw = raw.replaceAll("\u256D", "\u250C").replaceAll("\u256E", "\u2510").replaceAll("\u2570", "\u2514").replaceAll("\u256F", "\u2518");
|
|
1642
|
+
chartLines = raw.split("\n");
|
|
1643
|
+
}
|
|
1644
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingLeft: 1, children: [
|
|
1645
|
+
/* @__PURE__ */ jsxs7(Box7, { marginBottom: 1, justifyContent: "space-between", children: [
|
|
1646
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
1647
|
+
/* @__PURE__ */ jsxs7(Text8, { bold: true, color: "#00ffff", children: [
|
|
1648
|
+
" \u{1F4CA} ",
|
|
1649
|
+
stock.name,
|
|
1650
|
+
" "
|
|
1651
|
+
] }),
|
|
1652
|
+
/* @__PURE__ */ jsx7(Text8, { dimColor: true, children: stock.code })
|
|
1653
|
+
] }),
|
|
1654
|
+
/* @__PURE__ */ jsx7(Text8, { dimColor: true, children: `\u6BCF ${countdown}s \u5237\u65B0` })
|
|
1655
|
+
] }),
|
|
1656
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
1657
|
+
/* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { bold: true, color: "#888888", children: "\u5F53\u524D\u4EF7" }) }),
|
|
1658
|
+
/* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text8, { bold: true, color: colorCode, children: [
|
|
1659
|
+
arrow,
|
|
1660
|
+
" ",
|
|
1661
|
+
formatPrice(stock.price)
|
|
1662
|
+
] }) })
|
|
1663
|
+
] }),
|
|
1664
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
1665
|
+
/* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { color: "#888888", children: "\u6DA8\u8DCC\u5E45" }) }),
|
|
1666
|
+
/* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text8, { color: colorCode, children: [
|
|
1667
|
+
isUp ? "+" : "",
|
|
1668
|
+
stock.changePercent.toFixed(2),
|
|
1669
|
+
"%",
|
|
1670
|
+
" ",
|
|
1671
|
+
isUp ? "+" : "",
|
|
1672
|
+
stock.changeAmount.toFixed(3)
|
|
1673
|
+
] }) })
|
|
1674
|
+
] }),
|
|
1675
|
+
chartLines.length > 0 && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: chartLines.map((line, i) => /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { color: colorCode, children: line || " " }) }, i)) }),
|
|
1676
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " Space/q \u8FD4\u56DE\u5217\u8868" }) })
|
|
1677
|
+
] });
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
// src/cli/index.tsx
|
|
1681
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1682
|
+
var SUBCOMMANDS = ["chat", "run", "setup", "init", "completion", "game", "stock"];
|
|
1081
1683
|
function createCli() {
|
|
1082
1684
|
const program2 = new Command();
|
|
1083
1685
|
program2.exitOverride();
|
|
1084
1686
|
program2.name("dskcode").description("\u57FA\u4E8E DeepSeek \u7684 AI \u7F16\u7A0B\u52A9\u624B\u7EC8\u7AEF\u5DE5\u5177").version("0.0.0", "-V, --version", "\u663E\u793A\u7248\u672C\u53F7").option("--verbose", "\u5F00\u542F\u8BE6\u7EC6\u65E5\u5FD7\u8F93\u51FA").option("--config <path>", "\u6307\u5B9A\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84");
|
|
1085
1687
|
program2.helpInformation = () => customHelp(program2);
|
|
1086
|
-
program2.hook("preAction", async (thisCommand) => {
|
|
1688
|
+
program2.hook("preAction", async (thisCommand, actionCommand) => {
|
|
1087
1689
|
const ctx = await loadConfigMiddleware.call(thisCommand);
|
|
1088
|
-
|
|
1690
|
+
actionCommand.dskcodeCtx = ctx;
|
|
1089
1691
|
});
|
|
1090
1692
|
program2.command("chat").description("\u542F\u52A8\u4EA4\u4E92\u5F0F\u5BF9\u8BDD\u4F1A\u8BDD").action(async function() {
|
|
1091
1693
|
if (!process.stdin.isTTY) {
|
|
1092
1694
|
console.error("dskcode chat \u9700\u8981\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u3002\u5982\u9700\u6267\u884C\u4E00\u6B21\u6027\u4EFB\u52A1\uFF0C\u8BF7\u4F7F\u7528 dskcode run\u3002");
|
|
1093
1695
|
process.exit(1);
|
|
1094
1696
|
}
|
|
1095
|
-
|
|
1697
|
+
let ctx = this.dskcodeCtx;
|
|
1698
|
+
if (ctx && !hasApiKey(ctx.config.providers)) {
|
|
1699
|
+
const key = await promptForApiKey();
|
|
1700
|
+
if (!key) process.exit(1);
|
|
1701
|
+
const savedPath = await saveApiKey(key);
|
|
1702
|
+
console.log(` ${chalk3.green("\u2714")} API Key \u5DF2\u4FDD\u5B58\u5230 ${chalk3.dim(savedPath)}
|
|
1703
|
+
`);
|
|
1704
|
+
const result = await loadAndValidate();
|
|
1705
|
+
ctx = { ...ctx, config: result.config };
|
|
1706
|
+
}
|
|
1096
1707
|
const app = renderApp(
|
|
1097
|
-
/* @__PURE__ */
|
|
1708
|
+
/* @__PURE__ */ jsx8(
|
|
1098
1709
|
ChatSession,
|
|
1099
1710
|
{
|
|
1100
1711
|
providerCount: ctx?.config.providers.length ?? 1,
|
|
@@ -1141,12 +1752,26 @@ _dskcode_completion() {
|
|
|
1141
1752
|
"init:\u751F\u6210\u9879\u76EE\u8BB0\u5FC6\u6587\u4EF6"
|
|
1142
1753
|
"completion:\u8F93\u51FA shell \u81EA\u52A8\u8865\u5168\u8BF4\u660E"
|
|
1143
1754
|
"game:\u5185\u7F6E\u5C0F\u6E38\u620F"
|
|
1755
|
+
"stock:\u67E5\u770B\u81EA\u9009\u80A1\u5B9E\u65F6\u884C\u60C5"
|
|
1144
1756
|
)
|
|
1145
1757
|
_describe 'dskcode commands' commands
|
|
1146
1758
|
}
|
|
1147
1759
|
compdef _dskcode_completion dskcode`);
|
|
1148
1760
|
}
|
|
1149
1761
|
});
|
|
1762
|
+
program2.command("stock").description("\u67E5\u770B\u81EA\u9009\u80A1\u5B9E\u65F6\u884C\u60C5").argument("[codes...]", "\u80A1\u7968\u4EE3\u7801\uFF08\u7A7A\u683C\u5206\u9694\uFF09\uFF0C\u5982 513090 600519").action(async function(codes) {
|
|
1763
|
+
const codeList = codes && codes.length > 0 ? codes : ["sh000001", "sz399006", "sh601688"];
|
|
1764
|
+
const app = renderApp(
|
|
1765
|
+
/* @__PURE__ */ jsx8(
|
|
1766
|
+
StockList,
|
|
1767
|
+
{
|
|
1768
|
+
codes: codeList,
|
|
1769
|
+
onExit: () => process.exit(0)
|
|
1770
|
+
}
|
|
1771
|
+
)
|
|
1772
|
+
);
|
|
1773
|
+
await app.waitUntilExit;
|
|
1774
|
+
});
|
|
1150
1775
|
initGames();
|
|
1151
1776
|
program2.command("game").description("\u542F\u52A8\u5185\u7F6E\u5C0F\u6E38\u620F").argument("[name]", "\u6E38\u620F\u540D\u79F0\uFF0C\u4E0D\u6307\u5B9A\u5219\u663E\u793A\u4EA4\u4E92\u5F0F\u6E38\u620F\u5217\u8868").action(async function(name) {
|
|
1152
1777
|
if (name) {
|
|
@@ -1166,7 +1791,7 @@ compdef _dskcode_completion dskcode`);
|
|
|
1166
1791
|
}
|
|
1167
1792
|
const selectedGame = await new Promise((resolve) => {
|
|
1168
1793
|
const { unmount } = render4(
|
|
1169
|
-
/* @__PURE__ */
|
|
1794
|
+
/* @__PURE__ */ jsx8(
|
|
1170
1795
|
GamePicker,
|
|
1171
1796
|
{
|
|
1172
1797
|
games,
|
|
@@ -1184,7 +1809,7 @@ compdef _dskcode_completion dskcode`);
|
|
|
1184
1809
|
});
|
|
1185
1810
|
if (selectedGame) {
|
|
1186
1811
|
console.log(`
|
|
1187
|
-
\u542F\u52A8\u6E38\u620F: ${
|
|
1812
|
+
\u542F\u52A8\u6E38\u620F: ${chalk3.green(selectedGame.name)}
|
|
1188
1813
|
`);
|
|
1189
1814
|
await selectedGame.play();
|
|
1190
1815
|
}
|