dskcode 0.1.5 → 0.1.7

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/index.js CHANGED
@@ -29,7 +29,14 @@ var defaultConfig = {
29
29
  { name: "ls", enabled: true },
30
30
  { name: "fetch", enabled: true }
31
31
  ],
32
- plugins: []
32
+ plugins: [],
33
+ stock: {
34
+ symbols: [
35
+ { code: "sh000001" },
36
+ { code: "sz399300" },
37
+ { code: "sh601899" }
38
+ ]
39
+ }
33
40
  };
34
41
  function resolveConfigFiles(configPath) {
35
42
  if (configPath) {
@@ -67,6 +74,9 @@ function mergeConfig(base, overlay) {
67
74
  if (overlay.plugins !== void 0) {
68
75
  result.plugins = overlay.plugins;
69
76
  }
77
+ if (overlay.stock !== void 0) {
78
+ result.stock = overlay.stock;
79
+ }
70
80
  return result;
71
81
  }
72
82
  var ENV_PREFIX = "DSKCODE_";
@@ -78,28 +88,28 @@ var ENV_MAP = {
78
88
  [`${ENV_PREFIX}MAX_TOOL_ROUNDS`]: "maxToolRounds"
79
89
  };
80
90
  function applyEnvVars(config) {
91
+ const result = { ...config, providers: [...config.providers] };
81
92
  for (const [envKey, configKey] of Object.entries(ENV_MAP)) {
82
93
  const raw = process.env[envKey];
83
94
  if (raw === void 0) continue;
84
- const cfg = config;
85
95
  switch (configKey) {
86
96
  case "verbose":
87
97
  case "defaultProvider": {
88
- cfg[configKey] = raw;
98
+ result[configKey] = raw;
89
99
  break;
90
100
  }
91
101
  case "maxTokens":
92
102
  case "maxToolRounds": {
93
103
  const n = Number(raw);
94
104
  if (Number.isFinite(n) && n > 0) {
95
- cfg[configKey] = n;
105
+ result[configKey] = n;
96
106
  }
97
107
  break;
98
108
  }
99
109
  case "temperature": {
100
110
  const n = Number(raw);
101
111
  if (Number.isFinite(n) && n >= 0 && n <= 2) {
102
- cfg[configKey] = n;
112
+ result[configKey] = n;
103
113
  }
104
114
  break;
105
115
  }
@@ -107,12 +117,14 @@ function applyEnvVars(config) {
107
117
  }
108
118
  const apiKey = process.env.DEEPSEEK_API_KEY;
109
119
  if (apiKey) {
110
- const deepseek = config.providers.find((p) => p.name === "deepseek");
111
- if (deepseek && !deepseek.apiKey) {
112
- deepseek.apiKey = apiKey;
113
- }
114
- if (!deepseek) {
115
- config.providers.unshift({
120
+ const deepseekIdx = result.providers.findIndex((p) => p.name === "deepseek");
121
+ if (deepseekIdx !== -1) {
122
+ const existing = result.providers[deepseekIdx];
123
+ if (!existing.apiKey) {
124
+ result.providers[deepseekIdx] = { ...existing, apiKey };
125
+ }
126
+ } else {
127
+ result.providers.unshift({
116
128
  name: "deepseek",
117
129
  baseUrl: "https://api.deepseek.com",
118
130
  model: "deepseek-v4-flash",
@@ -120,27 +132,31 @@ function applyEnvVars(config) {
120
132
  });
121
133
  }
122
134
  }
123
- return config;
135
+ return result;
124
136
  }
125
137
  function applyCliOverrides(config, flags) {
138
+ const result = { ...config, providers: [...config.providers] };
126
139
  if (flags.verbose !== void 0) {
127
- config.verbose = flags.verbose;
140
+ result.verbose = flags.verbose;
128
141
  }
129
142
  if (flags.model !== void 0) {
130
- const provider = config.providers.find(
131
- (p) => p.name === config.defaultProvider
143
+ const providerIdx = result.providers.findIndex(
144
+ (p) => p.name === result.defaultProvider
132
145
  );
133
- if (provider) {
134
- provider.model = flags.model;
146
+ if (providerIdx !== -1) {
147
+ result.providers[providerIdx] = {
148
+ ...result.providers[providerIdx],
149
+ model: flags.model
150
+ };
135
151
  }
136
152
  }
137
153
  if (flags.maxTokens !== void 0 && flags.maxTokens > 0) {
138
- config.maxTokens = flags.maxTokens;
154
+ result.maxTokens = flags.maxTokens;
139
155
  }
140
156
  if (flags.temperature !== void 0 && flags.temperature >= 0 && flags.temperature <= 2) {
141
- config.temperature = flags.temperature;
157
+ result.temperature = flags.temperature;
142
158
  }
143
- return config;
159
+ return result;
144
160
  }
145
161
  function validateConfig(config) {
146
162
  const errors = [];
@@ -182,6 +198,12 @@ function validateConfig(config) {
182
198
  message: "temperature \u5FC5\u987B\u5728 0.0 ~ 2.0 \u4E4B\u95F4\u3002"
183
199
  });
184
200
  }
201
+ if (config.maxTokens !== void 0 && config.maxTokens < 1) {
202
+ errors.push({
203
+ field: "maxTokens",
204
+ message: "maxTokens \u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E 1\u3002"
205
+ });
206
+ }
185
207
  if (config.maxToolRounds !== void 0 && config.maxToolRounds < 1) {
186
208
  errors.push({
187
209
  field: "maxToolRounds",
@@ -214,11 +236,12 @@ async function saveApiKey(apiKey) {
214
236
  const configDir = join(home, ".dskcode");
215
237
  const configFile = join(configDir, "settings.json");
216
238
  await mkdir(configDir, { recursive: true });
217
- let configData = {};
239
+ let configData;
218
240
  try {
219
241
  const raw = await readFile(configFile, "utf-8");
220
242
  configData = JSON.parse(raw);
221
243
  } catch {
244
+ configData = structuredClone(defaultConfig);
222
245
  }
223
246
  const providers = configData.providers ?? [];
224
247
  const existing = providers.find((p) => p.name === "deepseek");
@@ -312,6 +335,10 @@ function customHelp(program2) {
312
335
  lines.push(" dskcode setup");
313
336
  lines.push(` ${chalk.dim("# \u751F\u6210 shell \u81EA\u52A8\u8865\u5168")}`);
314
337
  lines.push(" dskcode completion");
338
+ lines.push(` ${chalk.dim("# \u67E5\u770B\u81EA\u9009\u80A1\u884C\u60C5")}`);
339
+ lines.push(" dskcode stock");
340
+ lines.push(` ${chalk.dim("# \u67E5\u770B\u6307\u5B9A\u80A1\u7968\u884C\u60C5")}`);
341
+ lines.push(" dskcode stock sh513090 sz000001");
315
342
  lines.push("");
316
343
  return lines.join("\n");
317
344
  }
@@ -324,36 +351,32 @@ function hasApiKey(providers) {
324
351
  if (process.env.DEEPSEEK_API_KEY) return true;
325
352
  return false;
326
353
  }
354
+ var MIN_API_KEY_LENGTH = 10;
327
355
  async function promptForApiKey() {
328
- console.log(
329
- chalk2.yellow("\n \u26A0 \u672A\u68C0\u6D4B\u5230 API Key \u914D\u7F6E")
330
- );
331
- console.log(
332
- chalk2.dim(" \u4F60\u53EF\u4EE5\u901A\u8FC7\u4EE5\u4E0B\u4EFB\u4E00\u65B9\u5F0F\u914D\u7F6E\uFF1A")
333
- );
334
- console.log(
335
- chalk2.dim(" \xB7 \u73AF\u5883\u53D8\u91CF: export DEEPSEEK_API_KEY=sk-xxx")
336
- );
337
- console.log(
338
- chalk2.dim(" \xB7 \u914D\u7F6E\u6587\u4EF6: ~/.dskcode/settings.json")
339
- );
340
- console.log(
341
- chalk2.dim(" \xB7 \u4E0B\u9762\u76F4\u63A5\u8F93\u5165\uFF0C\u81EA\u52A8\u4FDD\u5B58\u5230\u5168\u5C40\u914D\u7F6E\n")
342
- );
356
+ console.log(chalk2.yellow("\n \u26A0 \u672A\u68C0\u6D4B\u5230 API Key \u914D\u7F6E"));
357
+ console.log(chalk2.dim(" \u4F60\u53EF\u4EE5\u901A\u8FC7\u4EE5\u4E0B\u4EFB\u4E00\u65B9\u5F0F\u914D\u7F6E\uFF1A"));
358
+ console.log(chalk2.dim(" \xB7 \u73AF\u5883\u53D8\u91CF: export DEEPSEEK_API_KEY=sk-xxx"));
359
+ console.log(chalk2.dim(" \xB7 \u914D\u7F6E\u6587\u4EF6: ~/.dskcode/settings.json"));
360
+ console.log(chalk2.dim(" \xB7 \u4E0B\u9762\u76F4\u63A5\u8F93\u5165\uFF0C\u81EA\u52A8\u4FDD\u5B58\u5230\u5168\u5C40\u914D\u7F6E\n"));
343
361
  const rl = createInterface({
344
362
  input: process.stdin,
345
363
  output: process.stdout
346
364
  });
347
365
  return new Promise((resolve) => {
366
+ let resolved = false;
348
367
  const cleanup = () => {
368
+ if (resolved) return;
369
+ resolved = true;
370
+ process.stdin.removeListener("keypress", onKeypress);
349
371
  rl.close();
350
372
  };
351
- process.stdin.on("keypress", (_, key) => {
373
+ const onKeypress = (_, key) => {
352
374
  if (key.ctrl && key.name === "c") {
353
375
  cleanup();
354
376
  resolve(null);
355
377
  }
356
- });
378
+ };
379
+ process.stdin.on("keypress", onKeypress);
357
380
  rl.question(
358
381
  ` ${chalk2.cyan("\u{1F511}")} ${chalk2.bold("\u8BF7\u8F93\u5165\u4F60\u7684 DeepSeek API Key:")} `,
359
382
  (answer) => {
@@ -364,7 +387,7 @@ async function promptForApiKey() {
364
387
  resolve(null);
365
388
  return;
366
389
  }
367
- if (trimmed.length < 10) {
390
+ if (trimmed.length < MIN_API_KEY_LENGTH) {
368
391
  console.log(chalk2.red(" \u2716 API Key \u683C\u5F0F\u4E0D\u6B63\u786E\uFF0C\u957F\u5EA6\u81F3\u5C11 10 \u4F4D"));
369
392
  resolve(null);
370
393
  return;
@@ -378,7 +401,7 @@ async function promptForApiKey() {
378
401
  // src/ui/RenderScope.tsx
379
402
  import { render } from "ink";
380
403
  function renderApp(node) {
381
- const { waitUntilExit, clear, unmount } = render(node);
404
+ const { waitUntilExit, clear, unmount } = render(node, { exitOnCtrlC: false });
382
405
  return { waitUntilExit: waitUntilExit(), clear, unmount };
383
406
  }
384
407
 
@@ -395,12 +418,6 @@ import { jsxs as jsxs2 } from "react/jsx-runtime";
395
418
  import { Box as Box2, Text as Text3 } from "ink";
396
419
  import { useEffect, useState } from "react";
397
420
  import { jsx as jsx2 } from "react/jsx-runtime";
398
-
399
- // src/ui/ChatSession.tsx
400
- import { Box as Box3, Text as Text4 } from "ink";
401
- import TextInput from "ink-text-input";
402
- import { useEffect as useEffect2, useState as useState2, useCallback } from "react";
403
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
404
421
  var CYBER_PALETTE = ["#00ffff", "#ff00ff", "#00ff41", "#ff1493", "#8b00ff"];
405
422
  var LOGO_LINES = [
406
423
  " \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557",
@@ -410,57 +427,119 @@ var LOGO_LINES = [
410
427
  " \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2557",
411
428
  " \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D"
412
429
  ];
413
- var COMMANDS = {
414
- "/exit": { desc: "\u9000\u51FA\u5BF9\u8BDD", handler: () => "" },
415
- "/quit": { desc: "\u9000\u51FA\u5BF9\u8BDD", handler: () => "" },
416
- "/help": {
417
- desc: "\u663E\u793A\u5E2E\u52A9\u4FE1\u606F",
418
- handler: () => [
419
- "\u53EF\u7528\u547D\u4EE4\uFF1A",
420
- " /exit, /quit \u9000\u51FA\u5BF9\u8BDD",
421
- " /help \u663E\u793A\u6B64\u5E2E\u52A9",
422
- " /clear \u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2",
423
- " /version \u663E\u793A\u7248\u672C\u4FE1\u606F"
424
- ].join("\n")
425
- },
426
- "/clear": { desc: "\u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2", handler: () => "" },
427
- "/version": { desc: "\u663E\u793A\u7248\u672C\u4FE1\u606F", handler: () => "dskcode v0.0.0" }
428
- };
429
- function ChatSession({ providerCount, toolCount, verbose }) {
430
- const [offset, setOffset] = useState2(0);
431
- const [messages, setMessages] = useState2([]);
432
- const [input, setInput] = useState2("");
430
+
431
+ // src/ui/ChatSession.tsx
432
+ import { Box as Box3, Text as Text4, useInput } from "ink";
433
+ import TextInput from "ink-text-input";
434
+ import { useEffect as useEffect3, useState as useState3, useCallback as useCallback2 } from "react";
435
+
436
+ // src/ui/useDoubleCtrlC.ts
437
+ import { useState as useState2, useCallback, useEffect as useEffect2, useRef } from "react";
438
+ var CTRL_C_TIMEOUT_MS = 1500;
439
+ function useDoubleCtrlC(onExit) {
440
+ const [doubleCtrlC, setDoubleCtrlC] = useState2(false);
441
+ const timerRef = useRef(null);
442
+ const onExitRef = useRef(onExit);
443
+ onExitRef.current = onExit;
433
444
  useEffect2(() => {
445
+ return () => {
446
+ if (timerRef.current) clearTimeout(timerRef.current);
447
+ };
448
+ }, []);
449
+ const handleCtrlC = useCallback(() => {
450
+ if (doubleCtrlC) {
451
+ onExitRef.current();
452
+ return;
453
+ }
454
+ setDoubleCtrlC(true);
455
+ if (timerRef.current) clearTimeout(timerRef.current);
456
+ timerRef.current = setTimeout(() => {
457
+ setDoubleCtrlC(false);
458
+ timerRef.current = null;
459
+ }, CTRL_C_TIMEOUT_MS);
460
+ }, [doubleCtrlC]);
461
+ return { doubleCtrlC, handleCtrlC };
462
+ }
463
+
464
+ // src/ui/ChatSession.tsx
465
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
466
+ var commandRegistry = /* @__PURE__ */ new Map();
467
+ function registerCommand(name, cmd) {
468
+ commandRegistry.set(name, cmd);
469
+ }
470
+ function getRegisteredCommands() {
471
+ return commandRegistry;
472
+ }
473
+ registerCommand("/exit", { desc: "\u9000\u51FA\u5BF9\u8BDD", handler: () => ({ kind: "exit" }) });
474
+ registerCommand("/quit", { desc: "\u9000\u51FA\u5BF9\u8BDD", handler: () => ({ kind: "exit" }) });
475
+ registerCommand("/help", {
476
+ desc: "\u663E\u793A\u5E2E\u52A9\u4FE1\u606F",
477
+ handler: () => {
478
+ const commands = getRegisteredCommands();
479
+ const lines = ["\u53EF\u7528\u547D\u4EE4\uFF1A"];
480
+ for (const [name, cmd] of commands) {
481
+ lines.push(` ${name.padEnd(16)}${cmd.desc}`);
482
+ }
483
+ return { kind: "text", content: lines.join("\n") };
484
+ }
485
+ });
486
+ registerCommand("/clear", { desc: "\u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2", handler: () => ({ kind: "clear" }) });
487
+ registerCommand("/version", { desc: "\u663E\u793A\u7248\u672C\u4FE1\u606F", handler: () => ({ kind: "text", content: "dskcode v0.0.0" }) });
488
+ registerCommand("/game", { desc: "\u542F\u52A8\u6E38\u620F", handler: () => ({ kind: "navigate", target: "game" }) });
489
+ registerCommand("/stock", { desc: "\u67E5\u770B\u80A1\u7968\u884C\u60C5", handler: () => ({ kind: "navigate", target: "stock" }) });
490
+ function ChatSession({ providerCount, toolCount, verbose, onLaunchGame, onLaunchStock }) {
491
+ const [offset, setOffset] = useState3(0);
492
+ const [messages, setMessages] = useState3([]);
493
+ const [input, setInput] = useState3("");
494
+ const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(() => process.exit(0));
495
+ useInput(
496
+ useCallback2(
497
+ (input2, key) => {
498
+ if (input2 === "c" && key.ctrl) {
499
+ handleCtrlC();
500
+ }
501
+ },
502
+ [handleCtrlC]
503
+ )
504
+ );
505
+ useEffect3(() => {
434
506
  const timer = setInterval(() => {
435
507
  setOffset((prev) => (prev + 1) % CYBER_PALETTE.length);
436
508
  }, 500);
437
509
  return () => clearInterval(timer);
438
510
  }, []);
439
- const handleSubmit = useCallback((value) => {
511
+ const handleSubmit = useCallback2((value) => {
440
512
  const trimmed = value.trim();
441
513
  if (!trimmed) return;
442
514
  if (trimmed.startsWith("/")) {
443
- const cmd = COMMANDS[trimmed.toLowerCase()];
515
+ const cmd = commandRegistry.get(trimmed.toLowerCase());
444
516
  if (cmd) {
445
- if (trimmed.toLowerCase() === "/exit" || trimmed.toLowerCase() === "/quit") {
446
- process.exit(0);
447
- return;
448
- }
449
- if (trimmed.toLowerCase() === "/clear") {
450
- setMessages([]);
451
- setInput("");
452
- return;
453
- }
454
517
  const result = cmd.handler();
455
- if (result) {
456
- setMessages((prev) => [
457
- ...prev,
458
- { role: "user", content: trimmed },
459
- { role: "assistant", content: result }
460
- ]);
518
+ switch (result.kind) {
519
+ case "exit":
520
+ process.exit(0);
521
+ return;
522
+ case "clear":
523
+ setMessages([]);
524
+ setInput("");
525
+ return;
526
+ case "navigate":
527
+ setInput("");
528
+ if (result.target === "game") {
529
+ onLaunchGame?.();
530
+ } else if (result.target === "stock") {
531
+ onLaunchStock?.();
532
+ }
533
+ return;
534
+ case "text":
535
+ setMessages((prev) => [
536
+ ...prev,
537
+ { role: "user", content: trimmed },
538
+ { role: "assistant", content: result.content }
539
+ ]);
540
+ setInput("");
541
+ return;
461
542
  }
462
- setInput("");
463
- return;
464
543
  }
465
544
  setMessages((prev) => [
466
545
  ...prev,
@@ -476,7 +555,7 @@ function ChatSession({ providerCount, toolCount, verbose }) {
476
555
  { role: "assistant", content: "dskcode AI \u2014 \u5F85\u5B9E\u73B0\uFF08\u7B2C07\u7AE0\uFF09\u3002\u5F53\u524D\u4E3A CLI \u6846\u67B6\u6F14\u793A\u6A21\u5F0F\u3002" }
477
556
  ]);
478
557
  setInput("");
479
- }, []);
558
+ }, [onLaunchGame, onLaunchStock]);
480
559
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [
481
560
  /* @__PURE__ */ jsxs3(Box3, { flexDirection: "row", marginBottom: 1, children: [
482
561
  /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginRight: 4, children: LOGO_LINES.map((line, i) => {
@@ -515,19 +594,26 @@ function ChatSession({ providerCount, toolCount, verbose }) {
515
594
  }
516
595
  ) })
517
596
  ] }),
518
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text4, { color: "#00ffff", dimColor: true, children: " " + "\u2500".repeat(36) }) })
597
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text4, { color: "#00ffff", dimColor: true, children: " " + "\u2500".repeat(36) }) }),
598
+ doubleCtrlC && /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text4, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
519
599
  ] });
520
600
  }
521
601
 
522
602
  // src/ui/GamePicker.tsx
523
- import { Box as Box4, Text as Text5, useInput } from "ink";
524
- import { useState as useState3, useCallback as useCallback2 } from "react";
603
+ import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
604
+ import { useState as useState4, useCallback as useCallback3 } from "react";
525
605
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
526
- function GamePicker({ games, onSelect, onExit }) {
527
- const [selectedIndex, setSelectedIndex] = useState3(0);
528
- useInput(
529
- useCallback2(
606
+ function GamePicker({ games, onSelect, onExit, onBackToChat }) {
607
+ const [selectedIndex, setSelectedIndex] = useState4(0);
608
+ const exitAction = onBackToChat ?? onExit ?? (() => process.exit(0));
609
+ const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(exitAction);
610
+ useInput2(
611
+ useCallback3(
530
612
  (input, key) => {
613
+ if (input === "c" && key.ctrl) {
614
+ handleCtrlC();
615
+ return;
616
+ }
531
617
  if (games.length === 0) return;
532
618
  if (key.upArrow || input === "k") {
533
619
  setSelectedIndex((prev) => prev > 0 ? prev - 1 : games.length - 1);
@@ -537,10 +623,11 @@ function GamePicker({ games, onSelect, onExit }) {
537
623
  const game = games[selectedIndex];
538
624
  if (game) onSelect(game);
539
625
  } else if (key.escape || input === "q") {
540
- onExit();
626
+ if (onBackToChat) onBackToChat();
627
+ else onExit?.();
541
628
  }
542
629
  },
543
- [games, selectedIndex, onSelect, onExit]
630
+ [games, selectedIndex, onSelect, onExit, onBackToChat, handleCtrlC]
544
631
  )
545
632
  );
546
633
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
@@ -553,7 +640,8 @@ function GamePicker({ games, onSelect, onExit }) {
553
640
  /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text5, { color: "#888888", children: game.description }) })
554
641
  ] }, game.id);
555
642
  }) }),
556
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text5, { dimColor: true, children: " \u2191/\u2193 \u9009\u62E9 Enter \u542F\u52A8 q \u8FD4\u56DE" }) })
643
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text5, { dimColor: true, children: " \u2191/\u2193 \u9009\u62E9 Enter \u542F\u52A8 q \u8FD4\u56DE" }) }),
644
+ doubleCtrlC && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text5, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
557
645
  ] });
558
646
  }
559
647
 
@@ -570,8 +658,8 @@ function listGames() {
570
658
  }
571
659
 
572
660
  // src/game/brick-breaker/index.tsx
573
- import { Box as Box5, Text as Text6, useInput as useInput2, render as render2 } from "ink";
574
- import { useState as useState4, useEffect as useEffect3, useRef, useCallback as useCallback3 } from "react";
661
+ import { Box as Box5, Text as Text6, useInput as useInput3, render as render2 } from "ink";
662
+ import { useState as useState5, useEffect as useEffect4, useRef as useRef2, useCallback as useCallback4 } from "react";
575
663
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
576
664
  var GAME_WIDTH = 40;
577
665
  var GAME_HEIGHT = 18;
@@ -703,13 +791,13 @@ function buildBoard(state) {
703
791
  return lines.map((l) => `\u2502${l}\u2502`).join("\n");
704
792
  }
705
793
  function BrickBreakerGame({ onExit: _onExit }) {
706
- const [initialLevel, setInitialLevel] = useState4(1);
707
- const [selectingLevel, setSelectingLevel] = useState4(true);
708
- const stateRef = useRef(createInitialState(initialLevel));
709
- const [tick, setTick] = useState4(0);
710
- const onExitRef = useRef(_onExit);
794
+ const [initialLevel, setInitialLevel] = useState5(1);
795
+ const [selectingLevel, setSelectingLevel] = useState5(true);
796
+ const stateRef = useRef2(createInitialState(initialLevel));
797
+ const [tick, setTick] = useState5(0);
798
+ const onExitRef = useRef2(_onExit);
711
799
  onExitRef.current = _onExit;
712
- useEffect3(() => {
800
+ useEffect4(() => {
713
801
  if (selectingLevel) return;
714
802
  const interval = setInterval(() => {
715
803
  update(stateRef.current);
@@ -717,18 +805,18 @@ function BrickBreakerGame({ onExit: _onExit }) {
717
805
  }, 80);
718
806
  return () => clearInterval(interval);
719
807
  }, [selectingLevel]);
720
- const restart = useCallback3((level) => {
808
+ const restart = useCallback4((level) => {
721
809
  const lv = level ?? stateRef.current.level;
722
810
  stateRef.current = createInitialState(lv);
723
811
  setInitialLevel(lv);
724
812
  setSelectingLevel(false);
725
813
  setTick(0);
726
814
  }, []);
727
- const startLevelSelect = useCallback3(() => {
815
+ const startLevelSelect = useCallback4(() => {
728
816
  setSelectingLevel(true);
729
817
  }, []);
730
- useInput2(
731
- useCallback3((input, key) => {
818
+ useInput3(
819
+ useCallback4((input, key) => {
732
820
  const s2 = stateRef.current;
733
821
  if (selectingLevel) {
734
822
  if (input >= "1" && input <= "9") {
@@ -842,8 +930,8 @@ var brick_breaker_default = {
842
930
  };
843
931
 
844
932
  // src/game/coder-check/index.tsx
845
- import { Box as Box6, Text as Text7, useInput as useInput3, render as render3 } from "ink";
846
- import { useState as useState5, useEffect as useEffect4, useRef as useRef2, useCallback as useCallback4 } from "react";
933
+ import { Box as Box6, Text as Text7, useInput as useInput4, render as render3 } from "ink";
934
+ import { useState as useState6, useEffect as useEffect5, useRef as useRef3, useCallback as useCallback5 } from "react";
847
935
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
848
936
  var GAME_W = 66;
849
937
  var GAME_H = 20;
@@ -1235,26 +1323,26 @@ function buildGameView(s, scoreLines, scoreColor, message) {
1235
1323
  return rows;
1236
1324
  }
1237
1325
  function CoderCheck({ onExit: _onExit }) {
1238
- const stateRef = useRef2(createInitialState2());
1239
- const [tick, setTick] = useState5(0);
1240
- const [colorOffset, setColorOffset] = useState5(0);
1241
- const onExitRef = useRef2(_onExit);
1326
+ const stateRef = useRef3(createInitialState2());
1327
+ const [tick, setTick] = useState6(0);
1328
+ const [colorOffset, setColorOffset] = useState6(0);
1329
+ const onExitRef = useRef3(_onExit);
1242
1330
  onExitRef.current = _onExit;
1243
- useEffect4(() => {
1331
+ useEffect5(() => {
1244
1332
  const timer = setInterval(() => {
1245
1333
  setColorOffset((prev) => (prev + 1) % CYBER_PALETTE2.length);
1246
1334
  }, 400);
1247
1335
  return () => clearInterval(timer);
1248
1336
  }, []);
1249
- useEffect4(() => {
1337
+ useEffect5(() => {
1250
1338
  const interval = setInterval(() => {
1251
1339
  update2(stateRef.current);
1252
1340
  setTick((t) => t + 1);
1253
1341
  }, 60);
1254
1342
  return () => clearInterval(interval);
1255
1343
  }, []);
1256
- useInput3(
1257
- useCallback4((input, key) => {
1344
+ useInput4(
1345
+ useCallback5((input, key) => {
1258
1346
  const s2 = stateRef.current;
1259
1347
  if (s2.gameOver) {
1260
1348
  if (input === "r") {
@@ -1381,8 +1469,296 @@ function initGames() {
1381
1469
  // src/cli/index.tsx
1382
1470
  import { render as render4 } from "ink";
1383
1471
  import chalk3 from "chalk";
1384
- import { jsx as jsx7 } from "react/jsx-runtime";
1385
- var SUBCOMMANDS = ["chat", "run", "setup", "init", "completion", "game"];
1472
+
1473
+ // src/stock/StockList.tsx
1474
+ import { Box as Box7, Text as Text8, useInput as useInput5 } from "ink";
1475
+ import { useState as useState7, useCallback as useCallback6, useEffect as useEffect6 } from "react";
1476
+ import asciichart from "asciichart";
1477
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1478
+ var MINUTE_API = "https://web.ifzq.gtimg.cn/appstock/app/minute/query?code={code}&r=0.1";
1479
+ function normalizeApiCode(code) {
1480
+ if (code.startsWith("sh") || code.startsWith("sz")) return code;
1481
+ if (/^60|^68|^51/.test(code)) return "sh" + code;
1482
+ if (/^00|^30|^39/.test(code)) return "sz" + code;
1483
+ return "sh" + code;
1484
+ }
1485
+ async function fetchStockMinute(code) {
1486
+ const url = MINUTE_API.replace("{code}", normalizeApiCode(code));
1487
+ try {
1488
+ const resp = await fetch(url);
1489
+ const json = await resp.json();
1490
+ if (json.code !== 0) return null;
1491
+ const stockKey = normalizeApiCode(code);
1492
+ const stockData = json.data?.[stockKey];
1493
+ if (!stockData) return null;
1494
+ const rawMinutes = stockData.data?.data ?? [];
1495
+ const prices = [];
1496
+ for (const line of rawMinutes) {
1497
+ const parts = line.split(" ");
1498
+ if (parts.length >= 2) {
1499
+ const p = parseFloat(parts[1]);
1500
+ if (!isNaN(p)) prices.push(p);
1501
+ }
1502
+ }
1503
+ const qtKey = stockKey;
1504
+ const qt = stockData.qt?.[qtKey];
1505
+ let quote = null;
1506
+ if (qt && qt.length >= 35) {
1507
+ quote = {
1508
+ code,
1509
+ name: qt[1] ?? "",
1510
+ price: parseFloat(qt[3] ?? "0"),
1511
+ changePercent: parseFloat(qt[32] ?? "0"),
1512
+ changeAmount: parseFloat(qt[31] ?? "0"),
1513
+ high: parseFloat(qt[33] ?? "0"),
1514
+ low: parseFloat(qt[34] ?? "0"),
1515
+ volume: parseInt(qt[6] ?? "0", 10)
1516
+ };
1517
+ }
1518
+ return {
1519
+ prices,
1520
+ quote,
1521
+ date: stockData.data?.date ?? ""
1522
+ };
1523
+ } catch {
1524
+ return null;
1525
+ }
1526
+ }
1527
+ var minuteCache = /* @__PURE__ */ new Map();
1528
+ function cacheMinute(code, prices) {
1529
+ minuteCache.set(code, prices);
1530
+ }
1531
+ var FALLBACK_STOCKS = [
1532
+ { code: "000001", name: "\u4E0A\u8BC1\u6307\u6570", price: 3150, changePercent: 0.35, changeAmount: 11.02, high: 3160, low: 3140, volume: 28543e4 },
1533
+ { code: "399006", name: "\u521B\u4E1A\u677F\u6307", price: 1820, changePercent: -0.52, changeAmount: -9.5, high: 1835, low: 1815, volume: 9865e4 },
1534
+ { code: "601688", name: "\u534E\u6CF0\u8BC1\u5238", price: 14.25, changePercent: 1.05, changeAmount: 0.15, high: 14.38, low: 14.1, volume: 452100 }
1535
+ ];
1536
+ async function fetchStocks(codes) {
1537
+ const results = await Promise.all(
1538
+ codes.map(async (code) => {
1539
+ const data = await fetchStockMinute(code);
1540
+ if (data?.quote) {
1541
+ if (data.prices.length > 0) cacheMinute(code, data.prices);
1542
+ return data.quote;
1543
+ }
1544
+ return null;
1545
+ })
1546
+ );
1547
+ const real = results.filter((r) => r !== null);
1548
+ if (real.length > 0) return real;
1549
+ return FALLBACK_STOCKS;
1550
+ }
1551
+ function formatPrice(p) {
1552
+ return p >= 100 ? p.toFixed(2) : p.toFixed(3);
1553
+ }
1554
+ function formatVolume(v) {
1555
+ if (v >= 1e4) return (v / 1e4).toFixed(1) + "\u4E07";
1556
+ return v.toLocaleString();
1557
+ }
1558
+ function latestPoints(data, maxPoints = 60) {
1559
+ if (data.length <= maxPoints) return data;
1560
+ return data.slice(data.length - maxPoints);
1561
+ }
1562
+ function StockList({ codes, onExit, onBackToChat }) {
1563
+ const [stocks, setStocks] = useState7([]);
1564
+ const [selectedIndex, setSelectedIndex] = useState7(0);
1565
+ const [loading, setLoading] = useState7(true);
1566
+ const [lastUpdate, setLastUpdate] = useState7("");
1567
+ const [detailView, setDetailView] = useState7(null);
1568
+ const [detailPrices, setDetailPrices] = useState7(null);
1569
+ const [detailLoading, setDetailLoading] = useState7(false);
1570
+ const [detailCountdown, setDetailCountdown] = useState7(10);
1571
+ const [countdown, setCountdown] = useState7(5);
1572
+ const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(onExit);
1573
+ const loadData = useCallback6(async () => {
1574
+ setLoading(true);
1575
+ try {
1576
+ const data = await fetchStocks(codes ?? []);
1577
+ setStocks(data);
1578
+ setLastUpdate((/* @__PURE__ */ new Date()).toLocaleTimeString());
1579
+ } catch {
1580
+ }
1581
+ setLoading(false);
1582
+ }, [codes]);
1583
+ useEffect6(() => {
1584
+ loadData();
1585
+ }, [loadData]);
1586
+ useEffect6(() => {
1587
+ const interval = setInterval(() => {
1588
+ setCountdown((prev) => {
1589
+ if (prev <= 1) {
1590
+ loadData();
1591
+ return 5;
1592
+ }
1593
+ return prev - 1;
1594
+ });
1595
+ }, 1e3);
1596
+ return () => clearInterval(interval);
1597
+ }, [loadData]);
1598
+ useEffect6(() => {
1599
+ if (!detailView) {
1600
+ setDetailPrices(null);
1601
+ setDetailLoading(false);
1602
+ return;
1603
+ }
1604
+ const loadDetail = () => {
1605
+ minuteCache.delete(detailView.code);
1606
+ fetchStockMinute(detailView.code).then((data) => {
1607
+ if (data && data.prices.length > 0) {
1608
+ cacheMinute(detailView.code, data.prices);
1609
+ setDetailPrices(data.prices);
1610
+ }
1611
+ });
1612
+ };
1613
+ loadDetail();
1614
+ setDetailCountdown(10);
1615
+ const timer = setInterval(loadDetail, 1e4);
1616
+ return () => clearInterval(timer);
1617
+ }, [detailView]);
1618
+ useEffect6(() => {
1619
+ if (!detailView) return;
1620
+ const timer = setInterval(() => {
1621
+ setDetailCountdown((prev) => prev > 0 ? prev - 1 : 10);
1622
+ }, 1e3);
1623
+ return () => clearInterval(timer);
1624
+ }, [detailView]);
1625
+ useInput5(
1626
+ useCallback6(
1627
+ (input, key) => {
1628
+ if (input === "c" && key.ctrl) {
1629
+ handleCtrlC();
1630
+ return;
1631
+ }
1632
+ if (detailView) {
1633
+ if (key.escape || input === "q" || input === " ") {
1634
+ setDetailView(null);
1635
+ }
1636
+ return;
1637
+ }
1638
+ if (stocks.length === 0) return;
1639
+ if (key.upArrow || input === "k") {
1640
+ setSelectedIndex((prev) => prev > 0 ? prev - 1 : stocks.length - 1);
1641
+ } else if (key.downArrow || input === "j") {
1642
+ setSelectedIndex((prev) => prev < stocks.length - 1 ? prev + 1 : 0);
1643
+ } else if (key.return) {
1644
+ const stock = stocks[selectedIndex];
1645
+ if (stock) setDetailView(stock);
1646
+ } else if (key.escape || input === "q") {
1647
+ if (onBackToChat) onBackToChat();
1648
+ else onExit();
1649
+ } else if (input === "r") {
1650
+ setCountdown(5);
1651
+ loadData();
1652
+ }
1653
+ },
1654
+ [stocks, selectedIndex, detailView, onExit, onBackToChat, loadData, handleCtrlC]
1655
+ )
1656
+ );
1657
+ if (detailView) {
1658
+ if (detailLoading) {
1659
+ return /* @__PURE__ */ jsx7(Box7, { paddingLeft: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " \u27F3 \u52A0\u8F7D\u5206\u65F6\u6570\u636E..." }) });
1660
+ }
1661
+ return renderDetail(detailView, () => setDetailView(null), detailPrices ?? void 0, detailCountdown);
1662
+ }
1663
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1664
+ /* @__PURE__ */ jsxs7(Box7, { marginBottom: 1, justifyContent: "space-between", children: [
1665
+ /* @__PURE__ */ jsx7(Text8, { bold: true, color: "#00ffff", children: " \u{1F4C8} \u81EA\u9009\u80A1\u76D1\u63A7" }),
1666
+ /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: loading ? " \u27F3 \u5237\u65B0\u4E2D..." : ` \u6BCF ${countdown}s \u81EA\u52A8\u5237\u65B0` })
1667
+ ] }),
1668
+ /* @__PURE__ */ jsxs7(Box7, { children: [
1669
+ /* @__PURE__ */ jsx7(Box7, { width: 3 }),
1670
+ /* @__PURE__ */ jsx7(Box7, { width: 9, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u4EE3\u7801" }) }),
1671
+ /* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u540D\u79F0" }) }),
1672
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6700\u65B0\u4EF7" }) }),
1673
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6DA8\u8DCC\u5E45" }) }),
1674
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6DA8\u8DCC\u989D" }) }),
1675
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6700\u9AD8" }) }),
1676
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6700\u4F4E" }) }),
1677
+ /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6210\u4EA4\u91CF" }) })
1678
+ ] }),
1679
+ /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " " + "\u2500".repeat(100) }) }),
1680
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: stocks.map((stock, index) => {
1681
+ const isSelected = index === selectedIndex;
1682
+ const isUp = stock.changePercent >= 0;
1683
+ const color = isUp ? "#ff1493" : "#00ff41";
1684
+ return /* @__PURE__ */ jsxs7(Box7, { children: [
1685
+ /* @__PURE__ */ jsx7(Box7, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx7(Text8, { bold: true, color: "#00ffff", children: "\u25B8 " }) : /* @__PURE__ */ jsx7(Text8, { children: " " }) }),
1686
+ /* @__PURE__ */ jsx7(Box7, { width: 9, children: /* @__PURE__ */ jsx7(Text8, { bold: true, color: isSelected ? "#00ffff" : "#ffffff", children: stock.code }) }),
1687
+ /* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { color: isSelected ? "#ffffff" : "#cccccc", children: stock.name }) }),
1688
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { bold: true, color, children: formatPrice(stock.price) }) }),
1689
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { color, children: [
1690
+ isUp ? "+" : "",
1691
+ stock.changePercent.toFixed(2),
1692
+ "%"
1693
+ ] }) }),
1694
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { color, children: [
1695
+ isUp ? "+" : "",
1696
+ stock.changeAmount.toFixed(3)
1697
+ ] }) }),
1698
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { color: "#cccccc", children: formatPrice(stock.high) }) }),
1699
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { color: "#cccccc", children: formatPrice(stock.low) }) }),
1700
+ /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { color: "#888888", children: formatVolume(stock.volume) }) })
1701
+ ] }, stock.code);
1702
+ }) }),
1703
+ /* @__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` }) }),
1704
+ /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: ` \u6700\u540E\u66F4\u65B0: ${lastUpdate}` }) }),
1705
+ doubleCtrlC && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text8, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
1706
+ ] });
1707
+ }
1708
+ function renderDetail(stock, _onBack, prices, countdown = 10) {
1709
+ const isUp = stock.changePercent >= 0;
1710
+ const colorCode = isUp ? "#ff1493" : "#00ff41";
1711
+ const arrow = isUp ? "\u25B2" : "\u25BC";
1712
+ let chartLines = [];
1713
+ if (prices && prices.length > 0) {
1714
+ const chartColor = isUp ? asciichart.red : asciichart.green;
1715
+ const latest = latestPoints(prices, 60);
1716
+ let raw = asciichart.plot(latest, {
1717
+ height: 10,
1718
+ colors: [chartColor]
1719
+ });
1720
+ raw = raw.replaceAll("\u256D", "\u250C").replaceAll("\u256E", "\u2510").replaceAll("\u2570", "\u2514").replaceAll("\u256F", "\u2518");
1721
+ chartLines = raw.split("\n");
1722
+ }
1723
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingLeft: 1, children: [
1724
+ /* @__PURE__ */ jsxs7(Box7, { marginBottom: 1, justifyContent: "space-between", children: [
1725
+ /* @__PURE__ */ jsxs7(Box7, { children: [
1726
+ /* @__PURE__ */ jsxs7(Text8, { bold: true, color: "#00ffff", children: [
1727
+ " \u{1F4CA} ",
1728
+ stock.name,
1729
+ " "
1730
+ ] }),
1731
+ /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: stock.code })
1732
+ ] }),
1733
+ /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: `\u6BCF ${countdown}s \u5237\u65B0` })
1734
+ ] }),
1735
+ /* @__PURE__ */ jsxs7(Box7, { children: [
1736
+ /* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { bold: true, color: "#888888", children: "\u5F53\u524D\u4EF7" }) }),
1737
+ /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text8, { bold: true, color: colorCode, children: [
1738
+ arrow,
1739
+ " ",
1740
+ formatPrice(stock.price)
1741
+ ] }) })
1742
+ ] }),
1743
+ /* @__PURE__ */ jsxs7(Box7, { children: [
1744
+ /* @__PURE__ */ jsx7(Box7, { width: 16, children: /* @__PURE__ */ jsx7(Text8, { color: "#888888", children: "\u6DA8\u8DCC\u5E45" }) }),
1745
+ /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text8, { color: colorCode, children: [
1746
+ isUp ? "+" : "",
1747
+ stock.changePercent.toFixed(2),
1748
+ "%",
1749
+ " ",
1750
+ isUp ? "+" : "",
1751
+ stock.changeAmount.toFixed(3)
1752
+ ] }) })
1753
+ ] }),
1754
+ 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)) }),
1755
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " Space/q \u8FD4\u56DE\u5217\u8868" }) })
1756
+ ] });
1757
+ }
1758
+
1759
+ // src/cli/index.tsx
1760
+ import { jsx as jsx8 } from "react/jsx-runtime";
1761
+ var SUBCOMMANDS = ["chat", "run", "setup", "init", "completion", "game", "stock"];
1386
1762
  function createCli() {
1387
1763
  const program2 = new Command();
1388
1764
  program2.exitOverride();
@@ -1407,18 +1783,64 @@ function createCli() {
1407
1783
  const result = await loadAndValidate();
1408
1784
  ctx = { ...ctx, config: result.config };
1409
1785
  }
1410
- const app = renderApp(
1411
- /* @__PURE__ */ jsx7(
1786
+ startChat(ctx);
1787
+ });
1788
+ function startChat(ctx) {
1789
+ const chatApp = renderApp(
1790
+ /* @__PURE__ */ jsx8(
1412
1791
  ChatSession,
1413
1792
  {
1414
1793
  providerCount: ctx?.config.providers.length ?? 1,
1415
1794
  toolCount: ctx?.config.tools.length ?? 0,
1416
- verbose: ctx?.verbose ?? false
1795
+ verbose: ctx?.verbose ?? false,
1796
+ onLaunchGame: () => {
1797
+ chatApp.unmount();
1798
+ setImmediate(() => {
1799
+ initGames();
1800
+ const games = listGames();
1801
+ const { unmount } = render4(
1802
+ /* @__PURE__ */ jsx8(
1803
+ GamePicker,
1804
+ {
1805
+ games,
1806
+ onSelect: async (game) => {
1807
+ unmount();
1808
+ await game.play();
1809
+ startChat(ctx);
1810
+ },
1811
+ onBackToChat: () => {
1812
+ unmount();
1813
+ setImmediate(() => startChat(ctx));
1814
+ }
1815
+ }
1816
+ ),
1817
+ { exitOnCtrlC: false }
1818
+ );
1819
+ });
1820
+ },
1821
+ onLaunchStock: () => {
1822
+ chatApp.unmount();
1823
+ setImmediate(() => {
1824
+ const defaultStockCodes = ctx?.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
1825
+ const stockApp = renderApp(
1826
+ /* @__PURE__ */ jsx8(
1827
+ StockList,
1828
+ {
1829
+ codes: defaultStockCodes,
1830
+ onBackToChat: () => {
1831
+ stockApp.unmount();
1832
+ setImmediate(() => startChat(ctx));
1833
+ },
1834
+ onExit: () => process.exit(0)
1835
+ }
1836
+ )
1837
+ );
1838
+ });
1839
+ }
1417
1840
  }
1418
1841
  )
1419
1842
  );
1420
- await app.waitUntilExit;
1421
- });
1843
+ }
1422
1844
  program2.command("run").description("\u6267\u884C\u4E00\u6B21\u6027\u4EFB\u52A1").argument("[prompt...]", "\u4EFB\u52A1\u63CF\u8FF0").option("--model <name>", "\u6307\u5B9A\u4F7F\u7528\u7684\u6A21\u578B").action(async function(_prompt) {
1423
1845
  console.log("dskcode run \u2014 \u5F85\u5B9E\u73B0\uFF08\u7B2C07\u7AE0\uFF09");
1424
1846
  });
@@ -1455,12 +1877,27 @@ _dskcode_completion() {
1455
1877
  "init:\u751F\u6210\u9879\u76EE\u8BB0\u5FC6\u6587\u4EF6"
1456
1878
  "completion:\u8F93\u51FA shell \u81EA\u52A8\u8865\u5168\u8BF4\u660E"
1457
1879
  "game:\u5185\u7F6E\u5C0F\u6E38\u620F"
1880
+ "stock:\u67E5\u770B\u81EA\u9009\u80A1\u5B9E\u65F6\u884C\u60C5"
1458
1881
  )
1459
1882
  _describe 'dskcode commands' commands
1460
1883
  }
1461
1884
  compdef _dskcode_completion dskcode`);
1462
1885
  }
1463
1886
  });
1887
+ 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) {
1888
+ const ctx = this.dskcodeCtx;
1889
+ const codeList = codes && codes.length > 0 ? codes : ctx?.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
1890
+ const app = renderApp(
1891
+ /* @__PURE__ */ jsx8(
1892
+ StockList,
1893
+ {
1894
+ codes: codeList,
1895
+ onExit: () => process.exit(0)
1896
+ }
1897
+ )
1898
+ );
1899
+ await app.waitUntilExit;
1900
+ });
1464
1901
  initGames();
1465
1902
  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) {
1466
1903
  if (name) {
@@ -1480,7 +1917,7 @@ compdef _dskcode_completion dskcode`);
1480
1917
  }
1481
1918
  const selectedGame = await new Promise((resolve) => {
1482
1919
  const { unmount } = render4(
1483
- /* @__PURE__ */ jsx7(
1920
+ /* @__PURE__ */ jsx8(
1484
1921
  GamePicker,
1485
1922
  {
1486
1923
  games,
@@ -1493,7 +1930,8 @@ compdef _dskcode_completion dskcode`);
1493
1930
  resolve(null);
1494
1931
  }
1495
1932
  }
1496
- )
1933
+ ),
1934
+ { exitOnCtrlC: false }
1497
1935
  );
1498
1936
  });
1499
1937
  if (selectedGame) {
@@ -1520,8 +1958,18 @@ var ExitCode = {
1520
1958
  };
1521
1959
 
1522
1960
  // src/index.ts
1961
+ var sigintCount = 0;
1962
+ var sigintTimer = null;
1523
1963
  process.on("SIGINT", () => {
1524
- process.exit(ExitCode.SIGINT);
1964
+ sigintCount++;
1965
+ if (sigintCount >= 2) {
1966
+ process.exit(ExitCode.SIGINT);
1967
+ }
1968
+ process.stdout.write("\n \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode\n");
1969
+ if (sigintTimer) clearTimeout(sigintTimer);
1970
+ sigintTimer = setTimeout(() => {
1971
+ sigintCount = 0;
1972
+ }, 1500);
1525
1973
  });
1526
1974
  var program = createCli();
1527
1975
  try {