@deepseek-harness-tui/dsh-tui 0.8.6 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +13 -3
  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/HelpMenu.d.ts +8 -1
  19. package/lib/types/components/HelpMenu.d.ts.map +1 -1
  20. package/lib/types/components/HelpMenu.js +15 -4
  21. package/lib/types/components/LogoV2.d.ts +3 -1
  22. package/lib/types/components/LogoV2.d.ts.map +1 -1
  23. package/lib/types/components/LogoV2.js +2 -2
  24. package/lib/types/components/MessageList.d.ts +6 -2
  25. package/lib/types/components/MessageList.d.ts.map +1 -1
  26. package/lib/types/components/MessageList.js +17 -6
  27. package/lib/types/components/ModelPicker.js +2 -2
  28. package/lib/types/components/PromptInput.d.ts +1 -1
  29. package/lib/types/components/PromptInput.d.ts.map +1 -1
  30. package/lib/types/components/PromptInput.js +75 -15
  31. package/lib/types/components/SkillsPicker.js +2 -2
  32. package/lib/types/components/SplitDiffView.d.ts +1 -13
  33. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  34. package/lib/types/components/SplitDiffView.js +6 -105
  35. package/lib/types/components/SubagentCard.d.ts +9 -0
  36. package/lib/types/components/SubagentCard.d.ts.map +1 -0
  37. package/lib/types/components/SubagentCard.js +26 -0
  38. package/lib/types/components/SubagentDashboard.d.ts +13 -0
  39. package/lib/types/components/SubagentDashboard.d.ts.map +1 -0
  40. package/lib/types/components/SubagentDashboard.js +50 -0
  41. package/lib/types/components/SubagentDetailScene.d.ts +16 -0
  42. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -0
  43. package/lib/types/components/SubagentDetailScene.js +141 -0
  44. package/lib/types/components/SyntaxText.d.ts +15 -0
  45. package/lib/types/components/SyntaxText.d.ts.map +1 -0
  46. package/lib/types/components/SyntaxText.js +34 -0
  47. package/lib/types/components/ThinkingToggle.d.ts +3 -10
  48. package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
  49. package/lib/types/components/ThinkingToggle.js +4 -9
  50. package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
  51. package/lib/types/components/design-system/ListItem.js +1 -1
  52. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  53. package/lib/types/components/messages/AssistantThinkingMessage.js +18 -7
  54. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -0
  55. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  56. package/lib/types/components/messages/AssistantToolUseMessage.js +42 -6
  57. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  58. package/lib/types/components/messages/UserPromptMessage.js +15 -2
  59. package/lib/types/dsh-adapter/channel.d.ts +53 -2
  60. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  61. package/lib/types/dsh-adapter/channel.js +294 -7
  62. package/lib/types/dsh-adapter/contract.d.ts +27 -4
  63. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  64. package/lib/types/dsh-adapter/contract.js +56 -10
  65. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  66. package/lib/types/dsh-adapter/index.js +9 -3
  67. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  68. package/lib/types/dsh-adapter/plugin.js +38 -0
  69. package/lib/types/dsh-adapter/shortcuts.js +3 -3
  70. package/lib/types/dsh-adapter/subagents.d.ts +87 -0
  71. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -0
  72. package/lib/types/dsh-adapter/subagents.js +204 -0
  73. package/lib/types/herdr.d.ts +28 -0
  74. package/lib/types/herdr.d.ts.map +1 -0
  75. package/lib/types/herdr.js +133 -0
  76. package/lib/types/i18n.d.ts +215 -31
  77. package/lib/types/i18n.d.ts.map +1 -1
  78. package/lib/types/i18n.js +65 -14
  79. package/lib/types/ink/ink.d.ts +4 -0
  80. package/lib/types/ink/ink.d.ts.map +1 -1
  81. package/lib/types/ink/ink.js +46 -15
  82. package/lib/types/minimalMode.d.ts +13 -0
  83. package/lib/types/minimalMode.d.ts.map +1 -0
  84. package/lib/types/minimalMode.js +17 -0
  85. package/lib/types/screens/Chat.d.ts +1 -1
  86. package/lib/types/screens/Chat.d.ts.map +1 -1
  87. package/lib/types/screens/Chat.js +94 -43
  88. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  89. package/lib/types/screens/StatusLine.js +6 -2
  90. package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
  91. package/lib/types/screens/TrajectoryScene.js +4 -2
  92. package/lib/types/theme.d.ts +8 -0
  93. package/lib/types/theme.d.ts.map +1 -1
  94. package/lib/types/theme.js +38 -14
  95. package/lib/types/tips.js +3 -3
  96. package/lib/types/update.d.ts +18 -0
  97. package/lib/types/update.d.ts.map +1 -1
  98. package/lib/types/update.js +73 -2
  99. package/lib/types/utils/execFileNoThrow.d.ts +3 -20
  100. package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
  101. package/lib/types/utils/execFileNoThrow.js +53 -32
  102. package/lib/types/utils/externalEditor.d.ts +12 -11
  103. package/lib/types/utils/externalEditor.d.ts.map +1 -1
  104. package/lib/types/utils/externalEditor.js +11 -11
  105. package/package.json +64 -58
@@ -95,16 +95,16 @@ const darkTheme = {
95
95
  toolDotWrite: rgb('#B3A0D4'), // soft violet — edit/write
96
96
  toolDotWeb: rgb('#7DA1DE'), // mist blue — web search/fetch
97
97
  toolDotTask: rgb('#D194AE'), // mist rose — subagent/jobs
98
- syntaxKeyword: rgb('#82AAFF'), // vivid blue (theme anchor)
99
- syntaxString: rgb('#E5C07B'), // gold - echoes the user-message gold accent
100
- syntaxComment: rgb('#6B7280'), // neutral grey
101
- syntaxNumber: rgb('#D19A66'), // warm orange
102
- syntaxFunction: rgb('#56B6C2'), // vivid cyan
103
- syntaxType: rgb('#C678DD'), // vivid violet
98
+ syntaxKeyword: rgb('#78A0D6'), // muted anchor blue
99
+ syntaxString: rgb('#79AD91'), // mist green, distinct without neon saturation
100
+ syntaxComment: rgb('#74808D'), // neutral blue-grey
101
+ syntaxNumber: rgb('#C89B70'), // softened warm amber
102
+ syntaxFunction: rgb('#6FAEB5'), // muted cyan
103
+ syntaxType: rgb('#A98FBF'), // softened violet
104
104
  syntaxVariable: rgb('#C9D1D9'), // near-text
105
105
  syntaxOperator: rgb('#93A1B0'), // blue grey
106
106
  syntaxPunctuation: rgb('#7A8694'), // dim blue grey
107
- syntaxConstant: rgb('#E48A9B'), // soft rose
107
+ syntaxConstant: rgb('#C98291'), // softened rose
108
108
  red_FOR_SUBAGENTS_ONLY: rgb('#D4685E'),
109
109
  blue_FOR_SUBAGENTS_ONLY: rgb('#7496D6'),
110
110
  green_FOR_SUBAGENTS_ONLY: rgb('#66B285'),
@@ -143,6 +143,14 @@ const darkTheme = {
143
143
  rainbow_blue_shimmer: rgb('#AFBFE2'),
144
144
  rainbow_indigo_shimmer: rgb('#BFB4DE'),
145
145
  rainbow_violet_shimmer: rgb('#D1B4D1'),
146
+ subagentBullet: rgb('#D194AE'),
147
+ subagentDescription: rgb('#E8E6E0'),
148
+ subagentModel: rgb('#8D95A6'),
149
+ subagentElapsed: rgb('#8D95A6'),
150
+ subagentToolName: rgb('#7DA1DE'),
151
+ subagentStatusRunning: rgb('#7DA1DE'),
152
+ subagentStatusCompleted: rgb('#82B89D'),
153
+ subagentStatusFailed: rgb('#DA8A93'),
146
154
  };
147
155
  /**
148
156
  * Gentle Mist Blue light theme — the strict original card. Blue carries
@@ -191,16 +199,16 @@ const lightTheme = {
191
199
  toolDotWrite: rgb('#7A5CA8'),
192
200
  toolDotWeb: rgb('#4A63A8'),
193
201
  toolDotTask: rgb('#B04A5A'),
194
- syntaxKeyword: rgb('#2557C7'),
195
- syntaxString: rgb('#8A6A00'),
196
- syntaxComment: rgb('#8A8F98'),
197
- syntaxNumber: rgb('#B25E1E'),
198
- syntaxFunction: rgb('#0F7A8A'),
199
- syntaxType: rgb('#8A3FC7'),
202
+ syntaxKeyword: rgb('#3F68B5'), // clear primary blue without neon saturation
203
+ syntaxString: rgb('#3F805F'), // readable muted green
204
+ syntaxComment: rgb('#7D858F'), // neutral blue-grey
205
+ syntaxNumber: rgb('#A7652B'), // warm amber accent
206
+ syntaxFunction: rgb('#2E7E8A'), // muted cyan
207
+ syntaxType: rgb('#7E55A4'), // softened violet
200
208
  syntaxVariable: rgb('#343945'),
201
209
  syntaxOperator: rgb('#5B6672'),
202
210
  syntaxPunctuation: rgb('#9AA0A8'),
203
- syntaxConstant: rgb('#C2186B'),
211
+ syntaxConstant: rgb('#A84472'), // muted rose accent
204
212
  red_FOR_SUBAGENTS_ONLY: rgb('#BE5A52'),
205
213
  blue_FOR_SUBAGENTS_ONLY: rgb('#3F6CC4'),
206
214
  green_FOR_SUBAGENTS_ONLY: rgb('#4E9675'),
@@ -239,6 +247,14 @@ const lightTheme = {
239
247
  rainbow_blue_shimmer: rgb('#A9BCE0'),
240
248
  rainbow_indigo_shimmer: rgb('#B7AFD8'),
241
249
  rainbow_violet_shimmer: rgb('#CFB0CC'),
250
+ subagentBullet: rgb('#C07A93'),
251
+ subagentDescription: rgb('#343945'),
252
+ subagentModel: rgb('#8991A0'),
253
+ subagentElapsed: rgb('#8991A0'),
254
+ subagentToolName: rgb('#3F6CC4'),
255
+ subagentStatusRunning: rgb('#3F6CC4'),
256
+ subagentStatusCompleted: rgb('#4E9675'),
257
+ subagentStatusFailed: rgb('#C65D6B'),
242
258
  };
243
259
  /**
244
260
  * Dark ANSI theme using only the 16 standard ANSI colors, for terminals
@@ -337,6 +353,14 @@ const darkAnsiTheme = {
337
353
  rainbow_blue_shimmer: 'ansi:cyanBright',
338
354
  rainbow_indigo_shimmer: 'ansi:blueBright',
339
355
  rainbow_violet_shimmer: 'ansi:magentaBright',
356
+ subagentBullet: 'ansi:magentaBright',
357
+ subagentDescription: 'ansi:whiteBright',
358
+ subagentModel: 'ansi:white',
359
+ subagentElapsed: 'ansi:white',
360
+ subagentToolName: 'ansi:cyanBright',
361
+ subagentStatusRunning: 'ansi:blueBright',
362
+ subagentStatusCompleted: 'ansi:greenBright',
363
+ subagentStatusFailed: 'ansi:redBright',
340
364
  };
341
365
  /**
342
366
  * Resolve a theme name to its concrete color palette.
package/lib/types/tips.js CHANGED
@@ -60,10 +60,10 @@ export const TIPS = [
60
60
  en: 'In trajectory, / queries fields like tool:, err:, >10s, tok>1k',
61
61
  },
62
62
  {
63
- id: 'keys-ctrl-x',
63
+ id: 'keys-ctrl-g',
64
64
  group: 'keys',
65
- zh: 'Ctrl+X 用 $VISUAL 编辑器编辑当前输入',
66
- en: 'Ctrl+X edits your input in the $VISUAL editor',
65
+ zh: 'Ctrl+G 用 $VISUAL/$EDITOR 编辑器编辑当前输入',
66
+ en: 'Ctrl+G edits your input in the $VISUAL/$EDITOR editor',
67
67
  },
68
68
  {
69
69
  id: 'keys-ctrl-enter',
@@ -88,6 +88,24 @@ export declare function tuiUpdatePluginArgs(profile: string, targetVersion?: str
88
88
  * token, so matching both keeps the retry from masking real failures.
89
89
  */
90
90
  export declare function isTransientUpdateFailure(stderr: string): boolean;
91
+ /**
92
+ * Best-effort migrate the GLOBAL launcher to the delegating shim (0.8.7):
93
+ * after a successful profile update, copy this package's `bin/dsh-tui.js`
94
+ * and `package.json` over the global install so the launcher can never lag
95
+ * the profile again — the shim delegates all logic to the profile copy it
96
+ * just updated. Single-file-safe by contract: the new bin imports nothing
97
+ * from lib/ (see its header), so overwriting it inside an older global
98
+ * install cannot dangle a missing helper.
99
+ *
100
+ * Locating the global dir relies on argv[1] being the global `dsh-tui.js`
101
+ * (true when booted through the `dsh-tui` command). Source checkouts and
102
+ * direct `dsh --profile` boots resolve nothing — the migration is a silent
103
+ * no-op there. Write failures (permissions, locked files) are equally
104
+ * silent: the launcher-alignment warning remains the fallback diagnosis.
105
+ *
106
+ * @returns true when the global launcher files were replaced.
107
+ */
108
+ export declare function migrateGlobalLauncher(): boolean;
91
109
  /**
92
110
  * Update the installed dsh-tui package and restart the same launcher while
93
111
  * preserving the active session. The TUI must already be unmounted before
@@ -1 +1 @@
1
- {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/update.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAIlD,OAAO,EAAE,UAAU,EAAE,CAAA;AAQrB,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACf;AAED,4DAA4D;AAC5D,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3E;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAA;AAEvB,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,SAAS,CAiBxD;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,SAAS,MAAM,EAAiB,GAAG,MAAM,GAAG,SAAS,CAahG;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAW5C;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIzE;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAG7D;AA4BD;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,eAAe,CAAC,CAcvE;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAG5E;AA6DD,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAIrF;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,eAAe,CAAC,CAuE1B"}
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/update.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAIlD,OAAO,EAAE,UAAU,EAAE,CAAA;AAQrB,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACf;AAED,4DAA4D;AAC5D,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3E;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAA;AAEvB,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,SAAS,CAiBxD;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,SAAS,MAAM,EAAiB,GAAG,MAAM,GAAG,SAAS,CAahG;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAW5C;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIzE;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAG7D;AA4BD;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,eAAe,CAAC,CAcvE;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAG5E;AA6DD,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAIrF;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CA2C/C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,eAAe,CAAC,CA+E1B"}
@@ -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