@este.systems/dsc 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text, useInput } from "ink";
3
3
  import { useStore } from "./useStore.js";
4
4
  import { setState } from "../store.js";
5
+ const MAX_BODY_LINES = 24;
5
6
  export function ApprovalDialog() {
6
7
  const approval = useStore((s) => s.approval);
7
8
  useInput((input, key) => {
@@ -12,6 +13,14 @@ export function ApprovalDialog() {
12
13
  approval.resolve("y");
13
14
  setState({ approval: null });
14
15
  }
16
+ else if (ch === "a") {
17
+ // Approve this call AND auto-approve future calls of the same tool
18
+ // for the rest of this session. The asker translates "a" to the
19
+ // "always" ApprovalAnswer, and the tool's gateApproval adds the
20
+ // tool name to ctx.sessionApprovals.
21
+ approval.resolve("a");
22
+ setState({ approval: null });
23
+ }
15
24
  else if (ch === "n" || key.escape) {
16
25
  approval.resolve("n");
17
26
  setState({ approval: null });
@@ -19,6 +28,37 @@ export function ApprovalDialog() {
19
28
  }, { isActive: approval !== null });
20
29
  if (!approval)
21
30
  return null;
22
- return (_jsxs(Box, { borderStyle: "round", borderColor: "yellow", paddingX: 1, flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: approval.title }), approval.body ? _jsx(Text, { children: approval.body }) : null, _jsxs(Text, { dimColor: true, children: [approval.question, " [y]es / [n]o (Esc rejects)"] })] }));
31
+ const lines = approval.body ? approval.body.split("\n") : [];
32
+ const overflow = Math.max(0, lines.length - MAX_BODY_LINES);
33
+ const shown = overflow > 0 ? lines.slice(0, MAX_BODY_LINES) : lines;
34
+ return (_jsxs(Box, { borderStyle: "round", borderColor: "yellow", paddingX: 1, flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: approval.title }), shown.length > 0 ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [shown.map((line, i) => (_jsx(BodyLine, { line: line, kind: approval.kind }, i))), overflow > 0 ? (_jsxs(Text, { dimColor: true, children: ["\u2026 (", overflow, " more line", overflow === 1 ? "" : "s", ")"] })) : null] })) : null, _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: [approval.question, "[y]es / [n]o (Esc rejects)"] }) })] }));
35
+ }
36
+ // Structural coloring per line. Diff lines get green/red/cyan based on prefix;
37
+ // command bodies highlight the `$` prefix; everything else stays default.
38
+ // Keeps the body plain text in the store — color is a render concern.
39
+ function BodyLine({ line, kind, }) {
40
+ if (kind === "diff") {
41
+ if (line.startsWith("+++") || line.startsWith("---")) {
42
+ return _jsx(Text, { bold: true, children: line });
43
+ }
44
+ if (line.startsWith("@@"))
45
+ return _jsx(Text, { color: "cyan", children: line });
46
+ if (line.startsWith("+"))
47
+ return _jsx(Text, { color: "green", children: line });
48
+ if (line.startsWith("-"))
49
+ return _jsx(Text, { color: "red", children: line });
50
+ return _jsx(Text, { children: line });
51
+ }
52
+ if (kind === "command") {
53
+ if (line.startsWith("$ ")) {
54
+ return (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "$" }), " ", line.slice(2)] }));
55
+ }
56
+ return _jsx(Text, { dimColor: true, children: line });
57
+ }
58
+ if (kind === "url")
59
+ return _jsx(Text, { color: "cyan", children: line });
60
+ if (line.startsWith("--- "))
61
+ return _jsx(Text, { dimColor: true, children: line });
62
+ return _jsx(Text, { children: line });
23
63
  }
24
64
  //# sourceMappingURL=ApprovalDialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ApprovalDialog.js","sourceRoot":"","sources":["../../src/tui/ApprovalDialog.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,MAAM,UAAU,cAAc;IAC5B,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAE7C,QAAQ,CACN,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACb,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,MAAM,EAAE,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QAC/B,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACtB,QAAQ,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/B,CAAC;aAAM,IAAI,EAAE,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YACpC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACtB,QAAQ,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC,EACD,EAAE,QAAQ,EAAE,QAAQ,KAAK,IAAI,EAAE,CAChC,CAAC;IAEF,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,OAAO,CACL,MAAC,GAAG,IACF,WAAW,EAAC,OAAO,EACnB,WAAW,EAAC,QAAQ,EACpB,QAAQ,EAAE,CAAC,EACX,aAAa,EAAC,QAAQ,EACtB,YAAY,EAAE,CAAC,aAEf,KAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAC,QAAQ,YACtB,QAAQ,CAAC,KAAK,GACV,EACN,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,KAAC,IAAI,cAAE,QAAQ,CAAC,IAAI,GAAQ,CAAC,CAAC,CAAC,IAAI,EACpD,MAAC,IAAI,IAAC,QAAQ,mBAAE,QAAQ,CAAC,QAAQ,mCAAmC,IAChE,CACP,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"ApprovalDialog.js","sourceRoot":"","sources":["../../src/tui/ApprovalDialog.tsx"],"names":[],"mappings":"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"}
@@ -12,7 +12,7 @@ export function History() {
12
12
  export function MessageRow({ message: m, streaming = false }) {
13
13
  if (m.role === "tool") {
14
14
  const label = m.tool_name ? `← ${m.tool_name}` : "← tool";
15
- return (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: [label, ": ", truncate(m.content, 600)] }) }));
15
+ return (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: [label, ": ", toolTruncate(m.content, 600)] }) }));
16
16
  }
17
17
  if (m.role === "system") {
18
18
  // Errors get red; everything else (slash-command output, notices) goes
@@ -40,4 +40,17 @@ export function MessageRow({ message: m, streaming = false }) {
40
40
  function truncate(s, n) {
41
41
  return s.length <= n ? s : s.slice(0, n) + "…";
42
42
  }
43
+ // Tool-result truncation. The model sees the full output (it's stored as
44
+ // the tool message's content); the TUI just renders a head slice so the
45
+ // dynamic frame stays readable. The marker explains the gap so the user
46
+ // knows nothing was silently lost from the model's perspective.
47
+ function toolTruncate(s, maxChars) {
48
+ if (s.length <= maxChars)
49
+ return s;
50
+ const head = s.slice(0, maxChars);
51
+ const omittedChars = s.length - maxChars;
52
+ const omittedLines = s.slice(maxChars).split("\n").length - 1;
53
+ const lineHint = omittedLines > 0 ? ` / ${omittedLines} more lines` : "";
54
+ return `${head}\n… (${omittedChars} more chars${lineHint}, full output sent to model)`;
55
+ }
43
56
  //# sourceMappingURL=History.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"History.js","sourceRoot":"","sources":["../../src/tui/History.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"History.js","sourceRoot":"","sources":["../../src/tui/History.tsx"],"names":[],"mappings":"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"}
package/dist/tui/Input.js CHANGED
@@ -157,6 +157,23 @@ export function Input({ value, onChange, onSubmit, focus, history = [] }) {
157
157
  const pre = value.slice(0, cursor);
158
158
  const at = value[cursor] ?? " ";
159
159
  const post = value.slice(cursor + 1);
160
+ // Ghost-text suggestion: when typing a /slash command and the cursor is
161
+ // at the end, show what TAB would complete to as dim suffix. Skip when
162
+ // a multiline buffer continuation is active or when the cursor isn't at
163
+ // the end (mid-edit completions would look like the cursor jumped).
164
+ const isAtEnd = cursor === value.length;
165
+ let ghost = "";
166
+ if (isAtEnd && value.startsWith("/")) {
167
+ const { completion } = completeSlash(value);
168
+ if (completion && completion.startsWith(value) && completion.length > value.length) {
169
+ ghost = completion.slice(value.length);
170
+ }
171
+ }
172
+ if (ghost) {
173
+ // Render the cursor on top of the first ghost char so the user sees
174
+ // "press TAB to take this" with the caret positioned to commit.
175
+ return (_jsxs(Text, { children: [pre, focus ? (_jsx(Text, { inverse: true, dimColor: true, children: ghost[0] })) : (_jsx(Text, { dimColor: true, children: ghost[0] })), _jsx(Text, { dimColor: true, children: ghost.slice(1) })] }));
176
+ }
160
177
  return (_jsxs(Text, { children: [pre, focus ? _jsx(Text, { inverse: true, children: at }) : _jsx(Text, { children: at }), post] }));
161
178
  }
162
179
  //# sourceMappingURL=Input.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Input.js","sourceRoot":"","sources":["../../src/tui/Input.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Input.js","sourceRoot":"","sources":["../../src/tui/Input.tsx"],"names":[],"mappings":"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"}
package/dist/tui.js CHANGED
@@ -2,16 +2,20 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { render } from "ink";
3
3
  import { App } from "./tui/App.js";
4
4
  import { getState, setState } from "./store.js";
5
- import { AVAILABLE_MODELS, DEFAULT_MODEL, DeepSeekError, computeCostUsd, configPath, hasApiKey, recordUsage, } from "./api.js";
5
+ import { AVAILABLE_MODELS, DEFAULT_MODEL, DeepSeekError, apiKeySource, computeCostUsd, configPath, hasApiKey, recordUsage, saveApiKey, } from "./api.js";
6
6
  import { runAgent, formatCost, estimateContextTokens, } from "./agent.js";
7
7
  import * as history from "./history.js";
8
8
  import * as approval from "./approval.js";
9
9
  import * as audit from "./audit.js";
10
10
  import * as replHistory from "./repl_history.js";
11
11
  import { promises as fsp } from "node:fs";
12
+ import { homedir } from "node:os";
13
+ import * as path from "node:path";
12
14
  import { compactSession } from "./compact.js";
13
15
  import { formatVersionInfo } from "./version.js";
14
16
  import { openEditor } from "./editor.js";
17
+ import { checkForUpdate, runUpdate } from "./update.js";
18
+ import { copyToClipboard } from "./clipboard.js";
15
19
  const AUTO_COMPACT_AT_TOKENS = Number(process.env.DSC_AUTO_COMPACT ?? "0") || 0;
16
20
  const AUTO_COMPACT_KEEP = Number(process.env.DSC_AUTO_COMPACT_KEEP ?? "4") || 4;
17
21
  // Minimal arg parsing for the TUI entry. Mirrors the subset of flags the
@@ -66,12 +70,11 @@ async function main() {
66
70
  ].join("\n") + "\n");
67
71
  process.exit(0);
68
72
  }
69
- if (!hasApiKey()) {
70
- process.stderr.write(`No DeepSeek API key found.\n` +
71
- `Either export DEEPSEEK_API_KEY, or create ${configPath()} containing:\n` +
72
- ` {"api_key": "sk-..."}\n`);
73
- process.exit(1);
74
- }
73
+ // First-launch UX: don't hard-exit when there's no API key. Boot the
74
+ // TUI and surface a one-line system message telling the user how to
75
+ // set one via /api-key. Submitting a turn before setting the key will
76
+ // surface DeepSeekError, which we already render as a system error.
77
+ const startupKeyMissing = !hasApiKey();
75
78
  const cwd = process.cwd();
76
79
  await history.migrateLegacyIfPresent(cwd, DEFAULT_MODEL);
77
80
  // Auto-resume most recent for cwd unless --no-resume; otherwise fresh.
@@ -95,6 +98,7 @@ async function main() {
95
98
  yolo: !!cli.yolo,
96
99
  filesTouched: stats.files_touched,
97
100
  sessionId: session.id,
101
+ sessionApprovals: new Set(),
98
102
  };
99
103
  // Coalescing save (same shape as REPL).
100
104
  let savePromise = null;
@@ -166,15 +170,16 @@ async function main() {
166
170
  // 1-second tick so the session timer in StatusBar advances even when idle.
167
171
  const timerId = setInterval(syncStatus, 1000);
168
172
  // Install the approval asker — routes confirm* calls through the
169
- // ApprovalDialog component. The diff/preview body printed by confirm*
170
- // still goes to stdout and will appear above ink's dynamic frame; that's
171
- // acceptable for a first cut and gets cleaned up in a later commit.
172
- approval.setAsker((q) => new Promise((resolve) => {
173
+ // ApprovalDialog component. The structured payload (title + body + kind)
174
+ // goes into the dialog so the diff/preview renders inline in the yellow
175
+ // bordered box rather than bleeding above ink's dynamic frame.
176
+ approval.setAsker((req) => new Promise((resolve) => {
173
177
  setState({
174
178
  approval: {
175
- title: "Confirm",
176
- body: "",
177
- question: q,
179
+ title: req.title,
180
+ body: req.body ?? "",
181
+ kind: req.kind,
182
+ question: req.question,
178
183
  resolve,
179
184
  },
180
185
  });
@@ -204,20 +209,44 @@ async function main() {
204
209
  },
205
210
  onAssistantFinal: (turnId, msg) => {
206
211
  // Move from `current` into the static history (so it's selectable).
207
- // Drop assistant turns that produced neither content nor reasoning
208
- // (tool-only turns) the tool messages themselves will appear.
209
- const has = (msg.content && msg.content.length) || (msg.reasoning && msg.reasoning.length);
212
+ // Only skip when the turn was purely a tool dispatch (content empty,
213
+ // reasoning empty, and tool_calls present) those produce visible
214
+ // tool messages of their own, so an "(no content)" placeholder would
215
+ // be noise. A truly empty turn (no content, no reasoning, no tool
216
+ // calls) still gets a marker so the user knows the agent stopped
217
+ // rather than wondering whether something silently broke.
218
+ const hasText = (msg.content && msg.content.length) ||
219
+ (msg.reasoning && msg.reasoning.length);
220
+ const isToolOnly = !hasText && msg.tool_calls && msg.tool_calls.length;
210
221
  setState((s) => {
211
222
  const next = { current: null };
212
- if (has) {
213
- const final = {
214
- id: turnId,
215
- role: "assistant",
216
- content: msg.content,
217
- reasoning: msg.reasoning,
218
- tool_calls: msg.tool_calls,
219
- };
220
- next.history = [...s.history, final];
223
+ if (isToolOnly) {
224
+ // pure tool dispatch — tool messages will appear separately
225
+ }
226
+ else if (hasText) {
227
+ next.history = [
228
+ ...s.history,
229
+ {
230
+ id: turnId,
231
+ role: "assistant",
232
+ content: msg.content,
233
+ reasoning: msg.reasoning,
234
+ tool_calls: msg.tool_calls,
235
+ },
236
+ ];
237
+ }
238
+ else {
239
+ // Genuinely empty turn — show a marker rather than appearing to
240
+ // hang. Most often hit when an upstream filter or network drop
241
+ // cuts the stream before any tokens arrive.
242
+ next.history = [
243
+ ...s.history,
244
+ {
245
+ id: turnId,
246
+ role: "system",
247
+ content: "(model returned no content)",
248
+ },
249
+ ];
221
250
  }
222
251
  return next;
223
252
  });
@@ -415,11 +444,16 @@ async function main() {
415
444
  "/lang [name|off] force the model to reply in a language",
416
445
  "/auto-continue [N|off] auto-grant N extra MAX_TOOL_DEPTH budgets",
417
446
  "/cost show token usage and cost",
447
+ "/copy copy last assistant response to clipboard",
418
448
  "/version show version info",
419
449
  "/compact [keep] summarize old turns (default keep=4)",
420
450
  "/transcript dump full message log",
421
451
  "/audit [path|show N] audit log info",
422
452
  "/queue [clear] list or clear queued prompts",
453
+ "/export [path] write current session JSON for transfer",
454
+ "/import <path> load session JSON; rebinds cwd here (--keep-cwd to skip)",
455
+ "/api-key [key] show source / save api key to config file",
456
+ "/update check npm for a newer dsc and install it",
423
457
  "/exit exit",
424
458
  ].join("\n"));
425
459
  return true;
@@ -429,6 +463,10 @@ async function main() {
429
463
  stats = session.stats;
430
464
  toolCtx.filesTouched = stats.files_touched;
431
465
  toolCtx.sessionId = session.id;
466
+ // Per-tool always-approval is conversation-scoped, so /clear has
467
+ // to drop it — otherwise the user would carry over implicit trust
468
+ // they made to a session they just walked away from.
469
+ toolCtx.sessionApprovals = new Set();
432
470
  setState({ history: [], current: null });
433
471
  info(`new session started (${session.id})`);
434
472
  syncStatus();
@@ -536,6 +574,32 @@ async function main() {
536
574
  case "cost":
537
575
  info(formatCost(stats, model));
538
576
  return true;
577
+ case "copy": {
578
+ // Copy the most recent assistant response to the OS clipboard. The
579
+ // last *assistant* (not tool, not system, not user) is what the
580
+ // user usually wants — the actual answer text.
581
+ const state = getState();
582
+ let target;
583
+ for (let i = state.history.length - 1; i >= 0; i--) {
584
+ const m = state.history[i];
585
+ if (m.role === "assistant" && m.content) {
586
+ target = m;
587
+ break;
588
+ }
589
+ }
590
+ if (!target) {
591
+ info("no assistant message to copy");
592
+ return true;
593
+ }
594
+ try {
595
+ await copyToClipboard(target.content);
596
+ info(`copied ${target.content.length} chars to clipboard`);
597
+ }
598
+ catch (e) {
599
+ info(`error: ${e.message}`);
600
+ }
601
+ return true;
602
+ }
539
603
  case "model":
540
604
  if (!arg) {
541
605
  info(`current model: ${model}`);
@@ -624,6 +688,60 @@ async function main() {
624
688
  case "version":
625
689
  info(formatVersionInfo());
626
690
  return true;
691
+ case "update": {
692
+ // Two-phase: check first so we don't run npm install when we're
693
+ // already current. Force-fetches the latest (ignores the 24h cache
694
+ // because the user explicitly asked) and reports either way.
695
+ info("checking npm for a newer version…");
696
+ try {
697
+ const check = await checkForUpdate({ force: true });
698
+ if (!check.newerAvailable) {
699
+ info(`up to date (${check.current})`);
700
+ return true;
701
+ }
702
+ info(`installing ${check.latest} (currently ${check.current}) via npm…`);
703
+ const r = await runUpdate();
704
+ if (!r.ok) {
705
+ // npm prints reasonably descriptive errors itself; pass them
706
+ // through so EACCES / permission issues are obvious.
707
+ info(`error: update failed\n${r.output.slice(-1500)}\n\n` +
708
+ `If this is a permission error, try: sudo npm install -g @este.systems/dsc@latest`);
709
+ return true;
710
+ }
711
+ info(`installed ${check.latest}. Exit and re-run \`dsc\` to pick up the new version.`);
712
+ }
713
+ catch (e) {
714
+ info(`error: update failed: ${e.message}`);
715
+ }
716
+ return true;
717
+ }
718
+ case "api-key": {
719
+ const text = arg.trim();
720
+ if (!text) {
721
+ // No arg: show where the key is configured (don't print the key
722
+ // itself; that's not the kind of thing /command output should
723
+ // splash to scrollback).
724
+ const src = apiKeySource();
725
+ if (src === "env") {
726
+ info("api key: set via $DEEPSEEK_API_KEY (env)");
727
+ }
728
+ else if (src === "file") {
729
+ info(`api key: stored in ${configPath()}`);
730
+ }
731
+ else {
732
+ info(`api key: not set\nrun /api-key <key> to save one, or export DEEPSEEK_API_KEY`);
733
+ }
734
+ return true;
735
+ }
736
+ try {
737
+ const written = await saveApiKey(text);
738
+ info(`api key saved to ${written}`);
739
+ }
740
+ catch (e) {
741
+ info(`error: ${e.message}`);
742
+ }
743
+ return true;
744
+ }
627
745
  case "audit": {
628
746
  const sub = arg.trim();
629
747
  if (!sub || sub === "path") {
@@ -688,6 +806,70 @@ async function main() {
688
806
  await runCompaction(keep, false);
689
807
  return true;
690
808
  }
809
+ case "export": {
810
+ // `/export [path]` — writes the current session JSON. Relative
811
+ // paths resolve against the launch cwd, not against the session's
812
+ // recorded cwd, so it lands where the user actually is. Default
813
+ // target is the cwd, named after /save'd name (or id) and date.
814
+ const dest = arg.trim() || ".";
815
+ const absDest = path.isAbsolute(dest) ? dest : path.resolve(cwd, dest);
816
+ try {
817
+ await persist(); // flush any pending writes before export
818
+ const written = await history.exportSession(session.id, absDest);
819
+ info(`exported to ${written}`);
820
+ }
821
+ catch (e) {
822
+ info(`error: export failed: ${e.message}`);
823
+ }
824
+ return true;
825
+ }
826
+ case "import": {
827
+ // `/import <path>` — reads a session JSON, rebinds its cwd to the
828
+ // current directory so auto-resume picks it up here, and loads it
829
+ // as the active session immediately. Keep the previous cwd with
830
+ // `--keep-cwd` (positional flag, kept simple).
831
+ const tokens = arg.trim().split(/\s+/).filter(Boolean);
832
+ const keepCwd = tokens.includes("--keep-cwd");
833
+ const file = tokens.find((t) => !t.startsWith("--"));
834
+ if (!file) {
835
+ info("error: usage: /import <path> [--keep-cwd]");
836
+ return true;
837
+ }
838
+ const absFile = path.isAbsolute(file) ? file : path.resolve(cwd, file);
839
+ try {
840
+ const loaded = await history.importSession(absFile, {
841
+ rebindCwd: keepCwd ? undefined : cwd,
842
+ });
843
+ session = loaded;
844
+ messages = session.messages;
845
+ stats = session.stats;
846
+ model = session.model;
847
+ toolCtx.filesTouched = stats.files_touched;
848
+ toolCtx.sessionId = session.id;
849
+ // Refill the visible history so the user sees the imported
850
+ // conversation right away (same logic the /resume path uses).
851
+ const restored = [];
852
+ for (const m of messages) {
853
+ if (m.role === "system")
854
+ continue;
855
+ restored.push({
856
+ id: `r-${restored.length}`,
857
+ role: m.role,
858
+ content: typeof m.content === "string" ? m.content : "",
859
+ tool_call_id: m.tool_call_id,
860
+ });
861
+ }
862
+ setState({ history: restored, current: null, model });
863
+ syncStatus();
864
+ const userTurns = messages.filter((m) => m.role === "user").length;
865
+ const cwdNote = keepCwd ? "" : " (cwd rebound to here)";
866
+ info(`imported ${session.id} (${userTurns} turns, model ${model})${cwdNote}`);
867
+ }
868
+ catch (e) {
869
+ info(`error: import failed: ${e.message}`);
870
+ }
871
+ return true;
872
+ }
691
873
  case "edit": {
692
874
  // ink owns the terminal — unmount before spawning $EDITOR so the
693
875
  // editor gets a clean screen. We also clear `history` before the
@@ -723,6 +905,16 @@ async function main() {
723
905
  void replHistory.append(text);
724
906
  }
725
907
  if (text.startsWith("/")) {
908
+ // Echo the command into history so the user can see which command
909
+ // produced the system-line output that follows. Without this the
910
+ // info() lines look orphaned — especially when you scroll back
911
+ // and there's no record of what triggered each one.
912
+ setState((s) => ({
913
+ history: [
914
+ ...s.history,
915
+ { id: `u-${s.history.length}`, role: "user", content: text },
916
+ ],
917
+ }));
726
918
  void handleSlash(text);
727
919
  return;
728
920
  }
@@ -769,6 +961,63 @@ async function main() {
769
961
  process.exit(0);
770
962
  }
771
963
  mountApp();
964
+ // One-time welcome panel. Stamped via a touch-file under XDG_STATE_HOME
965
+ // so we never nag the same user twice. New users get an orientation
966
+ // (key, /help, hotkeys); the more-targeted "no API key" reminder fires
967
+ // on every subsequent launch when no key is configured.
968
+ const stateDir = (() => {
969
+ const xdg = process.env.XDG_STATE_HOME;
970
+ const base = xdg && xdg.length ? xdg : path.join(homedir(), ".local", "state");
971
+ return path.join(base, "dsc");
972
+ })();
973
+ const welcomeStamp = path.join(stateDir, "welcomed");
974
+ let alreadyWelcomed = false;
975
+ try {
976
+ await fsp.access(welcomeStamp);
977
+ alreadyWelcomed = true;
978
+ }
979
+ catch {
980
+ // first launch
981
+ }
982
+ if (!alreadyWelcomed) {
983
+ const lines = [
984
+ "Welcome to dsc — a CLI coding agent for DeepSeek.",
985
+ "",
986
+ " /help full command list (TAB completes any /command)",
987
+ " Up / Down recall past prompts",
988
+ " ESC abort a running turn",
989
+ " Ctrl+D exit",
990
+ ];
991
+ if (startupKeyMissing) {
992
+ lines.push("", `To get started, save your API key: /api-key sk-...`, ` (or export DEEPSEEK_API_KEY in your shell)`);
993
+ }
994
+ info(lines.join("\n"));
995
+ try {
996
+ await fsp.mkdir(stateDir, { recursive: true });
997
+ await fsp.writeFile(welcomeStamp, new Date().toISOString(), "utf8");
998
+ }
999
+ catch {
1000
+ // best-effort; missing perms just means the welcome shows again
1001
+ }
1002
+ }
1003
+ else if (startupKeyMissing) {
1004
+ // Targeted nudge for returning users who somehow lost their key.
1005
+ info(`No DeepSeek API key found. Run "/api-key <key>" to save one to ${configPath()}, or export DEEPSEEK_API_KEY in your shell.`);
1006
+ }
1007
+ // Fire-and-forget update probe. Uses the 24h cache by default so a
1008
+ // chatty notice doesn't appear on every launch — only once per day,
1009
+ // when the registry says we're behind. Failures are swallowed silently.
1010
+ void (async () => {
1011
+ try {
1012
+ const check = await checkForUpdate();
1013
+ if (check.newerAvailable) {
1014
+ info(`update available: ${check.latest} (you have ${check.current}). Run /update to install.`);
1015
+ }
1016
+ }
1017
+ catch {
1018
+ // best-effort; offline launches shouldn't yell at the user
1019
+ }
1020
+ })();
772
1021
  }
773
1022
  function truncate(s, n) {
774
1023
  return s.length <= n ? s : s.slice(0, n) + "…";