wave-code 1.0.10 → 1.1.1

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 (248) hide show
  1. package/bin/wave-code.js +8 -2
  2. package/dist/bundle/wave.mjs +3231 -0
  3. package/package.json +24 -25
  4. package/src/cli.tsx +6 -1
  5. package/src/commands/update.ts +2 -7
  6. package/src/components/App.tsx +3 -7
  7. package/src/components/BtwDisplay.tsx +10 -1
  8. package/src/components/ChatInterface.tsx +21 -52
  9. package/src/components/ConfirmationDetails.tsx +144 -37
  10. package/src/components/DiffDisplay.tsx +335 -301
  11. package/src/components/InputBox.tsx +17 -2
  12. package/src/components/LoadingIndicator.tsx +16 -2
  13. package/src/components/MessageBlockItem.tsx +2 -4
  14. package/src/components/MessageList.tsx +75 -14
  15. package/src/components/ReasoningDisplay.tsx +2 -4
  16. package/src/components/SessionSelector.tsx +82 -11
  17. package/src/components/SkillsManager.tsx +312 -0
  18. package/src/components/StatusCommand.tsx +2 -9
  19. package/src/components/ToolDisplay.tsx +2 -4
  20. package/src/constants/commands.ts +6 -0
  21. package/src/contexts/useChat.tsx +245 -301
  22. package/src/daemon/commands.ts +57 -27
  23. package/src/daemon/jsonRpcClient.ts +1 -1
  24. package/src/daemon/socketClient.ts +2 -2
  25. package/src/hooks/useInputManager.ts +7 -1
  26. package/src/index.ts +57 -10
  27. package/src/managers/inputHandlers.ts +2 -0
  28. package/src/managers/inputReducer.ts +195 -104
  29. package/src/print-cli.ts +25 -17
  30. package/src/reducers/selectorReducer.ts +10 -0
  31. package/src/reducers/skillsManagerReducer.ts +91 -0
  32. package/src/session-selector-cli.tsx +269 -28
  33. package/src/stdio/agentBridge.ts +30 -21
  34. package/src/stdio/daemonServer.ts +3 -1
  35. package/src/stdio/protocol.ts +2 -0
  36. package/src/utils/clipboard.ts +21 -0
  37. package/src/utils/highlightUtils.ts +46 -1
  38. package/src/utils/readPackageJson.ts +29 -0
  39. package/src/utils/streamingText.ts +11 -0
  40. package/src/utils/worktree.ts +191 -43
  41. package/dist/cli.d.ts +0 -6
  42. package/dist/cli.js +0 -53
  43. package/dist/commands/plugin/disable.d.ts +0 -5
  44. package/dist/commands/plugin/disable.js +0 -15
  45. package/dist/commands/plugin/enable.d.ts +0 -5
  46. package/dist/commands/plugin/enable.js +0 -15
  47. package/dist/commands/plugin/install.d.ts +0 -5
  48. package/dist/commands/plugin/install.js +0 -21
  49. package/dist/commands/plugin/list.d.ts +0 -1
  50. package/dist/commands/plugin/list.js +0 -31
  51. package/dist/commands/plugin/marketplace.d.ts +0 -10
  52. package/dist/commands/plugin/marketplace.js +0 -90
  53. package/dist/commands/plugin/uninstall.d.ts +0 -3
  54. package/dist/commands/plugin/uninstall.js +0 -16
  55. package/dist/commands/plugin/update.d.ts +0 -3
  56. package/dist/commands/plugin/update.js +0 -15
  57. package/dist/commands/update.d.ts +0 -1
  58. package/dist/commands/update.js +0 -97
  59. package/dist/components/AgentsManager.d.ts +0 -7
  60. package/dist/components/AgentsManager.js +0 -109
  61. package/dist/components/App.d.ts +0 -9
  62. package/dist/components/App.js +0 -76
  63. package/dist/components/BackgroundTaskManager.d.ts +0 -5
  64. package/dist/components/BackgroundTaskManager.js +0 -99
  65. package/dist/components/BangDisplay.d.ts +0 -8
  66. package/dist/components/BangDisplay.js +0 -17
  67. package/dist/components/BtwDisplay.d.ts +0 -7
  68. package/dist/components/BtwDisplay.js +0 -19
  69. package/dist/components/ChatInterface.d.ts +0 -2
  70. package/dist/components/ChatInterface.js +0 -61
  71. package/dist/components/CommandSelector.d.ts +0 -10
  72. package/dist/components/CommandSelector.js +0 -66
  73. package/dist/components/CompactDisplay.d.ts +0 -8
  74. package/dist/components/CompactDisplay.js +0 -13
  75. package/dist/components/ConfirmationDetails.d.ts +0 -9
  76. package/dist/components/ConfirmationDetails.js +0 -45
  77. package/dist/components/ConfirmationSelector.d.ts +0 -13
  78. package/dist/components/ConfirmationSelector.js +0 -127
  79. package/dist/components/DiffDisplay.d.ts +0 -8
  80. package/dist/components/DiffDisplay.js +0 -201
  81. package/dist/components/DiscoverView.d.ts +0 -2
  82. package/dist/components/DiscoverView.js +0 -37
  83. package/dist/components/FileSelector.d.ts +0 -11
  84. package/dist/components/FileSelector.js +0 -50
  85. package/dist/components/HelpView.d.ts +0 -7
  86. package/dist/components/HelpView.js +0 -74
  87. package/dist/components/HistorySearch.d.ts +0 -8
  88. package/dist/components/HistorySearch.js +0 -66
  89. package/dist/components/InputBox.d.ts +0 -24
  90. package/dist/components/InputBox.js +0 -149
  91. package/dist/components/InstalledView.d.ts +0 -2
  92. package/dist/components/InstalledView.js +0 -45
  93. package/dist/components/LoadingIndicator.d.ts +0 -10
  94. package/dist/components/LoadingIndicator.js +0 -6
  95. package/dist/components/LoginCommand.d.ts +0 -5
  96. package/dist/components/LoginCommand.js +0 -153
  97. package/dist/components/Markdown.d.ts +0 -5
  98. package/dist/components/Markdown.js +0 -127
  99. package/dist/components/MarketplaceAddForm.d.ts +0 -2
  100. package/dist/components/MarketplaceAddForm.js +0 -55
  101. package/dist/components/MarketplaceDetail.d.ts +0 -2
  102. package/dist/components/MarketplaceDetail.js +0 -74
  103. package/dist/components/MarketplaceList.d.ts +0 -8
  104. package/dist/components/MarketplaceList.js +0 -17
  105. package/dist/components/MarketplaceView.d.ts +0 -2
  106. package/dist/components/MarketplaceView.js +0 -41
  107. package/dist/components/McpManager.d.ts +0 -9
  108. package/dist/components/McpManager.js +0 -88
  109. package/dist/components/MessageBlockItem.d.ts +0 -8
  110. package/dist/components/MessageBlockItem.js +0 -15
  111. package/dist/components/MessageList.d.ts +0 -10
  112. package/dist/components/MessageList.js +0 -80
  113. package/dist/components/ModelSelector.d.ts +0 -8
  114. package/dist/components/ModelSelector.js +0 -45
  115. package/dist/components/Notifications.d.ts +0 -7
  116. package/dist/components/Notifications.js +0 -9
  117. package/dist/components/PlanDisplay.d.ts +0 -7
  118. package/dist/components/PlanDisplay.js +0 -6
  119. package/dist/components/PluginDetail.d.ts +0 -2
  120. package/dist/components/PluginDetail.js +0 -87
  121. package/dist/components/PluginList.d.ts +0 -13
  122. package/dist/components/PluginList.js +0 -15
  123. package/dist/components/PluginManagerShell.d.ts +0 -6
  124. package/dist/components/PluginManagerShell.js +0 -92
  125. package/dist/components/PluginManagerTypes.d.ts +0 -35
  126. package/dist/components/PluginManagerTypes.js +0 -1
  127. package/dist/components/QueuedMessageList.d.ts +0 -2
  128. package/dist/components/QueuedMessageList.js +0 -18
  129. package/dist/components/ReasoningDisplay.d.ts +0 -8
  130. package/dist/components/ReasoningDisplay.js +0 -13
  131. package/dist/components/RewindCommand.d.ts +0 -12
  132. package/dist/components/RewindCommand.js +0 -78
  133. package/dist/components/SessionSelector.d.ts +0 -12
  134. package/dist/components/SessionSelector.js +0 -47
  135. package/dist/components/StatusCommand.d.ts +0 -5
  136. package/dist/components/StatusCommand.js +0 -28
  137. package/dist/components/StatusLine.d.ts +0 -6
  138. package/dist/components/StatusLine.js +0 -9
  139. package/dist/components/TaskList.d.ts +0 -5
  140. package/dist/components/TaskList.js +0 -197
  141. package/dist/components/TaskNotificationMessage.d.ts +0 -5
  142. package/dist/components/TaskNotificationMessage.js +0 -12
  143. package/dist/components/ToolDisplay.d.ts +0 -9
  144. package/dist/components/ToolDisplay.js +0 -55
  145. package/dist/components/WorkflowManager.d.ts +0 -5
  146. package/dist/components/WorkflowManager.js +0 -102
  147. package/dist/components/WorktreeExitPrompt.d.ts +0 -12
  148. package/dist/components/WorktreeExitPrompt.js +0 -35
  149. package/dist/constants/commands.d.ts +0 -2
  150. package/dist/constants/commands.js +0 -92
  151. package/dist/contexts/PluginManagerContext.d.ts +0 -3
  152. package/dist/contexts/PluginManagerContext.js +0 -9
  153. package/dist/contexts/useAppConfig.d.ts +0 -10
  154. package/dist/contexts/useAppConfig.js +0 -13
  155. package/dist/contexts/useChat.d.ts +0 -101
  156. package/dist/contexts/useChat.js +0 -993
  157. package/dist/contracts/status.d.ts +0 -7
  158. package/dist/contracts/status.js +0 -1
  159. package/dist/daemon/commands.d.ts +0 -49
  160. package/dist/daemon/commands.js +0 -341
  161. package/dist/daemon/jsonRpcClient.d.ts +0 -38
  162. package/dist/daemon/jsonRpcClient.js +0 -129
  163. package/dist/daemon/socketClient.d.ts +0 -13
  164. package/dist/daemon/socketClient.js +0 -26
  165. package/dist/daemon-cli.d.ts +0 -10
  166. package/dist/daemon-cli.js +0 -15
  167. package/dist/hooks/useInputManager.d.ts +0 -75
  168. package/dist/hooks/useInputManager.js +0 -583
  169. package/dist/hooks/usePluginManager.d.ts +0 -4
  170. package/dist/hooks/usePluginManager.js +0 -265
  171. package/dist/hooks/useTasks.d.ts +0 -1
  172. package/dist/hooks/useTasks.js +0 -5
  173. package/dist/index.d.ts +0 -4
  174. package/dist/index.js +0 -447
  175. package/dist/managers/inputHandlers.d.ts +0 -30
  176. package/dist/managers/inputHandlers.js +0 -625
  177. package/dist/managers/inputReducer.d.ts +0 -271
  178. package/dist/managers/inputReducer.js +0 -1121
  179. package/dist/plugin-manager-cli.d.ts +0 -5
  180. package/dist/plugin-manager-cli.js +0 -12
  181. package/dist/print-cli.d.ts +0 -10
  182. package/dist/print-cli.js +0 -209
  183. package/dist/reducers/agentsManagerReducer.d.ts +0 -26
  184. package/dist/reducers/agentsManagerReducer.js +0 -54
  185. package/dist/reducers/backgroundTaskManagerReducer.d.ts +0 -62
  186. package/dist/reducers/backgroundTaskManagerReducer.js +0 -106
  187. package/dist/reducers/confirmationReducer.d.ts +0 -37
  188. package/dist/reducers/confirmationReducer.js +0 -193
  189. package/dist/reducers/helpSelectorReducer.d.ts +0 -25
  190. package/dist/reducers/helpSelectorReducer.js +0 -52
  191. package/dist/reducers/marketplaceAddFormReducer.d.ts +0 -24
  192. package/dist/reducers/marketplaceAddFormReducer.js +0 -58
  193. package/dist/reducers/mcpManagerReducer.d.ts +0 -37
  194. package/dist/reducers/mcpManagerReducer.js +0 -74
  195. package/dist/reducers/pluginDetailReducer.d.ts +0 -32
  196. package/dist/reducers/pluginDetailReducer.js +0 -70
  197. package/dist/reducers/questionReducer.d.ts +0 -82
  198. package/dist/reducers/questionReducer.js +0 -375
  199. package/dist/reducers/rewindSelectorReducer.d.ts +0 -21
  200. package/dist/reducers/rewindSelectorReducer.js +0 -37
  201. package/dist/reducers/selectorReducer.d.ts +0 -23
  202. package/dist/reducers/selectorReducer.js +0 -42
  203. package/dist/reducers/workflowManagerReducer.d.ts +0 -40
  204. package/dist/reducers/workflowManagerReducer.js +0 -100
  205. package/dist/session-selector-cli.d.ts +0 -3
  206. package/dist/session-selector-cli.js +0 -25
  207. package/dist/stdio/agentBridge.d.ts +0 -139
  208. package/dist/stdio/agentBridge.js +0 -999
  209. package/dist/stdio/daemonServer.d.ts +0 -67
  210. package/dist/stdio/daemonServer.js +0 -191
  211. package/dist/stdio/index.d.ts +0 -5
  212. package/dist/stdio/index.js +0 -5
  213. package/dist/stdio/jsonRpcConnection.d.ts +0 -39
  214. package/dist/stdio/jsonRpcConnection.js +0 -135
  215. package/dist/stdio/protocol.d.ts +0 -41
  216. package/dist/stdio/protocol.js +0 -26
  217. package/dist/stdio/stdioServer.d.ts +0 -36
  218. package/dist/stdio/stdioServer.js +0 -51
  219. package/dist/stdio-cli.d.ts +0 -8
  220. package/dist/stdio-cli.js +0 -15
  221. package/dist/types.d.ts +0 -20
  222. package/dist/types.js +0 -1
  223. package/dist/utils/bracketedPaste.d.ts +0 -39
  224. package/dist/utils/bracketedPaste.js +0 -122
  225. package/dist/utils/clipboard.d.ts +0 -21
  226. package/dist/utils/clipboard.js +0 -347
  227. package/dist/utils/constants.d.ts +0 -16
  228. package/dist/utils/constants.js +0 -18
  229. package/dist/utils/highlightUtils.d.ts +0 -1
  230. package/dist/utils/highlightUtils.js +0 -93
  231. package/dist/utils/inputUtils.d.ts +0 -17
  232. package/dist/utils/inputUtils.js +0 -67
  233. package/dist/utils/logger.d.ts +0 -71
  234. package/dist/utils/logger.js +0 -259
  235. package/dist/utils/markdownTable.d.ts +0 -34
  236. package/dist/utils/markdownTable.js +0 -302
  237. package/dist/utils/rewindCheckpoints.d.ts +0 -8
  238. package/dist/utils/rewindCheckpoints.js +0 -17
  239. package/dist/utils/throttle.d.ts +0 -14
  240. package/dist/utils/throttle.js +0 -55
  241. package/dist/utils/toolParameterTransforms.d.ts +0 -18
  242. package/dist/utils/toolParameterTransforms.js +0 -73
  243. package/dist/utils/usageSummary.d.ts +0 -28
  244. package/dist/utils/usageSummary.js +0 -121
  245. package/dist/utils/version.d.ts +0 -1
  246. package/dist/utils/version.js +0 -4
  247. package/dist/utils/worktree.d.ts +0 -35
  248. package/dist/utils/worktree.js +0 -219
@@ -138,6 +138,7 @@ export interface InputState {
138
138
  showPluginManager: boolean;
139
139
  showModelSelector: boolean;
140
140
  showWorkflowManager: boolean;
141
+ showSkillsManager: boolean;
141
142
  permissionMode: PermissionMode;
142
143
  selectorJustUsed: boolean;
143
144
  history: PromptEntry[];
@@ -176,6 +177,7 @@ export const initialState: InputState = {
176
177
  showPluginManager: false,
177
178
  showModelSelector: false,
178
179
  showWorkflowManager: false,
180
+ showSkillsManager: false,
179
181
  permissionMode: "default",
180
182
  selectorJustUsed: false,
181
183
  history: [],
@@ -255,6 +257,53 @@ function insertTextWithPlaceholder(
255
257
  return newState;
256
258
  }
257
259
 
260
+ /**
261
+ * Delete a placeholder token ([LongText#N] / [Image #N]) as a whole block
262
+ * when the cursor sits at the token's end, aligned with Claude Code's
263
+ * Cursor.deleteTokenBefore (Cursor.ts:937-969): the token must be preceded
264
+ * by start-of-input or whitespace and followed by whitespace or EOL, so a
265
+ * mid-token cursor or a token glued to following text falls back to
266
+ * one-character deletion. Also drops the deleted token's longTextMap entry.
267
+ * Returns null when no token is at the cursor position.
268
+ */
269
+ function deleteTokenBefore(
270
+ inputText: string,
271
+ cursorPosition: number,
272
+ longTextMap: Record<string, string>,
273
+ ): {
274
+ inputText: string;
275
+ cursorPosition: number;
276
+ longTextMap: Record<string, string>;
277
+ } | null {
278
+ if (cursorPosition <= 0) {
279
+ return null;
280
+ }
281
+
282
+ // Word-boundary guard (aligned with CC): only trigger when the char after
283
+ // the cursor is whitespace or the end of the string.
284
+ const charAfter = inputText[cursorPosition];
285
+ if (charAfter !== undefined && !/\s/.test(charAfter)) {
286
+ return null;
287
+ }
288
+
289
+ const textBefore = inputText.slice(0, cursorPosition);
290
+ const tokenMatch = textBefore.match(/(^|\s)\[(LongText#\d+|Image #\d+)\]$/);
291
+ if (!tokenMatch) {
292
+ return null;
293
+ }
294
+
295
+ const tokenStart = tokenMatch.index! + tokenMatch[1]!.length;
296
+ const token = tokenMatch[0].slice(tokenMatch[1]!.length);
297
+ const newLongTextMap = { ...longTextMap };
298
+ delete newLongTextMap[token];
299
+
300
+ return {
301
+ inputText: inputText.slice(0, tokenStart) + inputText.slice(cursorPosition),
302
+ cursorPosition: tokenStart,
303
+ longTextMap: newLongTextMap,
304
+ };
305
+ }
306
+
258
307
  /**
259
308
  * Submit the current input text: extract [Image #N] references, route /btw
260
309
  * and CLI-internal slash commands, otherwise send as a message. Returns null
@@ -410,6 +459,7 @@ export type InputAction =
410
459
  | { type: "SET_SHOW_PLUGIN_MANAGER"; payload: boolean }
411
460
  | { type: "SET_SHOW_MODEL_SELECTOR"; payload: boolean }
412
461
  | { type: "SET_SHOW_WORKFLOW_MANAGER"; payload: boolean }
462
+ | { type: "SET_SHOW_SKILLS_MANAGER"; payload: boolean }
413
463
  | { type: "SET_PERMISSION_MODE"; payload: PermissionMode }
414
464
  | { type: "SET_SELECTOR_JUST_USED"; payload: boolean }
415
465
  | { type: "INSERT_TEXT_WITH_PLACEHOLDER"; payload: string }
@@ -640,6 +690,12 @@ export function inputReducer(
640
690
  showWorkflowManager: action.payload,
641
691
  selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
642
692
  };
693
+ case "SET_SHOW_SKILLS_MANAGER":
694
+ return {
695
+ ...state,
696
+ showSkillsManager: action.payload,
697
+ selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
698
+ };
643
699
  case "SET_PERMISSION_MODE":
644
700
  return { ...state, permissionMode: action.payload };
645
701
  case "SET_SELECTOR_JUST_USED":
@@ -823,7 +879,9 @@ export function inputReducer(
823
879
  // chunk (e.g. "\x7f\x7f") that ink cannot parse into a key event, so it
824
880
  // arrives as raw input and would otherwise be treated as a paste and
825
881
  // inserted literally. Treat each DEL as a synchronous backspace instead
826
- // (aligned with Claude Code Issue #1853).
882
+ // (aligned with Claude Code Issue #1853). Each DEL deletes a placeholder
883
+ // token as a whole block first, falling back to one character (aligned
884
+ // with Claude Code's useTextInput.ts:442-465).
827
885
  if (!key.backspace && !key.delete && input.includes("\x7f")) {
828
886
  const delCount = (input.match(/\x7f/g) || []).length;
829
887
 
@@ -834,74 +892,92 @@ export function inputReducer(
834
892
  };
835
893
  }
836
894
 
837
- if (state.cursorPosition > 0) {
838
- const newCursorPosition = Math.max(
839
- 0,
840
- state.cursorPosition - delCount,
841
- );
842
- const newInputText =
843
- state.inputText.substring(0, newCursorPosition) +
844
- state.inputText.substring(state.cursorPosition);
845
-
846
- const newState = {
847
- ...state,
848
- inputText: newInputText,
849
- cursorPosition: newCursorPosition,
850
- historyIndex: -1,
851
- };
852
-
853
- // Deactivate selectors if their trigger character was deleted
854
- if (
855
- newState.showFileSelector &&
856
- newCursorPosition <= newState.atPosition
857
- ) {
858
- newState.showFileSelector = false;
859
- newState.atPosition = -1;
860
- newState.fileSearchQuery = "";
861
- newState.isFileSearching = false;
862
- }
863
- if (
864
- newState.showCommandSelector &&
865
- newCursorPosition <= newState.slashPosition
866
- ) {
867
- newState.showCommandSelector = false;
868
- newState.slashPosition = -1;
869
- newState.commandSearchQuery = "";
870
- }
871
-
872
- // Reactivate selectors if cursor is within a trigger word
873
- const atPos = getAtSelectorPosition(newInputText, newCursorPosition);
874
- if (atPos !== -1 && !state.showFileSelector) {
875
- newState.showFileSelector = true;
876
- newState.atPosition = atPos;
877
- newState.isFileSearching = true;
878
- }
879
- const slashPos = getSlashSelectorPosition(
895
+ let newInputText = state.inputText;
896
+ let newCursorPosition = state.cursorPosition;
897
+ let newLongTextMap = state.longTextMap;
898
+ for (let i = 0; i < delCount; i++) {
899
+ const tokenDeletion = deleteTokenBefore(
880
900
  newInputText,
881
901
  newCursorPosition,
902
+ newLongTextMap,
882
903
  );
883
- if (slashPos !== -1 && !state.showCommandSelector) {
884
- newState.showCommandSelector = true;
885
- newState.slashPosition = slashPos;
904
+ if (tokenDeletion) {
905
+ newInputText = tokenDeletion.inputText;
906
+ newCursorPosition = tokenDeletion.cursorPosition;
907
+ newLongTextMap = tokenDeletion.longTextMap;
908
+ } else if (newCursorPosition > 0) {
909
+ newInputText =
910
+ newInputText.substring(0, newCursorPosition - 1) +
911
+ newInputText.substring(newCursorPosition);
912
+ newCursorPosition -= 1;
886
913
  }
914
+ }
887
915
 
888
- // Update queries
889
- if (newState.showFileSelector && newState.atPosition >= 0) {
890
- newState.fileSearchQuery = newInputText.substring(
891
- newState.atPosition + 1,
892
- newCursorPosition,
893
- );
894
- }
895
- if (newState.showCommandSelector && newState.slashPosition >= 0) {
896
- newState.commandSearchQuery = newInputText.substring(
897
- newState.slashPosition + 1,
898
- newCursorPosition,
899
- );
900
- }
916
+ if (
917
+ newInputText === state.inputText &&
918
+ newCursorPosition === state.cursorPosition
919
+ ) {
920
+ return state;
921
+ }
901
922
 
902
- return newState;
923
+ const newState = {
924
+ ...state,
925
+ inputText: newInputText,
926
+ cursorPosition: newCursorPosition,
927
+ longTextMap: newLongTextMap,
928
+ historyIndex: -1,
929
+ };
930
+
931
+ // Deactivate selectors if their trigger character was deleted
932
+ if (
933
+ newState.showFileSelector &&
934
+ newCursorPosition <= newState.atPosition
935
+ ) {
936
+ newState.showFileSelector = false;
937
+ newState.atPosition = -1;
938
+ newState.fileSearchQuery = "";
939
+ newState.isFileSearching = false;
903
940
  }
904
- return state;
941
+ if (
942
+ newState.showCommandSelector &&
943
+ newCursorPosition <= newState.slashPosition
944
+ ) {
945
+ newState.showCommandSelector = false;
946
+ newState.slashPosition = -1;
947
+ newState.commandSearchQuery = "";
948
+ }
949
+
950
+ // Reactivate selectors if cursor is within a trigger word
951
+ const atPos = getAtSelectorPosition(newInputText, newCursorPosition);
952
+ if (atPos !== -1 && !state.showFileSelector) {
953
+ newState.showFileSelector = true;
954
+ newState.atPosition = atPos;
955
+ newState.isFileSearching = true;
956
+ }
957
+ const slashPos = getSlashSelectorPosition(
958
+ newInputText,
959
+ newCursorPosition,
960
+ );
961
+ if (slashPos !== -1 && !state.showCommandSelector) {
962
+ newState.showCommandSelector = true;
963
+ newState.slashPosition = slashPos;
964
+ }
965
+
966
+ // Update queries
967
+ if (newState.showFileSelector && newState.atPosition >= 0) {
968
+ newState.fileSearchQuery = newInputText.substring(
969
+ newState.atPosition + 1,
970
+ newCursorPosition,
971
+ );
972
+ }
973
+ if (newState.showCommandSelector && newState.slashPosition >= 0) {
974
+ newState.commandSearchQuery = newInputText.substring(
975
+ newState.slashPosition + 1,
976
+ newCursorPosition,
977
+ );
978
+ }
979
+
980
+ return newState;
905
981
  }
906
982
 
907
983
  // 1. /btw overlay handling (active while a question is displayed, or
@@ -1425,54 +1501,69 @@ export function inputReducer(
1425
1501
 
1426
1502
  // 8. Backspace / Delete (Normal Mode)
1427
1503
  if (key.backspace || key.delete) {
1428
- if (state.cursorPosition > 0) {
1429
- const newCursorPosition = state.cursorPosition - 1;
1430
- const beforeCursor = state.inputText.substring(
1431
- 0,
1432
- state.cursorPosition - 1,
1433
- );
1434
- const afterCursor = state.inputText.substring(state.cursorPosition);
1435
- const newInputText = beforeCursor + afterCursor;
1504
+ // Placeholder token deletion first (aligned with Claude Code's
1505
+ // deleteTokenBefore, Cursor.ts:937-969): backspace at the end of a
1506
+ // [LongText#N] / [Image #N] token removes the whole token in one
1507
+ // press (and drops its longTextMap entry); otherwise delete one
1508
+ // character. Selector backspace (section 5) stays character-wise
1509
+ // @mention and /command words are intentionally not tokenized.
1510
+ const tokenDeletion = deleteTokenBefore(
1511
+ state.inputText,
1512
+ state.cursorPosition,
1513
+ state.longTextMap,
1514
+ );
1515
+ if (!tokenDeletion && state.cursorPosition <= 0) {
1516
+ return state;
1517
+ }
1518
+ const newInputText = tokenDeletion
1519
+ ? tokenDeletion.inputText
1520
+ : state.inputText.substring(0, state.cursorPosition - 1) +
1521
+ state.inputText.substring(state.cursorPosition);
1522
+ const newCursorPosition = tokenDeletion
1523
+ ? tokenDeletion.cursorPosition
1524
+ : state.cursorPosition - 1;
1436
1525
 
1437
- const newState = {
1438
- ...state,
1439
- inputText: newInputText,
1440
- cursorPosition: newCursorPosition,
1441
- historyIndex: -1,
1442
- };
1526
+ const newState = {
1527
+ ...state,
1528
+ inputText: newInputText,
1529
+ cursorPosition: newCursorPosition,
1530
+ longTextMap: tokenDeletion
1531
+ ? tokenDeletion.longTextMap
1532
+ : state.longTextMap,
1533
+ historyIndex: -1,
1534
+ };
1443
1535
 
1444
- // Reactivate selectors if cursor is within word
1445
- const atPos = getAtSelectorPosition(newInputText, newCursorPosition);
1446
- if (atPos !== -1 && !state.showFileSelector) {
1447
- newState.showFileSelector = true;
1448
- newState.atPosition = atPos;
1449
- newState.isFileSearching = true;
1450
- }
1536
+ // Reactivate selectors if cursor is within word
1537
+ const atPos = getAtSelectorPosition(newInputText, newCursorPosition);
1538
+ if (atPos !== -1 && !state.showFileSelector) {
1539
+ newState.showFileSelector = true;
1540
+ newState.atPosition = atPos;
1541
+ newState.isFileSearching = true;
1542
+ }
1451
1543
 
1452
- const slashPos = getSlashSelectorPosition(
1453
- newInputText,
1544
+ const slashPos = getSlashSelectorPosition(
1545
+ newInputText,
1546
+ newCursorPosition,
1547
+ );
1548
+ if (slashPos !== -1 && !state.showCommandSelector) {
1549
+ newState.showCommandSelector = true;
1550
+ newState.slashPosition = slashPos;
1551
+ }
1552
+
1553
+ // Update queries
1554
+ if (newState.showFileSelector && newState.atPosition >= 0) {
1555
+ newState.fileSearchQuery = newInputText.substring(
1556
+ newState.atPosition + 1,
1557
+ newCursorPosition,
1558
+ );
1559
+ }
1560
+ if (newState.showCommandSelector && newState.slashPosition >= 0) {
1561
+ newState.commandSearchQuery = newInputText.substring(
1562
+ newState.slashPosition + 1,
1454
1563
  newCursorPosition,
1455
1564
  );
1456
- if (slashPos !== -1 && !state.showCommandSelector) {
1457
- newState.showCommandSelector = true;
1458
- newState.slashPosition = slashPos;
1459
- }
1460
-
1461
- // Update queries
1462
- if (newState.showFileSelector && newState.atPosition >= 0) {
1463
- newState.fileSearchQuery = newInputText.substring(
1464
- newState.atPosition + 1,
1465
- newCursorPosition,
1466
- );
1467
- }
1468
- if (newState.showCommandSelector && newState.slashPosition >= 0) {
1469
- newState.commandSearchQuery = newInputText.substring(
1470
- newState.slashPosition + 1,
1471
- newCursorPosition,
1472
- );
1473
- }
1474
- return newState;
1475
1565
  }
1566
+ return newState;
1476
1567
  }
1477
1568
 
1478
1569
  // 9. Cursor Movement (Normal Mode)
package/src/print-cli.ts CHANGED
@@ -147,6 +147,7 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
147
147
  worktreeName: worktreeSession.name,
148
148
  isNew: worktreeSession.isNew,
149
149
  repoRoot: worktreeSession.repoRoot,
150
+ hookBased: worktreeSession.hookBased,
150
151
  };
151
152
  agent.setWorktreeSession(session);
152
153
  }
@@ -186,8 +187,7 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
186
187
  // Display timing information
187
188
  displayTimingInfo(startTime, showStats);
188
189
 
189
- // Trigger WorktreeRemove hook (before destroy it needs a live agent) and
190
- // decide whether the worktree is clean enough to remove
190
+ // Decide whether the worktree is clean enough to remove
191
191
  let cleanWorktree = false;
192
192
  if (worktreeSession) {
193
193
  const cwd = workdir || worktreeSession.path;
@@ -196,9 +196,7 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
196
196
  const hasCommits = hasNewCommits(cwd, baseBranch);
197
197
  cleanWorktree = !hasChanges && !hasCommits;
198
198
 
199
- if (cleanWorktree) {
200
- await agent.triggerWorktreeRemoveHook(worktreeSession.path);
201
- } else {
199
+ if (!cleanWorktree) {
202
200
  process.stdout.write(
203
201
  `\n⚠️ Worktree '${worktreeSession.name}' has changes or commits. Keeping it at: ${worktreeSession.path}\n`,
204
202
  );
@@ -211,11 +209,17 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
211
209
  // Handle worktree cleanup for print mode (git removal stays after destroy)
212
210
  if (worktreeSession && cleanWorktree) {
213
211
  try {
214
- validateWorktreeRemovalPath(
215
- worktreeSession.path,
216
- worktreeSession.repoRoot,
217
- );
212
+ // Hook-based worktrees are owned by the hook; the git-root
213
+ // containment check does not apply to them
214
+ if (!worktreeSession.hookBased) {
215
+ validateWorktreeRemovalPath(
216
+ worktreeSession.path,
217
+ worktreeSession.repoRoot,
218
+ );
219
+ }
220
+ process.stdout.write("\nDeleting worktree ...\n");
218
221
  await removeWorktree(worktreeSession);
222
+ process.stdout.write("Done.\n");
219
223
  } catch (error) {
220
224
  // Never block print-mode exit on worktree cleanup failures
221
225
  process.stdout.write(
@@ -242,7 +246,7 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
242
246
  // Display timing information even on error
243
247
  displayTimingInfo(startTime, showStats);
244
248
 
245
- // Trigger WorktreeRemove hook (before destroy) when the worktree is clean
249
+ // Decide whether the worktree is clean enough to remove
246
250
  let cleanWorktree = false;
247
251
  if (worktreeSession) {
248
252
  const cwd = workdir || worktreeSession.path;
@@ -251,9 +255,7 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
251
255
  const hasCommits = hasNewCommits(cwd, baseBranch);
252
256
  cleanWorktree = !hasChanges && !hasCommits;
253
257
 
254
- if (cleanWorktree) {
255
- await agent.triggerWorktreeRemoveHook(worktreeSession.path);
256
- } else {
258
+ if (!cleanWorktree) {
257
259
  process.stdout.write(
258
260
  `\n⚠️ Worktree '${worktreeSession.name}' has changes or commits. Keeping it at: ${worktreeSession.path}\n`,
259
261
  );
@@ -265,11 +267,17 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
265
267
  // Handle worktree cleanup for print mode even on error
266
268
  if (worktreeSession && cleanWorktree) {
267
269
  try {
268
- validateWorktreeRemovalPath(
269
- worktreeSession.path,
270
- worktreeSession.repoRoot,
271
- );
270
+ // Hook-based worktrees are owned by the hook; the git-root
271
+ // containment check does not apply to them
272
+ if (!worktreeSession.hookBased) {
273
+ validateWorktreeRemovalPath(
274
+ worktreeSession.path,
275
+ worktreeSession.repoRoot,
276
+ );
277
+ }
278
+ process.stdout.write("\nDeleting worktree ...\n");
272
279
  await removeWorktree(worktreeSession);
280
+ process.stdout.write("Done.\n");
273
281
  } catch (error) {
274
282
  process.stdout.write(
275
283
  `\n⚠️ Skipping worktree removal: ${(error as Error).message}\n`,
@@ -4,6 +4,10 @@ export interface SelectorState<T = unknown> {
4
4
  selectedIndex: number;
5
5
  pendingDecision: "select" | "insert" | "cancel" | null;
6
6
  items: T[];
7
+ /** View state: show sessions from all projects (Ctrl+A toggle). */
8
+ showAllProjects?: boolean;
9
+ /** View state: show sessions from all same-repo worktrees (Ctrl+W toggle). */
10
+ showAllWorktrees?: boolean;
7
11
  }
8
12
 
9
13
  export type SelectorAction<T = unknown> =
@@ -12,6 +16,8 @@ export type SelectorAction<T = unknown> =
12
16
  | { type: "RESET_INDEX" }
13
17
  | { type: "SET_ITEMS"; items: T[] }
14
18
  | { type: "HANDLE_KEY"; key: Key; hasInsert: boolean }
19
+ | { type: "TOGGLE_ALL_PROJECTS" }
20
+ | { type: "TOGGLE_ALL_WORKTREES" }
15
21
  | { type: "CLEAR_DECISION" };
16
22
 
17
23
  export function selectorReducer<T = unknown>(
@@ -33,6 +39,10 @@ export function selectorReducer<T = unknown>(
33
39
  return { ...state, selectedIndex: 0 };
34
40
  case "SET_ITEMS":
35
41
  return { ...state, items: action.items, selectedIndex: 0 };
42
+ case "TOGGLE_ALL_PROJECTS":
43
+ return { ...state, showAllProjects: !state.showAllProjects };
44
+ case "TOGGLE_ALL_WORKTREES":
45
+ return { ...state, showAllWorktrees: !state.showAllWorktrees };
36
46
  case "HANDLE_KEY":
37
47
  if (action.key.upArrow) {
38
48
  return {
@@ -0,0 +1,91 @@
1
+ import { Key } from "ink";
2
+
3
+ export type PendingEffect = { type: "CANCEL" };
4
+
5
+ export interface SkillsManagerState {
6
+ selectedIndex: number;
7
+ viewMode: "list" | "detail";
8
+ pendingEffect: PendingEffect | null;
9
+ }
10
+
11
+ export type SkillsManagerAction =
12
+ | { type: "MOVE_UP" }
13
+ | { type: "MOVE_DOWN"; itemCount: number }
14
+ | { type: "SET_VIEW_MODE"; viewMode: "list" | "detail" }
15
+ | {
16
+ type: "HANDLE_KEY";
17
+ input: string;
18
+ key: Key;
19
+ itemCount: number;
20
+ }
21
+ | { type: "CLEAR_PENDING_EFFECT" };
22
+
23
+ export function skillsManagerReducer(
24
+ state: SkillsManagerState,
25
+ action: SkillsManagerAction,
26
+ ): SkillsManagerState {
27
+ switch (action.type) {
28
+ case "MOVE_UP":
29
+ return {
30
+ ...state,
31
+ selectedIndex: Math.max(0, state.selectedIndex - 1),
32
+ };
33
+ case "MOVE_DOWN":
34
+ return {
35
+ ...state,
36
+ selectedIndex: Math.min(
37
+ Math.max(0, action.itemCount - 1),
38
+ state.selectedIndex + 1,
39
+ ),
40
+ };
41
+ case "SET_VIEW_MODE":
42
+ return { ...state, viewMode: action.viewMode };
43
+ case "HANDLE_KEY": {
44
+ const { key, itemCount } = action;
45
+
46
+ if (key.return) {
47
+ if (state.viewMode === "list") {
48
+ return { ...state, viewMode: "detail" };
49
+ }
50
+ // Aligned with AgentsManager detail view: Enter returns to the list.
51
+ return { ...state, viewMode: "list" };
52
+ }
53
+
54
+ if (key.escape) {
55
+ if (state.viewMode === "detail") {
56
+ return { ...state, viewMode: "list" };
57
+ }
58
+ return { ...state, pendingEffect: { type: "CANCEL" } };
59
+ }
60
+
61
+ // Detail view does not respond to arrow keys (aligned with
62
+ // AgentsManager, which only Esc/Enter back to the list).
63
+ if (state.viewMode === "detail") {
64
+ return state;
65
+ }
66
+
67
+ if (key.upArrow) {
68
+ return {
69
+ ...state,
70
+ selectedIndex: Math.max(0, state.selectedIndex - 1),
71
+ };
72
+ }
73
+
74
+ if (key.downArrow) {
75
+ return {
76
+ ...state,
77
+ selectedIndex: Math.min(
78
+ Math.max(0, itemCount - 1),
79
+ state.selectedIndex + 1,
80
+ ),
81
+ };
82
+ }
83
+
84
+ return state;
85
+ }
86
+ case "CLEAR_PENDING_EFFECT":
87
+ return { ...state, pendingEffect: null };
88
+ default:
89
+ return state;
90
+ }
91
+ }