@deepseek-harness-tui/dsh-tui 0.8.6 → 0.8.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.
Files changed (120) hide show
  1. package/README.md +15 -5
  2. package/bin/dsh-tui.js +269 -218
  3. package/cordis.patch.yml +48 -22
  4. package/lib/types/cc/figures.d.ts.map +1 -1
  5. package/lib/types/cc/figures.js +14 -13
  6. package/lib/types/cc/syntaxRuns.d.ts +16 -0
  7. package/lib/types/cc/syntaxRuns.d.ts.map +1 -0
  8. package/lib/types/cc/syntaxRuns.js +79 -0
  9. package/lib/types/commands.d.ts +14 -0
  10. package/lib/types/commands.d.ts.map +1 -1
  11. package/lib/types/commands.js +20 -1
  12. package/lib/types/components/Chat/SubagentMessage.d.ts +19 -0
  13. package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -0
  14. package/lib/types/components/Chat/SubagentMessage.js +75 -0
  15. package/lib/types/components/EffortInputBorder.d.ts.map +1 -1
  16. package/lib/types/components/EffortInputBorder.js +2 -2
  17. package/lib/types/components/EffortTierBadge.js +1 -1
  18. package/lib/types/components/FileSuggestions.d.ts +8 -4
  19. package/lib/types/components/FileSuggestions.d.ts.map +1 -1
  20. package/lib/types/components/FileSuggestions.js +22 -12
  21. package/lib/types/components/HelpMenu.d.ts +8 -1
  22. package/lib/types/components/HelpMenu.d.ts.map +1 -1
  23. package/lib/types/components/HelpMenu.js +15 -4
  24. package/lib/types/components/LogoV2.d.ts +7 -1
  25. package/lib/types/components/LogoV2.d.ts.map +1 -1
  26. package/lib/types/components/LogoV2.js +13 -4
  27. package/lib/types/components/MessageList.d.ts +6 -2
  28. package/lib/types/components/MessageList.d.ts.map +1 -1
  29. package/lib/types/components/MessageList.js +17 -6
  30. package/lib/types/components/ModelPicker.js +2 -2
  31. package/lib/types/components/PromptInput.d.ts +1 -1
  32. package/lib/types/components/PromptInput.d.ts.map +1 -1
  33. package/lib/types/components/PromptInput.js +100 -36
  34. package/lib/types/components/SkillsPicker.js +2 -2
  35. package/lib/types/components/SplitDiffView.d.ts +1 -13
  36. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  37. package/lib/types/components/SplitDiffView.js +6 -105
  38. package/lib/types/components/SubagentCard.d.ts +9 -0
  39. package/lib/types/components/SubagentCard.d.ts.map +1 -0
  40. package/lib/types/components/SubagentCard.js +26 -0
  41. package/lib/types/components/SubagentDashboard.d.ts +13 -0
  42. package/lib/types/components/SubagentDashboard.d.ts.map +1 -0
  43. package/lib/types/components/SubagentDashboard.js +50 -0
  44. package/lib/types/components/SubagentDetailScene.d.ts +16 -0
  45. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -0
  46. package/lib/types/components/SubagentDetailScene.js +141 -0
  47. package/lib/types/components/SyntaxText.d.ts +15 -0
  48. package/lib/types/components/SyntaxText.d.ts.map +1 -0
  49. package/lib/types/components/SyntaxText.js +34 -0
  50. package/lib/types/components/ThinkingToggle.d.ts +3 -10
  51. package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
  52. package/lib/types/components/ThinkingToggle.js +4 -9
  53. package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
  54. package/lib/types/components/design-system/ListItem.js +1 -1
  55. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  56. package/lib/types/components/messages/AssistantThinkingMessage.js +18 -7
  57. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -0
  58. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  59. package/lib/types/components/messages/AssistantToolUseMessage.js +42 -6
  60. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  61. package/lib/types/components/messages/UserPromptMessage.js +15 -2
  62. package/lib/types/dsh-adapter/channel.d.ts +64 -3
  63. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  64. package/lib/types/dsh-adapter/channel.js +387 -37
  65. package/lib/types/dsh-adapter/contract.d.ts +67 -4
  66. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  67. package/lib/types/dsh-adapter/contract.js +149 -15
  68. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  69. package/lib/types/dsh-adapter/index.js +6 -6
  70. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  71. package/lib/types/dsh-adapter/plugin.js +38 -0
  72. package/lib/types/dsh-adapter/shortcuts.js +3 -3
  73. package/lib/types/dsh-adapter/subagents.d.ts +87 -0
  74. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -0
  75. package/lib/types/dsh-adapter/subagents.js +204 -0
  76. package/lib/types/herdr.d.ts +28 -0
  77. package/lib/types/herdr.d.ts.map +1 -0
  78. package/lib/types/herdr.js +133 -0
  79. package/lib/types/i18n.d.ts +233 -32
  80. package/lib/types/i18n.d.ts.map +1 -1
  81. package/lib/types/i18n.js +87 -16
  82. package/lib/types/ink/ink.d.ts +4 -0
  83. package/lib/types/ink/ink.d.ts.map +1 -1
  84. package/lib/types/ink/ink.js +84 -38
  85. package/lib/types/ink/render-node-to-output.d.ts +6 -3
  86. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  87. package/lib/types/ink/render-node-to-output.js +33 -7
  88. package/lib/types/ink/selection.d.ts +29 -4
  89. package/lib/types/ink/selection.d.ts.map +1 -1
  90. package/lib/types/ink/selection.js +41 -5
  91. package/lib/types/minimalMode.d.ts +13 -0
  92. package/lib/types/minimalMode.d.ts.map +1 -0
  93. package/lib/types/minimalMode.js +17 -0
  94. package/lib/types/screens/Chat.d.ts +1 -1
  95. package/lib/types/screens/Chat.d.ts.map +1 -1
  96. package/lib/types/screens/Chat.js +94 -43
  97. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  98. package/lib/types/screens/StatusLine.js +6 -2
  99. package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
  100. package/lib/types/screens/TrajectoryScene.js +4 -2
  101. package/lib/types/theme.d.ts +8 -0
  102. package/lib/types/theme.d.ts.map +1 -1
  103. package/lib/types/theme.js +38 -14
  104. package/lib/types/tips.d.ts.map +1 -1
  105. package/lib/types/tips.js +25 -7
  106. package/lib/types/update.d.ts +18 -0
  107. package/lib/types/update.d.ts.map +1 -1
  108. package/lib/types/update.js +73 -2
  109. package/lib/types/utils/execFileNoThrow.d.ts +3 -20
  110. package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
  111. package/lib/types/utils/execFileNoThrow.js +53 -32
  112. package/lib/types/utils/externalEditor.d.ts +12 -11
  113. package/lib/types/utils/externalEditor.d.ts.map +1 -1
  114. package/lib/types/utils/externalEditor.js +11 -11
  115. package/lib/types/utils/fileSuggestions.d.ts +19 -0
  116. package/lib/types/utils/fileSuggestions.d.ts.map +1 -0
  117. package/lib/types/utils/fileSuggestions.js +40 -0
  118. package/lib/types/utils/mentions.d.ts.map +1 -1
  119. package/lib/types/utils/mentions.js +3 -1
  120. package/package.json +81 -74
@@ -1,7 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { readFileSync } from 'node:fs';
2
+ import { readFileSync, realpathSync, renameSync, writeFileSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
- import { dirname, join } from 'node:path';
4
+ import { dirname, join, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { gte, gt, lt, valid } from 'semver';
7
7
  import { shellQuote } from './utils/shellQuote.js';
@@ -225,6 +225,70 @@ export function tuiUpdatePluginArgs(profile, targetVersion) {
225
225
  export function isTransientUpdateFailure(stderr) {
226
226
  return /ENOENT|EPERM|EBUSY/i.test(stderr) && /_tmp_\d+/i.test(stderr);
227
227
  }
228
+ /**
229
+ * Best-effort migrate the GLOBAL launcher to the delegating shim (0.8.7):
230
+ * after a successful profile update, copy this package's `bin/dsh-tui.js`
231
+ * and `package.json` over the global install so the launcher can never lag
232
+ * the profile again — the shim delegates all logic to the profile copy it
233
+ * just updated. Single-file-safe by contract: the new bin imports nothing
234
+ * from lib/ (see its header), so overwriting it inside an older global
235
+ * install cannot dangle a missing helper.
236
+ *
237
+ * Locating the global dir relies on argv[1] being the global `dsh-tui.js`
238
+ * (true when booted through the `dsh-tui` command). Source checkouts and
239
+ * direct `dsh --profile` boots resolve nothing — the migration is a silent
240
+ * no-op there. Write failures (permissions, locked files) are equally
241
+ * silent: the launcher-alignment warning remains the fallback diagnosis.
242
+ *
243
+ * @returns true when the global launcher files were replaced.
244
+ */
245
+ export function migrateGlobalLauncher() {
246
+ const launcherBin = process.argv[1];
247
+ if (launcherBin === undefined || !launcherBin.endsWith('dsh-tui.js'))
248
+ return false;
249
+ // Walk up from the bin to the containing package; accept it only when it
250
+ // is OUR package and not the profile copy we are running from (junction
251
+ // layouts collapse both onto the same real path — copying onto ourselves
252
+ // would be a no-op at best).
253
+ let dir = dirname(resolve(launcherBin));
254
+ const ownDir = dirname(dirname(fileURLToPath(import.meta.url)));
255
+ for (let depth = 0; depth < 4; depth++) {
256
+ const manifest = join(dir, 'package.json');
257
+ try {
258
+ const parsed = JSON.parse(readFileSync(manifest, 'utf8'));
259
+ if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
260
+ && parsed.name === PACKAGE_NAME) {
261
+ let same = false;
262
+ try {
263
+ same = realpathSync(dir) === realpathSync(ownDir);
264
+ }
265
+ catch {
266
+ same = resolve(dir) === resolve(ownDir);
267
+ }
268
+ if (same)
269
+ return false;
270
+ // tmp + rename keeps each file atomic; a crash mid-migration leaves
271
+ // either the old or the new file, never a truncated one.
272
+ const replace = (target, source) => {
273
+ const staged = `${target}.dsh-tui-migrate`;
274
+ writeFileSync(staged, readFileSync(source));
275
+ renameSync(staged, target);
276
+ };
277
+ replace(join(dir, 'bin', 'dsh-tui.js'), join(ownDir, 'bin', 'dsh-tui.js'));
278
+ replace(manifest, join(ownDir, 'package.json'));
279
+ return true;
280
+ }
281
+ }
282
+ catch {
283
+ // Unreadable manifest at this level — keep walking up.
284
+ }
285
+ const parent = dirname(dir);
286
+ if (parent === dir)
287
+ return false;
288
+ dir = parent;
289
+ }
290
+ return false;
291
+ }
228
292
  /**
229
293
  * Update the installed dsh-tui package and restart the same launcher while
230
294
  * preserving the active session. The TUI must already be unmounted before
@@ -297,6 +361,13 @@ export async function updateTuiAndRestart(sessionId, profile, targetVersion) {
297
361
  return { updateCode: 1, restartCode: 1 };
298
362
  }
299
363
  }
364
+ // Launcher migration (0.8.7): the freshly installed profile carries the
365
+ // delegating shim — stamp it over the global launcher so this is the LAST
366
+ // time the outer copy can lag. Best-effort; the alignment warning stays as
367
+ // the fallback when the copy is impossible.
368
+ if (migrateGlobalLauncher()) {
369
+ process.stderr.write('dsh-tui: global launcher aligned to the delegating shim (no manual npm i -g needed anymore).\n');
370
+ }
300
371
  const restartCode = await runProcess(process.execPath, [...process.execArgv, ...process.argv.slice(1)], {
301
372
  env: {
302
373
  ...process.env,
@@ -4,31 +4,14 @@ export interface ExecFileNoThrowResult {
4
4
  stdout: string;
5
5
  stderr: string;
6
6
  }
7
- /**
8
- * Options for {@link execFileNoThrow}: stdin payload, timeout, and child
9
- * working directory.
10
- */
7
+ /** Options for {@link execFileNoThrow}: stdin payload, timeout, and child
8
+ * working directory. */
11
9
  export interface ExecFileNoThrowOptions {
12
- /** Stdin payload (OSC 52 clipboard helpers and tmux load-buffer). */
13
10
  input?: string;
14
- /** Kill the child after this many milliseconds. */
15
11
  timeout?: number;
16
- /**
17
- * Accepted for call-site compatibility with the original execa-based
18
- * implementation; `false` (the only value the Ink core passes) matches this
19
- * shim's behavior of running in the current working directory.
20
- */
21
12
  useCwd?: boolean;
22
- /** Working directory for the child; defaults to the parent's cwd. */
23
13
  cwd?: string;
24
14
  }
25
- /**
26
- * Run a command without throwing: resolves with `{ code, stdout, stderr }`
27
- * even when the process exits non-zero or cannot spawn.
28
- * @param file - The executable path to spawn.
29
- * @param args - Command-line arguments; defaults to none.
30
- * @param options - Spawn options (input, timeout, cwd).
31
- * @returns The process outcome: exit code, captured stdout, and captured stderr.
32
- */
15
+ /** Run a command without throwing; child output is bounded for safety. */
33
16
  export declare function execFileNoThrow(file: string, args?: readonly string[], options?: ExecFileNoThrowOptions): Promise<ExecFileNoThrowResult>;
34
17
  //# sourceMappingURL=execFileNoThrow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"execFileNoThrow.d.ts","sourceRoot":"","sources":["../../../src/utils/execFileNoThrow.ts"],"names":[],"mappings":"AAEA,qDAAqD;AACrD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,qEAAqE;IACrE,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,qBAAqB,CAAC,CAiChC"}
1
+ {"version":3,"file":"execFileNoThrow.d.ts","sourceRoot":"","sources":["../../../src/utils/execFileNoThrow.ts"],"names":[],"mappings":"AAKA,qDAAqD;AACrD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED;wBACwB;AACxB,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AA6BD,0EAA0E;AAC1E,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,SAAS,MAAM,EAAO,EAC5B,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,qBAAqB,CAAC,CA+BhC"}
@@ -1,44 +1,65 @@
1
1
  import { spawn } from 'node:child_process';
2
- /**
3
- * Run a command without throwing: resolves with `{ code, stdout, stderr }`
4
- * even when the process exits non-zero or cannot spawn.
5
- * @param file - The executable path to spawn.
6
- * @param args - Command-line arguments; defaults to none.
7
- * @param options - Spawn options (input, timeout, cwd).
8
- * @returns The process outcome: exit code, captured stdout, and captured stderr.
9
- */
10
- export function execFileNoThrow(file, args, options) {
2
+ import { extname } from 'node:path';
3
+ const MAX_CAPTURE_BYTES = 64 * 1024;
4
+ function appendBounded(chunks, chunk, total) {
5
+ if (total.value >= MAX_CAPTURE_BYTES)
6
+ return;
7
+ const remaining = MAX_CAPTURE_BYTES - total.value;
8
+ const part = chunk.subarray(0, remaining);
9
+ chunks.push(part);
10
+ total.value += part.length;
11
+ }
12
+ /** Quote one argument for the Windows command interpreter without flattening
13
+ * the caller's argv into an unquoted shell command. */
14
+ function quoteWindowsArg(value) {
15
+ const escaped = value.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/g, '$1$1');
16
+ return `"${escaped.replace(/%/g, '%%')}"`;
17
+ }
18
+ function spawnCommand(file, args, options) {
19
+ const isWindowsScript = process.platform === 'win32' && /\.(?:cmd|bat)$/iu.test(extname(file));
20
+ if (!isWindowsScript)
21
+ return spawn(file, args, { timeout: options?.timeout, cwd: options?.cwd });
22
+ const command = `"${[file, ...args].map(quoteWindowsArg).join(' ')}"`;
23
+ return spawn(process.env.ComSpec ?? 'cmd.exe', ['/d', '/s', '/c', command], {
24
+ timeout: options?.timeout,
25
+ cwd: options?.cwd,
26
+ windowsHide: true,
27
+ windowsVerbatimArguments: true,
28
+ });
29
+ }
30
+ /** Run a command without throwing; child output is bounded for safety. */
31
+ export function execFileNoThrow(file, args = [], options) {
11
32
  return new Promise(resolve => {
12
- const child = spawn(file, args ?? [], { timeout: options?.timeout, cwd: options?.cwd });
13
- // Collect raw bytes and decode once at close: per-chunk toString() splits
14
- // multi-byte UTF-8 characters straddling a chunk boundary into U+FFFD.
33
+ let child;
34
+ try {
35
+ child = spawnCommand(file, args, options);
36
+ }
37
+ catch {
38
+ resolve({ code: 1, stdout: '', stderr: '' });
39
+ return;
40
+ }
15
41
  const stdoutChunks = [];
16
42
  const stderrChunks = [];
17
- child.stdout.on('data', (chunk) => {
18
- stdoutChunks.push(chunk);
19
- });
20
- child.stderr.on('data', (chunk) => {
21
- stderrChunks.push(chunk);
22
- });
23
- // A child may exit before consuming the supplied input. The resulting
24
- // EPIPE/EOF belongs to that child outcome and must not crash the caller.
25
- child.stdin.on('error', () => { });
26
- child.on('error', () => {
27
- resolve({
28
- code: 1,
29
- stdout: Buffer.concat(stdoutChunks).toString('utf8'),
30
- stderr: Buffer.concat(stderrChunks).toString('utf8'),
31
- });
32
- });
33
- child.on('close', code => {
43
+ const stdoutTotal = { value: 0 };
44
+ const stderrTotal = { value: 0 };
45
+ let settled = false;
46
+ const finish = (code) => {
47
+ if (settled)
48
+ return;
49
+ settled = true;
34
50
  resolve({
35
51
  code,
36
52
  stdout: Buffer.concat(stdoutChunks).toString('utf8'),
37
53
  stderr: Buffer.concat(stderrChunks).toString('utf8'),
38
54
  });
39
- });
55
+ };
56
+ child.stdout?.on('data', (chunk) => appendBounded(stdoutChunks, chunk, stdoutTotal));
57
+ child.stderr?.on('data', (chunk) => appendBounded(stderrChunks, chunk, stderrTotal));
58
+ child.stdin?.on('error', () => { });
59
+ child.on('error', () => finish(1));
60
+ child.on('close', code => finish(code));
40
61
  if (options?.input !== undefined)
41
- child.stdin.write(options.input);
42
- child.stdin.end();
62
+ child.stdin?.write(options.input);
63
+ child.stdin?.end();
43
64
  });
44
65
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * External editor round-trip for the prompt input (issue #123): Ctrl+X dumps
2
+ * External editor round-trip for the prompt input (issue #123): Ctrl+G dumps
3
3
  * the current draft into a temp file, hands the terminal to `$VISUAL` /
4
4
  * `$EDITOR` (nvim, vim, nano, `code --wait`, …), and returns the saved text
5
5
  * for the input to adopt.
@@ -16,11 +16,12 @@
16
16
  * failing restore never overrides the outcome.
17
17
  *
18
18
  * Editor resolution order mirrors readline's edit-and-execute-command:
19
- * `$VISUAL` → `$EDITOR` → `vi` on POSIX (always present). Windows has no
20
- * console-editor guarantee and `notepad` does not block, so an unresolved
21
- * editor there reports `unavailable` and the UI asks the user to set
22
- * `$EDITOR`. The variable may carry arguments (`EDITOR="code --wait"`), so
23
- * the command line is split quote-aware before spawning.
19
+ * `$VISUAL` → `$EDITOR`. There is deliberately NO fallback editor: an
20
+ * unresolved editor reports `unavailable` and the UI asks the user to set
21
+ * `$VISUAL` or `$EDITOR` (dropping someone into an unconfigured `vi` is a
22
+ * trap for users who don't know how to quit it). The variable may carry
23
+ * arguments (`EDITOR="code --wait"`), so the command line is split
24
+ * quote-aware before spawning.
24
25
  *
25
26
  * Windows launch: libuv resolves bare names to `.exe` on PATH but will NOT
26
27
  * execute `.cmd`/`.bat` shims (VS Code's `code` on PATH is `code.cmd`), and
@@ -37,7 +38,8 @@
37
38
  * - `edited`: the saved content differs from the draft — adopt `text`
38
39
  * - `unchanged`: the file matches the draft (modulo newline convention), or
39
40
  * the editor exited non-zero (`:cq` abort semantics) — keep the draft
40
- * - `unavailable`: no editor could be resolved (Windows without `$EDITOR`)
41
+ * - `unavailable`: no editor could be resolved (neither `$VISUAL` nor
42
+ * `$EDITOR` is set)
41
43
  * - `failed`: the editor process or the temp-file round-trip errored
42
44
  * (`message` names the failed command or carries the fs error)
43
45
  */
@@ -59,11 +61,10 @@ export type EditorOutcome = {
59
61
  export declare function splitEditorCommand(commandLine: string): string[];
60
62
  /**
61
63
  * Resolve the editor argv from the environment. `$VISUAL` wins over
62
- * `$EDITOR` (readline convention); POSIX falls back to `vi`, Windows has no
63
- * blocking console editor fallback and returns undefined. `platform` is a
64
- * parameter so the Windows branch is unit-testable from CI's Linux runners.
64
+ * `$EDITOR` (readline convention); when neither is set there is no fallback
65
+ * editor — returns undefined and the caller reports `unavailable`.
65
66
  */
66
- export declare function resolveEditorCommand(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string[] | undefined;
67
+ export declare function resolveEditorCommand(env?: NodeJS.ProcessEnv): string[] | undefined;
67
68
  /**
68
69
  * Windows shim resolution: a bare command like `code` usually lives on PATH
69
70
  * as `code.cmd`, which libuv refuses to execute directly. Walk PATH with
@@ -1 +1 @@
1
- {"version":3,"file":"externalEditor.d.ts","sourceRoot":"","sources":["../../../src/utils/externalEditor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAUH;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvC;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CA0BhE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,EAAE,GAAG,SAAS,CAOtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAqBtC;AAKD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,CAalD;AA8CD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAsDhF"}
1
+ {"version":3,"file":"externalEditor.d.ts","sourceRoot":"","sources":["../../../src/utils/externalEditor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAUH;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvC;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CA0BhE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,EAAE,GAAG,SAAS,CAOtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAqBtC;AAKD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,CAalD;AA8CD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAsDhF"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * External editor round-trip for the prompt input (issue #123): Ctrl+X dumps
2
+ * External editor round-trip for the prompt input (issue #123): Ctrl+G dumps
3
3
  * the current draft into a temp file, hands the terminal to `$VISUAL` /
4
4
  * `$EDITOR` (nvim, vim, nano, `code --wait`, …), and returns the saved text
5
5
  * for the input to adopt.
@@ -16,11 +16,12 @@
16
16
  * failing restore never overrides the outcome.
17
17
  *
18
18
  * Editor resolution order mirrors readline's edit-and-execute-command:
19
- * `$VISUAL` → `$EDITOR` → `vi` on POSIX (always present). Windows has no
20
- * console-editor guarantee and `notepad` does not block, so an unresolved
21
- * editor there reports `unavailable` and the UI asks the user to set
22
- * `$EDITOR`. The variable may carry arguments (`EDITOR="code --wait"`), so
23
- * the command line is split quote-aware before spawning.
19
+ * `$VISUAL` → `$EDITOR`. There is deliberately NO fallback editor: an
20
+ * unresolved editor reports `unavailable` and the UI asks the user to set
21
+ * `$VISUAL` or `$EDITOR` (dropping someone into an unconfigured `vi` is a
22
+ * trap for users who don't know how to quit it). The variable may carry
23
+ * arguments (`EDITOR="code --wait"`), so the command line is split
24
+ * quote-aware before spawning.
24
25
  *
25
26
  * Windows launch: libuv resolves bare names to `.exe` on PATH but will NOT
26
27
  * execute `.cmd`/`.bat` shims (VS Code's `code` on PATH is `code.cmd`), and
@@ -76,17 +77,16 @@ export function splitEditorCommand(commandLine) {
76
77
  }
77
78
  /**
78
79
  * Resolve the editor argv from the environment. `$VISUAL` wins over
79
- * `$EDITOR` (readline convention); POSIX falls back to `vi`, Windows has no
80
- * blocking console editor fallback and returns undefined. `platform` is a
81
- * parameter so the Windows branch is unit-testable from CI's Linux runners.
80
+ * `$EDITOR` (readline convention); when neither is set there is no fallback
81
+ * editor — returns undefined and the caller reports `unavailable`.
82
82
  */
83
- export function resolveEditorCommand(env = process.env, platform = process.platform) {
83
+ export function resolveEditorCommand(env = process.env) {
84
84
  const raw = (env.VISUAL ?? '').trim() || (env.EDITOR ?? '').trim();
85
85
  if (raw !== '') {
86
86
  const args = splitEditorCommand(raw);
87
87
  return args.length > 0 ? args : undefined;
88
88
  }
89
- return platform === 'win32' ? undefined : ['vi'];
89
+ return undefined;
90
90
  }
91
91
  /**
92
92
  * Windows shim resolution: a bare command like `code` usually lives on PATH
@@ -0,0 +1,19 @@
1
+ export type FileCandidateKind = 'file' | 'directory';
2
+ export interface FileCandidate {
3
+ id: string;
4
+ path: string;
5
+ displayPath: string;
6
+ name: string;
7
+ kind: FileCandidateKind;
8
+ score: number;
9
+ }
10
+ export interface FileSuggestionOptions {
11
+ topK?: number;
12
+ maxFiles?: number;
13
+ maxDirectories?: number;
14
+ }
15
+ export declare function isPathLikeQuery(query: string): boolean;
16
+ export declare function fuzzySubsequenceScore(query: string, candidate: string): number | undefined;
17
+ export declare function rankFileCandidates(candidates: readonly FileCandidate[], query: string, topK?: number): FileCandidate[];
18
+ export declare function preserveSelection(previous: FileCandidate | undefined, next: readonly FileCandidate[], fallback?: number): number;
19
+ //# sourceMappingURL=fileSuggestions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileSuggestions.d.ts","sourceRoot":"","sources":["../../../src/utils/fileSuggestions.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,WAAW,CAAA;AAEpD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,iBAAiB,CAAA;IACvB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAID,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAiB1F;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,aAAa,EAAE,CAMlH;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS,EAAE,IAAI,EAAE,SAAS,aAAa,EAAE,EAAE,QAAQ,SAAI,GAAG,MAAM,CAM3H"}
@@ -0,0 +1,40 @@
1
+ const pathSeparators = /[\\/]/;
2
+ export function isPathLikeQuery(query) {
3
+ return query.startsWith('.') || query.startsWith('~') || query.startsWith('/') || /^[A-Za-z]:[\\/]/.test(query) || pathSeparators.test(query);
4
+ }
5
+ export function fuzzySubsequenceScore(query, candidate) {
6
+ const needle = query.toLocaleLowerCase();
7
+ const haystack = candidate.toLocaleLowerCase();
8
+ if (!needle)
9
+ return 0;
10
+ let cursor = 0;
11
+ let first = -1;
12
+ let gaps = 0;
13
+ for (const char of needle) {
14
+ const found = haystack.indexOf(char, cursor);
15
+ if (found === -1)
16
+ return undefined;
17
+ if (first === -1)
18
+ first = found;
19
+ gaps += found - cursor;
20
+ cursor = found + 1;
21
+ }
22
+ const prefixBonus = first === 0 ? 20 : 0;
23
+ const boundaryBonus = first > 0 && /[\\/_. -]/.test(haystack[first - 1] ?? '') ? 8 : 0;
24
+ return needle.length * 10 + prefixBonus + boundaryBonus - gaps - haystack.length / 100;
25
+ }
26
+ export function rankFileCandidates(candidates, query, topK = 50) {
27
+ const ranked = candidates
28
+ .map(candidate => ({ ...candidate, score: fuzzySubsequenceScore(query, `${candidate.path} ${candidate.name}`) ?? -Infinity }))
29
+ .filter(candidate => Number.isFinite(candidate.score))
30
+ .sort((a, b) => b.score - a.score || a.kind.localeCompare(b.kind) || a.path.localeCompare(b.path));
31
+ return ranked.slice(0, Math.max(0, topK));
32
+ }
33
+ export function preserveSelection(previous, next, fallback = 0) {
34
+ if (previous) {
35
+ const index = next.findIndex(candidate => candidate.id === previous.id);
36
+ if (index >= 0)
37
+ return index;
38
+ }
39
+ return next.length === 0 ? 0 : Math.min(Math.max(0, fallback), next.length - 1);
40
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"mentions.d.ts","sourceRoot":"","sources":["../../../src/utils/mentions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAA;IACb,gEAAgE;IAChE,GAAG,EAAE,MAAM,CAAA;IACX,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAA;CACb;AAMD,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,CA8B5D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAe3D"}
1
+ {"version":3,"file":"mentions.d.ts","sourceRoot":"","sources":["../../../src/utils/mentions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAA;IACb,gEAAgE;IAChE,GAAG,EAAE,MAAM,CAAA;IACX,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAA;CACb;AAQD,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,CA8B5D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAe3D"}
@@ -6,7 +6,9 @@
6
6
  * either a run of non-whitespace characters (`@src/a.ts`) or a double-quoted
7
7
  * path (`@"my dir/a.ts"`, for paths containing spaces).
8
8
  */
9
- /** True when `ch` delimits a token (whitespace). */
9
+ /** True when `ch` delimits a token (whitespace). Path separators and the
10
+ * quote char are ordinary token characters on every platform — Windows
11
+ * backslash paths must survive both caret tracking and submission. */
10
12
  const isBoundary = (ch) => ch === undefined || /\s/.test(ch);
11
13
  /** Extract every `@` mention in `text` (typed order, duplicates kept out). */
12
14
  export function extractMentions(text) {