dskcode 0.1.6 → 0.1.8

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
@@ -88,28 +88,28 @@ var ENV_MAP = {
88
88
  [`${ENV_PREFIX}MAX_TOOL_ROUNDS`]: "maxToolRounds"
89
89
  };
90
90
  function applyEnvVars(config) {
91
+ const result = { ...config, providers: [...config.providers] };
91
92
  for (const [envKey, configKey] of Object.entries(ENV_MAP)) {
92
93
  const raw = process.env[envKey];
93
94
  if (raw === void 0) continue;
94
- const cfg = config;
95
95
  switch (configKey) {
96
96
  case "verbose":
97
97
  case "defaultProvider": {
98
- cfg[configKey] = raw;
98
+ result[configKey] = raw;
99
99
  break;
100
100
  }
101
101
  case "maxTokens":
102
102
  case "maxToolRounds": {
103
103
  const n = Number(raw);
104
104
  if (Number.isFinite(n) && n > 0) {
105
- cfg[configKey] = n;
105
+ result[configKey] = n;
106
106
  }
107
107
  break;
108
108
  }
109
109
  case "temperature": {
110
110
  const n = Number(raw);
111
111
  if (Number.isFinite(n) && n >= 0 && n <= 2) {
112
- cfg[configKey] = n;
112
+ result[configKey] = n;
113
113
  }
114
114
  break;
115
115
  }
@@ -117,12 +117,14 @@ function applyEnvVars(config) {
117
117
  }
118
118
  const apiKey = process.env.DEEPSEEK_API_KEY;
119
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({
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({
126
128
  name: "deepseek",
127
129
  baseUrl: "https://api.deepseek.com",
128
130
  model: "deepseek-v4-flash",
@@ -130,27 +132,31 @@ function applyEnvVars(config) {
130
132
  });
131
133
  }
132
134
  }
133
- return config;
135
+ return result;
134
136
  }
135
137
  function applyCliOverrides(config, flags) {
138
+ const result = { ...config, providers: [...config.providers] };
136
139
  if (flags.verbose !== void 0) {
137
- config.verbose = flags.verbose;
140
+ result.verbose = flags.verbose;
138
141
  }
139
142
  if (flags.model !== void 0) {
140
- const provider = config.providers.find(
141
- (p) => p.name === config.defaultProvider
143
+ const providerIdx = result.providers.findIndex(
144
+ (p) => p.name === result.defaultProvider
142
145
  );
143
- if (provider) {
144
- provider.model = flags.model;
146
+ if (providerIdx !== -1) {
147
+ result.providers[providerIdx] = {
148
+ ...result.providers[providerIdx],
149
+ model: flags.model
150
+ };
145
151
  }
146
152
  }
147
153
  if (flags.maxTokens !== void 0 && flags.maxTokens > 0) {
148
- config.maxTokens = flags.maxTokens;
154
+ result.maxTokens = flags.maxTokens;
149
155
  }
150
156
  if (flags.temperature !== void 0 && flags.temperature >= 0 && flags.temperature <= 2) {
151
- config.temperature = flags.temperature;
157
+ result.temperature = flags.temperature;
152
158
  }
153
- return config;
159
+ return result;
154
160
  }
155
161
  function validateConfig(config) {
156
162
  const errors = [];
@@ -192,6 +198,12 @@ function validateConfig(config) {
192
198
  message: "temperature \u5FC5\u987B\u5728 0.0 ~ 2.0 \u4E4B\u95F4\u3002"
193
199
  });
194
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
+ }
195
207
  if (config.maxToolRounds !== void 0 && config.maxToolRounds < 1) {
196
208
  errors.push({
197
209
  field: "maxToolRounds",
@@ -247,6 +259,22 @@ async function saveApiKey(apiKey) {
247
259
  await writeFile(configFile, JSON.stringify(configData, null, 2), "utf-8");
248
260
  return configFile;
249
261
  }
262
+ async function saveStockConfig(symbols) {
263
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
264
+ const configDir = join(home, ".dskcode");
265
+ const configFile = join(configDir, "settings.json");
266
+ await mkdir(configDir, { recursive: true });
267
+ let configData;
268
+ try {
269
+ const raw = await readFile(configFile, "utf-8");
270
+ configData = JSON.parse(raw);
271
+ } catch {
272
+ configData = structuredClone(defaultConfig);
273
+ }
274
+ configData.stock = { symbols };
275
+ await writeFile(configFile, JSON.stringify(configData, null, 2), "utf-8");
276
+ return configFile;
277
+ }
250
278
 
251
279
  // src/cli/middleware.ts
252
280
  async function loadConfigMiddleware() {
@@ -339,36 +367,32 @@ function hasApiKey(providers) {
339
367
  if (process.env.DEEPSEEK_API_KEY) return true;
340
368
  return false;
341
369
  }
370
+ var MIN_API_KEY_LENGTH = 10;
342
371
  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
- );
372
+ console.log(chalk2.yellow("\n \u26A0 \u672A\u68C0\u6D4B\u5230 API Key \u914D\u7F6E"));
373
+ console.log(chalk2.dim(" \u4F60\u53EF\u4EE5\u901A\u8FC7\u4EE5\u4E0B\u4EFB\u4E00\u65B9\u5F0F\u914D\u7F6E\uFF1A"));
374
+ console.log(chalk2.dim(" \xB7 \u73AF\u5883\u53D8\u91CF: export DEEPSEEK_API_KEY=sk-xxx"));
375
+ console.log(chalk2.dim(" \xB7 \u914D\u7F6E\u6587\u4EF6: ~/.dskcode/settings.json"));
376
+ console.log(chalk2.dim(" \xB7 \u4E0B\u9762\u76F4\u63A5\u8F93\u5165\uFF0C\u81EA\u52A8\u4FDD\u5B58\u5230\u5168\u5C40\u914D\u7F6E\n"));
358
377
  const rl = createInterface({
359
378
  input: process.stdin,
360
379
  output: process.stdout
361
380
  });
362
381
  return new Promise((resolve) => {
382
+ let resolved = false;
363
383
  const cleanup = () => {
384
+ if (resolved) return;
385
+ resolved = true;
386
+ process.stdin.removeListener("keypress", onKeypress);
364
387
  rl.close();
365
388
  };
366
- process.stdin.on("keypress", (_, key) => {
389
+ const onKeypress = (_, key) => {
367
390
  if (key.ctrl && key.name === "c") {
368
391
  cleanup();
369
392
  resolve(null);
370
393
  }
371
- });
394
+ };
395
+ process.stdin.on("keypress", onKeypress);
372
396
  rl.question(
373
397
  ` ${chalk2.cyan("\u{1F511}")} ${chalk2.bold("\u8BF7\u8F93\u5165\u4F60\u7684 DeepSeek API Key:")} `,
374
398
  (answer) => {
@@ -379,7 +403,7 @@ async function promptForApiKey() {
379
403
  resolve(null);
380
404
  return;
381
405
  }
382
- if (trimmed.length < 10) {
406
+ if (trimmed.length < MIN_API_KEY_LENGTH) {
383
407
  console.log(chalk2.red(" \u2716 API Key \u683C\u5F0F\u4E0D\u6B63\u786E\uFF0C\u957F\u5EA6\u81F3\u5C11 10 \u4F4D"));
384
408
  resolve(null);
385
409
  return;
@@ -390,10 +414,14 @@ async function promptForApiKey() {
390
414
  });
391
415
  }
392
416
 
417
+ // src/cli/index.tsx
418
+ import { readFile as readFile2 } from "fs/promises";
419
+ import { join as join2 } from "path";
420
+
393
421
  // src/ui/RenderScope.tsx
394
422
  import { render } from "ink";
395
423
  function renderApp(node) {
396
- const { waitUntilExit, clear, unmount } = render(node);
424
+ const { waitUntilExit, clear, unmount } = render(node, { exitOnCtrlC: false });
397
425
  return { waitUntilExit: waitUntilExit(), clear, unmount };
398
426
  }
399
427
 
@@ -410,12 +438,6 @@ import { jsxs as jsxs2 } from "react/jsx-runtime";
410
438
  import { Box as Box2, Text as Text3 } from "ink";
411
439
  import { useEffect, useState } from "react";
412
440
  import { jsx as jsx2 } from "react/jsx-runtime";
413
-
414
- // src/ui/ChatSession.tsx
415
- import { Box as Box3, Text as Text4 } from "ink";
416
- import TextInput from "ink-text-input";
417
- import { useEffect as useEffect2, useState as useState2, useCallback } from "react";
418
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
419
441
  var CYBER_PALETTE = ["#00ffff", "#ff00ff", "#00ff41", "#ff1493", "#8b00ff"];
420
442
  var LOGO_LINES = [
421
443
  " \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557",
@@ -425,57 +447,119 @@ var LOGO_LINES = [
425
447
  " \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2557",
426
448
  " \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D"
427
449
  ];
428
- var COMMANDS = {
429
- "/exit": { desc: "\u9000\u51FA\u5BF9\u8BDD", handler: () => "" },
430
- "/quit": { desc: "\u9000\u51FA\u5BF9\u8BDD", handler: () => "" },
431
- "/help": {
432
- desc: "\u663E\u793A\u5E2E\u52A9\u4FE1\u606F",
433
- handler: () => [
434
- "\u53EF\u7528\u547D\u4EE4\uFF1A",
435
- " /exit, /quit \u9000\u51FA\u5BF9\u8BDD",
436
- " /help \u663E\u793A\u6B64\u5E2E\u52A9",
437
- " /clear \u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2",
438
- " /version \u663E\u793A\u7248\u672C\u4FE1\u606F"
439
- ].join("\n")
440
- },
441
- "/clear": { desc: "\u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2", handler: () => "" },
442
- "/version": { desc: "\u663E\u793A\u7248\u672C\u4FE1\u606F", handler: () => "dskcode v0.0.0" }
443
- };
444
- function ChatSession({ providerCount, toolCount, verbose }) {
445
- const [offset, setOffset] = useState2(0);
446
- const [messages, setMessages] = useState2([]);
447
- const [input, setInput] = useState2("");
450
+
451
+ // src/ui/ChatSession.tsx
452
+ import { Box as Box3, Text as Text4, useInput } from "ink";
453
+ import TextInput from "ink-text-input";
454
+ import { useEffect as useEffect3, useState as useState3, useCallback as useCallback2 } from "react";
455
+
456
+ // src/ui/useDoubleCtrlC.ts
457
+ import { useState as useState2, useCallback, useEffect as useEffect2, useRef } from "react";
458
+ var CTRL_C_TIMEOUT_MS = 1500;
459
+ function useDoubleCtrlC(onExit) {
460
+ const [doubleCtrlC, setDoubleCtrlC] = useState2(false);
461
+ const timerRef = useRef(null);
462
+ const onExitRef = useRef(onExit);
463
+ onExitRef.current = onExit;
448
464
  useEffect2(() => {
465
+ return () => {
466
+ if (timerRef.current) clearTimeout(timerRef.current);
467
+ };
468
+ }, []);
469
+ const handleCtrlC = useCallback(() => {
470
+ if (doubleCtrlC) {
471
+ onExitRef.current();
472
+ return;
473
+ }
474
+ setDoubleCtrlC(true);
475
+ if (timerRef.current) clearTimeout(timerRef.current);
476
+ timerRef.current = setTimeout(() => {
477
+ setDoubleCtrlC(false);
478
+ timerRef.current = null;
479
+ }, CTRL_C_TIMEOUT_MS);
480
+ }, [doubleCtrlC]);
481
+ return { doubleCtrlC, handleCtrlC };
482
+ }
483
+
484
+ // src/ui/ChatSession.tsx
485
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
486
+ var commandRegistry = /* @__PURE__ */ new Map();
487
+ function registerCommand(name, cmd) {
488
+ commandRegistry.set(name, cmd);
489
+ }
490
+ function getRegisteredCommands() {
491
+ return commandRegistry;
492
+ }
493
+ registerCommand("/exit", { desc: "\u9000\u51FA\u5BF9\u8BDD", handler: () => ({ kind: "exit" }) });
494
+ registerCommand("/quit", { desc: "\u9000\u51FA\u5BF9\u8BDD", handler: () => ({ kind: "exit" }) });
495
+ registerCommand("/help", {
496
+ desc: "\u663E\u793A\u5E2E\u52A9\u4FE1\u606F",
497
+ handler: () => {
498
+ const commands = getRegisteredCommands();
499
+ const lines = ["\u53EF\u7528\u547D\u4EE4\uFF1A"];
500
+ for (const [name, cmd] of commands) {
501
+ lines.push(` ${name.padEnd(16)}${cmd.desc}`);
502
+ }
503
+ return { kind: "text", content: lines.join("\n") };
504
+ }
505
+ });
506
+ registerCommand("/clear", { desc: "\u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2", handler: () => ({ kind: "clear" }) });
507
+ registerCommand("/version", { desc: "\u663E\u793A\u7248\u672C\u4FE1\u606F", handler: () => ({ kind: "text", content: "dskcode v0.0.0" }) });
508
+ registerCommand("/game", { desc: "\u542F\u52A8\u6E38\u620F", handler: () => ({ kind: "navigate", target: "game" }) });
509
+ registerCommand("/stock", { desc: "\u67E5\u770B\u80A1\u7968\u884C\u60C5", handler: () => ({ kind: "navigate", target: "stock" }) });
510
+ function ChatSession({ providerCount, toolCount, verbose, onLaunchGame, onLaunchStock }) {
511
+ const [offset, setOffset] = useState3(0);
512
+ const [messages, setMessages] = useState3([]);
513
+ const [input, setInput] = useState3("");
514
+ const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(() => process.exit(0));
515
+ useInput(
516
+ useCallback2(
517
+ (input2, key) => {
518
+ if (input2 === "c" && key.ctrl) {
519
+ handleCtrlC();
520
+ }
521
+ },
522
+ [handleCtrlC]
523
+ )
524
+ );
525
+ useEffect3(() => {
449
526
  const timer = setInterval(() => {
450
527
  setOffset((prev) => (prev + 1) % CYBER_PALETTE.length);
451
528
  }, 500);
452
529
  return () => clearInterval(timer);
453
530
  }, []);
454
- const handleSubmit = useCallback((value) => {
531
+ const handleSubmit = useCallback2((value) => {
455
532
  const trimmed = value.trim();
456
533
  if (!trimmed) return;
457
534
  if (trimmed.startsWith("/")) {
458
- const cmd = COMMANDS[trimmed.toLowerCase()];
535
+ const cmd = commandRegistry.get(trimmed.toLowerCase());
459
536
  if (cmd) {
460
- if (trimmed.toLowerCase() === "/exit" || trimmed.toLowerCase() === "/quit") {
461
- process.exit(0);
462
- return;
463
- }
464
- if (trimmed.toLowerCase() === "/clear") {
465
- setMessages([]);
466
- setInput("");
467
- return;
468
- }
469
537
  const result = cmd.handler();
470
- if (result) {
471
- setMessages((prev) => [
472
- ...prev,
473
- { role: "user", content: trimmed },
474
- { role: "assistant", content: result }
475
- ]);
538
+ switch (result.kind) {
539
+ case "exit":
540
+ process.exit(0);
541
+ return;
542
+ case "clear":
543
+ setMessages([]);
544
+ setInput("");
545
+ return;
546
+ case "navigate":
547
+ setInput("");
548
+ if (result.target === "game") {
549
+ onLaunchGame?.();
550
+ } else if (result.target === "stock") {
551
+ onLaunchStock?.();
552
+ }
553
+ return;
554
+ case "text":
555
+ setMessages((prev) => [
556
+ ...prev,
557
+ { role: "user", content: trimmed },
558
+ { role: "assistant", content: result.content }
559
+ ]);
560
+ setInput("");
561
+ return;
476
562
  }
477
- setInput("");
478
- return;
479
563
  }
480
564
  setMessages((prev) => [
481
565
  ...prev,
@@ -491,7 +575,7 @@ function ChatSession({ providerCount, toolCount, verbose }) {
491
575
  { 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" }
492
576
  ]);
493
577
  setInput("");
494
- }, []);
578
+ }, [onLaunchGame, onLaunchStock]);
495
579
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [
496
580
  /* @__PURE__ */ jsxs3(Box3, { flexDirection: "row", marginBottom: 1, children: [
497
581
  /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginRight: 4, children: LOGO_LINES.map((line, i) => {
@@ -530,19 +614,26 @@ function ChatSession({ providerCount, toolCount, verbose }) {
530
614
  }
531
615
  ) })
532
616
  ] }),
533
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text4, { color: "#00ffff", dimColor: true, children: " " + "\u2500".repeat(36) }) })
617
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text4, { color: "#00ffff", dimColor: true, children: " " + "\u2500".repeat(36) }) }),
618
+ 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" }) })
534
619
  ] });
535
620
  }
536
621
 
537
622
  // src/ui/GamePicker.tsx
538
- import { Box as Box4, Text as Text5, useInput } from "ink";
539
- import { useState as useState3, useCallback as useCallback2 } from "react";
623
+ import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
624
+ import { useState as useState4, useCallback as useCallback3 } from "react";
540
625
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
541
- function GamePicker({ games, onSelect, onExit }) {
542
- const [selectedIndex, setSelectedIndex] = useState3(0);
543
- useInput(
544
- useCallback2(
626
+ function GamePicker({ games, onSelect, onExit, onBackToChat }) {
627
+ const [selectedIndex, setSelectedIndex] = useState4(0);
628
+ const exitAction = onBackToChat ?? onExit ?? (() => process.exit(0));
629
+ const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(exitAction);
630
+ useInput2(
631
+ useCallback3(
545
632
  (input, key) => {
633
+ if (input === "c" && key.ctrl) {
634
+ handleCtrlC();
635
+ return;
636
+ }
546
637
  if (games.length === 0) return;
547
638
  if (key.upArrow || input === "k") {
548
639
  setSelectedIndex((prev) => prev > 0 ? prev - 1 : games.length - 1);
@@ -552,10 +643,11 @@ function GamePicker({ games, onSelect, onExit }) {
552
643
  const game = games[selectedIndex];
553
644
  if (game) onSelect(game);
554
645
  } else if (key.escape || input === "q") {
555
- onExit();
646
+ if (onBackToChat) onBackToChat();
647
+ else onExit?.();
556
648
  }
557
649
  },
558
- [games, selectedIndex, onSelect, onExit]
650
+ [games, selectedIndex, onSelect, onExit, onBackToChat, handleCtrlC]
559
651
  )
560
652
  );
561
653
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
@@ -568,7 +660,8 @@ function GamePicker({ games, onSelect, onExit }) {
568
660
  /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text5, { color: "#888888", children: game.description }) })
569
661
  ] }, game.id);
570
662
  }) }),
571
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text5, { dimColor: true, children: " \u2191/\u2193 \u9009\u62E9 Enter \u542F\u52A8 q \u8FD4\u56DE" }) })
663
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text5, { dimColor: true, children: " \u2191/\u2193 \u9009\u62E9 Enter \u542F\u52A8 q \u8FD4\u56DE" }) }),
664
+ 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" }) })
572
665
  ] });
573
666
  }
574
667
 
@@ -585,8 +678,8 @@ function listGames() {
585
678
  }
586
679
 
587
680
  // src/game/brick-breaker/index.tsx
588
- import { Box as Box5, Text as Text6, useInput as useInput2, render as render2 } from "ink";
589
- import { useState as useState4, useEffect as useEffect3, useRef, useCallback as useCallback3 } from "react";
681
+ import { Box as Box5, Text as Text6, useInput as useInput3, render as render2 } from "ink";
682
+ import { useState as useState5, useEffect as useEffect4, useRef as useRef2, useCallback as useCallback4 } from "react";
590
683
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
591
684
  var GAME_WIDTH = 40;
592
685
  var GAME_HEIGHT = 18;
@@ -718,13 +811,13 @@ function buildBoard(state) {
718
811
  return lines.map((l) => `\u2502${l}\u2502`).join("\n");
719
812
  }
720
813
  function BrickBreakerGame({ onExit: _onExit }) {
721
- const [initialLevel, setInitialLevel] = useState4(1);
722
- const [selectingLevel, setSelectingLevel] = useState4(true);
723
- const stateRef = useRef(createInitialState(initialLevel));
724
- const [tick, setTick] = useState4(0);
725
- const onExitRef = useRef(_onExit);
814
+ const [initialLevel, setInitialLevel] = useState5(1);
815
+ const [selectingLevel, setSelectingLevel] = useState5(true);
816
+ const stateRef = useRef2(createInitialState(initialLevel));
817
+ const [tick, setTick] = useState5(0);
818
+ const onExitRef = useRef2(_onExit);
726
819
  onExitRef.current = _onExit;
727
- useEffect3(() => {
820
+ useEffect4(() => {
728
821
  if (selectingLevel) return;
729
822
  const interval = setInterval(() => {
730
823
  update(stateRef.current);
@@ -732,18 +825,18 @@ function BrickBreakerGame({ onExit: _onExit }) {
732
825
  }, 80);
733
826
  return () => clearInterval(interval);
734
827
  }, [selectingLevel]);
735
- const restart = useCallback3((level) => {
828
+ const restart = useCallback4((level) => {
736
829
  const lv = level ?? stateRef.current.level;
737
830
  stateRef.current = createInitialState(lv);
738
831
  setInitialLevel(lv);
739
832
  setSelectingLevel(false);
740
833
  setTick(0);
741
834
  }, []);
742
- const startLevelSelect = useCallback3(() => {
835
+ const startLevelSelect = useCallback4(() => {
743
836
  setSelectingLevel(true);
744
837
  }, []);
745
- useInput2(
746
- useCallback3((input, key) => {
838
+ useInput3(
839
+ useCallback4((input, key) => {
747
840
  const s2 = stateRef.current;
748
841
  if (selectingLevel) {
749
842
  if (input >= "1" && input <= "9") {
@@ -857,8 +950,8 @@ var brick_breaker_default = {
857
950
  };
858
951
 
859
952
  // src/game/coder-check/index.tsx
860
- import { Box as Box6, Text as Text7, useInput as useInput3, render as render3 } from "ink";
861
- import { useState as useState5, useEffect as useEffect4, useRef as useRef2, useCallback as useCallback4 } from "react";
953
+ import { Box as Box6, Text as Text7, useInput as useInput4, render as render3 } from "ink";
954
+ import { useState as useState6, useEffect as useEffect5, useRef as useRef3, useCallback as useCallback5 } from "react";
862
955
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
863
956
  var GAME_W = 66;
864
957
  var GAME_H = 20;
@@ -1250,26 +1343,26 @@ function buildGameView(s, scoreLines, scoreColor, message) {
1250
1343
  return rows;
1251
1344
  }
1252
1345
  function CoderCheck({ onExit: _onExit }) {
1253
- const stateRef = useRef2(createInitialState2());
1254
- const [tick, setTick] = useState5(0);
1255
- const [colorOffset, setColorOffset] = useState5(0);
1256
- const onExitRef = useRef2(_onExit);
1346
+ const stateRef = useRef3(createInitialState2());
1347
+ const [tick, setTick] = useState6(0);
1348
+ const [colorOffset, setColorOffset] = useState6(0);
1349
+ const onExitRef = useRef3(_onExit);
1257
1350
  onExitRef.current = _onExit;
1258
- useEffect4(() => {
1351
+ useEffect5(() => {
1259
1352
  const timer = setInterval(() => {
1260
1353
  setColorOffset((prev) => (prev + 1) % CYBER_PALETTE2.length);
1261
1354
  }, 400);
1262
1355
  return () => clearInterval(timer);
1263
1356
  }, []);
1264
- useEffect4(() => {
1357
+ useEffect5(() => {
1265
1358
  const interval = setInterval(() => {
1266
1359
  update2(stateRef.current);
1267
1360
  setTick((t) => t + 1);
1268
1361
  }, 60);
1269
1362
  return () => clearInterval(interval);
1270
1363
  }, []);
1271
- useInput3(
1272
- useCallback4((input, key) => {
1364
+ useInput4(
1365
+ useCallback5((input, key) => {
1273
1366
  const s2 = stateRef.current;
1274
1367
  if (s2.gameOver) {
1275
1368
  if (input === "r") {
@@ -1398,25 +1491,24 @@ import { render as render4 } from "ink";
1398
1491
  import chalk3 from "chalk";
1399
1492
 
1400
1493
  // 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";
1494
+ import { Box as Box7, Text as Text8, useInput as useInput5 } from "ink";
1495
+ import { useState as useState7, useCallback as useCallback6, useEffect as useEffect6 } from "react";
1403
1496
  import asciichart from "asciichart";
1404
1497
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1405
1498
  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;
1499
+ function normalizeApiCode(code) {
1500
+ if (code.startsWith("sh") || code.startsWith("sz")) return code;
1501
+ if (/^60|^68|^51/.test(code)) return "sh" + code;
1502
+ if (/^00|^30|^39/.test(code)) return "sz" + code;
1411
1503
  return "sh" + code;
1412
1504
  }
1413
1505
  async function fetchStockMinute(code) {
1414
- const url = MINUTE_API.replace("{code}", toApiCode(code));
1506
+ const url = MINUTE_API.replace("{code}", normalizeApiCode(code));
1415
1507
  try {
1416
1508
  const resp = await fetch(url);
1417
1509
  const json = await resp.json();
1418
1510
  if (json.code !== 0) return null;
1419
- const stockKey = toApiCode(code);
1511
+ const stockKey = normalizeApiCode(code);
1420
1512
  const stockData = json.data?.[stockKey];
1421
1513
  if (!stockData) return null;
1422
1514
  const rawMinutes = stockData.data?.data ?? [];
@@ -1440,7 +1532,9 @@ async function fetchStockMinute(code) {
1440
1532
  changeAmount: parseFloat(qt[31] ?? "0"),
1441
1533
  high: parseFloat(qt[33] ?? "0"),
1442
1534
  low: parseFloat(qt[34] ?? "0"),
1443
- volume: parseInt(qt[6] ?? "0", 10)
1535
+ volume: parseInt(qt[6] ?? "0", 10),
1536
+ // 成交额(单位:万元)→ 转为元
1537
+ amount: parseFloat(qt[37] ?? "0") * 1e4
1444
1538
  };
1445
1539
  }
1446
1540
  return {
@@ -1457,9 +1551,11 @@ function cacheMinute(code, prices) {
1457
1551
  minuteCache.set(code, prices);
1458
1552
  }
1459
1553
  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 }
1554
+ // 上证指数成交额约 1.56 万亿
1555
+ { code: "000001", name: "\u4E0A\u8BC1\u6307\u6570", price: 3150, changePercent: 0.35, changeAmount: 11.02, high: 3160, low: 3140, volume: 28543e4, amount: 156e10 },
1556
+ // 紫金矿业成交额约 108.5 亿
1557
+ { code: "601899", name: "\u7D2B\u91D1\u77FF\u4E1A", price: 18.2, changePercent: 1.85, changeAmount: 0.33, high: 18.45, low: 17.9, volume: 59615384, amount: 1085e7 },
1558
+ { code: "399006", name: "\u521B\u4E1A\u677F\u6307", price: 1820, changePercent: -0.52, changeAmount: -9.5, high: 1835, low: 1815, volume: 9865e4, amount: 1824e7 }
1463
1559
  ];
1464
1560
  async function fetchStocks(codes) {
1465
1561
  const results = await Promise.all(
@@ -1479,25 +1575,37 @@ async function fetchStocks(codes) {
1479
1575
  function formatPrice(p) {
1480
1576
  return p >= 100 ? p.toFixed(2) : p.toFixed(3);
1481
1577
  }
1482
- function formatVolume(v) {
1483
- if (v >= 1e4) return (v / 1e4).toFixed(1) + "\u4E07";
1484
- return v.toLocaleString();
1578
+ function formatAmount(yuan) {
1579
+ if (yuan >= 1e12) return (yuan / 1e12).toFixed(2) + "\u4E07\u4EBF";
1580
+ if (yuan >= 1e8) return (yuan / 1e8).toFixed(2) + "\u4EBF";
1581
+ if (yuan >= 1e4) return (yuan / 1e4).toFixed(1) + "\u4E07";
1582
+ return yuan.toLocaleString();
1485
1583
  }
1486
1584
  function latestPoints(data, maxPoints = 60) {
1487
1585
  if (data.length <= maxPoints) return data;
1488
1586
  return data.slice(data.length - maxPoints);
1489
1587
  }
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 () => {
1588
+ function StockList({ codes, onExit, onBackToChat }) {
1589
+ const [stocks, setStocks] = useState7([]);
1590
+ const [selectedIndex, setSelectedIndex] = useState7(0);
1591
+ const [loading, setLoading] = useState7(true);
1592
+ const [lastUpdate, setLastUpdate] = useState7("");
1593
+ const [detailView, setDetailView] = useState7(null);
1594
+ const [detailPrices, setDetailPrices] = useState7(null);
1595
+ const [detailLoading, setDetailLoading] = useState7(false);
1596
+ const [detailCountdown, setDetailCountdown] = useState7(10);
1597
+ const [countdown, setCountdown] = useState7(5);
1598
+ const [currentTime, setCurrentTime] = useState7(
1599
+ () => (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false })
1600
+ );
1601
+ const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(onExit);
1602
+ useEffect6(() => {
1603
+ const timer = setInterval(() => {
1604
+ setCurrentTime((/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false }));
1605
+ }, 1e3);
1606
+ return () => clearInterval(timer);
1607
+ }, []);
1608
+ const loadData = useCallback6(async () => {
1501
1609
  setLoading(true);
1502
1610
  try {
1503
1611
  const data = await fetchStocks(codes ?? []);
@@ -1507,10 +1615,10 @@ function StockList({ codes, onExit }) {
1507
1615
  }
1508
1616
  setLoading(false);
1509
1617
  }, [codes]);
1510
- useEffect5(() => {
1618
+ useEffect6(() => {
1511
1619
  loadData();
1512
1620
  }, [loadData]);
1513
- useEffect5(() => {
1621
+ useEffect6(() => {
1514
1622
  const interval = setInterval(() => {
1515
1623
  setCountdown((prev) => {
1516
1624
  if (prev <= 1) {
@@ -1522,7 +1630,7 @@ function StockList({ codes, onExit }) {
1522
1630
  }, 1e3);
1523
1631
  return () => clearInterval(interval);
1524
1632
  }, [loadData]);
1525
- useEffect5(() => {
1633
+ useEffect6(() => {
1526
1634
  if (!detailView) {
1527
1635
  setDetailPrices(null);
1528
1636
  setDetailLoading(false);
@@ -1542,16 +1650,20 @@ function StockList({ codes, onExit }) {
1542
1650
  const timer = setInterval(loadDetail, 1e4);
1543
1651
  return () => clearInterval(timer);
1544
1652
  }, [detailView]);
1545
- useEffect5(() => {
1653
+ useEffect6(() => {
1546
1654
  if (!detailView) return;
1547
1655
  const timer = setInterval(() => {
1548
1656
  setDetailCountdown((prev) => prev > 0 ? prev - 1 : 10);
1549
1657
  }, 1e3);
1550
1658
  return () => clearInterval(timer);
1551
1659
  }, [detailView]);
1552
- useInput4(
1553
- useCallback5(
1660
+ useInput5(
1661
+ useCallback6(
1554
1662
  (input, key) => {
1663
+ if (input === "c" && key.ctrl) {
1664
+ handleCtrlC();
1665
+ return;
1666
+ }
1555
1667
  if (detailView) {
1556
1668
  if (key.escape || input === "q" || input === " ") {
1557
1669
  setDetailView(null);
@@ -1567,24 +1679,29 @@ function StockList({ codes, onExit }) {
1567
1679
  const stock = stocks[selectedIndex];
1568
1680
  if (stock) setDetailView(stock);
1569
1681
  } else if (key.escape || input === "q") {
1570
- onExit();
1682
+ if (onBackToChat) onBackToChat();
1683
+ else onExit();
1571
1684
  } else if (input === "r") {
1572
1685
  setCountdown(5);
1573
1686
  loadData();
1574
1687
  }
1575
1688
  },
1576
- [stocks, selectedIndex, detailView, onExit, loadData]
1689
+ [stocks, selectedIndex, detailView, onExit, onBackToChat, loadData, handleCtrlC]
1577
1690
  )
1578
1691
  );
1579
1692
  if (detailView) {
1580
1693
  if (detailLoading) {
1581
1694
  return /* @__PURE__ */ jsx7(Box7, { paddingLeft: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " \u27F3 \u52A0\u8F7D\u5206\u65F6\u6570\u636E..." }) });
1582
1695
  }
1583
- return renderDetail(detailView, () => setDetailView(null), detailPrices ?? void 0, detailCountdown);
1696
+ return renderDetail(detailView, () => setDetailView(null), detailPrices ?? void 0, detailCountdown, currentTime);
1584
1697
  }
1585
1698
  return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1586
1699
  /* @__PURE__ */ jsxs7(Box7, { marginBottom: 1, justifyContent: "space-between", children: [
1587
1700
  /* @__PURE__ */ jsx7(Text8, { bold: true, color: "#00ffff", children: " \u{1F4C8} \u81EA\u9009\u80A1\u76D1\u63A7" }),
1701
+ /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
1702
+ " \u{1F550} ",
1703
+ currentTime
1704
+ ] }),
1588
1705
  /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: loading ? " \u27F3 \u5237\u65B0\u4E2D..." : ` \u6BCF ${countdown}s \u81EA\u52A8\u5237\u65B0` })
1589
1706
  ] }),
1590
1707
  /* @__PURE__ */ jsxs7(Box7, { children: [
@@ -1596,7 +1713,7 @@ function StockList({ codes, onExit }) {
1596
1713
  /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6DA8\u8DCC\u989D" }) }),
1597
1714
  /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6700\u9AD8" }) }),
1598
1715
  /* @__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" }) })
1716
+ /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6210\u4EA4\u989D" }) })
1600
1717
  ] }),
1601
1718
  /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " " + "\u2500".repeat(100) }) }),
1602
1719
  /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: stocks.map((stock, index) => {
@@ -1618,15 +1735,16 @@ function StockList({ codes, onExit }) {
1618
1735
  stock.changeAmount.toFixed(3)
1619
1736
  ] }) }),
1620
1737
  /* @__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) }) })
1738
+ /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsx7(Text8, { color: "#888888", children: formatPrice(stock.low) }) }),
1739
+ /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { color: "#888888", children: formatAmount(stock.amount) }) })
1623
1740
  ] }, stock.code);
1624
1741
  }) }),
1625
1742
  /* @__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}` }) })
1743
+ /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: ` \u6700\u540E\u66F4\u65B0: ${lastUpdate} \u7F16\u8F91\u81EA\u9009\u80A1: ~/.dskcode/settings.json` }) }),
1744
+ 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" }) })
1627
1745
  ] });
1628
1746
  }
1629
- function renderDetail(stock, _onBack, prices, countdown = 10) {
1747
+ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
1630
1748
  const isUp = stock.changePercent >= 0;
1631
1749
  const colorCode = isUp ? "#ff1493" : "#00ff41";
1632
1750
  const arrow = isUp ? "\u25B2" : "\u25BC";
@@ -1649,7 +1767,11 @@ function renderDetail(stock, _onBack, prices, countdown = 10) {
1649
1767
  stock.name,
1650
1768
  " "
1651
1769
  ] }),
1652
- /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: stock.code })
1770
+ /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: stock.code }),
1771
+ currentTime && /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
1772
+ " \u{1F550} ",
1773
+ currentTime
1774
+ ] })
1653
1775
  ] }),
1654
1776
  /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: `\u6BCF ${countdown}s \u5237\u65B0` })
1655
1777
  ] }),
@@ -1704,18 +1826,64 @@ function createCli() {
1704
1826
  const result = await loadAndValidate();
1705
1827
  ctx = { ...ctx, config: result.config };
1706
1828
  }
1707
- const app = renderApp(
1829
+ startChat(ctx);
1830
+ });
1831
+ function startChat(ctx) {
1832
+ const chatApp = renderApp(
1708
1833
  /* @__PURE__ */ jsx8(
1709
1834
  ChatSession,
1710
1835
  {
1711
1836
  providerCount: ctx?.config.providers.length ?? 1,
1712
1837
  toolCount: ctx?.config.tools.length ?? 0,
1713
- verbose: ctx?.verbose ?? false
1838
+ verbose: ctx?.verbose ?? false,
1839
+ onLaunchGame: () => {
1840
+ chatApp.unmount();
1841
+ setImmediate(() => {
1842
+ initGames();
1843
+ const games = listGames();
1844
+ const { unmount } = render4(
1845
+ /* @__PURE__ */ jsx8(
1846
+ GamePicker,
1847
+ {
1848
+ games,
1849
+ onSelect: async (game) => {
1850
+ unmount();
1851
+ await game.play();
1852
+ startChat(ctx);
1853
+ },
1854
+ onBackToChat: () => {
1855
+ unmount();
1856
+ setImmediate(() => startChat(ctx));
1857
+ }
1858
+ }
1859
+ ),
1860
+ { exitOnCtrlC: false }
1861
+ );
1862
+ });
1863
+ },
1864
+ onLaunchStock: () => {
1865
+ chatApp.unmount();
1866
+ setImmediate(() => {
1867
+ const defaultStockCodes = ctx?.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
1868
+ const stockApp = renderApp(
1869
+ /* @__PURE__ */ jsx8(
1870
+ StockList,
1871
+ {
1872
+ codes: defaultStockCodes,
1873
+ onBackToChat: () => {
1874
+ stockApp.unmount();
1875
+ setImmediate(() => startChat(ctx));
1876
+ },
1877
+ onExit: () => process.exit(0)
1878
+ }
1879
+ )
1880
+ );
1881
+ });
1882
+ }
1714
1883
  }
1715
1884
  )
1716
1885
  );
1717
- await app.waitUntilExit;
1718
- });
1886
+ }
1719
1887
  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) {
1720
1888
  console.log("dskcode run \u2014 \u5F85\u5B9E\u73B0\uFF08\u7B2C07\u7AE0\uFF09");
1721
1889
  });
@@ -1760,7 +1928,30 @@ compdef _dskcode_completion dskcode`);
1760
1928
  }
1761
1929
  });
1762
1930
  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"];
1931
+ const ctx = this.dskcodeCtx;
1932
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
1933
+ const globalConfigPath = join2(home, ".dskcode", "settings.json");
1934
+ let globalConfigHasStock = false;
1935
+ try {
1936
+ const raw = await readFile2(globalConfigPath, "utf-8");
1937
+ const parsed = JSON.parse(raw);
1938
+ const stock = parsed.stock;
1939
+ globalConfigHasStock = Array.isArray(stock?.symbols) && stock.symbols.length > 0;
1940
+ } catch {
1941
+ }
1942
+ if (!globalConfigHasStock) {
1943
+ const defaultSymbols = [
1944
+ { code: "sh000001" },
1945
+ { code: "sz399300" },
1946
+ { code: "sh601899" }
1947
+ ];
1948
+ const savedPath = await saveStockConfig(defaultSymbols);
1949
+ console.log(`${chalk3.green("\u2714")} \u5DF2\u751F\u6210\u81EA\u9009\u80A1\u914D\u7F6E: ${chalk3.dim(savedPath)}`);
1950
+ console.log(`${chalk3.dim(" \u63D0\u793A: \u53EF\u7F16\u8F91\u4E0A\u8FF0\u6587\u4EF6\u81EA\u5B9A\u4E49\u81EA\u9009\u80A1\u5217\u8868")}
1951
+ `);
1952
+ }
1953
+ const freshResult = await loadAndValidate();
1954
+ const codeList = codes && codes.length > 0 ? codes : freshResult.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
1764
1955
  const app = renderApp(
1765
1956
  /* @__PURE__ */ jsx8(
1766
1957
  StockList,
@@ -1804,7 +1995,8 @@ compdef _dskcode_completion dskcode`);
1804
1995
  resolve(null);
1805
1996
  }
1806
1997
  }
1807
- )
1998
+ ),
1999
+ { exitOnCtrlC: false }
1808
2000
  );
1809
2001
  });
1810
2002
  if (selectedGame) {
@@ -1831,8 +2023,18 @@ var ExitCode = {
1831
2023
  };
1832
2024
 
1833
2025
  // src/index.ts
2026
+ var sigintCount = 0;
2027
+ var sigintTimer = null;
1834
2028
  process.on("SIGINT", () => {
1835
- process.exit(ExitCode.SIGINT);
2029
+ sigintCount++;
2030
+ if (sigintCount >= 2) {
2031
+ process.exit(ExitCode.SIGINT);
2032
+ }
2033
+ process.stdout.write("\n \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode\n");
2034
+ if (sigintTimer) clearTimeout(sigintTimer);
2035
+ sigintTimer = setTimeout(() => {
2036
+ sigintCount = 0;
2037
+ }, 1500);
1836
2038
  });
1837
2039
  var program = createCli();
1838
2040
  try {