centaurus-cli 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (552) hide show
  1. package/dist/ai/types.js +0 -1
  2. package/dist/ai/types.js.map +1 -1
  3. package/dist/cli-adapter.js +5047 -5037
  4. package/dist/cli-adapter.js.map +1 -1
  5. package/dist/commands/CommandParser.js +372 -315
  6. package/dist/commands/CommandParser.js.map +1 -1
  7. package/dist/config/build-config.js +11 -42
  8. package/dist/config/build-config.js.map +1 -1
  9. package/dist/config/defaultConfig.js +94 -82
  10. package/dist/config/defaultConfig.js.map +1 -1
  11. package/dist/config/manager.js +144 -160
  12. package/dist/config/manager.js.map +1 -1
  13. package/dist/config/mcp-config-manager.js +411 -364
  14. package/dist/config/mcp-config-manager.js.map +1 -1
  15. package/dist/config/models.js +118 -185
  16. package/dist/config/models.js.map +1 -1
  17. package/dist/config/slash-commands.js +186 -184
  18. package/dist/config/slash-commands.js.map +1 -1
  19. package/dist/config/types.js +33 -26
  20. package/dist/config/types.js.map +1 -1
  21. package/dist/context/command-detector.js +63 -67
  22. package/dist/context/command-detector.js.map +1 -1
  23. package/dist/context/context-manager.js +533 -518
  24. package/dist/context/context-manager.js.map +1 -1
  25. package/dist/context/handlers/docker-handler.js +518 -576
  26. package/dist/context/handlers/docker-handler.js.map +1 -1
  27. package/dist/context/handlers/ssh-handler.js +1050 -1109
  28. package/dist/context/handlers/ssh-handler.js.map +1 -1
  29. package/dist/context/handlers/wsl-handler.js +558 -630
  30. package/dist/context/handlers/wsl-handler.js.map +1 -1
  31. package/dist/context/index.js +42 -6
  32. package/dist/context/index.js.map +1 -1
  33. package/dist/context/subshell-handler.js +0 -4
  34. package/dist/context/subshell-handler.js.map +1 -1
  35. package/dist/context/types.js +20 -31
  36. package/dist/context/types.js.map +1 -1
  37. package/dist/hooks/useConnectivity.js +13 -10
  38. package/dist/hooks/useConnectivity.js.map +1 -1
  39. package/dist/hooks/useTerminalDimensions.js +67 -79
  40. package/dist/hooks/useTerminalDimensions.js.map +1 -1
  41. package/dist/index.js +228 -251
  42. package/dist/index.js.map +1 -1
  43. package/dist/mcp/mcp-command-handler.js +297 -260
  44. package/dist/mcp/mcp-command-handler.js.map +1 -1
  45. package/dist/mcp/mcp-server-manager.js +139 -155
  46. package/dist/mcp/mcp-server-manager.js.map +1 -1
  47. package/dist/mcp/mcp-tool-wrapper.js +74 -94
  48. package/dist/mcp/mcp-tool-wrapper.js.map +1 -1
  49. package/dist/services/ai-autocomplete-agent.js +169 -181
  50. package/dist/services/ai-autocomplete-agent.js.map +1 -1
  51. package/dist/services/ai-context-injector.js +180 -93
  52. package/dist/services/ai-context-injector.js.map +1 -1
  53. package/dist/services/ai-service-client.js +513 -456
  54. package/dist/services/ai-service-client.js.map +1 -1
  55. package/dist/services/api-client.js +443 -441
  56. package/dist/services/api-client.js.map +1 -1
  57. package/dist/services/auth-handler.js +162 -198
  58. package/dist/services/auth-handler.js.map +1 -1
  59. package/dist/services/background-task-manager.js +258 -282
  60. package/dist/services/background-task-manager.js.map +1 -1
  61. package/dist/services/checkpoint-manager.js +1513 -973
  62. package/dist/services/checkpoint-manager.js.map +1 -1
  63. package/dist/services/clipboard-service.js +151 -200
  64. package/dist/services/clipboard-service.js.map +1 -1
  65. package/dist/services/connectivity-manager.js +63 -65
  66. package/dist/services/connectivity-manager.js.map +1 -1
  67. package/dist/services/conversation-manager.js +118 -121
  68. package/dist/services/conversation-manager.js.map +1 -1
  69. package/dist/services/environment-context-injector.js +160 -187
  70. package/dist/services/environment-context-injector.js.map +1 -1
  71. package/dist/services/fast-context-agent.js +203 -243
  72. package/dist/services/fast-context-agent.js.map +1 -1
  73. package/dist/services/input-detection-agent.js +190 -202
  74. package/dist/services/input-detection-agent.js.map +1 -1
  75. package/dist/services/input-requirement-detector.js +155 -189
  76. package/dist/services/input-requirement-detector.js.map +1 -1
  77. package/dist/services/local-chat-storage.js +342 -365
  78. package/dist/services/local-chat-storage.js.map +1 -1
  79. package/dist/services/monitored-shell-manager.js +225 -233
  80. package/dist/services/monitored-shell-manager.js.map +1 -1
  81. package/dist/services/ollama-service.js +293 -310
  82. package/dist/services/ollama-service.js.map +1 -1
  83. package/dist/services/rules-storage.js +142 -0
  84. package/dist/services/rules-storage.js.map +1 -0
  85. package/dist/services/session-quota-manager.js +219 -235
  86. package/dist/services/session-quota-manager.js.map +1 -1
  87. package/dist/services/shell-input-agent.js +299 -334
  88. package/dist/services/shell-input-agent.js.map +1 -1
  89. package/dist/services/sub-agent-manager.js +459 -501
  90. package/dist/services/sub-agent-manager.js.map +1 -1
  91. package/dist/services/warpify-detector.js +133 -183
  92. package/dist/services/warpify-detector.js.map +1 -1
  93. package/dist/services/workflow-storage.js +202 -217
  94. package/dist/services/workflow-storage.js.map +1 -1
  95. package/dist/test-ssh-handler.js +148 -193
  96. package/dist/test-ssh-handler.js.map +1 -1
  97. package/dist/tools/add-mcp.js +161 -0
  98. package/dist/tools/add-mcp.js.map +1 -0
  99. package/dist/tools/background-command.js +240 -273
  100. package/dist/tools/background-command.js.map +1 -1
  101. package/dist/tools/command.js +447 -440
  102. package/dist/tools/command.js.map +1 -1
  103. package/dist/tools/create-image.js +172 -202
  104. package/dist/tools/create-image.js.map +1 -1
  105. package/dist/tools/enter-remote-session.js +169 -215
  106. package/dist/tools/enter-remote-session.js.map +1 -1
  107. package/dist/tools/fast-context.js +60 -67
  108. package/dist/tools/fast-context.js.map +1 -1
  109. package/dist/tools/file-ops.js +605 -537
  110. package/dist/tools/file-ops.js.map +1 -1
  111. package/dist/tools/find-files.js +262 -303
  112. package/dist/tools/find-files.js.map +1 -1
  113. package/dist/tools/get-diff.js +423 -400
  114. package/dist/tools/get-diff.js.map +1 -1
  115. package/dist/tools/grep-search.js +966 -948
  116. package/dist/tools/grep-search.js.map +1 -1
  117. package/dist/tools/inspect-symbol.js +308 -323
  118. package/dist/tools/inspect-symbol.js.map +1 -1
  119. package/dist/tools/plan-mode.js +459 -503
  120. package/dist/tools/plan-mode.js.map +1 -1
  121. package/dist/tools/read-binary-file.js +160 -190
  122. package/dist/tools/read-binary-file.js.map +1 -1
  123. package/dist/tools/registry.js +100 -84
  124. package/dist/tools/registry.js.map +1 -1
  125. package/dist/tools/reproduce_issue.js +170 -151
  126. package/dist/tools/reproduce_issue.js.map +1 -1
  127. package/dist/tools/sub-agent.js +223 -228
  128. package/dist/tools/sub-agent.js.map +1 -1
  129. package/dist/tools/task-complete.js +28 -27
  130. package/dist/tools/task-complete.js.map +1 -1
  131. package/dist/tools/types.js +0 -1
  132. package/dist/tools/types.js.map +1 -1
  133. package/dist/tools/validation.js +96 -118
  134. package/dist/tools/validation.js.map +1 -1
  135. package/dist/tools/web-search.js +194 -194
  136. package/dist/tools/web-search.js.map +1 -1
  137. package/dist/tools/workflow-tool.js +77 -82
  138. package/dist/tools/workflow-tool.js.map +1 -1
  139. package/dist/types/index.js +0 -1
  140. package/dist/types/index.js.map +1 -1
  141. package/dist/types/rule.js +1 -0
  142. package/dist/types/rule.js.map +1 -0
  143. package/dist/types/workflow.js +0 -7
  144. package/dist/types/workflow.js.map +1 -1
  145. package/dist/ui/components/AgentTimer.js +24 -25
  146. package/dist/ui/components/AgentTimer.js.map +1 -1
  147. package/dist/ui/components/App.js +3266 -3249
  148. package/dist/ui/components/App.js.map +1 -1
  149. package/dist/ui/components/AuthScreen.js +22 -34
  150. package/dist/ui/components/AuthScreen.js.map +1 -1
  151. package/dist/ui/components/AuthWelcomeScreen.js +30 -24
  152. package/dist/ui/components/AuthWelcomeScreen.js.map +1 -1
  153. package/dist/ui/components/Breadcrumbs.js +53 -82
  154. package/dist/ui/components/Breadcrumbs.js.map +1 -1
  155. package/dist/ui/components/CircularSelectInput.js +59 -67
  156. package/dist/ui/components/CircularSelectInput.js.map +1 -1
  157. package/dist/ui/components/ClipboardFileAutocomplete.js +78 -39
  158. package/dist/ui/components/ClipboardFileAutocomplete.js.map +1 -1
  159. package/dist/ui/components/CodeBlock.js +24 -42
  160. package/dist/ui/components/CodeBlock.js.map +1 -1
  161. package/dist/ui/components/ConfigViewer.js +18 -25
  162. package/dist/ui/components/ConfigViewer.js.map +1 -1
  163. package/dist/ui/components/ConfirmPrompt.js +49 -71
  164. package/dist/ui/components/ConfirmPrompt.js.map +1 -1
  165. package/dist/ui/components/ConnectionStatusMessage.js +32 -83
  166. package/dist/ui/components/ConnectionStatusMessage.js.map +1 -1
  167. package/dist/ui/components/ContextWindowIndicator.js +34 -49
  168. package/dist/ui/components/ContextWindowIndicator.js.map +1 -1
  169. package/dist/ui/components/DetailedPlanReviewScreen.js +104 -106
  170. package/dist/ui/components/DetailedPlanReviewScreen.js.map +1 -1
  171. package/dist/ui/components/DiffViewer.js +68 -121
  172. package/dist/ui/components/DiffViewer.js.map +1 -1
  173. package/dist/ui/components/ErrorBoundary.js +40 -48
  174. package/dist/ui/components/ErrorBoundary.js.map +1 -1
  175. package/dist/ui/components/FileCreationPreview.js +29 -60
  176. package/dist/ui/components/FileCreationPreview.js.map +1 -1
  177. package/dist/ui/components/FileOperation.js +34 -29
  178. package/dist/ui/components/FileOperation.js.map +1 -1
  179. package/dist/ui/components/FileTagAutocomplete.js +55 -25
  180. package/dist/ui/components/FileTagAutocomplete.js.map +1 -1
  181. package/dist/ui/components/FontRecommendation.js.map +1 -1
  182. package/dist/ui/components/GitDiffBreadcrumb.js +29 -0
  183. package/dist/ui/components/GitDiffBreadcrumb.js.map +1 -0
  184. package/dist/ui/components/InputBox.js +1620 -2150
  185. package/dist/ui/components/InputBox.js.map +1 -1
  186. package/dist/ui/components/InteractiveShell.js +234 -352
  187. package/dist/ui/components/InteractiveShell.js.map +1 -1
  188. package/dist/ui/components/KeyboardHelp.js +34 -35
  189. package/dist/ui/components/KeyboardHelp.js.map +1 -1
  190. package/dist/ui/components/LoadingIndicator.js +22 -25
  191. package/dist/ui/components/LoadingIndicator.js.map +1 -1
  192. package/dist/ui/components/MCPAddScreen.js +40 -51
  193. package/dist/ui/components/MCPAddScreen.js.map +1 -1
  194. package/dist/ui/components/MCPListScreen.js +40 -48
  195. package/dist/ui/components/MCPListScreen.js.map +1 -1
  196. package/dist/ui/components/MCPServerListScreen.js +49 -56
  197. package/dist/ui/components/MCPServerListScreen.js.map +1 -1
  198. package/dist/ui/components/MarkdownRenderer.js +69 -96
  199. package/dist/ui/components/MarkdownRenderer.js.map +1 -1
  200. package/dist/ui/components/MessageBox.js +66 -48
  201. package/dist/ui/components/MessageBox.js.map +1 -1
  202. package/dist/ui/components/MessageDisplay.js +150 -142
  203. package/dist/ui/components/MessageDisplay.js.map +1 -1
  204. package/dist/ui/components/MonitorModeAIPanel.js +46 -65
  205. package/dist/ui/components/MonitorModeAIPanel.js.map +1 -1
  206. package/dist/ui/components/MultiLineInput.js +243 -277
  207. package/dist/ui/components/MultiLineInput.js.map +1 -1
  208. package/dist/ui/components/PasswordPrompt.js +37 -18
  209. package/dist/ui/components/PasswordPrompt.js.map +1 -1
  210. package/dist/ui/components/PlanAcceptedMessage.js +27 -38
  211. package/dist/ui/components/PlanAcceptedMessage.js.map +1 -1
  212. package/dist/ui/components/PlanReviewScreen.js +46 -50
  213. package/dist/ui/components/PlanReviewScreen.js.map +1 -1
  214. package/dist/ui/components/RulesEditorScreen.js +81 -0
  215. package/dist/ui/components/RulesEditorScreen.js.map +1 -0
  216. package/dist/ui/components/SelectPrompt.js +19 -8
  217. package/dist/ui/components/SelectPrompt.js.map +1 -1
  218. package/dist/ui/components/ShimmerText.js +44 -0
  219. package/dist/ui/components/ShimmerText.js.map +1 -0
  220. package/dist/ui/components/SlashCommandAutocomplete.js +49 -22
  221. package/dist/ui/components/SlashCommandAutocomplete.js.map +1 -1
  222. package/dist/ui/components/StatusBar.js +56 -87
  223. package/dist/ui/components/StatusBar.js.map +1 -1
  224. package/dist/ui/components/StreamingMessageDisplay.js +116 -99
  225. package/dist/ui/components/StreamingMessageDisplay.js.map +1 -1
  226. package/dist/ui/components/TaskCompletedMessage.js +28 -23
  227. package/dist/ui/components/TaskCompletedMessage.js.map +1 -1
  228. package/dist/ui/components/TaskProgressIndicator.js +44 -70
  229. package/dist/ui/components/TaskProgressIndicator.js.map +1 -1
  230. package/dist/ui/components/ThinkingDisplay.js +44 -41
  231. package/dist/ui/components/ThinkingDisplay.js.map +1 -1
  232. package/dist/ui/components/ToolExecutionMessage.js +772 -1326
  233. package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
  234. package/dist/ui/components/ToolExecutionStatus.js +53 -84
  235. package/dist/ui/components/ToolExecutionStatus.js.map +1 -1
  236. package/dist/ui/components/ToolResult.js +22 -15
  237. package/dist/ui/components/ToolResult.js.map +1 -1
  238. package/dist/ui/components/VersionUpdatePrompt.js +88 -120
  239. package/dist/ui/components/VersionUpdatePrompt.js.map +1 -1
  240. package/dist/ui/components/WelcomeBanner.js +176 -26
  241. package/dist/ui/components/WelcomeBanner.js.map +1 -1
  242. package/dist/ui/components/WorkflowCreatorScreen.js +94 -161
  243. package/dist/ui/components/WorkflowCreatorScreen.js.map +1 -1
  244. package/dist/utils/ansi-encoder.js +30 -61
  245. package/dist/utils/ansi-encoder.js.map +1 -1
  246. package/dist/utils/chat-formatter.js +327 -305
  247. package/dist/utils/chat-formatter.js.map +1 -1
  248. package/dist/utils/command-history.js +152 -174
  249. package/dist/utils/command-history.js.map +1 -1
  250. package/dist/utils/context-sanitizer.js +49 -112
  251. package/dist/utils/context-sanitizer.js.map +1 -1
  252. package/dist/utils/conversation-logger.js +292 -324
  253. package/dist/utils/conversation-logger.js.map +1 -1
  254. package/dist/utils/custom-commands-manager.js +126 -131
  255. package/dist/utils/custom-commands-manager.js.map +1 -1
  256. package/dist/utils/editor-utils.js +732 -837
  257. package/dist/utils/editor-utils.js.map +1 -1
  258. package/dist/utils/file.js +174 -213
  259. package/dist/utils/file.js.map +1 -1
  260. package/dist/utils/git-stats.js +169 -0
  261. package/dist/utils/git-stats.js.map +1 -0
  262. package/dist/utils/input-classifier.js +960 -482
  263. package/dist/utils/input-classifier.js.map +1 -1
  264. package/dist/utils/logger.js +48 -73
  265. package/dist/utils/logger.js.map +1 -1
  266. package/dist/utils/markdown-parser.js +277 -310
  267. package/dist/utils/markdown-parser.js.map +1 -1
  268. package/dist/utils/rule-reference-resolver.js +54 -0
  269. package/dist/utils/rule-reference-resolver.js.map +1 -0
  270. package/dist/utils/shell.js +144 -156
  271. package/dist/utils/shell.js.map +1 -1
  272. package/dist/utils/state.js +23 -22
  273. package/dist/utils/state.js.map +1 -1
  274. package/dist/utils/syntax-checker.js +279 -327
  275. package/dist/utils/syntax-checker.js.map +1 -1
  276. package/dist/utils/terminal-output.js +199 -302
  277. package/dist/utils/terminal-output.js.map +1 -1
  278. package/dist/utils/text-clipboard.js +47 -70
  279. package/dist/utils/text-clipboard.js.map +1 -1
  280. package/dist/utils/unicode-sanitizer.js +134 -197
  281. package/dist/utils/unicode-sanitizer.js.map +1 -1
  282. package/dist/utils/version-checker.js +46 -56
  283. package/dist/utils/version-checker.js.map +1 -1
  284. package/package.json +6 -4
  285. package/dist/ai/types.d.ts +0 -20
  286. package/dist/ai/types.d.ts.map +0 -1
  287. package/dist/cli-adapter.d.ts +0 -511
  288. package/dist/cli-adapter.d.ts.map +0 -1
  289. package/dist/commands/CommandParser.d.ts +0 -27
  290. package/dist/commands/CommandParser.d.ts.map +0 -1
  291. package/dist/config/build-config.d.ts +0 -42
  292. package/dist/config/build-config.d.ts.map +0 -1
  293. package/dist/config/defaultConfig.d.ts +0 -79
  294. package/dist/config/defaultConfig.d.ts.map +0 -1
  295. package/dist/config/manager.d.ts +0 -62
  296. package/dist/config/manager.d.ts.map +0 -1
  297. package/dist/config/mcp-config-manager.d.ts +0 -79
  298. package/dist/config/mcp-config-manager.d.ts.map +0 -1
  299. package/dist/config/models.d.ts +0 -83
  300. package/dist/config/models.d.ts.map +0 -1
  301. package/dist/config/slash-commands.d.ts +0 -23
  302. package/dist/config/slash-commands.d.ts.map +0 -1
  303. package/dist/config/types.d.ts +0 -35
  304. package/dist/config/types.d.ts.map +0 -1
  305. package/dist/context/command-detector.d.ts +0 -50
  306. package/dist/context/command-detector.d.ts.map +0 -1
  307. package/dist/context/context-manager.d.ts +0 -157
  308. package/dist/context/context-manager.d.ts.map +0 -1
  309. package/dist/context/handlers/docker-handler.d.ts +0 -130
  310. package/dist/context/handlers/docker-handler.d.ts.map +0 -1
  311. package/dist/context/handlers/ssh-handler.d.ts +0 -201
  312. package/dist/context/handlers/ssh-handler.d.ts.map +0 -1
  313. package/dist/context/handlers/wsl-handler.d.ts +0 -146
  314. package/dist/context/handlers/wsl-handler.d.ts.map +0 -1
  315. package/dist/context/index.d.ts +0 -8
  316. package/dist/context/index.d.ts.map +0 -1
  317. package/dist/context/subshell-handler.d.ts +0 -165
  318. package/dist/context/subshell-handler.d.ts.map +0 -1
  319. package/dist/context/types.d.ts +0 -70
  320. package/dist/context/types.d.ts.map +0 -1
  321. package/dist/hooks/useConnectivity.d.ts +0 -2
  322. package/dist/hooks/useConnectivity.d.ts.map +0 -1
  323. package/dist/hooks/useTerminalDimensions.d.ts +0 -41
  324. package/dist/hooks/useTerminalDimensions.d.ts.map +0 -1
  325. package/dist/index.d.ts +0 -9
  326. package/dist/index.d.ts.map +0 -1
  327. package/dist/mcp/mcp-command-handler.d.ts +0 -47
  328. package/dist/mcp/mcp-command-handler.d.ts.map +0 -1
  329. package/dist/mcp/mcp-server-manager.d.ts +0 -30
  330. package/dist/mcp/mcp-server-manager.d.ts.map +0 -1
  331. package/dist/mcp/mcp-tool-wrapper.d.ts +0 -12
  332. package/dist/mcp/mcp-tool-wrapper.d.ts.map +0 -1
  333. package/dist/services/ai-autocomplete-agent.d.ts +0 -39
  334. package/dist/services/ai-autocomplete-agent.d.ts.map +0 -1
  335. package/dist/services/ai-context-injector.d.ts +0 -41
  336. package/dist/services/ai-context-injector.d.ts.map +0 -1
  337. package/dist/services/ai-service-client.d.ts +0 -128
  338. package/dist/services/ai-service-client.d.ts.map +0 -1
  339. package/dist/services/api-client.d.ts +0 -353
  340. package/dist/services/api-client.d.ts.map +0 -1
  341. package/dist/services/auth-handler.d.ts +0 -30
  342. package/dist/services/auth-handler.d.ts.map +0 -1
  343. package/dist/services/background-task-manager.d.ts +0 -114
  344. package/dist/services/background-task-manager.d.ts.map +0 -1
  345. package/dist/services/checkpoint-manager.d.ts +0 -167
  346. package/dist/services/checkpoint-manager.d.ts.map +0 -1
  347. package/dist/services/clipboard-service.d.ts +0 -37
  348. package/dist/services/clipboard-service.d.ts.map +0 -1
  349. package/dist/services/connectivity-manager.d.ts +0 -18
  350. package/dist/services/connectivity-manager.d.ts.map +0 -1
  351. package/dist/services/conversation-manager.d.ts +0 -73
  352. package/dist/services/conversation-manager.d.ts.map +0 -1
  353. package/dist/services/environment-context-injector.d.ts +0 -69
  354. package/dist/services/environment-context-injector.d.ts.map +0 -1
  355. package/dist/services/fast-context-agent.d.ts +0 -12
  356. package/dist/services/fast-context-agent.d.ts.map +0 -1
  357. package/dist/services/input-detection-agent.d.ts +0 -40
  358. package/dist/services/input-detection-agent.d.ts.map +0 -1
  359. package/dist/services/input-requirement-detector.d.ts +0 -28
  360. package/dist/services/input-requirement-detector.d.ts.map +0 -1
  361. package/dist/services/local-chat-storage.d.ts +0 -182
  362. package/dist/services/local-chat-storage.d.ts.map +0 -1
  363. package/dist/services/monitored-shell-manager.d.ts +0 -120
  364. package/dist/services/monitored-shell-manager.d.ts.map +0 -1
  365. package/dist/services/ollama-service.d.ts +0 -197
  366. package/dist/services/ollama-service.d.ts.map +0 -1
  367. package/dist/services/session-quota-manager.d.ts +0 -101
  368. package/dist/services/session-quota-manager.d.ts.map +0 -1
  369. package/dist/services/shell-input-agent.d.ts +0 -89
  370. package/dist/services/shell-input-agent.d.ts.map +0 -1
  371. package/dist/services/sub-agent-manager.d.ts +0 -140
  372. package/dist/services/sub-agent-manager.d.ts.map +0 -1
  373. package/dist/services/warpify-detector.d.ts +0 -43
  374. package/dist/services/warpify-detector.d.ts.map +0 -1
  375. package/dist/services/workflow-storage.d.ts +0 -72
  376. package/dist/services/workflow-storage.d.ts.map +0 -1
  377. package/dist/test-ssh-handler.d.ts +0 -8
  378. package/dist/test-ssh-handler.d.ts.map +0 -1
  379. package/dist/tools/background-command.d.ts +0 -11
  380. package/dist/tools/background-command.d.ts.map +0 -1
  381. package/dist/tools/command.d.ts +0 -3
  382. package/dist/tools/command.d.ts.map +0 -1
  383. package/dist/tools/create-image.d.ts +0 -10
  384. package/dist/tools/create-image.d.ts.map +0 -1
  385. package/dist/tools/enter-remote-session.d.ts +0 -48
  386. package/dist/tools/enter-remote-session.d.ts.map +0 -1
  387. package/dist/tools/fast-context.d.ts +0 -3
  388. package/dist/tools/fast-context.d.ts.map +0 -1
  389. package/dist/tools/file-ops.d.ts +0 -7
  390. package/dist/tools/file-ops.d.ts.map +0 -1
  391. package/dist/tools/find-files.d.ts +0 -49
  392. package/dist/tools/find-files.d.ts.map +0 -1
  393. package/dist/tools/get-diff.d.ts +0 -14
  394. package/dist/tools/get-diff.d.ts.map +0 -1
  395. package/dist/tools/grep-search.d.ts +0 -155
  396. package/dist/tools/grep-search.d.ts.map +0 -1
  397. package/dist/tools/inspect-symbol.d.ts +0 -32
  398. package/dist/tools/inspect-symbol.d.ts.map +0 -1
  399. package/dist/tools/plan-mode.d.ts +0 -140
  400. package/dist/tools/plan-mode.d.ts.map +0 -1
  401. package/dist/tools/read-binary-file.d.ts +0 -10
  402. package/dist/tools/read-binary-file.d.ts.map +0 -1
  403. package/dist/tools/registry.d.ts +0 -31
  404. package/dist/tools/registry.d.ts.map +0 -1
  405. package/dist/tools/reproduce_issue.d.ts +0 -2
  406. package/dist/tools/reproduce_issue.d.ts.map +0 -1
  407. package/dist/tools/sub-agent.d.ts +0 -9
  408. package/dist/tools/sub-agent.d.ts.map +0 -1
  409. package/dist/tools/task-complete.d.ts +0 -3
  410. package/dist/tools/task-complete.d.ts.map +0 -1
  411. package/dist/tools/types.d.ts +0 -40
  412. package/dist/tools/types.d.ts.map +0 -1
  413. package/dist/tools/validation.d.ts +0 -47
  414. package/dist/tools/validation.d.ts.map +0 -1
  415. package/dist/tools/web-search.d.ts +0 -24
  416. package/dist/tools/web-search.d.ts.map +0 -1
  417. package/dist/tools/workflow-tool.d.ts +0 -11
  418. package/dist/tools/workflow-tool.d.ts.map +0 -1
  419. package/dist/types/index.d.ts +0 -123
  420. package/dist/types/index.d.ts.map +0 -1
  421. package/dist/types/workflow.d.ts +0 -110
  422. package/dist/types/workflow.d.ts.map +0 -1
  423. package/dist/ui/components/AgentTimer.d.ts +0 -7
  424. package/dist/ui/components/AgentTimer.d.ts.map +0 -1
  425. package/dist/ui/components/App.d.ts +0 -197
  426. package/dist/ui/components/App.d.ts.map +0 -1
  427. package/dist/ui/components/AuthScreen.d.ts +0 -8
  428. package/dist/ui/components/AuthScreen.d.ts.map +0 -1
  429. package/dist/ui/components/AuthWelcomeScreen.d.ts +0 -8
  430. package/dist/ui/components/AuthWelcomeScreen.d.ts.map +0 -1
  431. package/dist/ui/components/Breadcrumbs.d.ts +0 -13
  432. package/dist/ui/components/Breadcrumbs.d.ts.map +0 -1
  433. package/dist/ui/components/CircularSelectInput.d.ts +0 -24
  434. package/dist/ui/components/CircularSelectInput.d.ts.map +0 -1
  435. package/dist/ui/components/ClipboardFileAutocomplete.d.ts +0 -10
  436. package/dist/ui/components/ClipboardFileAutocomplete.d.ts.map +0 -1
  437. package/dist/ui/components/CodeBlock.d.ts +0 -9
  438. package/dist/ui/components/CodeBlock.d.ts.map +0 -1
  439. package/dist/ui/components/ConfigViewer.d.ts +0 -11
  440. package/dist/ui/components/ConfigViewer.d.ts.map +0 -1
  441. package/dist/ui/components/ConfirmPrompt.d.ts +0 -13
  442. package/dist/ui/components/ConfirmPrompt.d.ts.map +0 -1
  443. package/dist/ui/components/ConnectionStatusMessage.d.ts +0 -17
  444. package/dist/ui/components/ConnectionStatusMessage.d.ts.map +0 -1
  445. package/dist/ui/components/ContextWindowIndicator.d.ts +0 -8
  446. package/dist/ui/components/ContextWindowIndicator.d.ts.map +0 -1
  447. package/dist/ui/components/DetailedPlanReviewScreen.d.ts +0 -17
  448. package/dist/ui/components/DetailedPlanReviewScreen.d.ts.map +0 -1
  449. package/dist/ui/components/DiffViewer.d.ts +0 -9
  450. package/dist/ui/components/DiffViewer.d.ts.map +0 -1
  451. package/dist/ui/components/ErrorBoundary.d.ts +0 -17
  452. package/dist/ui/components/ErrorBoundary.d.ts.map +0 -1
  453. package/dist/ui/components/FileCreationPreview.d.ts +0 -8
  454. package/dist/ui/components/FileCreationPreview.d.ts.map +0 -1
  455. package/dist/ui/components/FileOperation.d.ts +0 -10
  456. package/dist/ui/components/FileOperation.d.ts.map +0 -1
  457. package/dist/ui/components/FileTagAutocomplete.d.ts +0 -11
  458. package/dist/ui/components/FileTagAutocomplete.d.ts.map +0 -1
  459. package/dist/ui/components/FontRecommendation.d.ts +0 -1
  460. package/dist/ui/components/FontRecommendation.d.ts.map +0 -1
  461. package/dist/ui/components/InputBox.d.ts +0 -42
  462. package/dist/ui/components/InputBox.d.ts.map +0 -1
  463. package/dist/ui/components/InteractiveShell.d.ts +0 -30
  464. package/dist/ui/components/InteractiveShell.d.ts.map +0 -1
  465. package/dist/ui/components/KeyboardHelp.d.ts +0 -7
  466. package/dist/ui/components/KeyboardHelp.d.ts.map +0 -1
  467. package/dist/ui/components/LoadingIndicator.d.ts +0 -3
  468. package/dist/ui/components/LoadingIndicator.d.ts.map +0 -1
  469. package/dist/ui/components/MCPAddScreen.d.ts +0 -13
  470. package/dist/ui/components/MCPAddScreen.d.ts.map +0 -1
  471. package/dist/ui/components/MCPListScreen.d.ts +0 -17
  472. package/dist/ui/components/MCPListScreen.d.ts.map +0 -1
  473. package/dist/ui/components/MCPServerListScreen.d.ts +0 -16
  474. package/dist/ui/components/MCPServerListScreen.d.ts.map +0 -1
  475. package/dist/ui/components/MarkdownRenderer.d.ts +0 -8
  476. package/dist/ui/components/MarkdownRenderer.d.ts.map +0 -1
  477. package/dist/ui/components/MessageBox.d.ts +0 -10
  478. package/dist/ui/components/MessageBox.d.ts.map +0 -1
  479. package/dist/ui/components/MessageDisplay.d.ts +0 -14
  480. package/dist/ui/components/MessageDisplay.d.ts.map +0 -1
  481. package/dist/ui/components/MonitorModeAIPanel.d.ts +0 -23
  482. package/dist/ui/components/MonitorModeAIPanel.d.ts.map +0 -1
  483. package/dist/ui/components/MultiLineInput.d.ts +0 -13
  484. package/dist/ui/components/MultiLineInput.d.ts.map +0 -1
  485. package/dist/ui/components/PasswordPrompt.d.ts +0 -9
  486. package/dist/ui/components/PasswordPrompt.d.ts.map +0 -1
  487. package/dist/ui/components/PlanAcceptedMessage.d.ts +0 -20
  488. package/dist/ui/components/PlanAcceptedMessage.d.ts.map +0 -1
  489. package/dist/ui/components/PlanReviewScreen.d.ts +0 -14
  490. package/dist/ui/components/PlanReviewScreen.d.ts.map +0 -1
  491. package/dist/ui/components/SelectPrompt.d.ts +0 -12
  492. package/dist/ui/components/SelectPrompt.d.ts.map +0 -1
  493. package/dist/ui/components/SlashCommandAutocomplete.d.ts +0 -13
  494. package/dist/ui/components/SlashCommandAutocomplete.d.ts.map +0 -1
  495. package/dist/ui/components/StatusBar.d.ts +0 -14
  496. package/dist/ui/components/StatusBar.d.ts.map +0 -1
  497. package/dist/ui/components/StreamingMessageDisplay.d.ts +0 -15
  498. package/dist/ui/components/StreamingMessageDisplay.d.ts.map +0 -1
  499. package/dist/ui/components/TaskCompletedMessage.d.ts +0 -14
  500. package/dist/ui/components/TaskCompletedMessage.d.ts.map +0 -1
  501. package/dist/ui/components/TaskProgressIndicator.d.ts +0 -18
  502. package/dist/ui/components/TaskProgressIndicator.d.ts.map +0 -1
  503. package/dist/ui/components/ThinkingDisplay.d.ts +0 -15
  504. package/dist/ui/components/ThinkingDisplay.d.ts.map +0 -1
  505. package/dist/ui/components/ToolExecutionMessage.d.ts +0 -8
  506. package/dist/ui/components/ToolExecutionMessage.d.ts.map +0 -1
  507. package/dist/ui/components/ToolExecutionStatus.d.ts +0 -10
  508. package/dist/ui/components/ToolExecutionStatus.d.ts.map +0 -1
  509. package/dist/ui/components/ToolResult.d.ts +0 -10
  510. package/dist/ui/components/ToolResult.d.ts.map +0 -1
  511. package/dist/ui/components/VersionUpdatePrompt.d.ts +0 -9
  512. package/dist/ui/components/VersionUpdatePrompt.d.ts.map +0 -1
  513. package/dist/ui/components/WelcomeBanner.d.ts +0 -3
  514. package/dist/ui/components/WelcomeBanner.d.ts.map +0 -1
  515. package/dist/ui/components/WorkflowCreatorScreen.d.ts +0 -25
  516. package/dist/ui/components/WorkflowCreatorScreen.d.ts.map +0 -1
  517. package/dist/utils/ansi-encoder.d.ts +0 -7
  518. package/dist/utils/ansi-encoder.d.ts.map +0 -1
  519. package/dist/utils/chat-formatter.d.ts +0 -12
  520. package/dist/utils/chat-formatter.d.ts.map +0 -1
  521. package/dist/utils/command-history.d.ts +0 -24
  522. package/dist/utils/command-history.d.ts.map +0 -1
  523. package/dist/utils/context-sanitizer.d.ts +0 -50
  524. package/dist/utils/context-sanitizer.d.ts.map +0 -1
  525. package/dist/utils/conversation-logger.d.ts +0 -142
  526. package/dist/utils/conversation-logger.d.ts.map +0 -1
  527. package/dist/utils/custom-commands-manager.d.ts +0 -59
  528. package/dist/utils/custom-commands-manager.d.ts.map +0 -1
  529. package/dist/utils/editor-utils.d.ts +0 -101
  530. package/dist/utils/editor-utils.d.ts.map +0 -1
  531. package/dist/utils/file.d.ts +0 -61
  532. package/dist/utils/file.d.ts.map +0 -1
  533. package/dist/utils/input-classifier.d.ts +0 -25
  534. package/dist/utils/input-classifier.d.ts.map +0 -1
  535. package/dist/utils/logger.d.ts +0 -17
  536. package/dist/utils/logger.d.ts.map +0 -1
  537. package/dist/utils/markdown-parser.d.ts +0 -60
  538. package/dist/utils/markdown-parser.d.ts.map +0 -1
  539. package/dist/utils/shell.d.ts +0 -47
  540. package/dist/utils/shell.d.ts.map +0 -1
  541. package/dist/utils/state.d.ts +0 -13
  542. package/dist/utils/state.d.ts.map +0 -1
  543. package/dist/utils/syntax-checker.d.ts +0 -24
  544. package/dist/utils/syntax-checker.d.ts.map +0 -1
  545. package/dist/utils/terminal-output.d.ts +0 -25
  546. package/dist/utils/terminal-output.d.ts.map +0 -1
  547. package/dist/utils/text-clipboard.d.ts +0 -12
  548. package/dist/utils/text-clipboard.d.ts.map +0 -1
  549. package/dist/utils/unicode-sanitizer.d.ts +0 -44
  550. package/dist/utils/unicode-sanitizer.d.ts.map +0 -1
  551. package/dist/utils/version-checker.d.ts +0 -14
  552. package/dist/utils/version-checker.d.ts.map +0 -1
@@ -1,2212 +1,1682 @@
1
- import React, { useState, useEffect, useRef, useMemo } from 'react';
2
- import { Box, Text, useInput } from 'ink';
3
- import * as fs from 'fs';
4
- import * as path from 'path';
5
- import { useConnectivity } from '../../hooks/useConnectivity.js';
6
- import { Breadcrumbs } from './Breadcrumbs.js';
7
- import { ContextWindowIndicator } from './ContextWindowIndicator.js';
8
- import { logDebug } from '../../utils/logger.js';
9
- import { detectIntent } from '../../utils/input-classifier.js';
10
- import { CommandHistoryManager } from '../../utils/command-history.js';
11
- import { SlashCommandAutocomplete } from './SlashCommandAutocomplete.js';
12
- import { FileTagAutocomplete } from './FileTagAutocomplete.js';
13
- import { filterCommands } from '../../config/slash-commands.js';
14
- import { getClipboardFiles } from '../../services/clipboard-service.js';
15
- import { useTerminalDimensions, TERMINAL_HEIGHT_CONSTANTS } from '../../hooks/useTerminalDimensions.js';
16
- import { AIAutocompleteAgent, AI_AUTOCOMPLETE_DEBOUNCE_MS } from '../../services/ai-autocomplete-agent.js';
17
- import { workflowStorage } from '../../services/workflow-storage.js';
1
+ import React, { useState, useEffect, useRef, useMemo } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import * as fs from "fs";
4
+ import * as path from "path";
5
+ import { useConnectivity } from "../../hooks/useConnectivity.js";
6
+ import { Breadcrumbs } from "./Breadcrumbs.js";
7
+ import { GitDiffBreadcrumb } from "./GitDiffBreadcrumb.js";
8
+ import { ContextWindowIndicator } from "./ContextWindowIndicator.js";
9
+ import { logDebug } from "../../utils/logger.js";
10
+ import { detectIntent } from "../../utils/input-classifier.js";
11
+ import { CommandHistoryManager } from "../../utils/command-history.js";
12
+ import { SlashCommandAutocomplete } from "./SlashCommandAutocomplete.js";
13
+ import { FileTagAutocomplete } from "./FileTagAutocomplete.js";
14
+ import { filterCommands } from "../../config/slash-commands.js";
15
+ import { getClipboardFiles } from "../../services/clipboard-service.js";
16
+ import { useTerminalDimensions, TERMINAL_HEIGHT_CONSTANTS } from "../../hooks/useTerminalDimensions.js";
17
+ import { AIAutocompleteAgent, AI_AUTOCOMPLETE_DEBOUNCE_MS } from "../../services/ai-autocomplete-agent.js";
18
+ import { workflowStorage } from "../../services/workflow-storage.js";
19
+ import { rulesStorage } from "../../services/rules-storage.js";
18
20
  const getVisualLines = (text, width) => {
19
- const logicalLines = text.split('\n');
20
- const visualLines = [];
21
- let currentOffset = 0;
22
- logicalLines.forEach((line, i) => {
23
- if (line.length === 0) {
24
- visualLines.push({ start: currentOffset, end: currentOffset, isHardEnd: true });
25
- currentOffset += 1;
26
- return;
27
- }
28
- let remaining = line;
29
- let lineStartOffset = currentOffset;
30
- while (remaining.length > 0) {
31
- let splitIndex = remaining.length;
32
- let isHardEnd = false;
33
- if (remaining.length > width) {
34
- splitIndex = width;
35
- const lastSpace = remaining.lastIndexOf(' ', width);
36
- if (lastSpace > 0) {
37
- splitIndex = lastSpace + 1;
38
- }
39
- }
40
- else {
41
- isHardEnd = true;
42
- }
43
- visualLines.push({
44
- start: lineStartOffset,
45
- end: lineStartOffset + splitIndex,
46
- isHardEnd: isHardEnd
47
- });
48
- lineStartOffset += splitIndex;
49
- remaining = remaining.slice(splitIndex);
21
+ const logicalLines = text.split("\n");
22
+ const visualLines = [];
23
+ let currentOffset = 0;
24
+ logicalLines.forEach((line, i) => {
25
+ if (line.length === 0) {
26
+ visualLines.push({ start: currentOffset, end: currentOffset, isHardEnd: true });
27
+ currentOffset += 1;
28
+ return;
29
+ }
30
+ let remaining = line;
31
+ let lineStartOffset = currentOffset;
32
+ while (remaining.length > 0) {
33
+ let splitIndex = remaining.length;
34
+ let isHardEnd = false;
35
+ if (remaining.length > width) {
36
+ splitIndex = width;
37
+ const lastSpace = remaining.lastIndexOf(" ", width);
38
+ if (lastSpace > 0) {
39
+ splitIndex = lastSpace + 1;
50
40
  }
51
- currentOffset += line.length + (i < logicalLines.length - 1 ? 1 : 0);
52
- });
53
- return visualLines;
41
+ } else {
42
+ isHardEnd = true;
43
+ }
44
+ visualLines.push({
45
+ start: lineStartOffset,
46
+ end: lineStartOffset + splitIndex,
47
+ isHardEnd
48
+ });
49
+ lineStartOffset += splitIndex;
50
+ remaining = remaining.slice(splitIndex);
51
+ }
52
+ currentOffset += line.length + (i < logicalLines.length - 1 ? 1 : 0);
53
+ });
54
+ return visualLines;
54
55
  };
55
- export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...', autoAcceptMode, model, planMode = false, commandMode = false, backgroundMode = false, currentWorkingDirectory, commandHistory = [], onToggleAutoAccept, onToggleCommandMode, onToggleBackgroundMode, isActive = true, subshellContext, subshellContextStack, currentTokens = 0, maxTokens = 1000000, contextLimitReached = false, isShellRunning = false, backgroundTaskCount = 0, initialValue = '', onValueChange, onSetAutoModeSetup, sessionQuotaExhausted = false, sessionQuotaTimeRemaining = '', subAgentCount = 0, aiAutoSuggestEnabled = false, sessionCommands = [], getCheckpoints, onSetInputSetup }) => {
56
- // Use initialValue for first mount, but manage state internally after that
57
- const [value, setValueInternal] = useState(initialValue);
58
- const [cursorOffset, setCursorOffset] = useState(0);
59
- const [completions, setCompletions] = useState([]);
60
- const [completionIndex, setCompletionIndex] = useState(0);
61
- const [historyIndex, setHistoryIndex] = useState(-1);
62
- const [tempValue, setTempValue] = useState('');
63
- const ignoreNextChangeRef = useRef(false);
64
- // Refs to track current value and cursor for paste handling
65
- // This prevents stale closure issues when Ink calls useInput multiple times during paste
66
- const valueRef = useRef(initialValue);
67
- const cursorOffsetRef = useRef(0);
68
- // Auto Mode State
69
- const [isAutoMode, setIsAutoMode] = useState(true);
70
- const [detectedIntent, setDetectedIntent] = useState('ai');
71
- // Autocomplete State
72
- const [autocompleteSuggestion, setAutocompleteSuggestion] = useState(null);
73
- // AI Autocomplete State
74
- const [aiAutocompleteSuggestion, setAiAutocompleteSuggestion] = useState(null);
75
- const [isAiAutocompleteLoading, setIsAiAutocompleteLoading] = useState(false);
76
- const aiAutocompleteDebounceRef = useRef(null);
77
- const aiAutocompleteAbortRef = useRef(null);
78
- // Undo/Redo State
79
- const [undoStack, setUndoStack] = useState([]);
80
- const [redoStack, setRedoStack] = useState([]);
81
- // Selection State
82
- const [selection, setSelection] = useState(null);
83
- // Platform Detection
84
- const isMac = process.platform === 'darwin';
85
- // Reject Flash State (turns border red when submission is blocked)
86
- const [rejectFlash, setRejectFlash] = useState(false);
87
- // Session Quota Message State (shows quota exhausted message)
88
- const [showQuotaMessage, setShowQuotaMessage] = useState(false);
89
- // Slash Command Autocomplete State
90
- const [slashAutocompleteVisible, setSlashAutocompleteVisible] = useState(false);
91
- const [slashAutocompleteCommands, setSlashAutocompleteCommands] = useState([]);
92
- const [slashAutocompleteSelectedIndex, setSlashAutocompleteSelectedIndex] = useState(0);
93
- const [slashAutocompleteScrollOffset, setSlashAutocompleteScrollOffset] = useState(0);
94
- // Connectivity State
95
- const isConnected = useConnectivity();
96
- // Terminal dimensions for height-aware autocomplete
97
- const dimensions = useTerminalDimensions();
98
- // Max 5 items, min 0 for very small terminals (use MIN_ROWS_FOR_STREAMING as threshold)
99
- const slashMaxVisibleItems = dimensions.rows < TERMINAL_HEIGHT_CONSTANTS.MIN_ROWS_FOR_STREAMING
100
- ? 0
101
- : Math.min(5, Math.max(1, Math.floor((dimensions.rows - 20) / 3)));
102
- // File Tag Autocomplete State (@ symbol)
103
- const [fileTagAutocompleteVisible, setFileTagAutocompleteVisible] = useState(false);
104
- const [fileTagSuggestions, setFileTagSuggestions] = useState([]);
105
- const [fileTagSelectedIndex, setFileTagSelectedIndex] = useState(0);
106
- const [activeFileTagStart, setActiveFileTagStart] = useState(null);
107
- const [confirmedFileTags, setConfirmedFileTags] = useState([]);
108
- // Clipboard File State (Alt+V paste)
109
- const [confirmedClipboardFiles, setConfirmedClipboardFiles] = useState([]);
110
- // Track visual line count to force re-renders when text wraps
111
- // This is necessary because Ink doesn't automatically update layout when text wraps
112
- const [visualLineCount, setVisualLineCount] = useState(1);
113
- // Configuration for scrolling
114
- const MAX_VISIBLE_LINES = 9;
115
- // Wrapper for setValue that also notifies parent of changes and updates ref
116
- const setValue = React.useCallback((newValue) => {
117
- setValueInternal(prev => {
118
- const resolvedValue = typeof newValue === 'function' ? newValue(prev) : newValue;
119
- // Update ref synchronously for paste handling
120
- valueRef.current = resolvedValue;
121
- // Notify parent of value change for preservation across screen transitions
122
- if (onValueChange) {
123
- onValueChange(resolvedValue);
124
- }
125
- return resolvedValue;
126
- });
127
- }, [onValueChange]);
128
- // Wrapper for setCursorOffset that also updates ref
129
- const setCursorOffsetWithRef = React.useCallback((newOffset) => {
130
- setCursorOffset(prev => {
131
- const resolvedOffset = typeof newOffset === 'function' ? newOffset(prev) : newOffset;
132
- // Update ref synchronously for paste handling
133
- cursorOffsetRef.current = resolvedOffset;
134
- return resolvedOffset;
135
- });
136
- }, []);
137
- // Initialize cursor position when initialValue is provided
138
- useEffect(() => {
139
- if (initialValue && initialValue.length > 0) {
140
- setCursorOffset(initialValue.length);
141
- cursorOffsetRef.current = initialValue.length;
142
- }
143
- }, []); // Only run on mount
144
- // Keep refs in sync with state (for cases where state is updated directly)
145
- useEffect(() => {
146
- valueRef.current = value;
147
- }, [value]);
148
- useEffect(() => {
149
- cursorOffsetRef.current = cursorOffset;
150
- }, [cursorOffset]);
151
- // Load history on mount
152
- useEffect(() => {
153
- CommandHistoryManager.getInstance().load();
154
- }, []);
155
- // Register setInput callback for external control (e.g., for revert functionality)
156
- useEffect(() => {
157
- if (onSetInputSetup) {
158
- onSetInputSetup((newValue) => {
159
- setValue(newValue);
160
- setCursorOffset(newValue.length);
161
- });
162
- }
163
- }, [onSetInputSetup, setValue, setCursorOffset]);
164
- // Register setIsAutoMode callback for external control (e.g., after background task starts)
165
- useEffect(() => {
166
- if (onSetInputSetup) {
167
- onSetInputSetup((newValue) => {
168
- setValue(newValue);
169
- setCursorOffset(newValue.length);
170
- });
171
- }
172
- }, [onSetInputSetup, setValue, setCursorOffset]);
173
- useEffect(() => {
174
- if (onSetAutoModeSetup) {
175
- onSetAutoModeSetup((enabled) => {
176
- setIsAutoMode(enabled);
177
- if (enabled) {
178
- // When enabling Auto mode, also update detected intent based on current value
179
- const intent = detectIntent(value);
180
- setDetectedIntent(intent);
181
- // Set command mode based on intent
182
- if (intent === 'command' && !commandMode && onToggleCommandMode) {
183
- onToggleCommandMode();
184
- }
185
- else if (intent === 'ai' && commandMode && onToggleCommandMode) {
186
- onToggleCommandMode();
187
- }
188
- }
189
- });
190
- }
191
- }, [onSetAutoModeSetup, value, commandMode, onToggleCommandMode]);
192
- // Force clear value if it becomes empty or whitespace-only after external changes
193
- useEffect(() => {
194
- if (value && value.trim() === '' && cursorOffset > 0) {
195
- setValue('');
196
- setCursorOffset(0);
197
- }
198
- }, [value, cursorOffset]);
199
- // Track visual line count changes to force re-renders when text wraps
200
- // This is crucial for the input box height to update correctly
201
- useEffect(() => {
202
- const termWidth = (process.stdout.columns || 80) - 6;
203
- const visualLines = getVisualLines(value, termWidth);
204
- const newLineCount = Math.max(1, visualLines.length);
205
- if (newLineCount !== visualLineCount) {
206
- setVisualLineCount(newLineCount);
207
- }
208
- }, [value, visualLineCount]);
209
- // Determine current working directory
210
- const currentDir = useMemo(() => {
211
- const cwd = currentWorkingDirectory || process.cwd();
212
- if (cwd.length > 60) {
213
- return '...' + cwd.slice(-57);
214
- }
215
- return cwd;
216
- }, [currentWorkingDirectory]);
217
- // Determine current environment for command history isolation
218
- // Format: 'local', 'ssh:user@host', or 'wsl:distroName'
219
- const currentEnvironment = useMemo(() => {
220
- if (!subshellContext)
221
- return 'local';
222
- if (subshellContext.type === 'ssh') {
223
- const user = subshellContext.metadata?.username || 'user';
224
- const host = subshellContext.metadata?.hostname || 'host';
225
- return `ssh:${user}@${host}`;
226
- }
227
- if (subshellContext.type === 'wsl') {
228
- const distro = subshellContext.metadata?.distroName || 'Ubuntu';
229
- return `wsl:${distro}`;
230
- }
231
- if (subshellContext.type === 'docker') {
232
- const container = subshellContext.metadata?.containerId || 'container';
233
- return `docker:${container}`;
234
- }
235
- return 'local';
236
- }, [subshellContext]);
237
- // Autocomplete Logic
238
- useEffect(() => {
239
- if (!value || value.trim() === '') {
240
- setAutocompleteSuggestion(null);
241
- return;
242
- }
243
- // Only show suggestions if we are in command mode (manual or auto-detected)
244
- // OR if we are in Auto mode and it looks like a command
245
- const shouldSuggest = commandMode || (isAutoMode && detectedIntent === 'command');
246
- if (shouldSuggest) {
247
- const matches = CommandHistoryManager.getInstance().getMatches(value, currentDir, currentEnvironment);
248
- if (matches.length > 0) {
249
- setAutocompleteSuggestion(matches[0]);
250
- }
251
- else {
252
- setAutocompleteSuggestion(null);
253
- }
254
- }
255
- else {
256
- setAutocompleteSuggestion(null);
257
- }
258
- }, [value, commandMode, isAutoMode, detectedIntent, aiAutoSuggestEnabled, currentDir, currentEnvironment]);
259
- // AI Autocomplete Logic (5-second debounce)
260
- useEffect(() => {
261
- // Clear AI suggestion when value changes
262
- setAiAutocompleteSuggestion(null);
263
- // Clear any existing debounce timer
264
- if (aiAutocompleteDebounceRef.current) {
265
- clearTimeout(aiAutocompleteDebounceRef.current);
266
- aiAutocompleteDebounceRef.current = null;
267
- }
268
- // Abort any pending AI request
269
- if (aiAutocompleteAbortRef.current) {
270
- aiAutocompleteAbortRef.current.abort();
271
- aiAutocompleteAbortRef.current = null;
272
- }
273
- // Debug logging for trigger ref
274
- /*
275
- try {
276
- if (detectedIntent !== 'ai' || value.length > 5) {
277
- quickLog(`[InputBox] AI Effect: val="${value}", enabled=${aiAutoSuggestEnabled}, mode=${commandMode}, auto=${isAutoMode}, intent=${detectedIntent}`);
56
+ const InputBox = React.memo(({
57
+ onSubmit,
58
+ placeholder = "Ask anything ...",
59
+ autoAcceptMode,
60
+ model,
61
+ planMode = false,
62
+ commandMode = false,
63
+ backgroundMode = false,
64
+ currentWorkingDirectory,
65
+ commandHistory = [],
66
+ onToggleAutoAccept,
67
+ onToggleCommandMode,
68
+ onToggleBackgroundMode,
69
+ isActive = true,
70
+ subshellContext,
71
+ subshellContextStack,
72
+ currentTokens = 0,
73
+ maxTokens = 1e6,
74
+ contextLimitReached = false,
75
+ isShellRunning = false,
76
+ backgroundTaskCount = 0,
77
+ initialValue = "",
78
+ onValueChange,
79
+ onSetAutoModeSetup,
80
+ sessionQuotaExhausted = false,
81
+ sessionQuotaTimeRemaining = "",
82
+ subAgentCount = 0,
83
+ aiAutoSuggestEnabled = false,
84
+ sessionCommands = [],
85
+ getCheckpoints,
86
+ onSetInputSetup,
87
+ gitDiffStats
88
+ }) => {
89
+ const [value, setValueInternal] = useState(initialValue);
90
+ const [cursorOffset, setCursorOffset] = useState(0);
91
+ const [completions, setCompletions] = useState([]);
92
+ const [completionIndex, setCompletionIndex] = useState(0);
93
+ const [historyIndex, setHistoryIndex] = useState(-1);
94
+ const [tempValue, setTempValue] = useState("");
95
+ const ignoreNextChangeRef = useRef(false);
96
+ const valueRef = useRef(initialValue);
97
+ const cursorOffsetRef = useRef(0);
98
+ const [isAutoMode, setIsAutoMode] = useState(true);
99
+ const [detectedIntent, setDetectedIntent] = useState("ai");
100
+ const [autocompleteSuggestion, setAutocompleteSuggestion] = useState(null);
101
+ const [aiAutocompleteSuggestion, setAiAutocompleteSuggestion] = useState(null);
102
+ const [isAiAutocompleteLoading, setIsAiAutocompleteLoading] = useState(false);
103
+ const aiAutocompleteDebounceRef = useRef(null);
104
+ const aiAutocompleteAbortRef = useRef(null);
105
+ const [undoStack, setUndoStack] = useState([]);
106
+ const [redoStack, setRedoStack] = useState([]);
107
+ const [selection, setSelection] = useState(null);
108
+ const isMac = process.platform === "darwin";
109
+ const [rejectFlash, setRejectFlash] = useState(false);
110
+ const [showQuotaMessage, setShowQuotaMessage] = useState(false);
111
+ const [slashAutocompleteVisible, setSlashAutocompleteVisible] = useState(false);
112
+ const [slashAutocompleteCommands, setSlashAutocompleteCommands] = useState([]);
113
+ const [slashAutocompleteSelectedIndex, setSlashAutocompleteSelectedIndex] = useState(0);
114
+ const [slashAutocompleteScrollOffset, setSlashAutocompleteScrollOffset] = useState(0);
115
+ const isConnected = useConnectivity();
116
+ const dimensions = useTerminalDimensions();
117
+ const slashMaxVisibleItems = dimensions.rows < TERMINAL_HEIGHT_CONSTANTS.MIN_ROWS_FOR_STREAMING ? 0 : Math.min(5, Math.max(1, Math.floor((dimensions.rows - 20) / 3)));
118
+ const showBottomHints = dimensions.columns >= 90;
119
+ const showModeIndicator = dimensions.columns >= 70;
120
+ const [fileTagAutocompleteVisible, setFileTagAutocompleteVisible] = useState(false);
121
+ const [fileTagSuggestions, setFileTagSuggestions] = useState([]);
122
+ const [fileTagSelectedIndex, setFileTagSelectedIndex] = useState(0);
123
+ const [activeFileTagStart, setActiveFileTagStart] = useState(null);
124
+ const [confirmedFileTags, setConfirmedFileTags] = useState([]);
125
+ const [confirmedClipboardFiles, setConfirmedClipboardFiles] = useState([]);
126
+ const [visualLineCount, setVisualLineCount] = useState(1);
127
+ const MAX_VISIBLE_LINES = 9;
128
+ const setValue = React.useCallback((newValue) => {
129
+ setValueInternal((prev) => {
130
+ const resolvedValue = typeof newValue === "function" ? newValue(prev) : newValue;
131
+ valueRef.current = resolvedValue;
132
+ if (onValueChange) {
133
+ onValueChange(resolvedValue);
134
+ }
135
+ return resolvedValue;
136
+ });
137
+ }, [onValueChange]);
138
+ const setCursorOffsetWithRef = React.useCallback((newOffset) => {
139
+ setCursorOffset((prev) => {
140
+ const resolvedOffset = typeof newOffset === "function" ? newOffset(prev) : newOffset;
141
+ cursorOffsetRef.current = resolvedOffset;
142
+ return resolvedOffset;
143
+ });
144
+ }, []);
145
+ useEffect(() => {
146
+ if (initialValue && initialValue.length > 0) {
147
+ setCursorOffset(initialValue.length);
148
+ cursorOffsetRef.current = initialValue.length;
149
+ }
150
+ }, []);
151
+ useEffect(() => {
152
+ valueRef.current = value;
153
+ }, [value]);
154
+ useEffect(() => {
155
+ cursorOffsetRef.current = cursorOffset;
156
+ }, [cursorOffset]);
157
+ useEffect(() => {
158
+ CommandHistoryManager.getInstance().load();
159
+ }, []);
160
+ useEffect(() => {
161
+ if (onSetInputSetup) {
162
+ onSetInputSetup((newValue) => {
163
+ setValue(newValue);
164
+ setCursorOffset(newValue.length);
165
+ });
166
+ }
167
+ }, [onSetInputSetup, setValue, setCursorOffset]);
168
+ useEffect(() => {
169
+ if (onSetInputSetup) {
170
+ onSetInputSetup((newValue) => {
171
+ setValue(newValue);
172
+ setCursorOffset(newValue.length);
173
+ });
174
+ }
175
+ }, [onSetInputSetup, setValue, setCursorOffset]);
176
+ useEffect(() => {
177
+ if (onSetAutoModeSetup) {
178
+ onSetAutoModeSetup((enabled) => {
179
+ setIsAutoMode(enabled);
180
+ if (enabled) {
181
+ const intent = detectIntent(value);
182
+ setDetectedIntent(intent);
183
+ if (intent === "command" && !commandMode && onToggleCommandMode) {
184
+ onToggleCommandMode();
185
+ } else if (intent === "ai" && commandMode && onToggleCommandMode) {
186
+ onToggleCommandMode();
278
187
  }
279
- } catch (e) {}
280
- */
281
- // Don't trigger AI autocomplete if disabled or not in command mode
282
- if (!aiAutoSuggestEnabled) {
283
- // quickLog('[InputBox] AI skipped: disabled');
284
- return;
285
- }
286
- const shouldSuggest = commandMode || (isAutoMode && detectedIntent === 'command');
287
- if (!shouldSuggest) {
288
- // if (value.length > 3) quickLog(`[InputBox] AI skipped: !shouldSuggest (cmd=${commandMode}, auto=${isAutoMode}, intent=${detectedIntent})`);
289
- return;
290
- }
291
- // Don't suggest for empty, very short, or slash commands
292
- if (!value || value.trim().length < 2 || value.startsWith('/')) {
293
- // quickLog('[InputBox] AI skipped: too short or slash');
294
- return;
295
188
  }
296
- // quickLog('[InputBox] AI Triggering debounce...');
297
- // Set up 5-second debounce timer
298
- aiAutocompleteDebounceRef.current = setTimeout(async () => {
299
- setIsAiAutocompleteLoading(true);
300
- // Create abort controller for this request
301
- const abortController = new AbortController();
302
- aiAutocompleteAbortRef.current = abortController;
189
+ });
190
+ }
191
+ }, [onSetAutoModeSetup, value, commandMode, onToggleCommandMode]);
192
+ useEffect(() => {
193
+ if (value && value.trim() === "" && cursorOffset > 0) {
194
+ setValue("");
195
+ setCursorOffset(0);
196
+ }
197
+ }, [value, cursorOffset]);
198
+ useEffect(() => {
199
+ const termWidth = (process.stdout.columns || 80) - 6;
200
+ const visualLines = getVisualLines(value, termWidth);
201
+ const newLineCount = Math.max(1, visualLines.length);
202
+ if (newLineCount !== visualLineCount) {
203
+ setVisualLineCount(newLineCount);
204
+ }
205
+ }, [value, visualLineCount]);
206
+ const currentDir = useMemo(() => {
207
+ const cwd = currentWorkingDirectory || process.cwd();
208
+ const sep = cwd.includes("/") ? "/" : "\\";
209
+ const parts = cwd.split(/[\\/]/).filter(Boolean);
210
+ const maxSegments = dimensions.columns >= 120 ? 3 : dimensions.columns >= 100 ? 2 : 1;
211
+ if (parts.length > maxSegments) {
212
+ return "..." + sep + parts.slice(-maxSegments).join(sep);
213
+ }
214
+ return cwd;
215
+ }, [currentWorkingDirectory, dimensions.columns]);
216
+ const currentEnvironment = useMemo(() => {
217
+ if (!subshellContext) return "local";
218
+ if (subshellContext.type === "ssh") {
219
+ const user = subshellContext.metadata?.username || "user";
220
+ const host = subshellContext.metadata?.hostname || "host";
221
+ return `ssh:${user}@${host}`;
222
+ }
223
+ if (subshellContext.type === "wsl") {
224
+ const distro = subshellContext.metadata?.distroName || "Ubuntu";
225
+ return `wsl:${distro}`;
226
+ }
227
+ if (subshellContext.type === "docker") {
228
+ const container = subshellContext.metadata?.containerId || "container";
229
+ return `docker:${container}`;
230
+ }
231
+ return "local";
232
+ }, [subshellContext]);
233
+ useEffect(() => {
234
+ if (!value || value.trim() === "") {
235
+ setAutocompleteSuggestion(null);
236
+ return;
237
+ }
238
+ const shouldSuggest = commandMode || isAutoMode && detectedIntent === "command";
239
+ if (shouldSuggest) {
240
+ const matches = CommandHistoryManager.getInstance().getMatches(value, currentDir, currentEnvironment);
241
+ if (matches.length > 0) {
242
+ setAutocompleteSuggestion(matches[0]);
243
+ } else {
244
+ setAutocompleteSuggestion(null);
245
+ }
246
+ } else {
247
+ setAutocompleteSuggestion(null);
248
+ }
249
+ }, [value, commandMode, isAutoMode, detectedIntent, aiAutoSuggestEnabled, currentDir, currentEnvironment]);
250
+ useEffect(() => {
251
+ setAiAutocompleteSuggestion(null);
252
+ if (aiAutocompleteDebounceRef.current) {
253
+ clearTimeout(aiAutocompleteDebounceRef.current);
254
+ aiAutocompleteDebounceRef.current = null;
255
+ }
256
+ if (aiAutocompleteAbortRef.current) {
257
+ aiAutocompleteAbortRef.current.abort();
258
+ aiAutocompleteAbortRef.current = null;
259
+ }
260
+ if (!aiAutoSuggestEnabled) {
261
+ return;
262
+ }
263
+ const shouldSuggest = commandMode || isAutoMode && detectedIntent === "command";
264
+ if (!shouldSuggest) {
265
+ return;
266
+ }
267
+ if (!value || value.trim().length < 2 || value.startsWith("/")) {
268
+ return;
269
+ }
270
+ aiAutocompleteDebounceRef.current = setTimeout(async () => {
271
+ setIsAiAutocompleteLoading(true);
272
+ const abortController = new AbortController();
273
+ aiAutocompleteAbortRef.current = abortController;
274
+ try {
275
+ if (!aiAutoSuggestEnabled) return;
276
+ const directoryHistory = CommandHistoryManager.getInstance().getDirectoryHistory(currentDir, currentEnvironment);
277
+ let files = [];
278
+ try {
279
+ if (subshellContext && subshellContext.type !== "local" && subshellContext.handler) {
303
280
  try {
304
- if (!aiAutoSuggestEnabled)
305
- return; // double check inside timeout
306
- // Get directory history using the correct method
307
- const directoryHistory = CommandHistoryManager.getInstance().getDirectoryHistory(currentDir, currentEnvironment);
308
- // Get files in current directory (local or remote)
309
- let files = [];
310
- try {
311
- if (subshellContext && subshellContext.type !== 'local' && subshellContext.handler) {
312
- // Remote/Subshell environment
313
- // Check if we can list files
314
- try {
315
- const dirEntries = await subshellContext.handler.listDirectory(currentDir);
316
- files = dirEntries
317
- .slice(0, 50)
318
- .map((e) => e.name + (e.type === 'directory' ? '/' : ''));
319
- }
320
- catch (remoteErr) {
321
- // quickLog('Remote list error: ' + remoteErr);
322
- }
323
- }
324
- else {
325
- // Local environment
326
- const dir = currentDir || process.cwd();
327
- if (fs.existsSync(dir)) {
328
- const entries = fs.readdirSync(dir, { withFileTypes: true });
329
- files = entries
330
- .slice(0, 50)
331
- .map(e => e.name + (e.isDirectory() ? '/' : ''));
332
- }
333
- }
334
- }
335
- catch (e) {
336
- // Ignore file access errors
337
- }
338
- // Determine OS and Platform from subshell context if available
339
- let osContext = process.platform;
340
- let platformContext = process.platform;
341
- if (subshellContext && subshellContext.type !== 'local' && subshellContext.metadata) {
342
- const metaOs = subshellContext.metadata.os;
343
- if (metaOs === 'windows') {
344
- osContext = 'win32';
345
- platformContext = 'win32';
346
- }
347
- else if (metaOs === 'macos') {
348
- osContext = 'darwin';
349
- platformContext = 'darwin';
350
- }
351
- else if (metaOs === 'linux') {
352
- osContext = 'linux';
353
- platformContext = 'linux';
354
- }
355
- }
356
- const context = {
357
- os: osContext,
358
- platform: platformContext,
359
- cwd: currentDir || process.cwd(),
360
- directoryHistory: directoryHistory.slice(0, 10),
361
- sessionCommands: sessionCommands.slice(-10),
362
- files: files,
363
- currentInput: value
364
- };
365
- const prediction = await AIAutocompleteAgent.predictCommand(context, abortController.signal);
366
- if (prediction && !abortController.signal.aborted) {
367
- setAiAutocompleteSuggestion(prediction);
368
- }
369
- }
370
- catch (error) {
371
- // Ignore errors (likely aborted)
372
- }
373
- finally {
374
- setIsAiAutocompleteLoading(false);
375
- if (aiAutocompleteAbortRef.current === abortController) {
376
- aiAutocompleteAbortRef.current = null;
377
- }
378
- }
379
- }, AI_AUTOCOMPLETE_DEBOUNCE_MS);
380
- return () => {
381
- if (aiAutocompleteDebounceRef.current) {
382
- clearTimeout(aiAutocompleteDebounceRef.current);
281
+ const dirEntries = await subshellContext.handler.listDirectory(currentDir);
282
+ files = dirEntries.slice(0, 50).map((e) => e.name + (e.type === "directory" ? "/" : ""));
283
+ } catch (remoteErr) {
383
284
  }
384
- if (aiAutocompleteAbortRef.current) {
385
- aiAutocompleteAbortRef.current.abort();
285
+ } else {
286
+ const dir = currentDir || process.cwd();
287
+ if (fs.existsSync(dir)) {
288
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
289
+ files = entries.slice(0, 50).map((e) => e.name + (e.isDirectory() ? "/" : ""));
386
290
  }
387
- };
388
- }, [value, commandMode, isAutoMode, detectedIntent, aiAutoSuggestEnabled, currentDir, currentEnvironment, sessionCommands]);
389
- // Auto-classification effect (Synchronous Heuristics Only)
390
- useEffect(() => {
391
- // Only run classification if in Auto Mode
392
- if (!isAutoMode) {
393
- return;
291
+ }
292
+ } catch (e) {
394
293
  }
395
- // Only classify if value is non-empty and not just whitespace
396
- const trimmedValue = value.trim();
397
- if (!trimmedValue || !onToggleCommandMode) {
398
- // Default to AI if empty
399
- setDetectedIntent('ai');
400
- if (commandMode)
401
- onToggleCommandMode();
402
- return;
294
+ let osContext = process.platform;
295
+ let platformContext = process.platform;
296
+ if (subshellContext && subshellContext.type !== "local" && subshellContext.metadata) {
297
+ const metaOs = subshellContext.metadata.os;
298
+ if (metaOs === "windows") {
299
+ osContext = "win32";
300
+ platformContext = "win32";
301
+ } else if (metaOs === "macos") {
302
+ osContext = "darwin";
303
+ platformContext = "darwin";
304
+ } else if (metaOs === "linux") {
305
+ osContext = "linux";
306
+ platformContext = "linux";
307
+ }
403
308
  }
404
- // Run local heuristic detection immediately
405
- const intent = detectIntent(trimmedValue);
406
- setDetectedIntent(intent);
407
- // Switch mode based on intent
408
- if (intent === 'command') {
409
- if (!commandMode)
410
- onToggleCommandMode();
309
+ const context = {
310
+ os: osContext,
311
+ platform: platformContext,
312
+ cwd: currentDir || process.cwd(),
313
+ directoryHistory: directoryHistory.slice(0, 10),
314
+ sessionCommands: sessionCommands.slice(-10),
315
+ files,
316
+ currentInput: value
317
+ };
318
+ const prediction = await AIAutocompleteAgent.predictCommand(context, abortController.signal);
319
+ if (prediction && !abortController.signal.aborted) {
320
+ setAiAutocompleteSuggestion(prediction);
411
321
  }
412
- else {
413
- // intent === 'ai'
414
- if (commandMode)
415
- onToggleCommandMode();
322
+ } catch (error) {
323
+ } finally {
324
+ setIsAiAutocompleteLoading(false);
325
+ if (aiAutocompleteAbortRef.current === abortController) {
326
+ aiAutocompleteAbortRef.current = null;
416
327
  }
417
- }, [value, commandMode, onToggleCommandMode, isAutoMode]);
418
- // Helper function to get files matching a query from the current directory
419
- const getMatchingFiles = (query) => {
328
+ }
329
+ }, AI_AUTOCOMPLETE_DEBOUNCE_MS);
330
+ return () => {
331
+ if (aiAutocompleteDebounceRef.current) {
332
+ clearTimeout(aiAutocompleteDebounceRef.current);
333
+ }
334
+ if (aiAutocompleteAbortRef.current) {
335
+ aiAutocompleteAbortRef.current.abort();
336
+ }
337
+ };
338
+ }, [value, commandMode, isAutoMode, detectedIntent, aiAutoSuggestEnabled, currentDir, currentEnvironment, sessionCommands]);
339
+ useEffect(() => {
340
+ if (!isAutoMode) {
341
+ return;
342
+ }
343
+ const trimmedValue = value.trim();
344
+ if (!trimmedValue || !onToggleCommandMode) {
345
+ setDetectedIntent("ai");
346
+ if (commandMode) onToggleCommandMode();
347
+ return;
348
+ }
349
+ const intent = detectIntent(trimmedValue);
350
+ setDetectedIntent(intent);
351
+ if (intent === "command") {
352
+ if (!commandMode) onToggleCommandMode();
353
+ } else {
354
+ if (commandMode) onToggleCommandMode();
355
+ }
356
+ }, [value, commandMode, onToggleCommandMode, isAutoMode]);
357
+ const showSlashAutocomplete = React.useCallback((commands) => {
358
+ if (commands.length > 0) {
359
+ setSlashAutocompleteCommands(commands);
360
+ setSlashAutocompleteVisible(true);
361
+ setSlashAutocompleteSelectedIndex(0);
362
+ setSlashAutocompleteScrollOffset(0);
363
+ } else {
364
+ setSlashAutocompleteVisible(false);
365
+ }
366
+ }, []);
367
+ const getRuleNameAutocompleteEntries = React.useCallback((partialName) => {
368
+ const normalized = partialName.toLowerCase();
369
+ const rules = rulesStorage.list();
370
+ if (rules.length === 0) {
371
+ return [{
372
+ name: "(no rules)",
373
+ description: "Create one with /rules add"
374
+ }];
375
+ }
376
+ const matches = rules.filter((rule) => rule.name.toLowerCase().includes(normalized)).slice(0, 10).map((rule) => ({
377
+ name: rule.name,
378
+ description: rule.preview || "Saved rule"
379
+ }));
380
+ if (matches.length === 0) {
381
+ return [{
382
+ name: "(no matches)",
383
+ description: "No saved rules match that name"
384
+ }];
385
+ }
386
+ return matches;
387
+ }, []);
388
+ const updateSlashAutocomplete = React.useCallback((nextValue) => {
389
+ if (!nextValue.startsWith("/")) {
390
+ setSlashAutocompleteVisible(false);
391
+ return;
392
+ }
393
+ if (!nextValue.includes(" ")) {
394
+ showSlashAutocomplete(filterCommands(nextValue.slice(1)));
395
+ return;
396
+ }
397
+ if (nextValue.startsWith("/mcp ")) {
398
+ showSlashAutocomplete(filterCommands(nextValue.slice(1)));
399
+ return;
400
+ }
401
+ if (nextValue.startsWith("/chat ")) {
402
+ showSlashAutocomplete(filterCommands(nextValue.slice(1)));
403
+ return;
404
+ }
405
+ if (nextValue.startsWith("/add-command ") || nextValue.startsWith("/add-command-auto-detect ")) {
406
+ showSlashAutocomplete(filterCommands(nextValue.slice(1)));
407
+ return;
408
+ }
409
+ if (nextValue.startsWith("/background-task ") || nextValue.startsWith("/bkg ") || nextValue.startsWith("/bg-task ")) {
410
+ showSlashAutocomplete(filterCommands(nextValue.slice(1)));
411
+ return;
412
+ }
413
+ if (nextValue.startsWith("/sync ")) {
414
+ showSlashAutocomplete(filterCommands(nextValue.slice(1)));
415
+ return;
416
+ }
417
+ if (nextValue.startsWith("/models ") || nextValue.startsWith("/model ")) {
418
+ showSlashAutocomplete(filterCommands(nextValue.slice(1)));
419
+ return;
420
+ }
421
+ if (nextValue.startsWith("/settings auto-suggest ")) {
422
+ showSlashAutocomplete(filterCommands(nextValue.slice(1)));
423
+ return;
424
+ }
425
+ if (nextValue.startsWith("/settings ")) {
426
+ showSlashAutocomplete(filterCommands(nextValue.slice(1)));
427
+ return;
428
+ }
429
+ if (nextValue.startsWith("/revert ")) {
430
+ const partialId = nextValue.slice(8).toLowerCase();
431
+ const checkpoints = getCheckpoints ? getCheckpoints() : [];
432
+ if (checkpoints.length === 0) {
433
+ showSlashAutocomplete([{
434
+ name: "(no checkpoints)",
435
+ description: "Ask AI a question first to create a checkpoint"
436
+ }]);
437
+ return;
438
+ }
439
+ const checkpointCommands = checkpoints.filter((cp) => cp.id.toLowerCase().includes(partialId) || cp.prompt.toLowerCase().includes(partialId)).slice(0, 10).map((cp) => ({
440
+ name: cp.id,
441
+ description: cp.prompt.length > 50 ? cp.prompt.slice(0, 50) + "..." : cp.prompt
442
+ }));
443
+ showSlashAutocomplete(checkpointCommands);
444
+ return;
445
+ }
446
+ if (nextValue.match(/^\/rules\s+(edit|delete)\s+/)) {
447
+ const match = nextValue.match(/^\/rules\s+(?:edit|delete)\s+(.*)$/);
448
+ const partialName = match ? match[1] : "";
449
+ showSlashAutocomplete(getRuleNameAutocompleteEntries(partialName));
450
+ return;
451
+ }
452
+ if (nextValue.startsWith("/rules ")) {
453
+ showSlashAutocomplete(filterCommands(nextValue.slice(1)));
454
+ return;
455
+ }
456
+ if (nextValue.match(/^\/workflow\s+(run|view|delete)\s+/) || nextValue.match(/^\/wf\s+(run|view|delete)\s+/)) {
457
+ const match = nextValue.match(/^\/(?:workflow|wf)\s+(?:run|view|delete)\s+(.*)$/);
458
+ const partialName = match ? match[1].toLowerCase() : "";
459
+ const workflows = workflowStorage.list();
460
+ const matchingWorkflows = workflows.filter((wf) => wf.name.toLowerCase().includes(partialName)).slice(0, 10).map((wf) => ({
461
+ name: wf.name,
462
+ description: wf.description || `${wf.stepCount} step${wf.stepCount !== 1 ? "s" : ""}`
463
+ }));
464
+ showSlashAutocomplete(matchingWorkflows);
465
+ return;
466
+ }
467
+ if (nextValue.match(/^\/workflow\s+new\s+/) || nextValue.match(/^\/wf\s+new\s+/)) {
468
+ showSlashAutocomplete(filterCommands(nextValue.slice(1)));
469
+ return;
470
+ }
471
+ if (nextValue.startsWith("/workflow ") || nextValue.startsWith("/wf ")) {
472
+ showSlashAutocomplete(filterCommands(nextValue.slice(1)));
473
+ return;
474
+ }
475
+ setSlashAutocompleteVisible(false);
476
+ }, [getCheckpoints, getRuleNameAutocompleteEntries, showSlashAutocomplete]);
477
+ const getMatchingFiles = React.useCallback((query) => {
478
+ try {
479
+ const cwd = currentWorkingDirectory || process.cwd();
480
+ if (!fs.existsSync(cwd)) return [];
481
+ const lowerQuery = query.toLowerCase();
482
+ const results = [];
483
+ const SKIP_DIRS = /* @__PURE__ */ new Set([
484
+ ".git",
485
+ "node_modules",
486
+ "dist",
487
+ "build",
488
+ ".next",
489
+ "__pycache__",
490
+ ".cache",
491
+ "coverage",
492
+ ".nyc_output",
493
+ ".turbo",
494
+ "out",
495
+ ".svelte-kit"
496
+ ]);
497
+ if (lowerQuery === "") {
498
+ let entries;
420
499
  try {
421
- const cwd = currentWorkingDirectory || process.cwd();
422
- if (!fs.existsSync(cwd))
423
- return [];
424
- const entries = fs.readdirSync(cwd, { withFileTypes: true });
425
- const lowerQuery = query.toLowerCase();
426
- return entries
427
- .filter(entry => {
428
- // Exclude hidden files (starting with .)
429
- if (entry.name.startsWith('.'))
430
- return false;
431
- // Match case-insensitive
432
- return entry.name.toLowerCase().includes(lowerQuery);
433
- })
434
- .map(entry => ({
435
- name: entry.name,
436
- isDirectory: entry.isDirectory()
437
- }))
438
- .sort((a, b) => {
439
- // Prioritize files that start with the query
440
- const aStarts = a.name.toLowerCase().startsWith(lowerQuery);
441
- const bStarts = b.name.toLowerCase().startsWith(lowerQuery);
442
- if (aStarts && !bStarts)
443
- return -1;
444
- if (!aStarts && bStarts)
445
- return 1;
446
- return a.name.localeCompare(b.name);
447
- })
448
- .slice(0, 10); // Get more than 6 initially, UI will limit display
500
+ entries = fs.readdirSync(cwd, { withFileTypes: true });
501
+ } catch {
502
+ return [];
449
503
  }
450
- catch (error) {
451
- return [];
504
+ for (const entry of entries) {
505
+ if (entry.name.startsWith(".")) continue;
506
+ results.push({
507
+ name: entry.name,
508
+ insertText: entry.isDirectory() ? `${entry.name}/` : entry.name,
509
+ kind: entry.isDirectory() ? "directory" : "file"
510
+ });
452
511
  }
453
- };
454
- // File tag (@) detection effect
455
- useEffect(() => {
456
- // Don't show file tag autocomplete in command mode
457
- if (commandMode) {
458
- setFileTagAutocompleteVisible(false);
459
- return;
512
+ results.sort((a, b) => a.name.localeCompare(b.name));
513
+ return results.slice(0, 15);
514
+ }
515
+ const walk = (dir, relativePath, depth) => {
516
+ if (depth > 6 || results.length >= 60) return;
517
+ let entries;
518
+ try {
519
+ entries = fs.readdirSync(dir, { withFileTypes: true });
520
+ } catch {
521
+ return;
460
522
  }
461
- // Find if cursor is within a potential file tag
462
- // Look backwards from cursor to find @
463
- let atPosition = -1;
464
- for (let i = cursorOffset - 1; i >= 0; i--) {
465
- const char = value[i];
466
- // Stop if we hit whitespace or another special char
467
- if (/[\s\n]/.test(char))
468
- break;
469
- if (char === '@') {
470
- atPosition = i;
471
- break;
523
+ for (const entry of entries) {
524
+ if (entry.name.startsWith(".")) continue;
525
+ const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
526
+ const lowerRelPath = entryRelPath.toLowerCase();
527
+ const lowerBasename = entry.name.toLowerCase();
528
+ if (entry.isDirectory()) {
529
+ if (SKIP_DIRS.has(entry.name)) continue;
530
+ if (lowerRelPath.includes(lowerQuery) || lowerBasename.includes(lowerQuery)) {
531
+ results.push({
532
+ name: entryRelPath,
533
+ insertText: `${entryRelPath}/`,
534
+ kind: "directory"
535
+ });
536
+ }
537
+ walk(path.join(dir, entry.name), entryRelPath, depth + 1);
538
+ } else {
539
+ if (lowerRelPath.includes(lowerQuery) || lowerBasename.includes(lowerQuery)) {
540
+ results.push({
541
+ name: entryRelPath,
542
+ insertText: entryRelPath,
543
+ kind: "file"
544
+ });
472
545
  }
546
+ }
473
547
  }
474
- // Only treat @ as file tag if it's at start of input OR preceded by whitespace
475
- // This prevents triggering on things like "rohan@localhost" or "email@domain.com"
476
- if (atPosition === -1 || (atPosition > 0 && !/[\s\n]/.test(value[atPosition - 1]))) {
477
- setFileTagAutocompleteVisible(false);
478
- setActiveFileTagStart(null);
479
- return;
548
+ };
549
+ walk(cwd, "", 0);
550
+ results.sort((a, b) => {
551
+ const aBasename = a.name.split("/").pop().toLowerCase();
552
+ const bBasename = b.name.split("/").pop().toLowerCase();
553
+ const aStartsBasename = aBasename.startsWith(lowerQuery);
554
+ const bStartsBasename = bBasename.startsWith(lowerQuery);
555
+ const aStartsFull = a.name.toLowerCase().startsWith(lowerQuery);
556
+ const bStartsFull = b.name.toLowerCase().startsWith(lowerQuery);
557
+ if ((aStartsFull || aStartsBasename) && !(bStartsFull || bStartsBasename)) return -1;
558
+ if (!(aStartsFull || aStartsBasename) && (bStartsFull || bStartsBasename)) return 1;
559
+ const aDepth = a.name.split("/").length;
560
+ const bDepth = b.name.split("/").length;
561
+ if (aDepth !== bDepth) return aDepth - bDepth;
562
+ return a.name.localeCompare(b.name);
563
+ });
564
+ return results.slice(0, 15);
565
+ } catch (error) {
566
+ return [];
567
+ }
568
+ }, [currentWorkingDirectory]);
569
+ const getMatchingMentionSuggestions = React.useCallback((query) => {
570
+ const normalized = query.toLowerCase();
571
+ if (normalized.startsWith("rules:")) {
572
+ const ruleQuery = query.slice("rules:".length).toLowerCase();
573
+ return rulesStorage.list().filter((rule) => rule.name.toLowerCase().includes(ruleQuery)).slice(0, 10).map((rule) => ({
574
+ name: `rules:${rule.name}`,
575
+ insertText: `rules:${rule.name}`,
576
+ kind: "rule",
577
+ description: rule.preview || "Saved rule"
578
+ }));
579
+ }
580
+ const suggestions = [];
581
+ if ("rules:".startsWith(normalized) || normalized.startsWith("rules")) {
582
+ suggestions.push({
583
+ name: "rules:",
584
+ insertText: "rules:",
585
+ kind: "rule-namespace",
586
+ description: "Insert a saved reusable rule"
587
+ });
588
+ }
589
+ return [...suggestions, ...getMatchingFiles(query)].slice(0, 15);
590
+ }, [getMatchingFiles]);
591
+ useEffect(() => {
592
+ if (commandMode) {
593
+ setFileTagAutocompleteVisible(false);
594
+ return;
595
+ }
596
+ let atPosition = -1;
597
+ for (let i = cursorOffset - 1; i >= 0; i--) {
598
+ const char = value[i];
599
+ if (/[\s\n]/.test(char)) break;
600
+ if (char === "@") {
601
+ atPosition = i;
602
+ break;
603
+ }
604
+ }
605
+ if (atPosition === -1 || atPosition > 0 && !/[\s\n]/.test(value[atPosition - 1])) {
606
+ setFileTagAutocompleteVisible(false);
607
+ setActiveFileTagStart(null);
608
+ return;
609
+ }
610
+ const query = value.slice(atPosition + 1, cursorOffset);
611
+ const matches = getMatchingMentionSuggestions(query);
612
+ if (matches.length > 0) {
613
+ setFileTagSuggestions(matches);
614
+ setFileTagAutocompleteVisible(true);
615
+ setActiveFileTagStart(atPosition);
616
+ setFileTagSelectedIndex(0);
617
+ } else {
618
+ setFileTagAutocompleteVisible(false);
619
+ setActiveFileTagStart(null);
620
+ }
621
+ }, [value, cursorOffset, commandMode, currentWorkingDirectory, getMatchingMentionSuggestions]);
622
+ const pushToUndoStack = () => {
623
+ setUndoStack((prev) => [...prev, { value, cursorOffset }]);
624
+ setRedoStack([]);
625
+ };
626
+ const handleUndo = () => {
627
+ if (undoStack.length === 0) return;
628
+ const previousState = undoStack[undoStack.length - 1];
629
+ setRedoStack((prev) => [...prev, { value, cursorOffset }]);
630
+ setUndoStack((prev) => prev.slice(0, -1));
631
+ setValue(previousState.value);
632
+ setCursorOffset(previousState.cursorOffset);
633
+ setSelection(null);
634
+ };
635
+ const applyMentionSuggestion = (selected, viaTab) => {
636
+ if (activeFileTagStart === null) {
637
+ return;
638
+ }
639
+ pushToUndoStack();
640
+ const beforeTag = value.slice(0, activeFileTagStart);
641
+ const afterCursor = value.slice(cursorOffset);
642
+ const baseValue = beforeTag + "@" + selected.insertText;
643
+ if (selected.kind === "rule-namespace") {
644
+ const newValue2 = baseValue + afterCursor;
645
+ const newCursorPos2 = activeFileTagStart + 1 + selected.insertText.length;
646
+ setValue(newValue2);
647
+ setCursorOffset(newCursorPos2);
648
+ return;
649
+ }
650
+ if (viaTab && selected.kind === "directory") {
651
+ const newValue2 = baseValue + afterCursor;
652
+ const newCursorPos2 = activeFileTagStart + 1 + selected.insertText.length;
653
+ setValue(newValue2);
654
+ setCursorOffset(newCursorPos2);
655
+ return;
656
+ }
657
+ const trailingSuffix = viaTab ? "" : " ";
658
+ const newValue = baseValue + trailingSuffix + afterCursor;
659
+ const newCursorPos = activeFileTagStart + 1 + selected.insertText.length + trailingSuffix.length;
660
+ setValue(newValue);
661
+ setCursorOffset(newCursorPos);
662
+ setConfirmedFileTags((prev) => [
663
+ ...prev.filter(
664
+ (tag) => !(tag.start >= activeFileTagStart && tag.start <= cursorOffset)
665
+ ),
666
+ {
667
+ start: activeFileTagStart,
668
+ end: activeFileTagStart + 1 + selected.insertText.length,
669
+ fileName: selected.name
670
+ }
671
+ ]);
672
+ setFileTagAutocompleteVisible(false);
673
+ setActiveFileTagStart(null);
674
+ };
675
+ useInput((input, key) => {
676
+ if (!isActive) return;
677
+ const isWindows = process.platform === "win32";
678
+ const inputCharCode = input ? input.charCodeAt(0) : null;
679
+ const normalizedInput = input ? input.toLowerCase() : "";
680
+ const isTerminalShiftEnterSequence = input === "\x1B\r" || input === "\x1B\n" || normalizedInput === "\x1B[13;2u" || normalizedInput === "\x1B[13;2~" || normalizedInput === "\x1B[27;2;13~";
681
+ const isEnterEvent = key.return || isTerminalShiftEnterSequence;
682
+ const isMacOptionWordBackwardSequence = isMac && (normalizedInput === "\x1Bb" || normalizedInput === "\x1B[1;3d" || normalizedInput === "\x1B[1;9d" || key.meta && normalizedInput === "b" || key.escape && key.leftArrow);
683
+ const isMacOptionWordForwardSequence = isMac && (normalizedInput === "\x1Bf" || normalizedInput === "\x1B[1;3c" || normalizedInput === "\x1B[1;9c" || key.meta && normalizedInput === "f" || key.escape && key.rightArrow);
684
+ if (slashAutocompleteVisible) {
685
+ if (key.downArrow) {
686
+ const newIndex = Math.min(slashAutocompleteSelectedIndex + 1, slashAutocompleteCommands.length - 1);
687
+ setSlashAutocompleteSelectedIndex(newIndex);
688
+ if (newIndex >= slashAutocompleteScrollOffset + slashMaxVisibleItems) {
689
+ setSlashAutocompleteScrollOffset(newIndex - slashMaxVisibleItems + 1);
480
690
  }
481
- // Extract the query after @
482
- const query = value.slice(atPosition + 1, cursorOffset);
483
- // Get matching files
484
- const matches = getMatchingFiles(query);
485
- if (matches.length > 0) {
486
- setFileTagSuggestions(matches);
487
- setFileTagAutocompleteVisible(true);
488
- setActiveFileTagStart(atPosition);
489
- setFileTagSelectedIndex(0);
691
+ return;
692
+ }
693
+ if (key.upArrow) {
694
+ const newIndex = Math.max(slashAutocompleteSelectedIndex - 1, 0);
695
+ setSlashAutocompleteSelectedIndex(newIndex);
696
+ if (newIndex < slashAutocompleteScrollOffset) {
697
+ setSlashAutocompleteScrollOffset(newIndex);
490
698
  }
491
- else {
492
- setFileTagAutocompleteVisible(false);
493
- setActiveFileTagStart(null);
699
+ return;
700
+ }
701
+ if (key.return && input.length <= 1 && !key.shift && !key.ctrl && !key.meta) {
702
+ const selected = slashAutocompleteCommands[slashAutocompleteSelectedIndex];
703
+ if (!selected || selected.name.startsWith("(no ")) {
704
+ setSlashAutocompleteVisible(false);
705
+ return;
494
706
  }
495
- }, [value, cursorOffset, commandMode, currentWorkingDirectory]);
496
- const pushToUndoStack = () => {
497
- setUndoStack(prev => [...prev, { value, cursorOffset }]);
498
- setRedoStack([]); // Clear redo stack on new action
499
- };
500
- const handleUndo = () => {
501
- if (undoStack.length === 0)
502
- return;
503
- const previousState = undoStack[undoStack.length - 1];
504
- setRedoStack(prev => [...prev, { value, cursorOffset }]);
505
- setUndoStack(prev => prev.slice(0, -1));
506
- setValue(previousState.value);
507
- setCursorOffset(previousState.cursorOffset);
508
- setSelection(null);
509
- };
510
- useInput((input, key) => {
511
- if (!isActive)
512
- return;
513
- // Detect OS for platform-specific key handling
514
- const isWindows = process.platform === 'win32';
515
- const inputCharCode = input ? input.charCodeAt(0) : null;
516
- // Handle slash command autocomplete navigation
517
- if (slashAutocompleteVisible) {
518
- if (key.downArrow) {
519
- const newIndex = Math.min(slashAutocompleteSelectedIndex + 1, slashAutocompleteCommands.length - 1);
520
- setSlashAutocompleteSelectedIndex(newIndex);
521
- // Scroll down if selected is below visible window
522
- if (newIndex >= slashAutocompleteScrollOffset + slashMaxVisibleItems) {
523
- setSlashAutocompleteScrollOffset(newIndex - slashMaxVisibleItems + 1);
524
- }
525
- return;
526
- }
527
- if (key.upArrow) {
528
- const newIndex = Math.max(slashAutocompleteSelectedIndex - 1, 0);
529
- setSlashAutocompleteSelectedIndex(newIndex);
530
- // Scroll up if selected is above visible window
531
- if (newIndex < slashAutocompleteScrollOffset) {
532
- setSlashAutocompleteScrollOffset(newIndex);
533
- }
534
- return;
535
- }
536
- if (key.return && input.length <= 1 && !key.shift && !key.ctrl) {
537
- // Select the highlighted command
538
- const selected = slashAutocompleteCommands[slashAutocompleteSelectedIndex];
539
- // Check if we're in MCP subcommand mode
540
- if (value.startsWith('/mcp ')) {
541
- // We're selecting an MCP subcommand, keep "/mcp " and append the subcommand
542
- const newValue = `/mcp ${selected.name} `;
543
- setValue(newValue);
544
- setCursorOffset(newValue.length);
545
- setSlashAutocompleteVisible(false);
546
- }
547
- else if (value.startsWith('/chat ')) {
548
- // We're selecting a chat subcommand, keep "/chat " and append the subcommand
549
- const newValue = `/chat ${selected.name} `;
550
- setValue(newValue);
551
- setCursorOffset(newValue.length);
552
- setSlashAutocompleteVisible(false);
553
- }
554
- else if (value.startsWith('/add-command ') || value.startsWith('/add-command-auto-detect ')) {
555
- // We're selecting an add-command subcommand
556
- const prefix = value.startsWith('/add-command-auto-detect ') ? '/add-command-auto-detect ' : '/add-command ';
557
- const newValue = `${prefix}${selected.name} `;
558
- setValue(newValue);
559
- setCursorOffset(newValue.length);
560
- setSlashAutocompleteVisible(false);
561
- }
562
- else if (value.startsWith('/background-task ') || value.startsWith('/bkg ') || value.startsWith('/bg-task ')) {
563
- // We're selecting a background-task subcommand
564
- const prefix = value.startsWith('/bkg ') ? '/bkg ' : (value.startsWith('/bg-task ') ? '/bg-task ' : '/background-task ');
565
- const newValue = `${prefix}${selected.name} `;
566
- setValue(newValue);
567
- setCursorOffset(newValue.length);
568
- setSlashAutocompleteVisible(false);
569
- }
570
- else if (value.startsWith('/sync ')) {
571
- // We're selecting a sync subcommand
572
- const newValue = `/sync ${selected.name} `;
573
- setValue(newValue);
574
- setCursorOffset(newValue.length);
575
- setSlashAutocompleteVisible(false);
576
- }
577
- else if (value.startsWith('/revert ')) {
578
- // We're selecting a checkpoint from /revert dropdown
579
- const newValue = `/revert ${selected.name}`;
580
- setValue(newValue);
581
- setCursorOffset(newValue.length);
582
- setSlashAutocompleteVisible(false);
583
- }
584
- else if (value.startsWith('/models ') || value.startsWith('/model ')) {
585
- // We're selecting a models subcommand
586
- const prefix = value.startsWith('/models ') ? '/models ' : '/model ';
587
- const newValue = `${prefix}${selected.name}`;
588
- setValue(newValue);
589
- setCursorOffset(newValue.length);
590
- setSlashAutocompleteVisible(false);
591
- }
592
- else if (value.startsWith('/workflow new ') || value.startsWith('/wf new ')) {
593
- // We're in workflow new subcommand mode (manual or learn-workflow)
594
- const prefix = value.startsWith('/workflow new ') ? '/workflow new ' : '/wf new ';
595
- const newValue = `${prefix}${selected.name}`;
596
- setValue(newValue);
597
- setCursorOffset(newValue.length);
598
- setSlashAutocompleteVisible(false);
599
- }
600
- else if (value.startsWith('/workflow ') || value.startsWith('/wf ')) {
601
- // Check if we're in workflow name selection mode (after run/view/delete)
602
- const workflowNameMatch = value.match(/^\/(?:workflow|wf)\s+(run|view|delete)\s+/);
603
- if (workflowNameMatch) {
604
- // We're selecting a workflow name
605
- const prefix = value.match(/^\/(?:workflow|wf)\s+(?:run|view|delete)\s+/)?.[0] || '';
606
- const newValue = `${prefix}${selected.name}`;
607
- setValue(newValue);
608
- setCursorOffset(newValue.length);
609
- setSlashAutocompleteVisible(false);
610
- }
611
- else {
612
- // We're selecting a workflow subcommand
613
- const prefix = value.startsWith('/workflow ') ? '/workflow ' : '/wf ';
614
- const newValue = `${prefix}${selected.name} `;
615
- setValue(newValue);
616
- setCursorOffset(newValue.length);
617
- // For run/view/delete, show workflow names immediately
618
- if (selected.name === 'run' || selected.name === 'view' || selected.name === 'delete') {
619
- const workflows = workflowStorage.list();
620
- const matchingWorkflows = workflows
621
- .slice(0, 10)
622
- .map(wf => ({
623
- name: wf.name,
624
- description: wf.description || `${wf.stepCount} step${wf.stepCount !== 1 ? 's' : ''}`
625
- }));
626
- if (matchingWorkflows.length > 0) {
627
- setSlashAutocompleteCommands(matchingWorkflows);
628
- setSlashAutocompleteSelectedIndex(0);
629
- setSlashAutocompleteScrollOffset(0);
630
- // Keep autocomplete visible
631
- }
632
- else {
633
- setSlashAutocompleteVisible(false);
634
- }
635
- }
636
- else if (selected.name === 'new') {
637
- // For 'new' subcommand, show manual/learn-workflow options
638
- const subcommandMatches = filterCommands('workflow new ');
639
- if (subcommandMatches.length > 0) {
640
- setSlashAutocompleteCommands(subcommandMatches);
641
- setSlashAutocompleteSelectedIndex(0);
642
- setSlashAutocompleteScrollOffset(0);
643
- // Keep autocomplete visible for next level
644
- }
645
- else {
646
- setSlashAutocompleteVisible(false);
647
- }
648
- }
649
- else {
650
- setSlashAutocompleteVisible(false);
651
- }
652
- }
653
- }
654
- else if (value.startsWith('/settings auto-suggest ')) {
655
- // We're selecting an auto-suggest option (on/off)
656
- const newValue = `/settings auto-suggest ${selected.name}`;
657
- setValue(newValue);
658
- setCursorOffset(newValue.length);
659
- setSlashAutocompleteVisible(false);
660
- }
661
- else if (value.startsWith('/settings ')) {
662
- // We're selecting a settings subcommand (e.g., auto-suggest)
663
- const newValue = `/settings ${selected.name} `;
664
- setValue(newValue);
665
- setCursorOffset(newValue.length);
666
- // Show the next level options (on/off for auto-suggest)
667
- if (selected.name === 'auto-suggest') {
668
- const optionMatches = filterCommands('settings auto-suggest ');
669
- if (optionMatches.length > 0) {
670
- setSlashAutocompleteCommands(optionMatches);
671
- setSlashAutocompleteSelectedIndex(0);
672
- setSlashAutocompleteScrollOffset(0);
673
- }
674
- else {
675
- setSlashAutocompleteVisible(false);
676
- }
677
- }
678
- else {
679
- setSlashAutocompleteVisible(false);
680
- }
681
- }
682
- else {
683
- // Regular slash command, replace everything
684
- const newValue = `/${selected.name} `;
685
- setValue(newValue);
686
- setCursorOffset(newValue.length);
687
- // Check if this command has subcommands (e.g., /mcp, /chat, /add-command)
688
- // If so, immediately show the subcommand list
689
- if (selected.name === 'mcp') {
690
- const subcommandMatches = filterCommands('mcp ');
691
- if (subcommandMatches.length > 0) {
692
- setSlashAutocompleteCommands(subcommandMatches);
693
- setSlashAutocompleteSelectedIndex(0);
694
- setSlashAutocompleteScrollOffset(0);
695
- // Keep autocomplete visible for subcommands
696
- }
697
- else {
698
- setSlashAutocompleteVisible(false);
699
- }
700
- }
701
- else if (selected.name === 'chat') {
702
- const subcommandMatches = filterCommands('chat ');
703
- if (subcommandMatches.length > 0) {
704
- setSlashAutocompleteCommands(subcommandMatches);
705
- setSlashAutocompleteSelectedIndex(0);
706
- setSlashAutocompleteScrollOffset(0);
707
- // Keep autocomplete visible for subcommands
708
- }
709
- else {
710
- setSlashAutocompleteVisible(false);
711
- }
712
- }
713
- else if (selected.name === 'add-command' || selected.name === 'add-command-auto-detect') {
714
- const subcommandMatches = filterCommands('add-command ');
715
- if (subcommandMatches.length > 0) {
716
- setSlashAutocompleteCommands(subcommandMatches);
717
- setSlashAutocompleteSelectedIndex(0);
718
- setSlashAutocompleteScrollOffset(0);
719
- // Keep autocomplete visible for subcommands
720
- }
721
- else {
722
- setSlashAutocompleteVisible(false);
723
- }
724
- }
725
- else if (selected.name === 'background-task' || selected.name === 'bkg' || selected.name === 'bg-task') {
726
- const subcommandMatches = filterCommands('background-task ');
727
- if (subcommandMatches.length > 0) {
728
- setSlashAutocompleteCommands(subcommandMatches);
729
- setSlashAutocompleteSelectedIndex(0);
730
- setSlashAutocompleteScrollOffset(0);
731
- // Keep autocomplete visible for subcommands
732
- }
733
- else {
734
- setSlashAutocompleteVisible(false);
735
- }
736
- }
737
- else if (selected.name === 'sync') {
738
- const subcommandMatches = filterCommands('sync ');
739
- if (subcommandMatches.length > 0) {
740
- setSlashAutocompleteCommands(subcommandMatches);
741
- setSlashAutocompleteSelectedIndex(0);
742
- setSlashAutocompleteScrollOffset(0);
743
- // Keep autocomplete visible for subcommands
744
- }
745
- else {
746
- setSlashAutocompleteVisible(false);
747
- }
748
- }
749
- else if (selected.name === 'models' || selected.name === 'model') {
750
- const subcommandMatches = filterCommands('models ');
751
- if (subcommandMatches.length > 0) {
752
- setSlashAutocompleteCommands(subcommandMatches);
753
- setSlashAutocompleteSelectedIndex(0);
754
- setSlashAutocompleteScrollOffset(0);
755
- // Keep autocomplete visible for subcommands
756
- }
757
- else {
758
- setSlashAutocompleteVisible(false);
759
- }
760
- }
761
- else if (selected.name === 'settings') {
762
- const subcommandMatches = filterCommands('settings ');
763
- if (subcommandMatches.length > 0) {
764
- setSlashAutocompleteCommands(subcommandMatches);
765
- setSlashAutocompleteSelectedIndex(0);
766
- setSlashAutocompleteScrollOffset(0);
767
- // Keep autocomplete visible for subcommands
768
- }
769
- else {
770
- setSlashAutocompleteVisible(false);
771
- }
772
- }
773
- else if (selected.name === 'workflow' || selected.name === 'wf') {
774
- const subcommandMatches = filterCommands('workflow ');
775
- if (subcommandMatches.length > 0) {
776
- setSlashAutocompleteCommands(subcommandMatches);
777
- setSlashAutocompleteSelectedIndex(0);
778
- setSlashAutocompleteScrollOffset(0);
779
- // Keep autocomplete visible for subcommands
780
- }
781
- else {
782
- setSlashAutocompleteVisible(false);
783
- }
784
- }
785
- else if (selected.name === 'revert') {
786
- // For revert, show checkpoints immediately
787
- const checkpoints = getCheckpoints ? getCheckpoints() : [];
788
- if (checkpoints.length === 0) {
789
- setSlashAutocompleteCommands([{
790
- name: '(no checkpoints)',
791
- description: 'Ask AI a question first to create a checkpoint'
792
- }]);
793
- setSlashAutocompleteSelectedIndex(0);
794
- setSlashAutocompleteScrollOffset(0);
795
- // Keep autocomplete visible
796
- }
797
- else {
798
- const checkpointCommands = checkpoints
799
- .slice(0, 10)
800
- .map(cp => {
801
- const truncatedPrompt = cp.prompt.length > 50 ? cp.prompt.slice(0, 50) + '...' : cp.prompt;
802
- return {
803
- name: cp.id,
804
- description: truncatedPrompt
805
- };
806
- });
807
- setSlashAutocompleteCommands(checkpointCommands);
808
- setSlashAutocompleteSelectedIndex(0);
809
- setSlashAutocompleteScrollOffset(0);
810
- // Keep autocomplete visible
811
- }
812
- }
813
- else {
814
- setSlashAutocompleteVisible(false);
815
- }
816
- }
817
- return;
818
- }
819
- if (key.escape) {
707
+ if (value.startsWith("/mcp ")) {
708
+ const newValue = `/mcp ${selected.name} `;
709
+ setValue(newValue);
710
+ setCursorOffset(newValue.length);
711
+ setSlashAutocompleteVisible(false);
712
+ } else if (value.startsWith("/chat ")) {
713
+ const newValue = `/chat ${selected.name} `;
714
+ setValue(newValue);
715
+ setCursorOffset(newValue.length);
716
+ setSlashAutocompleteVisible(false);
717
+ } else if (value.startsWith("/add-command ") || value.startsWith("/add-command-auto-detect ")) {
718
+ const prefix = value.startsWith("/add-command-auto-detect ") ? "/add-command-auto-detect " : "/add-command ";
719
+ const newValue = `${prefix}${selected.name} `;
720
+ setValue(newValue);
721
+ setCursorOffset(newValue.length);
722
+ setSlashAutocompleteVisible(false);
723
+ } else if (value.startsWith("/background-task ") || value.startsWith("/bkg ") || value.startsWith("/bg-task ")) {
724
+ const prefix = value.startsWith("/bkg ") ? "/bkg " : value.startsWith("/bg-task ") ? "/bg-task " : "/background-task ";
725
+ const newValue = `${prefix}${selected.name} `;
726
+ setValue(newValue);
727
+ setCursorOffset(newValue.length);
728
+ setSlashAutocompleteVisible(false);
729
+ } else if (value.startsWith("/sync ")) {
730
+ const newValue = `/sync ${selected.name} `;
731
+ setValue(newValue);
732
+ setCursorOffset(newValue.length);
733
+ setSlashAutocompleteVisible(false);
734
+ } else if (value.startsWith("/revert ")) {
735
+ const newValue = `/revert ${selected.name}`;
736
+ setValue(newValue);
737
+ setCursorOffset(newValue.length);
738
+ setSlashAutocompleteVisible(false);
739
+ } else if (value.startsWith("/models ") || value.startsWith("/model ")) {
740
+ const prefix = value.startsWith("/models ") ? "/models " : "/model ";
741
+ const newValue = `${prefix}${selected.name}`;
742
+ setValue(newValue);
743
+ setCursorOffset(newValue.length);
744
+ setSlashAutocompleteVisible(false);
745
+ } else if (value.startsWith("/rules ")) {
746
+ const ruleNameMatch = value.match(/^\/rules\s+(edit|delete)\s+/);
747
+ if (ruleNameMatch) {
748
+ const prefix = value.match(/^\/rules\s+(?:edit|delete)\s+/)?.[0] || "";
749
+ const newValue = `${prefix}${selected.name}`;
750
+ setValue(newValue);
751
+ setCursorOffset(newValue.length);
752
+ setSlashAutocompleteVisible(false);
753
+ } else {
754
+ const newValue = `/rules ${selected.name} `;
755
+ setValue(newValue);
756
+ setCursorOffset(newValue.length);
757
+ if (selected.name === "edit" || selected.name === "delete") {
758
+ const ruleMatches = getRuleNameAutocompleteEntries("");
759
+ if (ruleMatches.length > 0) {
760
+ setSlashAutocompleteCommands(ruleMatches);
761
+ setSlashAutocompleteSelectedIndex(0);
762
+ setSlashAutocompleteScrollOffset(0);
763
+ } else {
820
764
  setSlashAutocompleteVisible(false);
821
- return;
765
+ }
766
+ } else {
767
+ setSlashAutocompleteVisible(false);
822
768
  }
823
- }
824
- // Handle file tag (@) autocomplete navigation
825
- if (fileTagAutocompleteVisible) {
826
- if (key.downArrow) {
827
- setFileTagSelectedIndex(prev => Math.min(prev + 1, Math.min(fileTagSuggestions.length - 1, 5)));
828
- return;
829
- }
830
- if (key.upArrow) {
831
- setFileTagSelectedIndex(prev => Math.max(prev - 1, 0));
832
- return;
833
- }
834
- if (key.return && input.length <= 1 && !key.shift && !key.ctrl) {
835
- // Select the highlighted file
836
- const selected = fileTagSuggestions[fileTagSelectedIndex];
837
- if (selected && activeFileTagStart !== null) {
838
- pushToUndoStack();
839
- // Replace @query with @filename
840
- const beforeTag = value.slice(0, activeFileTagStart);
841
- const afterCursor = value.slice(cursorOffset);
842
- const fileName = selected.name + (selected.isDirectory ? '/' : '');
843
- const newValue = beforeTag + '@' + fileName + ' ' + afterCursor;
844
- const newCursorPos = activeFileTagStart + 1 + fileName.length + 1;
845
- setValue(newValue);
846
- setCursorOffset(newCursorPos);
847
- // Add to confirmed file tags
848
- setConfirmedFileTags(prev => [
849
- ...prev.filter(tag =>
850
- // Remove any overlapping tags
851
- !(tag.start >= activeFileTagStart && tag.start <= cursorOffset)),
852
- {
853
- start: activeFileTagStart,
854
- end: activeFileTagStart + 1 + fileName.length,
855
- fileName: selected.name
856
- }
857
- ]);
858
- setFileTagAutocompleteVisible(false);
859
- setActiveFileTagStart(null);
860
- }
861
- return;
862
- }
863
- if (key.escape) {
864
- setFileTagAutocompleteVisible(false);
865
- setActiveFileTagStart(null);
866
- return;
867
- }
868
- // If Tab is pressed, also select the current file
869
- if (key.tab && !key.shift) {
870
- const selected = fileTagSuggestions[fileTagSelectedIndex];
871
- if (selected && activeFileTagStart !== null) {
872
- pushToUndoStack();
873
- const beforeTag = value.slice(0, activeFileTagStart);
874
- const afterCursor = value.slice(cursorOffset);
875
- const fileName = selected.name + (selected.isDirectory ? '/' : '');
876
- const newValue = beforeTag + '@' + fileName + afterCursor;
877
- const newCursorPos = activeFileTagStart + 1 + fileName.length;
878
- setValue(newValue);
879
- setCursorOffset(newCursorPos);
880
- // If it's a directory, keep dropdown open for subdirectory navigation
881
- if (!selected.isDirectory) {
882
- setConfirmedFileTags(prev => [
883
- ...prev.filter(tag => !(tag.start >= activeFileTagStart && tag.start <= cursorOffset)),
884
- {
885
- start: activeFileTagStart,
886
- end: activeFileTagStart + 1 + fileName.length,
887
- fileName: selected.name
888
- }
889
- ]);
890
- setFileTagAutocompleteVisible(false);
891
- setActiveFileTagStart(null);
892
- }
893
- }
894
- return;
895
- }
896
- }
897
- // Alt+V: Paste image from clipboard
898
- // Detect Alt+V on Windows/Linux (key.meta is often Alt on Windows in Ink)
899
- // On Mac, we want to support Cmd+V explicitly as requested ("make alt key combinations work with cmd key")
900
- // Note: Cmd+V is often captured by the terminal for text paste, but if it gets through, we handle it.
901
- // Option+V on Mac often sends '√', which we also support.
902
- const isAltV = (key.meta && input === 'v') || (input === '√');
903
- if (isAltV && !commandMode) {
904
- // Check clipboard for files asynchronously
905
- (async () => {
906
- try {
907
- const files = await getClipboardFiles();
908
- if (files.length > 0) {
909
- // Check limits (max 5 files allowed)
910
- const currentCount = confirmedClipboardFiles.length;
911
- const newCount = files.length;
912
- if (currentCount + newCount > 5) {
913
- logDebug(`Alt+V: Rejected file paste, limit exceeded (${currentCount} + ${newCount} > 5)`);
914
- setRejectFlash(true);
915
- setTimeout(() => setRejectFlash(false), 1000);
916
- return;
917
- }
918
- logDebug(`Alt+V: Found ${files.length} file(s) in clipboard, adding to input`);
919
- // Add files to confirmed list with unique ID
920
- const newFilesWithIds = files.map(file => ({
921
- ...file,
922
- id: `${file.id}_${Date.now()}_${Math.random().toString(36).substring(7)}`
923
- }));
924
- setConfirmedClipboardFiles(prev => [...prev, ...newFilesWithIds]);
925
- }
926
- else {
927
- logDebug('Alt+V: No file in clipboard');
928
- }
929
- }
930
- catch (error) {
931
- logDebug(`Alt+V: Failed to check clipboard: ${error instanceof Error ? error.message : 'Unknown error'}`);
932
- }
933
- })();
934
- return;
935
- }
936
- // Alt+X / Cmd+X: Remove the last attached file/image
937
- // Detect Alt+X on Windows/Linux or Cmd+X on Mac.
938
- // Option+X on Mac often produces '≈'
939
- const isAltX = (key.meta && input === 'x') || (input === '≈');
940
- if (isAltX && !commandMode) {
941
- if (confirmedClipboardFiles.length > 0) {
942
- logDebug(`Alt+X: Removing last attached file`);
943
- setConfirmedClipboardFiles(prev => prev.slice(0, -1));
769
+ }
770
+ } else if (value.startsWith("/workflow new ") || value.startsWith("/wf new ")) {
771
+ const prefix = value.startsWith("/workflow new ") ? "/workflow new " : "/wf new ";
772
+ const newValue = `${prefix}${selected.name}`;
773
+ setValue(newValue);
774
+ setCursorOffset(newValue.length);
775
+ setSlashAutocompleteVisible(false);
776
+ } else if (value.startsWith("/workflow ") || value.startsWith("/wf ")) {
777
+ const workflowNameMatch = value.match(/^\/(?:workflow|wf)\s+(run|view|delete)\s+/);
778
+ if (workflowNameMatch) {
779
+ const prefix = value.match(/^\/(?:workflow|wf)\s+(?:run|view|delete)\s+/)?.[0] || "";
780
+ const newValue = `${prefix}${selected.name}`;
781
+ setValue(newValue);
782
+ setCursorOffset(newValue.length);
783
+ setSlashAutocompleteVisible(false);
784
+ } else {
785
+ const prefix = value.startsWith("/workflow ") ? "/workflow " : "/wf ";
786
+ const newValue = `${prefix}${selected.name} `;
787
+ setValue(newValue);
788
+ setCursorOffset(newValue.length);
789
+ if (selected.name === "run" || selected.name === "view" || selected.name === "delete") {
790
+ const workflows = workflowStorage.list();
791
+ const matchingWorkflows = workflows.slice(0, 10).map((wf) => ({
792
+ name: wf.name,
793
+ description: wf.description || `${wf.stepCount} step${wf.stepCount !== 1 ? "s" : ""}`
794
+ }));
795
+ if (matchingWorkflows.length > 0) {
796
+ setSlashAutocompleteCommands(matchingWorkflows);
797
+ setSlashAutocompleteSelectedIndex(0);
798
+ setSlashAutocompleteScrollOffset(0);
799
+ } else {
800
+ setSlashAutocompleteVisible(false);
801
+ }
802
+ } else if (selected.name === "new") {
803
+ const subcommandMatches = filterCommands("workflow new ");
804
+ if (subcommandMatches.length > 0) {
805
+ setSlashAutocompleteCommands(subcommandMatches);
806
+ setSlashAutocompleteSelectedIndex(0);
807
+ setSlashAutocompleteScrollOffset(0);
808
+ } else {
809
+ setSlashAutocompleteVisible(false);
810
+ }
811
+ } else {
812
+ setSlashAutocompleteVisible(false);
944
813
  }
945
- else {
946
- logDebug(`Alt+X: No files to remove`);
814
+ }
815
+ } else if (value.startsWith("/settings auto-suggest ")) {
816
+ const newValue = `/settings auto-suggest ${selected.name}`;
817
+ setValue(newValue);
818
+ setCursorOffset(newValue.length);
819
+ setSlashAutocompleteVisible(false);
820
+ } else if (value.startsWith("/settings ")) {
821
+ const newValue = `/settings ${selected.name} `;
822
+ setValue(newValue);
823
+ setCursorOffset(newValue.length);
824
+ if (selected.name === "auto-suggest") {
825
+ const optionMatches = filterCommands("settings auto-suggest ");
826
+ if (optionMatches.length > 0) {
827
+ setSlashAutocompleteCommands(optionMatches);
828
+ setSlashAutocompleteSelectedIndex(0);
829
+ setSlashAutocompleteScrollOffset(0);
830
+ } else {
831
+ setSlashAutocompleteVisible(false);
832
+ }
833
+ } else {
834
+ setSlashAutocompleteVisible(false);
835
+ }
836
+ } else {
837
+ const newValue = `/${selected.name} `;
838
+ setValue(newValue);
839
+ setCursorOffset(newValue.length);
840
+ if (selected.name === "mcp") {
841
+ const subcommandMatches = filterCommands("mcp ");
842
+ if (subcommandMatches.length > 0) {
843
+ setSlashAutocompleteCommands(subcommandMatches);
844
+ setSlashAutocompleteSelectedIndex(0);
845
+ setSlashAutocompleteScrollOffset(0);
846
+ } else {
847
+ setSlashAutocompleteVisible(false);
848
+ }
849
+ } else if (selected.name === "chat") {
850
+ const subcommandMatches = filterCommands("chat ");
851
+ if (subcommandMatches.length > 0) {
852
+ setSlashAutocompleteCommands(subcommandMatches);
853
+ setSlashAutocompleteSelectedIndex(0);
854
+ setSlashAutocompleteScrollOffset(0);
855
+ } else {
856
+ setSlashAutocompleteVisible(false);
857
+ }
858
+ } else if (selected.name === "add-command" || selected.name === "add-command-auto-detect") {
859
+ const subcommandMatches = filterCommands("add-command ");
860
+ if (subcommandMatches.length > 0) {
861
+ setSlashAutocompleteCommands(subcommandMatches);
862
+ setSlashAutocompleteSelectedIndex(0);
863
+ setSlashAutocompleteScrollOffset(0);
864
+ } else {
865
+ setSlashAutocompleteVisible(false);
866
+ }
867
+ } else if (selected.name === "background-task" || selected.name === "bkg" || selected.name === "bg-task") {
868
+ const subcommandMatches = filterCommands("background-task ");
869
+ if (subcommandMatches.length > 0) {
870
+ setSlashAutocompleteCommands(subcommandMatches);
871
+ setSlashAutocompleteSelectedIndex(0);
872
+ setSlashAutocompleteScrollOffset(0);
873
+ } else {
874
+ setSlashAutocompleteVisible(false);
875
+ }
876
+ } else if (selected.name === "sync") {
877
+ const subcommandMatches = filterCommands("sync ");
878
+ if (subcommandMatches.length > 0) {
879
+ setSlashAutocompleteCommands(subcommandMatches);
880
+ setSlashAutocompleteSelectedIndex(0);
881
+ setSlashAutocompleteScrollOffset(0);
882
+ } else {
883
+ setSlashAutocompleteVisible(false);
884
+ }
885
+ } else if (selected.name === "models" || selected.name === "model") {
886
+ const subcommandMatches = filterCommands("models ");
887
+ if (subcommandMatches.length > 0) {
888
+ setSlashAutocompleteCommands(subcommandMatches);
889
+ setSlashAutocompleteSelectedIndex(0);
890
+ setSlashAutocompleteScrollOffset(0);
891
+ } else {
892
+ setSlashAutocompleteVisible(false);
893
+ }
894
+ } else if (selected.name === "settings") {
895
+ const subcommandMatches = filterCommands("settings ");
896
+ if (subcommandMatches.length > 0) {
897
+ setSlashAutocompleteCommands(subcommandMatches);
898
+ setSlashAutocompleteSelectedIndex(0);
899
+ setSlashAutocompleteScrollOffset(0);
900
+ } else {
901
+ setSlashAutocompleteVisible(false);
902
+ }
903
+ } else if (selected.name === "workflow" || selected.name === "wf") {
904
+ const subcommandMatches = filterCommands("workflow ");
905
+ if (subcommandMatches.length > 0) {
906
+ setSlashAutocompleteCommands(subcommandMatches);
907
+ setSlashAutocompleteSelectedIndex(0);
908
+ setSlashAutocompleteScrollOffset(0);
909
+ } else {
910
+ setSlashAutocompleteVisible(false);
911
+ }
912
+ } else if (selected.name === "rules") {
913
+ const subcommandMatches = filterCommands("rules ");
914
+ if (subcommandMatches.length > 0) {
915
+ setSlashAutocompleteCommands(subcommandMatches);
916
+ setSlashAutocompleteSelectedIndex(0);
917
+ setSlashAutocompleteScrollOffset(0);
918
+ } else {
919
+ setSlashAutocompleteVisible(false);
920
+ }
921
+ } else if (selected.name === "revert") {
922
+ const checkpoints = getCheckpoints ? getCheckpoints() : [];
923
+ if (checkpoints.length === 0) {
924
+ setSlashAutocompleteCommands([{
925
+ name: "(no checkpoints)",
926
+ description: "Ask AI a question first to create a checkpoint"
927
+ }]);
928
+ setSlashAutocompleteSelectedIndex(0);
929
+ setSlashAutocompleteScrollOffset(0);
930
+ } else {
931
+ const checkpointCommands = checkpoints.slice(0, 10).map((cp) => {
932
+ const truncatedPrompt = cp.prompt.length > 50 ? cp.prompt.slice(0, 50) + "..." : cp.prompt;
933
+ return {
934
+ name: cp.id,
935
+ description: truncatedPrompt
936
+ };
937
+ });
938
+ setSlashAutocompleteCommands(checkpointCommands);
939
+ setSlashAutocompleteSelectedIndex(0);
940
+ setSlashAutocompleteScrollOffset(0);
947
941
  }
948
- return;
942
+ } else {
943
+ setSlashAutocompleteVisible(false);
944
+ }
949
945
  }
950
- // DELETE WORD BACKWARDS
951
- // 1. Ctrl+W
952
- // 2. Cmd+Backspace (Mac) - often mapped to delete line, but here we treat key.meta as modifier
953
- // 3. Alt+Backspace (Windows/Linux)
954
- const isDeleteWord = inputCharCode === 23 || // Ctrl+W
955
- (key.meta && (key.backspace || key.delete)) || // Cmd+Backspace (Mac) or Alt+Backspace (Win)
956
- (key.ctrl && key.delete) || // Ctrl+Delete
957
- (isWindows && inputCharCode === 127); // Windows: Ctrl+Del sends char 127
958
- if (isDeleteWord) {
959
- pushToUndoStack();
960
- if (cursorOffset > 0) {
961
- let newOffset = cursorOffset;
962
- // Skip whitespace backwards
963
- while (newOffset > 0 && /\s/.test(value[newOffset - 1])) {
964
- newOffset--;
965
- }
966
- // Skip non-whitespace backwards
967
- while (newOffset > 0 && !/\s/.test(value[newOffset - 1])) {
968
- newOffset--;
969
- }
970
- const newValue = value.slice(0, newOffset) + value.slice(cursorOffset);
971
- setValue(newValue);
972
- setCursorOffset(newOffset);
973
- // Update slash command autocomplete
974
- if (newValue.startsWith('/') && !newValue.includes(' ')) {
975
- const query = newValue.slice(1);
976
- const matches = filterCommands(query);
977
- if (matches.length > 0) {
978
- setSlashAutocompleteCommands(matches);
979
- setSlashAutocompleteVisible(true);
980
- setSlashAutocompleteSelectedIndex(0);
981
- setSlashAutocompleteScrollOffset(0);
982
- }
983
- else {
984
- setSlashAutocompleteVisible(false);
985
- }
986
- }
987
- else if (newValue.startsWith('/mcp ')) {
988
- // MCP subcommands
989
- const fullQuery = newValue.slice(1);
990
- const matches = filterCommands(fullQuery);
991
- if (matches.length > 0) {
992
- setSlashAutocompleteCommands(matches);
993
- setSlashAutocompleteVisible(true);
994
- setSlashAutocompleteSelectedIndex(0);
995
- setSlashAutocompleteScrollOffset(0);
996
- }
997
- else {
998
- setSlashAutocompleteVisible(false);
999
- }
1000
- }
1001
- else if (newValue.startsWith('/chat ')) {
1002
- // Chat subcommands
1003
- const fullQuery = newValue.slice(1);
1004
- const matches = filterCommands(fullQuery);
1005
- if (matches.length > 0) {
1006
- setSlashAutocompleteCommands(matches);
1007
- setSlashAutocompleteVisible(true);
1008
- setSlashAutocompleteSelectedIndex(0);
1009
- setSlashAutocompleteScrollOffset(0);
1010
- }
1011
- else {
1012
- setSlashAutocompleteVisible(false);
1013
- }
1014
- }
1015
- else if (newValue.startsWith('/add-command ') || newValue.startsWith('/add-command-auto-detect ')) {
1016
- // Add-command subcommands
1017
- const fullQuery = newValue.slice(1);
1018
- const matches = filterCommands(fullQuery);
1019
- if (matches.length > 0) {
1020
- setSlashAutocompleteCommands(matches);
1021
- setSlashAutocompleteVisible(true);
1022
- setSlashAutocompleteSelectedIndex(0);
1023
- setSlashAutocompleteScrollOffset(0);
1024
- }
1025
- else {
1026
- setSlashAutocompleteVisible(false);
1027
- }
1028
- }
1029
- else if (newValue.startsWith('/background-task ') || newValue.startsWith('/bkg ') || newValue.startsWith('/bg-task ')) {
1030
- // Background-task subcommands
1031
- const fullQuery = newValue.slice(1);
1032
- const matches = filterCommands(fullQuery);
1033
- if (matches.length > 0) {
1034
- setSlashAutocompleteCommands(matches);
1035
- setSlashAutocompleteVisible(true);
1036
- setSlashAutocompleteSelectedIndex(0);
1037
- setSlashAutocompleteScrollOffset(0);
1038
- }
1039
- else {
1040
- setSlashAutocompleteVisible(false);
1041
- }
1042
- }
1043
- else if (newValue.startsWith('/sync ')) {
1044
- // Sync subcommands
1045
- const fullQuery = newValue.slice(1);
1046
- const matches = filterCommands(fullQuery);
1047
- if (matches.length > 0) {
1048
- setSlashAutocompleteCommands(matches);
1049
- setSlashAutocompleteVisible(true);
1050
- setSlashAutocompleteSelectedIndex(0);
1051
- setSlashAutocompleteScrollOffset(0);
1052
- }
1053
- else {
1054
- setSlashAutocompleteVisible(false);
1055
- }
1056
- }
1057
- else if (newValue.startsWith('/models ') || newValue.startsWith('/model ')) {
1058
- // Models subcommands
1059
- const fullQuery = newValue.slice(1);
1060
- const matches = filterCommands(fullQuery);
1061
- if (matches.length > 0) {
1062
- setSlashAutocompleteCommands(matches);
1063
- setSlashAutocompleteVisible(true);
1064
- setSlashAutocompleteSelectedIndex(0);
1065
- setSlashAutocompleteScrollOffset(0);
1066
- }
1067
- else {
1068
- setSlashAutocompleteVisible(false);
1069
- }
1070
- }
1071
- else if (newValue.startsWith('/revert ')) {
1072
- // Checkpoint autocomplete for /revert command
1073
- const partialId = newValue.slice(8).toLowerCase(); // Remove "/revert "
1074
- const checkpoints = getCheckpoints ? getCheckpoints() : [];
1075
- if (checkpoints.length === 0) {
1076
- // No checkpoints yet - show informative message
1077
- setSlashAutocompleteCommands([{
1078
- name: '(no checkpoints)',
1079
- description: 'Ask AI a question first to create a checkpoint'
1080
- }]);
1081
- setSlashAutocompleteVisible(true);
1082
- setSlashAutocompleteSelectedIndex(0);
1083
- setSlashAutocompleteScrollOffset(0);
1084
- }
1085
- else {
1086
- const matchingCheckpoints = checkpoints
1087
- .filter(cp => cp.id.toLowerCase().includes(partialId) || cp.prompt.toLowerCase().includes(partialId))
1088
- .slice(0, 10)
1089
- .map(cp => {
1090
- const truncatedPrompt = cp.prompt.length > 50 ? cp.prompt.slice(0, 50) + '...' : cp.prompt;
1091
- return {
1092
- name: cp.id,
1093
- description: truncatedPrompt
1094
- };
1095
- });
1096
- if (matchingCheckpoints.length > 0) {
1097
- setSlashAutocompleteCommands(matchingCheckpoints);
1098
- setSlashAutocompleteVisible(true);
1099
- setSlashAutocompleteSelectedIndex(0);
1100
- setSlashAutocompleteScrollOffset(0);
1101
- }
1102
- else {
1103
- setSlashAutocompleteVisible(false);
1104
- }
1105
- }
1106
- }
1107
- else if (newValue.match(/^\/workflow\s+(run|view|delete)\s+/) ||
1108
- newValue.match(/^\/wf\s+(run|view|delete)\s+/)) {
1109
- // Workflow name autocomplete (when user types "/workflow run " or similar)
1110
- // This MUST come before the /workflow subcommand check since it's more specific
1111
- const match = newValue.match(/^\/(?:workflow|wf)\s+(?:run|view|delete)\s+(.*)$/);
1112
- const partialName = match ? match[1].toLowerCase() : '';
1113
- const workflows = workflowStorage.list();
1114
- const matchingWorkflows = workflows
1115
- .filter(wf => wf.name.toLowerCase().includes(partialName))
1116
- .slice(0, 10)
1117
- .map(wf => ({
1118
- name: wf.name,
1119
- description: wf.description || `${wf.stepCount} step${wf.stepCount !== 1 ? 's' : ''}`
1120
- }));
1121
- if (matchingWorkflows.length > 0) {
1122
- setSlashAutocompleteCommands(matchingWorkflows);
1123
- setSlashAutocompleteVisible(true);
1124
- setSlashAutocompleteSelectedIndex(0);
1125
- setSlashAutocompleteScrollOffset(0);
1126
- }
1127
- else {
1128
- setSlashAutocompleteVisible(false);
1129
- }
1130
- }
1131
- else if (newValue.match(/^\/workflow\s+new\s+/) ||
1132
- newValue.match(/^\/wf\s+new\s+/)) {
1133
- // Workflow new subcommand autocomplete (manual, learn-workflow)
1134
- const fullQuery = newValue.slice(1);
1135
- const matches = filterCommands(fullQuery);
1136
- if (matches.length > 0) {
1137
- setSlashAutocompleteCommands(matches);
1138
- setSlashAutocompleteVisible(true);
1139
- setSlashAutocompleteSelectedIndex(0);
1140
- setSlashAutocompleteScrollOffset(0);
1141
- }
1142
- else {
1143
- setSlashAutocompleteVisible(false);
1144
- }
1145
- }
1146
- else if (newValue.startsWith('/workflow ') || newValue.startsWith('/wf ')) {
1147
- // Workflow subcommands (when user types "/workflow " or "/wf ")
1148
- const fullQuery = newValue.slice(1);
1149
- const matches = filterCommands(fullQuery);
1150
- if (matches.length > 0) {
1151
- setSlashAutocompleteCommands(matches);
1152
- setSlashAutocompleteVisible(true);
1153
- setSlashAutocompleteSelectedIndex(0);
1154
- setSlashAutocompleteScrollOffset(0);
1155
- }
1156
- else {
1157
- setSlashAutocompleteVisible(false);
1158
- }
1159
- }
1160
- else {
1161
- setSlashAutocompleteVisible(false);
1162
- }
1163
- }
1164
- setHistoryIndex(-1);
1165
- setCompletions([]);
1166
- return;
946
+ return;
947
+ }
948
+ if (key.escape) {
949
+ setSlashAutocompleteVisible(false);
950
+ return;
951
+ }
952
+ }
953
+ if (fileTagAutocompleteVisible) {
954
+ if (key.downArrow) {
955
+ setFileTagSelectedIndex(
956
+ (prev) => Math.min(prev + 1, fileTagSuggestions.length - 1)
957
+ );
958
+ return;
959
+ }
960
+ if (key.upArrow) {
961
+ setFileTagSelectedIndex((prev) => Math.max(prev - 1, 0));
962
+ return;
963
+ }
964
+ if (key.return && input.length <= 1 && !key.shift && !key.ctrl && !key.meta) {
965
+ const selected = fileTagSuggestions[fileTagSelectedIndex];
966
+ if (selected && activeFileTagStart !== null) {
967
+ applyMentionSuggestion(selected, false);
1167
968
  }
1168
- // Ctrl+T: Toggle auto-accept
1169
- if (key.ctrl && input.toLowerCase() === 't') {
1170
- ignoreNextChangeRef.current = true;
1171
- onToggleAutoAccept();
1172
- setTimeout(() => { ignoreNextChangeRef.current = false; }, 100);
1173
- return;
969
+ return;
970
+ }
971
+ if (key.escape) {
972
+ setFileTagAutocompleteVisible(false);
973
+ setActiveFileTagStart(null);
974
+ return;
975
+ }
976
+ if (key.tab && !key.shift) {
977
+ const selected = fileTagSuggestions[fileTagSelectedIndex];
978
+ if (selected && activeFileTagStart !== null) {
979
+ applyMentionSuggestion(selected, true);
1174
980
  }
1175
- // Ctrl+D: Cycle modes (Agent -> Terminal -> Background -> Auto -> Agent)
1176
- if (key.ctrl && input.toLowerCase() === 'd') {
1177
- if (onToggleCommandMode) {
1178
- ignoreNextChangeRef.current = true;
1179
- // Cycle Logic: Agent -> Terminal -> Background -> Auto -> Agent
1180
- if (!isAutoMode && !commandMode && !backgroundMode) {
1181
- // Agent -> Terminal
1182
- onToggleCommandMode();
1183
- }
1184
- else if (!isAutoMode && commandMode && !backgroundMode) {
1185
- // Terminal -> Background
1186
- onToggleCommandMode(); // Exit terminal mode
1187
- if (onToggleBackgroundMode)
1188
- onToggleBackgroundMode(); // Enter background mode
1189
- }
1190
- else if (!isAutoMode && !commandMode && backgroundMode) {
1191
- // Background -> Auto
1192
- if (onToggleBackgroundMode)
1193
- onToggleBackgroundMode(); // Exit background mode
1194
- setIsAutoMode(true);
1195
- // Trigger initial detection for Auto mode
1196
- const intent = detectIntent(value);
1197
- setDetectedIntent(intent);
1198
- // Set command mode based on intent
1199
- if (intent === 'command')
1200
- onToggleCommandMode();
1201
- }
1202
- else if (isAutoMode) {
1203
- // Auto -> Agent
1204
- setIsAutoMode(false);
1205
- // Ensure we go back to Agent mode (commandMode = false, backgroundMode = false)
1206
- if (commandMode)
1207
- onToggleCommandMode();
1208
- if (backgroundMode && onToggleBackgroundMode)
1209
- onToggleBackgroundMode();
1210
- }
1211
- setTimeout(() => { ignoreNextChangeRef.current = false; }, 100);
1212
- }
1213
- return;
981
+ return;
982
+ }
983
+ }
984
+ const isAltV = key.meta && input === "v" || input === "\u221A";
985
+ if (isAltV && !commandMode) {
986
+ (async () => {
987
+ try {
988
+ const files = await getClipboardFiles();
989
+ if (files.length > 0) {
990
+ const currentCount = confirmedClipboardFiles.length;
991
+ const newCount = files.length;
992
+ if (currentCount + newCount > 5) {
993
+ logDebug(`Alt+V: Rejected file paste, limit exceeded (${currentCount} + ${newCount} > 5)`);
994
+ setRejectFlash(true);
995
+ setTimeout(() => setRejectFlash(false), 1e3);
996
+ return;
997
+ }
998
+ logDebug(`Alt+V: Found ${files.length} file(s) in clipboard, adding to input`);
999
+ const newFilesWithIds = files.map((file) => ({
1000
+ ...file,
1001
+ id: `${file.id}_${Date.now()}_${Math.random().toString(36).substring(7)}`
1002
+ }));
1003
+ setConfirmedClipboardFiles((prev) => [...prev, ...newFilesWithIds]);
1004
+ } else {
1005
+ logDebug("Alt+V: No file in clipboard");
1006
+ }
1007
+ } catch (error) {
1008
+ logDebug(`Alt+V: Failed to check clipboard: ${error instanceof Error ? error.message : "Unknown error"}`);
1214
1009
  }
1215
- // Ctrl+Z / Cmd+Z: Undo
1216
- if ((key.ctrl && input.toLowerCase() === 'z') || (key.meta && input.toLowerCase() === 'z')) {
1217
- handleUndo();
1218
- return;
1010
+ })();
1011
+ return;
1012
+ }
1013
+ const isAltX = key.meta && input === "x" || input === "\u2248";
1014
+ if (isAltX && !commandMode) {
1015
+ if (confirmedClipboardFiles.length > 0) {
1016
+ logDebug(`Alt+X: Removing last attached file`);
1017
+ setConfirmedClipboardFiles((prev) => prev.slice(0, -1));
1018
+ } else {
1019
+ logDebug(`Alt+X: No files to remove`);
1020
+ }
1021
+ return;
1022
+ }
1023
+ if (isMacOptionWordBackwardSequence) {
1024
+ if (cursorOffset > 0) {
1025
+ let newOffset = cursorOffset;
1026
+ while (newOffset > 0 && /\s/.test(value[newOffset - 1])) {
1027
+ newOffset--;
1219
1028
  }
1220
- // Ctrl+A / Cmd+A: Select All
1221
- if ((key.ctrl && input.toLowerCase() === 'a') || (key.meta && input.toLowerCase() === 'a')) {
1222
- setSelection({ start: 0, end: value.length });
1223
- setCursorOffset(value.length);
1224
- return;
1029
+ while (newOffset > 0 && !/\s/.test(value[newOffset - 1])) {
1030
+ newOffset--;
1225
1031
  }
1226
- // Home: Start of Line
1227
- // Note: In single-line inputs, this goes to start of text.
1228
- // In multi-line wrapped view, we ideally want start of logical line or start of text?
1229
- // Standard terminal Home = Start of command. Editor Home = Start of line.
1230
- // Let's stick to Start of Text for now as it's a single "input box".
1231
- // Home: Start of Line
1232
- // @ts-ignore
1233
- if (key.home) {
1234
- const width = (process.stdout.columns || 80) - 6;
1235
- const visualLines = getVisualLines(value, width);
1236
- const currentLine = visualLines.find(line => (cursorOffset >= line.start && cursorOffset < line.end) ||
1237
- (cursorOffset === line.end && line.isHardEnd));
1238
- if (currentLine) {
1239
- setCursorOffset(currentLine.start);
1240
- }
1241
- else {
1242
- setCursorOffset(0);
1243
- }
1244
- return;
1032
+ setCursorOffset(newOffset);
1033
+ }
1034
+ return;
1035
+ }
1036
+ if (isMacOptionWordForwardSequence) {
1037
+ if (cursorOffset < value.length) {
1038
+ let newOffset = cursorOffset;
1039
+ while (newOffset < value.length && /\s/.test(value[newOffset])) {
1040
+ newOffset++;
1245
1041
  }
1246
- // End: End of Line
1247
- // @ts-ignore
1248
- if (key.end) {
1249
- const width = (process.stdout.columns || 80) - 6;
1250
- const visualLines = getVisualLines(value, width);
1251
- const currentLine = visualLines.find(line => (cursorOffset >= line.start && cursorOffset < line.end) ||
1252
- (cursorOffset === line.end && line.isHardEnd));
1253
- if (currentLine) {
1254
- setCursorOffset(currentLine.end);
1255
- }
1256
- else {
1257
- setCursorOffset(value.length);
1258
- }
1259
- return;
1042
+ while (newOffset < value.length && !/\s/.test(value[newOffset])) {
1043
+ newOffset++;
1260
1044
  }
1261
- // Note: Clipboard images are handled via Alt+V keyboard shortcut
1262
- // DELETE CHAR - Only runs if Delete Word did NOT trigger
1263
- // Triggers on:
1264
- // 1. Backspace or Delete key flag is present
1265
- // 2. OR char code 8 (standard backspace)
1266
- // 3. OR char code 127 (DEL) on non-Windows (Mac/Linux treat 127 as normal backspace)
1267
- // NOTE: On Windows/Ink, Backspace often reports as 'delete: true', so we treat key.delete as Backspace to ensure the Backspace key works correctly.
1268
- const isDeleteChar = key.backspace ||
1269
- key.delete ||
1270
- inputCharCode === 8 ||
1271
- (!isWindows && inputCharCode === 127);
1272
- if (isDeleteChar) {
1273
- pushToUndoStack();
1274
- let newValue = value;
1275
- if (selection) {
1276
- // Delete selection
1277
- const start = Math.min(selection.start, selection.end);
1278
- const end = Math.max(selection.start, selection.end);
1279
- newValue = value.slice(0, start) + value.slice(end);
1280
- setValue(newValue);
1281
- setCursorOffset(start);
1282
- setSelection(null);
1283
- }
1284
- else if (cursorOffset > 0) {
1285
- // Backspace (or Delete acting as Backspace): Delete character before cursor
1286
- newValue = value.slice(0, cursorOffset - 1) + value.slice(cursorOffset);
1287
- setValue(newValue);
1288
- setCursorOffset(cursorOffset - 1);
1289
- }
1290
- // Reset history/completions on edit
1291
- setHistoryIndex(-1);
1292
- setCompletions([]);
1293
- // Update slash command autocomplete
1294
- if (newValue.startsWith('/') && !newValue.includes(' ')) {
1295
- const query = newValue.slice(1);
1296
- const matches = filterCommands(query);
1297
- if (matches.length > 0) {
1298
- setSlashAutocompleteCommands(matches);
1299
- setSlashAutocompleteVisible(true);
1300
- setSlashAutocompleteSelectedIndex(0);
1301
- setSlashAutocompleteScrollOffset(0);
1302
- }
1303
- else {
1304
- setSlashAutocompleteVisible(false);
1305
- }
1306
- }
1307
- else if (newValue.startsWith('/mcp ')) {
1308
- // MCP subcommands
1309
- const fullQuery = newValue.slice(1);
1310
- const matches = filterCommands(fullQuery);
1311
- if (matches.length > 0) {
1312
- setSlashAutocompleteCommands(matches);
1313
- setSlashAutocompleteVisible(true);
1314
- setSlashAutocompleteSelectedIndex(0);
1315
- setSlashAutocompleteScrollOffset(0);
1316
- }
1317
- else {
1318
- setSlashAutocompleteVisible(false);
1319
- }
1320
- }
1321
- else if (newValue.startsWith('/chat ')) {
1322
- // Chat subcommands
1323
- const fullQuery = newValue.slice(1);
1324
- const matches = filterCommands(fullQuery);
1325
- if (matches.length > 0) {
1326
- setSlashAutocompleteCommands(matches);
1327
- setSlashAutocompleteVisible(true);
1328
- setSlashAutocompleteSelectedIndex(0);
1329
- setSlashAutocompleteScrollOffset(0);
1330
- }
1331
- else {
1332
- setSlashAutocompleteVisible(false);
1333
- }
1334
- }
1335
- else if (newValue.startsWith('/add-command ') || newValue.startsWith('/add-command-auto-detect ')) {
1336
- // Add-command subcommands
1337
- const fullQuery = newValue.slice(1);
1338
- const matches = filterCommands(fullQuery);
1339
- if (matches.length > 0) {
1340
- setSlashAutocompleteCommands(matches);
1341
- setSlashAutocompleteVisible(true);
1342
- setSlashAutocompleteSelectedIndex(0);
1343
- setSlashAutocompleteScrollOffset(0);
1344
- }
1345
- else {
1346
- setSlashAutocompleteVisible(false);
1347
- }
1348
- }
1349
- else if (newValue.startsWith('/background-task ') || newValue.startsWith('/bkg ') || newValue.startsWith('/bg-task ')) {
1350
- // Background-task subcommands
1351
- const fullQuery = newValue.slice(1);
1352
- const matches = filterCommands(fullQuery);
1353
- if (matches.length > 0) {
1354
- setSlashAutocompleteCommands(matches);
1355
- setSlashAutocompleteVisible(true);
1356
- setSlashAutocompleteSelectedIndex(0);
1357
- setSlashAutocompleteScrollOffset(0);
1358
- }
1359
- else {
1360
- setSlashAutocompleteVisible(false);
1361
- }
1362
- }
1363
- else if (newValue.startsWith('/sync ')) {
1364
- // Sync subcommands
1365
- const fullQuery = newValue.slice(1);
1366
- const matches = filterCommands(fullQuery);
1367
- if (matches.length > 0) {
1368
- setSlashAutocompleteCommands(matches);
1369
- setSlashAutocompleteVisible(true);
1370
- setSlashAutocompleteSelectedIndex(0);
1371
- setSlashAutocompleteScrollOffset(0);
1372
- }
1373
- else {
1374
- setSlashAutocompleteVisible(false);
1375
- }
1376
- }
1377
- else if (newValue.startsWith('/models ') || newValue.startsWith('/model ')) {
1378
- // Models subcommands
1379
- const fullQuery = newValue.slice(1);
1380
- const matches = filterCommands(fullQuery);
1381
- if (matches.length > 0) {
1382
- setSlashAutocompleteCommands(matches);
1383
- setSlashAutocompleteVisible(true);
1384
- setSlashAutocompleteSelectedIndex(0);
1385
- setSlashAutocompleteScrollOffset(0);
1386
- }
1387
- else {
1388
- setSlashAutocompleteVisible(false);
1389
- }
1390
- }
1391
- else {
1392
- setSlashAutocompleteVisible(false);
1393
- }
1394
- return;
1045
+ setCursorOffset(newOffset);
1046
+ }
1047
+ return;
1048
+ }
1049
+ const isDeleteWord = inputCharCode === 23 || // Ctrl+W
1050
+ key.meta && (key.backspace || key.delete) || // Cmd+Backspace (Mac) or Alt+Backspace (Win)
1051
+ key.ctrl && key.delete || // Ctrl+Delete
1052
+ isWindows && inputCharCode === 127;
1053
+ if (isDeleteWord) {
1054
+ pushToUndoStack();
1055
+ if (cursorOffset > 0) {
1056
+ let newOffset = cursorOffset;
1057
+ while (newOffset > 0 && /\s/.test(value[newOffset - 1])) {
1058
+ newOffset--;
1395
1059
  }
1396
- // Handle Enter
1397
- // Check input length to distinguish single key press from paste
1398
- if (key.return && input.length <= 1) {
1399
- // Check for Shift+Enter, Ctrl+Enter, OR explicit newline char
1400
- // Note: Shift+Enter doesn't work on Windows/Ink, so Ctrl+Enter is the alternative
1401
- // Alt+Enter can't be used as it fullscreens the terminal on Windows
1402
- if (key.shift || key.ctrl || input === '\n') {
1403
- pushToUndoStack();
1404
- // Shift+Enter or Ctrl+Enter: Insert newline
1405
- // If selection exists, replace it
1406
- let newValue = value;
1407
- let newOffset = cursorOffset;
1408
- if (selection) {
1409
- const start = Math.min(selection.start, selection.end);
1410
- const end = Math.max(selection.start, selection.end);
1411
- newValue = value.slice(0, start) + '\n' + value.slice(end);
1412
- newOffset = start + 1;
1413
- setSelection(null);
1414
- }
1415
- else {
1416
- newValue = value.slice(0, cursorOffset) + '\n' + value.slice(cursorOffset);
1417
- newOffset = cursorOffset + 1;
1418
- }
1419
- setValue(newValue);
1420
- setCursorOffset(newOffset);
1421
- }
1422
- else {
1423
- // Enter: Submit
1424
- // Check if submission is allowed when disconnected
1425
- // Allowed: /exit command, OR Command Mode is active, OR detected intent is 'command'
1426
- const isExitCommand = value.trim() === '/exit';
1427
- const isCommandIntent = detectedIntent === 'command';
1428
- const isAllowedOffline = isConnected || isExitCommand || commandMode || isCommandIntent;
1429
- if (!isAllowedOffline) {
1430
- setRejectFlash(true);
1431
- setTimeout(() => setRejectFlash(false), 1000);
1432
- return;
1433
- }
1434
- // Check context limit (only for AI mode, not command mode or slash commands)
1435
- // Slash commands and terminal commands should always be allowed
1436
- const isSlashCommand = value.trim().startsWith('/');
1437
- if (!commandMode && !isCommandIntent && !isSlashCommand && contextLimitReached) {
1438
- setRejectFlash(true);
1439
- setTimeout(() => {
1440
- setRejectFlash(false);
1441
- }, 3000); // Flash red for 3 seconds
1442
- return;
1443
- }
1444
- // Check session quota (only for AI mode, not command mode or slash commands)
1445
- // Slash commands and terminal commands should always be allowed
1446
- if (!commandMode && !isCommandIntent && !isSlashCommand && sessionQuotaExhausted) {
1447
- setRejectFlash(true);
1448
- setShowQuotaMessage(true);
1449
- setTimeout(() => {
1450
- setRejectFlash(false);
1451
- setShowQuotaMessage(false);
1452
- }, 3000); // Show quota message for 3 seconds
1453
- return;
1454
- }
1455
- handleSubmit();
1456
- }
1457
- return;
1060
+ while (newOffset > 0 && !/\s/.test(value[newOffset - 1])) {
1061
+ newOffset--;
1458
1062
  }
1459
- // Handle Arrows
1460
- if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
1461
- // Clear selection on arrow keys
1462
- if (selection) {
1463
- setSelection(null);
1464
- // If left/right, maybe move cursor to start/end of selection?
1465
- // For now, just clear selection and let default logic run (or reset cursor to one end)
1466
- if (key.leftArrow)
1467
- setCursorOffset(Math.min(selection.start, selection.end));
1468
- if (key.rightArrow)
1469
- setCursorOffset(Math.max(selection.start, selection.end));
1470
- if (key.upArrow || key.downArrow) {
1471
- // Keep cursor where it is (at the end usually) or move it?
1472
- // Let's just fall through to normal arrow logic from current cursorOffset
1473
- }
1474
- if (key.leftArrow || key.rightArrow)
1475
- return; // We handled the move
1476
- }
1063
+ const newValue = value.slice(0, newOffset) + value.slice(cursorOffset);
1064
+ setValue(newValue);
1065
+ setCursorOffset(newOffset);
1066
+ updateSlashAutocomplete(newValue);
1067
+ }
1068
+ setHistoryIndex(-1);
1069
+ setCompletions([]);
1070
+ return;
1071
+ }
1072
+ if (key.ctrl && input.toLowerCase() === "t") {
1073
+ ignoreNextChangeRef.current = true;
1074
+ onToggleAutoAccept();
1075
+ setTimeout(() => {
1076
+ ignoreNextChangeRef.current = false;
1077
+ }, 100);
1078
+ return;
1079
+ }
1080
+ if (key.ctrl && input.toLowerCase() === "d") {
1081
+ if (onToggleCommandMode) {
1082
+ ignoreNextChangeRef.current = true;
1083
+ if (!isAutoMode && !commandMode && !backgroundMode) {
1084
+ onToggleCommandMode();
1085
+ } else if (!isAutoMode && commandMode && !backgroundMode) {
1086
+ onToggleCommandMode();
1087
+ if (onToggleBackgroundMode) onToggleBackgroundMode();
1088
+ } else if (!isAutoMode && !commandMode && backgroundMode) {
1089
+ if (onToggleBackgroundMode) onToggleBackgroundMode();
1090
+ setIsAutoMode(true);
1091
+ const intent = detectIntent(value);
1092
+ setDetectedIntent(intent);
1093
+ if (intent === "command") onToggleCommandMode();
1094
+ } else if (isAutoMode) {
1095
+ setIsAutoMode(false);
1096
+ if (commandMode) onToggleCommandMode();
1097
+ if (backgroundMode && onToggleBackgroundMode) onToggleBackgroundMode();
1477
1098
  }
1478
- if (key.upArrow) {
1479
- const width = (process.stdout.columns || 80) - 6;
1480
- const visualLines = getVisualLines(value, width);
1481
- let currentVisualLineIndex = visualLines.findIndex(line => {
1482
- if (cursorOffset >= line.start && cursorOffset < line.end)
1483
- return true;
1484
- if (cursorOffset === line.end && line.isHardEnd)
1485
- return true;
1486
- return false;
1487
- });
1488
- // If cursor is at the very end of the last line
1489
- if (currentVisualLineIndex === -1 && cursorOffset === value.length) {
1490
- currentVisualLineIndex = visualLines.length - 1;
1491
- }
1492
- if (currentVisualLineIndex <= 0) {
1493
- // Top line: History navigation
1494
- if (commandHistory.length > 0) {
1495
- if (historyIndex === -1) {
1496
- setTempValue(value);
1497
- const newIndex = commandHistory.length - 1;
1498
- setHistoryIndex(newIndex);
1499
- const newValue = commandHistory[newIndex];
1500
- setValue(newValue);
1501
- setCursorOffset(newValue.length);
1502
- }
1503
- else if (historyIndex > 0) {
1504
- const newIndex = historyIndex - 1;
1505
- setHistoryIndex(newIndex);
1506
- const newValue = commandHistory[newIndex];
1507
- setValue(newValue);
1508
- setCursorOffset(newValue.length);
1509
- }
1510
- }
1511
- }
1512
- else {
1513
- // Move cursor up one visual line
1514
- const currentLine = visualLines[currentVisualLineIndex];
1515
- const targetLine = visualLines[currentVisualLineIndex - 1];
1516
- const offsetInLine = cursorOffset - currentLine.start;
1517
- const newOffset = targetLine.start + Math.min(offsetInLine, targetLine.end - targetLine.start);
1518
- setCursorOffset(newOffset);
1519
- }
1520
- return;
1099
+ setTimeout(() => {
1100
+ ignoreNextChangeRef.current = false;
1101
+ }, 100);
1102
+ }
1103
+ return;
1104
+ }
1105
+ if (key.ctrl && input.toLowerCase() === "z" || key.meta && input.toLowerCase() === "z") {
1106
+ handleUndo();
1107
+ return;
1108
+ }
1109
+ if (key.ctrl && input.toLowerCase() === "a" || key.meta && input.toLowerCase() === "a") {
1110
+ setSelection({ start: 0, end: value.length });
1111
+ setCursorOffset(value.length);
1112
+ return;
1113
+ }
1114
+ if (key.home) {
1115
+ const width = (process.stdout.columns || 80) - 6;
1116
+ const visualLines = getVisualLines(value, width);
1117
+ const currentLine = visualLines.find(
1118
+ (line) => cursorOffset >= line.start && cursorOffset < line.end || cursorOffset === line.end && line.isHardEnd
1119
+ );
1120
+ if (currentLine) {
1121
+ setCursorOffset(currentLine.start);
1122
+ } else {
1123
+ setCursorOffset(0);
1124
+ }
1125
+ return;
1126
+ }
1127
+ if (key.end) {
1128
+ const width = (process.stdout.columns || 80) - 6;
1129
+ const visualLines = getVisualLines(value, width);
1130
+ const currentLine = visualLines.find(
1131
+ (line) => cursorOffset >= line.start && cursorOffset < line.end || cursorOffset === line.end && line.isHardEnd
1132
+ );
1133
+ if (currentLine) {
1134
+ setCursorOffset(currentLine.end);
1135
+ } else {
1136
+ setCursorOffset(value.length);
1137
+ }
1138
+ return;
1139
+ }
1140
+ const isDeleteChar = key.backspace || key.delete || inputCharCode === 8 || !isWindows && inputCharCode === 127;
1141
+ if (isDeleteChar) {
1142
+ pushToUndoStack();
1143
+ let newValue = value;
1144
+ if (selection) {
1145
+ const start = Math.min(selection.start, selection.end);
1146
+ const end = Math.max(selection.start, selection.end);
1147
+ newValue = value.slice(0, start) + value.slice(end);
1148
+ setValue(newValue);
1149
+ setCursorOffset(start);
1150
+ setSelection(null);
1151
+ } else if (cursorOffset > 0) {
1152
+ newValue = value.slice(0, cursorOffset - 1) + value.slice(cursorOffset);
1153
+ setValue(newValue);
1154
+ setCursorOffset(cursorOffset - 1);
1155
+ }
1156
+ setHistoryIndex(-1);
1157
+ setCompletions([]);
1158
+ updateSlashAutocomplete(newValue);
1159
+ return;
1160
+ }
1161
+ if (isEnterEvent && (input.length <= 1 || isTerminalShiftEnterSequence)) {
1162
+ const isBackslashEnterFallback = !key.shift && !key.ctrl && !key.meta && !isTerminalShiftEnterSequence && !selection && cursorOffset > 0 && value[cursorOffset - 1] === "\\";
1163
+ if (key.shift || key.ctrl || key.meta || input === "\n" || isTerminalShiftEnterSequence || isBackslashEnterFallback) {
1164
+ pushToUndoStack();
1165
+ let newValue = value;
1166
+ let newOffset = cursorOffset;
1167
+ if (selection) {
1168
+ const start = Math.min(selection.start, selection.end);
1169
+ const end = Math.max(selection.start, selection.end);
1170
+ newValue = value.slice(0, start) + "\n" + value.slice(end);
1171
+ newOffset = start + 1;
1172
+ setSelection(null);
1173
+ } else if (isBackslashEnterFallback) {
1174
+ newValue = value.slice(0, cursorOffset - 1) + "\n" + value.slice(cursorOffset);
1175
+ newOffset = cursorOffset;
1176
+ } else {
1177
+ newValue = value.slice(0, cursorOffset) + "\n" + value.slice(cursorOffset);
1178
+ newOffset = cursorOffset + 1;
1521
1179
  }
1522
- if (key.downArrow) {
1523
- const width = (process.stdout.columns || 80) - 6;
1524
- const visualLines = getVisualLines(value, width);
1525
- let currentVisualLineIndex = visualLines.findIndex(line => {
1526
- if (cursorOffset >= line.start && cursorOffset < line.end)
1527
- return true;
1528
- if (cursorOffset === line.end && line.isHardEnd)
1529
- return true;
1530
- return false;
1531
- });
1532
- // If cursor is at the very end of the last line
1533
- if (currentVisualLineIndex === -1 && cursorOffset === value.length) {
1534
- currentVisualLineIndex = visualLines.length - 1;
1535
- }
1536
- if (currentVisualLineIndex === visualLines.length - 1) {
1537
- // Bottom line: History navigation
1538
- if (historyIndex !== -1) {
1539
- if (historyIndex < commandHistory.length - 1) {
1540
- const newIndex = historyIndex + 1;
1541
- setHistoryIndex(newIndex);
1542
- const newValue = commandHistory[newIndex];
1543
- setValue(newValue);
1544
- setCursorOffset(newValue.length);
1545
- }
1546
- else {
1547
- setHistoryIndex(-1);
1548
- setValue(tempValue);
1549
- setCursorOffset(tempValue.length);
1550
- }
1551
- }
1552
- }
1553
- else {
1554
- // Move cursor down one visual line
1555
- const currentLine = visualLines[currentVisualLineIndex];
1556
- const targetLine = visualLines[currentVisualLineIndex + 1];
1557
- const offsetInLine = cursorOffset - currentLine.start;
1558
- const newOffset = targetLine.start + Math.min(offsetInLine, targetLine.end - targetLine.start);
1559
- setCursorOffset(newOffset);
1560
- }
1561
- return;
1180
+ setValue(newValue);
1181
+ setCursorOffset(newOffset);
1182
+ } else {
1183
+ const isExitCommand = value.trim() === "/exit";
1184
+ const isCommandIntent = detectedIntent === "command";
1185
+ const isAllowedOffline = isConnected || isExitCommand || commandMode || isCommandIntent;
1186
+ if (!isAllowedOffline) {
1187
+ setRejectFlash(true);
1188
+ setTimeout(() => setRejectFlash(false), 1e3);
1189
+ return;
1562
1190
  }
1563
- if (key.leftArrow) {
1564
- if (isMac && key.meta) {
1565
- // Mac: Cmd+Left -> Start of line (Home)
1566
- setCursorOffset(0);
1567
- return;
1568
- }
1569
- if (key.ctrl || (!isMac && key.meta)) {
1570
- // Ctrl+Left (All) or Alt+Left (Win/Linux): Move word backwards
1571
- // Note: On Mac, Option+Left is the standard for word back, but Ink doesn't expose 'alt'.
1572
- // Users can use Ctrl+Left or rely on terminal mapping Option to Meta which effectively makes it key.meta
1573
- // BUT if key.meta is Cmd on Mac, we map that to Home.
1574
- // So standard Mac "Option+Left" might not work unless it sends Esc sequence or mapped to Ctrl.
1575
- // We allow Ctrl+Left for Mac users.
1576
- let newOffset = cursorOffset;
1577
- if (newOffset > 0) {
1578
- // Skip whitespace backwards
1579
- while (newOffset > 0 && /\s/.test(value[newOffset - 1])) {
1580
- newOffset--;
1581
- }
1582
- // Skip non-whitespace backwards
1583
- while (newOffset > 0 && !/\s/.test(value[newOffset - 1])) {
1584
- newOffset--;
1585
- }
1586
- setCursorOffset(newOffset);
1587
- }
1588
- return;
1589
- }
1590
- // Standard Left Arrow
1591
- if (cursorOffset > 0) {
1592
- setCursorOffset(cursorOffset - 1);
1593
- }
1594
- return;
1191
+ const isSlashCommand = value.trim().startsWith("/");
1192
+ if (!commandMode && !isCommandIntent && !isSlashCommand && sessionQuotaExhausted) {
1193
+ setRejectFlash(true);
1194
+ setShowQuotaMessage(true);
1195
+ setTimeout(() => {
1196
+ setRejectFlash(false);
1197
+ setShowQuotaMessage(false);
1198
+ }, 3e3);
1199
+ return;
1595
1200
  }
1596
- if (key.rightArrow) {
1597
- // Autocomplete Logic (Only at end of line)
1598
- // AI suggestion takes priority over passive suggestion
1599
- // Autocomplete Logic
1600
- const effectiveSuggestion = aiAutocompleteSuggestion || autocompleteSuggestion;
1601
- if (effectiveSuggestion && cursorOffset === value.length) {
1602
- if (key.ctrl || (isMac && key.meta)) {
1603
- // Ctrl+Right (Win) or Cmd+Right (Mac): Accept FULL suggestion
1604
- // (Wait, Cmd+Right is usually End of Line on Mac. We should prioritize Navigation over Autocomplete?
1605
- // Actually, if we are at end of line, End of Line does nothing. So we can use it for accept full?)
1606
- // Convention: Right Arrow accepts word. Ctrl+Right accepts full?
1607
- // On Mac, Cmd+Right is End. If at End, it's a no-op for nav.
1608
- // Let's allow Cmd+Right to accept full IF at end.
1609
- setValue(effectiveSuggestion);
1610
- setCursorOffset(effectiveSuggestion.length);
1611
- setAutocompleteSuggestion(null);
1612
- setAiAutocompleteSuggestion(null);
1613
- return;
1614
- }
1615
- else {
1616
- // Right: Accept NEXT WORD
1617
- const remaining = effectiveSuggestion.slice(value.length);
1618
- // Match next chunk of non-whitespace
1619
- const match = remaining.match(/^(\s*\S+)/);
1620
- if (match) {
1621
- const toAdd = match[0];
1622
- const newValue = value + toAdd;
1623
- setValue(newValue);
1624
- setCursorOffset(newValue.length);
1625
- return; // Done
1626
- }
1627
- else if (remaining.length > 0) {
1628
- const newValue = value + remaining;
1629
- setValue(newValue);
1630
- setCursorOffset(newValue.length);
1631
- return;
1632
- }
1633
- }
1634
- }
1635
- // Navigation Logic
1636
- if (isMac && key.meta) {
1637
- // Cmd+Right -> End of line
1638
- setCursorOffset(value.length);
1639
- return;
1640
- }
1641
- if ((!isMac && key.meta) || key.ctrl) {
1642
- // Alt+Right (Win) or Ctrl+Right: Word Forward
1643
- // (Note: Ink uses key.meta for Alt on Windows)
1644
- let newOffset = cursorOffset;
1645
- if (newOffset < value.length) {
1646
- // Skip whitespace forwards
1647
- while (newOffset < value.length && /\s/.test(value[newOffset])) {
1648
- newOffset++;
1649
- }
1650
- // Skip non-whitespace forwards
1651
- while (newOffset < value.length && !/\s/.test(value[newOffset])) {
1652
- newOffset++;
1653
- }
1654
- setCursorOffset(newOffset);
1655
- }
1656
- return;
1657
- }
1658
- // Navigation Logic (if not completing)
1659
- if (key.ctrl || key.meta) {
1660
- // Ctrl+Right / Meta+Right (Option+Right): Move word forwards
1661
- let newOffset = cursorOffset;
1662
- if (newOffset < value.length) {
1663
- // Skip non-whitespace forwards
1664
- while (newOffset < value.length && !/\s/.test(value[newOffset])) {
1665
- newOffset++;
1666
- }
1667
- // Skip whitespace forwards
1668
- while (newOffset < value.length && /\s/.test(value[newOffset])) {
1669
- newOffset++;
1670
- }
1671
- setCursorOffset(newOffset);
1672
- }
1673
- }
1674
- else if (cursorOffset < value.length) {
1675
- setCursorOffset(cursorOffset + 1);
1676
- }
1677
- return;
1201
+ handleSubmit();
1202
+ }
1203
+ return;
1204
+ }
1205
+ if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
1206
+ if (selection) {
1207
+ setSelection(null);
1208
+ if (key.leftArrow) setCursorOffset(Math.min(selection.start, selection.end));
1209
+ if (key.rightArrow) setCursorOffset(Math.max(selection.start, selection.end));
1210
+ if (key.upArrow || key.downArrow) {
1678
1211
  }
1679
- // Tab Completion
1680
- if (key.tab && !key.shift) {
1681
- // Only file completion (only in command mode)
1682
- if (commandMode) {
1683
- handleTabCompletion();
1684
- return;
1685
- }
1212
+ if (key.leftArrow || key.rightArrow) return;
1213
+ }
1214
+ }
1215
+ if (key.upArrow) {
1216
+ const width = (process.stdout.columns || 80) - 6;
1217
+ const visualLines = getVisualLines(value, width);
1218
+ let currentVisualLineIndex = visualLines.findIndex((line) => {
1219
+ if (cursorOffset >= line.start && cursorOffset < line.end) return true;
1220
+ if (cursorOffset === line.end && line.isHardEnd) return true;
1221
+ return false;
1222
+ });
1223
+ if (currentVisualLineIndex === -1 && cursorOffset === value.length) {
1224
+ currentVisualLineIndex = visualLines.length - 1;
1225
+ }
1226
+ if (currentVisualLineIndex <= 0) {
1227
+ if (commandHistory.length > 0) {
1228
+ if (historyIndex === -1) {
1229
+ setTempValue(value);
1230
+ const newIndex = commandHistory.length - 1;
1231
+ setHistoryIndex(newIndex);
1232
+ const newValue = commandHistory[newIndex];
1233
+ setValue(newValue);
1234
+ setCursorOffset(newValue.length);
1235
+ } else if (historyIndex > 0) {
1236
+ const newIndex = historyIndex - 1;
1237
+ setHistoryIndex(newIndex);
1238
+ const newValue = commandHistory[newIndex];
1239
+ setValue(newValue);
1240
+ setCursorOffset(newValue.length);
1241
+ }
1686
1242
  }
1687
- // Regular Input
1688
- // Ignore control keys to prevent printing garbage (like 'v' for Ctrl+V)
1689
- if (input && !key.ctrl && !key.meta) {
1690
- pushToUndoStack();
1691
- // Handle paste with newlines
1692
- const cleanedInput = input.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
1693
- // Use refs to get the latest value and cursor position
1694
- // This prevents stale closure issues when Ink calls useInput multiple times during paste
1695
- const currentValue = valueRef.current;
1696
- const currentCursorOffset = cursorOffsetRef.current;
1697
- let newValue = currentValue;
1698
- let newOffset = currentCursorOffset;
1699
- if (selection) {
1700
- const start = Math.min(selection.start, selection.end);
1701
- const end = Math.max(selection.start, selection.end);
1702
- newValue = currentValue.slice(0, start) + cleanedInput + currentValue.slice(end);
1703
- newOffset = start + cleanedInput.length;
1704
- setSelection(null);
1705
- }
1706
- else {
1707
- newValue = currentValue.slice(0, currentCursorOffset) + cleanedInput + currentValue.slice(currentCursorOffset);
1708
- newOffset = currentCursorOffset + cleanedInput.length;
1709
- }
1710
- // Update refs immediately for subsequent paste chunks
1711
- valueRef.current = newValue;
1712
- cursorOffsetRef.current = newOffset;
1243
+ } else {
1244
+ const currentLine = visualLines[currentVisualLineIndex];
1245
+ const targetLine = visualLines[currentVisualLineIndex - 1];
1246
+ const offsetInLine = cursorOffset - currentLine.start;
1247
+ const newOffset = targetLine.start + Math.min(offsetInLine, targetLine.end - targetLine.start);
1248
+ setCursorOffset(newOffset);
1249
+ }
1250
+ return;
1251
+ }
1252
+ if (key.downArrow) {
1253
+ const width = (process.stdout.columns || 80) - 6;
1254
+ const visualLines = getVisualLines(value, width);
1255
+ let currentVisualLineIndex = visualLines.findIndex((line) => {
1256
+ if (cursorOffset >= line.start && cursorOffset < line.end) return true;
1257
+ if (cursorOffset === line.end && line.isHardEnd) return true;
1258
+ return false;
1259
+ });
1260
+ if (currentVisualLineIndex === -1 && cursorOffset === value.length) {
1261
+ currentVisualLineIndex = visualLines.length - 1;
1262
+ }
1263
+ if (currentVisualLineIndex === visualLines.length - 1) {
1264
+ if (historyIndex !== -1) {
1265
+ if (historyIndex < commandHistory.length - 1) {
1266
+ const newIndex = historyIndex + 1;
1267
+ setHistoryIndex(newIndex);
1268
+ const newValue = commandHistory[newIndex];
1713
1269
  setValue(newValue);
1714
- setCursorOffsetWithRef(newOffset);
1715
- // Reset history/completions
1270
+ setCursorOffset(newValue.length);
1271
+ } else {
1716
1272
  setHistoryIndex(-1);
1717
- setCompletions([]);
1718
- // Check for slash command autocomplete
1719
- if (newValue.startsWith('/') && !newValue.includes(' ')) {
1720
- // Main slash commands (no space yet)
1721
- const query = newValue.slice(1);
1722
- const matches = filterCommands(query);
1723
- if (matches.length > 0) {
1724
- setSlashAutocompleteCommands(matches);
1725
- setSlashAutocompleteVisible(true);
1726
- setSlashAutocompleteSelectedIndex(0);
1727
- setSlashAutocompleteScrollOffset(0);
1728
- }
1729
- else {
1730
- setSlashAutocompleteVisible(false);
1731
- }
1732
- }
1733
- else if (newValue.startsWith('/mcp ')) {
1734
- // MCP subcommands (when user types "/mcp ")
1735
- const fullQuery = newValue.slice(1); // Remove leading "/", pass "mcp <subquery>" to filterCommands
1736
- const matches = filterCommands(fullQuery);
1737
- if (matches.length > 0) {
1738
- setSlashAutocompleteCommands(matches);
1739
- setSlashAutocompleteVisible(true);
1740
- setSlashAutocompleteSelectedIndex(0);
1741
- setSlashAutocompleteScrollOffset(0);
1742
- }
1743
- else {
1744
- setSlashAutocompleteVisible(false);
1745
- }
1746
- }
1747
- else if (newValue.startsWith('/chat ')) {
1748
- // Chat subcommands (when user types "/chat ")
1749
- const fullQuery = newValue.slice(1); // Remove leading "/", pass "chat <subquery>" to filterCommands
1750
- const matches = filterCommands(fullQuery);
1751
- if (matches.length > 0) {
1752
- setSlashAutocompleteCommands(matches);
1753
- setSlashAutocompleteVisible(true);
1754
- setSlashAutocompleteSelectedIndex(0);
1755
- setSlashAutocompleteScrollOffset(0);
1756
- }
1757
- else {
1758
- setSlashAutocompleteVisible(false);
1759
- }
1760
- }
1761
- else if (newValue.startsWith('/add-command ') || newValue.startsWith('/add-command-auto-detect ')) {
1762
- // Add-command subcommands (when user types "/add-command ")
1763
- const fullQuery = newValue.slice(1); // Remove leading "/", pass "add-command <subquery>" to filterCommands
1764
- const matches = filterCommands(fullQuery);
1765
- if (matches.length > 0) {
1766
- setSlashAutocompleteCommands(matches);
1767
- setSlashAutocompleteVisible(true);
1768
- setSlashAutocompleteSelectedIndex(0);
1769
- setSlashAutocompleteScrollOffset(0);
1770
- }
1771
- else {
1772
- setSlashAutocompleteVisible(false);
1773
- }
1774
- }
1775
- else if (newValue.startsWith('/background-task ') || newValue.startsWith('/bkg ') || newValue.startsWith('/bg-task ')) {
1776
- // Background-task subcommands (when user types "/background-task ", "/bkg ", or "/bg-task ")
1777
- const fullQuery = newValue.slice(1); // Remove leading "/", pass "background-task <subquery>" to filterCommands
1778
- const matches = filterCommands(fullQuery);
1779
- if (matches.length > 0) {
1780
- setSlashAutocompleteCommands(matches);
1781
- setSlashAutocompleteVisible(true);
1782
- setSlashAutocompleteSelectedIndex(0);
1783
- setSlashAutocompleteScrollOffset(0);
1784
- }
1785
- else {
1786
- setSlashAutocompleteVisible(false);
1787
- }
1788
- }
1789
- else if (newValue.startsWith('/sync ')) {
1790
- // Sync subcommands (when user types "/sync ")
1791
- const fullQuery = newValue.slice(1); // Remove leading "/", pass "sync <subquery>" to filterCommands
1792
- const matches = filterCommands(fullQuery);
1793
- if (matches.length > 0) {
1794
- setSlashAutocompleteCommands(matches);
1795
- setSlashAutocompleteVisible(true);
1796
- setSlashAutocompleteSelectedIndex(0);
1797
- setSlashAutocompleteScrollOffset(0);
1798
- }
1799
- else {
1800
- setSlashAutocompleteVisible(false);
1801
- }
1802
- }
1803
- else if (newValue.startsWith('/models ') || newValue.startsWith('/model ')) {
1804
- // Models subcommands (when user types "/models " or "/model ")
1805
- const fullQuery = newValue.slice(1); // Remove leading "/", pass "models <subquery>" to filterCommands
1806
- const matches = filterCommands(fullQuery);
1807
- if (matches.length > 0) {
1808
- setSlashAutocompleteCommands(matches);
1809
- setSlashAutocompleteVisible(true);
1810
- setSlashAutocompleteSelectedIndex(0);
1811
- setSlashAutocompleteScrollOffset(0);
1812
- }
1813
- else {
1814
- setSlashAutocompleteVisible(false);
1815
- }
1816
- }
1817
- else if (newValue.startsWith('/revert ')) {
1818
- // Checkpoint autocomplete for /revert command (on backspace)
1819
- const partialId = newValue.slice(8).toLowerCase(); // Remove "/revert "
1820
- const checkpoints = getCheckpoints ? getCheckpoints() : [];
1821
- if (checkpoints.length === 0) {
1822
- // No checkpoints yet - show informative message
1823
- setSlashAutocompleteCommands([{
1824
- name: '(no checkpoints)',
1825
- description: 'Ask AI a question first to create a checkpoint'
1826
- }]);
1827
- setSlashAutocompleteVisible(true);
1828
- setSlashAutocompleteSelectedIndex(0);
1829
- setSlashAutocompleteScrollOffset(0);
1830
- }
1831
- else {
1832
- const matchingCheckpoints = checkpoints
1833
- .filter(cp => cp.id.toLowerCase().includes(partialId) || cp.prompt.toLowerCase().includes(partialId))
1834
- .slice(0, 10)
1835
- .map(cp => {
1836
- const truncatedPrompt = cp.prompt.length > 50 ? cp.prompt.slice(0, 50) + '...' : cp.prompt;
1837
- return {
1838
- name: cp.id,
1839
- description: truncatedPrompt
1840
- };
1841
- });
1842
- if (matchingCheckpoints.length > 0) {
1843
- setSlashAutocompleteCommands(matchingCheckpoints);
1844
- setSlashAutocompleteVisible(true);
1845
- setSlashAutocompleteSelectedIndex(0);
1846
- setSlashAutocompleteScrollOffset(0);
1847
- }
1848
- else {
1849
- setSlashAutocompleteVisible(false);
1850
- }
1851
- }
1852
- }
1853
- else if (newValue.match(/^\/workflow\s+(run|view|delete)\s+/) ||
1854
- newValue.match(/^\/wf\s+(run|view|delete)\s+/)) {
1855
- // Workflow name autocomplete (when user types "/workflow run " or similar)
1856
- // This MUST come before the /workflow subcommand check since it's more specific
1857
- const match = newValue.match(/^\/(?:workflow|wf)\s+(?:run|view|delete)\s+(.*)$/);
1858
- const partialName = match ? match[1].toLowerCase() : '';
1859
- const workflows = workflowStorage.list();
1860
- const matchingWorkflows = workflows
1861
- .filter(wf => wf.name.toLowerCase().includes(partialName))
1862
- .slice(0, 10)
1863
- .map(wf => ({
1864
- name: wf.name,
1865
- description: wf.description || `${wf.stepCount} step${wf.stepCount !== 1 ? 's' : ''}`
1866
- }));
1867
- if (matchingWorkflows.length > 0) {
1868
- setSlashAutocompleteCommands(matchingWorkflows);
1869
- setSlashAutocompleteVisible(true);
1870
- setSlashAutocompleteSelectedIndex(0);
1871
- setSlashAutocompleteScrollOffset(0);
1872
- }
1873
- else {
1874
- setSlashAutocompleteVisible(false);
1875
- }
1876
- }
1877
- else if (newValue.startsWith('/workflow ') || newValue.startsWith('/wf ')) {
1878
- // Workflow subcommands (when user types "/workflow " or "/wf ")
1879
- const fullQuery = newValue.slice(1); // Remove leading "/", pass "workflow <subquery>" to filterCommands
1880
- const matches = filterCommands(fullQuery);
1881
- if (matches.length > 0) {
1882
- setSlashAutocompleteCommands(matches);
1883
- setSlashAutocompleteVisible(true);
1884
- setSlashAutocompleteSelectedIndex(0);
1885
- setSlashAutocompleteScrollOffset(0);
1886
- }
1887
- else {
1888
- setSlashAutocompleteVisible(false);
1889
- }
1890
- }
1891
- else {
1892
- setSlashAutocompleteVisible(false);
1893
- }
1273
+ setValue(tempValue);
1274
+ setCursorOffset(tempValue.length);
1275
+ }
1276
+ }
1277
+ } else {
1278
+ const currentLine = visualLines[currentVisualLineIndex];
1279
+ const targetLine = visualLines[currentVisualLineIndex + 1];
1280
+ const offsetInLine = cursorOffset - currentLine.start;
1281
+ const newOffset = targetLine.start + Math.min(offsetInLine, targetLine.end - targetLine.start);
1282
+ setCursorOffset(newOffset);
1283
+ }
1284
+ return;
1285
+ }
1286
+ if (key.leftArrow) {
1287
+ if (isMac && key.meta) {
1288
+ let newOffset = cursorOffset;
1289
+ if (newOffset > 0) {
1290
+ while (newOffset > 0 && /\s/.test(value[newOffset - 1])) {
1291
+ newOffset--;
1292
+ }
1293
+ while (newOffset > 0 && !/\s/.test(value[newOffset - 1])) {
1294
+ newOffset--;
1295
+ }
1296
+ setCursorOffset(newOffset);
1297
+ }
1298
+ return;
1299
+ }
1300
+ if (key.ctrl || !isMac && key.meta) {
1301
+ let newOffset = cursorOffset;
1302
+ if (newOffset > 0) {
1303
+ while (newOffset > 0 && /\s/.test(value[newOffset - 1])) {
1304
+ newOffset--;
1305
+ }
1306
+ while (newOffset > 0 && !/\s/.test(value[newOffset - 1])) {
1307
+ newOffset--;
1308
+ }
1309
+ setCursorOffset(newOffset);
1894
1310
  }
1895
- }, { isActive });
1896
- const handleTabCompletion = async () => {
1897
- if (!value)
1311
+ return;
1312
+ }
1313
+ if (cursorOffset > 0) {
1314
+ setCursorOffset(cursorOffset - 1);
1315
+ }
1316
+ return;
1317
+ }
1318
+ if (key.rightArrow) {
1319
+ const effectiveSuggestion = aiAutocompleteSuggestion || autocompleteSuggestion;
1320
+ if (effectiveSuggestion && cursorOffset === value.length) {
1321
+ if (key.ctrl || !isMac && key.meta) {
1322
+ setValue(effectiveSuggestion);
1323
+ setCursorOffset(effectiveSuggestion.length);
1324
+ setAutocompleteSuggestion(null);
1325
+ setAiAutocompleteSuggestion(null);
1326
+ return;
1327
+ } else {
1328
+ const remaining = effectiveSuggestion.slice(value.length);
1329
+ const match = remaining.match(/^(\s*\S+)/);
1330
+ if (match) {
1331
+ const toAdd = match[0];
1332
+ const newValue = value + toAdd;
1333
+ setValue(newValue);
1334
+ setCursorOffset(newValue.length);
1898
1335
  return;
1899
- const words = value.split(' ');
1900
- const lastWord = words[words.length - 1];
1901
- if (completions.length > 0) {
1902
- const nextIndex = (completionIndex + 1) % completions.length;
1903
- setCompletionIndex(nextIndex);
1904
- words[words.length - 1] = completions[nextIndex];
1905
- const newValue = words.join(' ');
1336
+ } else if (remaining.length > 0) {
1337
+ const newValue = value + remaining;
1906
1338
  setValue(newValue);
1907
1339
  setCursorOffset(newValue.length);
1908
1340
  return;
1341
+ }
1909
1342
  }
1910
- try {
1911
- const cwd = currentWorkingDirectory || process.cwd();
1912
- let searchDir = cwd;
1913
- let searchPattern = lastWord;
1914
- let dirPart = '';
1915
- if (lastWord.includes('/') || lastWord.includes('\\')) {
1916
- const lastSep = Math.max(lastWord.lastIndexOf('/'), lastWord.lastIndexOf('\\'));
1917
- dirPart = lastWord.substring(0, lastSep + 1);
1918
- searchPattern = lastWord.substring(lastSep + 1);
1919
- if (subshellContext && subshellContext.type !== 'local') {
1920
- searchDir = dirPart.startsWith('/') ? dirPart.slice(0, -1) : cwd + '/' + dirPart.slice(0, -1);
1921
- }
1922
- else {
1923
- searchDir = path.resolve(cwd, dirPart);
1924
- }
1925
- }
1926
- let entries;
1927
- if (subshellContext && subshellContext.type !== 'local' && subshellContext.handler) {
1928
- const dirEntries = await subshellContext.handler.listDirectory(searchDir);
1929
- entries = dirEntries.map(entry => ({ name: entry.name, type: entry.type }));
1930
- }
1931
- else {
1932
- if (!fs.existsSync(searchDir))
1933
- return;
1934
- const fsEntries = fs.readdirSync(searchDir, { withFileTypes: true });
1935
- entries = fsEntries.map(entry => ({
1936
- name: entry.name,
1937
- type: entry.isDirectory() ? 'directory' : 'file'
1938
- }));
1939
- }
1940
- const matches = entries
1941
- .filter(entry => entry.name.toLowerCase().startsWith(searchPattern.toLowerCase()))
1942
- .map(entry => {
1943
- const separator = (subshellContext && subshellContext.type !== 'local') ? '/' : path.sep;
1944
- const fullPath = dirPart ? dirPart + entry.name : entry.name;
1945
- return entry.type === 'directory' ? fullPath + separator : fullPath;
1946
- })
1947
- .sort();
1948
- if (matches.length > 0) {
1949
- if (matches.length > 1) {
1950
- setCompletions(matches);
1951
- setCompletionIndex(0);
1952
- }
1953
- words[words.length - 1] = matches[0];
1954
- const newValue = words.join(' ');
1955
- setValue(newValue);
1956
- setCursorOffset(newValue.length);
1957
- }
1343
+ }
1344
+ if (isMac && key.meta) {
1345
+ let newOffset = cursorOffset;
1346
+ if (newOffset < value.length) {
1347
+ while (newOffset < value.length && /\s/.test(value[newOffset])) {
1348
+ newOffset++;
1349
+ }
1350
+ while (newOffset < value.length && !/\s/.test(value[newOffset])) {
1351
+ newOffset++;
1352
+ }
1353
+ setCursorOffset(newOffset);
1958
1354
  }
1959
- catch (error) {
1960
- // Ignore errors
1355
+ return;
1356
+ }
1357
+ if (!isMac && key.meta || key.ctrl) {
1358
+ let newOffset = cursorOffset;
1359
+ if (newOffset < value.length) {
1360
+ while (newOffset < value.length && /\s/.test(value[newOffset])) {
1361
+ newOffset++;
1362
+ }
1363
+ while (newOffset < value.length && !/\s/.test(value[newOffset])) {
1364
+ newOffset++;
1365
+ }
1366
+ setCursorOffset(newOffset);
1961
1367
  }
1962
- };
1963
- const handleSubmit = () => {
1964
- if (!isActive)
1965
- return;
1966
- // Block submission while shell command is running - flash red
1967
- if (isShellRunning) {
1968
- setRejectFlash(true);
1969
- setTimeout(() => setRejectFlash(false), 1000); // Flash for 1.0 seconds
1970
- return;
1368
+ return;
1369
+ }
1370
+ if (key.ctrl || key.meta) {
1371
+ let newOffset = cursorOffset;
1372
+ if (newOffset < value.length) {
1373
+ while (newOffset < value.length && !/\s/.test(value[newOffset])) {
1374
+ newOffset++;
1375
+ }
1376
+ while (newOffset < value.length && /\s/.test(value[newOffset])) {
1377
+ newOffset++;
1378
+ }
1379
+ setCursorOffset(newOffset);
1971
1380
  }
1972
- const trimmedValue = value.trim();
1973
- if (trimmedValue) {
1974
- // Save to history if it was a command
1975
- if (commandMode) {
1976
- CommandHistoryManager.getInstance().addCommand(trimmedValue, currentDir, currentEnvironment);
1977
- }
1978
- // Resolve file tags (@filename -> absolute path)
1979
- let resolvedValue = trimmedValue;
1980
- if (!commandMode) {
1981
- const cwd = currentWorkingDirectory || process.cwd();
1982
- // Find all @filename patterns (@ followed by non-whitespace)
1983
- resolvedValue = trimmedValue.replace(/@([^\s@]+)/g, (match, fileName) => {
1984
- // Check if file exists
1985
- const filePath = path.resolve(cwd, fileName);
1986
- if (fs.existsSync(filePath)) {
1987
- return filePath;
1988
- }
1989
- // If file doesn't exist, keep original
1990
- return match;
1991
- });
1992
- }
1993
- onSubmit(resolvedValue, confirmedClipboardFiles.length > 0 ? confirmedClipboardFiles : undefined);
1994
- setValue('');
1995
- setCursorOffset(0);
1996
- setCompletions([]);
1997
- setCompletionIndex(0);
1998
- setHistoryIndex(-1);
1999
- setTempValue('');
2000
- setUndoStack([]);
2001
- setRedoStack([]);
2002
- setSelection(null);
2003
- setAutocompleteSuggestion(null);
2004
- setConfirmedFileTags([]); // Clear confirmed tags on submit
2005
- setConfirmedClipboardFiles([]); // Clear confirmed clipboard files on submit
1381
+ } else if (cursorOffset < value.length) {
1382
+ setCursorOffset(cursorOffset + 1);
1383
+ }
1384
+ return;
1385
+ }
1386
+ if (key.tab && !key.shift) {
1387
+ if (commandMode) {
1388
+ handleTabCompletion();
1389
+ return;
1390
+ }
1391
+ }
1392
+ if (input && !key.ctrl && !key.meta) {
1393
+ pushToUndoStack();
1394
+ const cleanedInput = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1395
+ const currentValue = valueRef.current;
1396
+ const currentCursorOffset = cursorOffsetRef.current;
1397
+ let newValue = currentValue;
1398
+ let newOffset = currentCursorOffset;
1399
+ if (selection) {
1400
+ const start = Math.min(selection.start, selection.end);
1401
+ const end = Math.max(selection.start, selection.end);
1402
+ newValue = currentValue.slice(0, start) + cleanedInput + currentValue.slice(end);
1403
+ newOffset = start + cleanedInput.length;
1404
+ setSelection(null);
1405
+ } else {
1406
+ newValue = currentValue.slice(0, currentCursorOffset) + cleanedInput + currentValue.slice(currentCursorOffset);
1407
+ newOffset = currentCursorOffset + cleanedInput.length;
1408
+ }
1409
+ valueRef.current = newValue;
1410
+ cursorOffsetRef.current = newOffset;
1411
+ setValue(newValue);
1412
+ setCursorOffsetWithRef(newOffset);
1413
+ setHistoryIndex(-1);
1414
+ setCompletions([]);
1415
+ updateSlashAutocomplete(newValue);
1416
+ }
1417
+ }, { isActive });
1418
+ const handleTabCompletion = async () => {
1419
+ if (!value) return;
1420
+ const words = value.split(" ");
1421
+ const lastWord = words[words.length - 1];
1422
+ if (completions.length > 0) {
1423
+ const nextIndex = (completionIndex + 1) % completions.length;
1424
+ setCompletionIndex(nextIndex);
1425
+ words[words.length - 1] = completions[nextIndex];
1426
+ const newValue = words.join(" ");
1427
+ setValue(newValue);
1428
+ setCursorOffset(newValue.length);
1429
+ return;
1430
+ }
1431
+ try {
1432
+ const cwd = currentWorkingDirectory || process.cwd();
1433
+ let searchDir = cwd;
1434
+ let searchPattern = lastWord;
1435
+ let dirPart = "";
1436
+ if (lastWord.includes("/") || lastWord.includes("\\")) {
1437
+ const lastSep = Math.max(lastWord.lastIndexOf("/"), lastWord.lastIndexOf("\\"));
1438
+ dirPart = lastWord.substring(0, lastSep + 1);
1439
+ searchPattern = lastWord.substring(lastSep + 1);
1440
+ if (subshellContext && subshellContext.type !== "local") {
1441
+ searchDir = dirPart.startsWith("/") ? dirPart.slice(0, -1) : cwd + "/" + dirPart.slice(0, -1);
1442
+ } else {
1443
+ searchDir = path.resolve(cwd, dirPart);
2006
1444
  }
2007
- };
2008
- // Rendering Logic with Scrolling
2009
- const renderInput = () => {
2010
- // Get terminal width for visual line calculation
2011
- // Account for borders (2) + padding (2) + prompt "> " (2) = 6 chars
2012
- const termWidth = (process.stdout.columns || 80) - 6;
2013
- // Use getVisualLines to properly calculate wrapped lines
2014
- const visualLines = getVisualLines(value, termWidth);
2015
- const logicalLines = value.split('\n');
2016
- // If empty, show placeholder
2017
- if (logicalLines.length === 1 && logicalLines[0] === '') {
2018
- return React.createElement(Text, { color: "gray" }, placeholder);
1445
+ }
1446
+ let entries;
1447
+ if (subshellContext && subshellContext.type !== "local" && subshellContext.handler) {
1448
+ const dirEntries = await subshellContext.handler.listDirectory(searchDir);
1449
+ entries = dirEntries.map((entry) => ({ name: entry.name, type: entry.type }));
1450
+ } else {
1451
+ if (!fs.existsSync(searchDir)) return;
1452
+ const fsEntries = fs.readdirSync(searchDir, { withFileTypes: true });
1453
+ entries = fsEntries.map((entry) => ({
1454
+ name: entry.name,
1455
+ type: entry.isDirectory() ? "directory" : "file"
1456
+ }));
1457
+ }
1458
+ const matches = entries.filter((entry) => entry.name.toLowerCase().startsWith(searchPattern.toLowerCase())).map((entry) => {
1459
+ const separator = subshellContext && subshellContext.type !== "local" ? "/" : path.sep;
1460
+ const fullPath = dirPart ? dirPart + entry.name : entry.name;
1461
+ return entry.type === "directory" ? fullPath + separator : fullPath;
1462
+ }).sort();
1463
+ if (matches.length > 0) {
1464
+ if (matches.length > 1) {
1465
+ setCompletions(matches);
1466
+ setCompletionIndex(0);
2019
1467
  }
2020
- // For rendering, we still use logical lines (split by \n) since we render character-by-character
2021
- // But for height calculation, we use visualLines.length
2022
- const lines = logicalLines;
2023
- // Calculate cursor line and column (based on logical lines)
2024
- let currentPos = 0;
2025
- let cursorLine = 0;
2026
- let cursorCol = 0;
2027
- for (let i = 0; i < lines.length; i++) {
2028
- if (currentPos + lines[i].length >= cursorOffset) {
2029
- cursorLine = i;
2030
- cursorCol = cursorOffset - currentPos;
2031
- break;
2032
- }
2033
- currentPos += lines[i].length + 1; // +1 for newline
1468
+ words[words.length - 1] = matches[0];
1469
+ const newValue = words.join(" ");
1470
+ setValue(newValue);
1471
+ setCursorOffset(newValue.length);
1472
+ }
1473
+ } catch (error) {
1474
+ }
1475
+ };
1476
+ const handleSubmit = () => {
1477
+ if (!isActive) return;
1478
+ if (isShellRunning) {
1479
+ setRejectFlash(true);
1480
+ setTimeout(() => setRejectFlash(false), 1e3);
1481
+ return;
1482
+ }
1483
+ const trimmedValue = value.trim();
1484
+ if (trimmedValue) {
1485
+ if (commandMode) {
1486
+ CommandHistoryManager.getInstance().addCommand(trimmedValue, currentDir, currentEnvironment);
1487
+ }
1488
+ onSubmit(trimmedValue, confirmedClipboardFiles.length > 0 ? confirmedClipboardFiles : void 0);
1489
+ setValue("");
1490
+ setCursorOffset(0);
1491
+ setCompletions([]);
1492
+ setCompletionIndex(0);
1493
+ setHistoryIndex(-1);
1494
+ setTempValue("");
1495
+ setUndoStack([]);
1496
+ setRedoStack([]);
1497
+ setSelection(null);
1498
+ setAutocompleteSuggestion(null);
1499
+ setConfirmedFileTags([]);
1500
+ setConfirmedClipboardFiles([]);
1501
+ }
1502
+ };
1503
+ const renderInput = () => {
1504
+ const effectivePlaceholder = backgroundMode ? "Run commands in the background" : commandMode ? "Run a command" : placeholder;
1505
+ const termWidth = (process.stdout.columns || 80) - 6;
1506
+ const visualLines = getVisualLines(value, termWidth);
1507
+ const logicalLines = value.split("\n");
1508
+ if (logicalLines.length === 1 && logicalLines[0] === "") {
1509
+ return /* @__PURE__ */ React.createElement(Text, { color: "gray" }, effectivePlaceholder);
1510
+ }
1511
+ const lines = logicalLines;
1512
+ let currentPos = 0;
1513
+ let cursorLine = 0;
1514
+ let cursorCol = 0;
1515
+ for (let i = 0; i < lines.length; i++) {
1516
+ if (currentPos + lines[i].length >= cursorOffset) {
1517
+ cursorLine = i;
1518
+ cursorCol = cursorOffset - currentPos;
1519
+ break;
1520
+ }
1521
+ currentPos += lines[i].length + 1;
1522
+ }
1523
+ if (cursorOffset === value.length && lines.length > 0) {
1524
+ cursorLine = lines.length - 1;
1525
+ cursorCol = lines[lines.length - 1].length;
1526
+ }
1527
+ let cursorVisualLine = 0;
1528
+ for (let i = 0; i < visualLines.length; i++) {
1529
+ if (cursorOffset >= visualLines[i].start && cursorOffset <= visualLines[i].end) {
1530
+ cursorVisualLine = i;
1531
+ break;
1532
+ }
1533
+ }
1534
+ if (cursorOffset === value.length && visualLines.length > 0) {
1535
+ cursorVisualLine = visualLines.length - 1;
1536
+ }
1537
+ const totalVisualLines = visualLines.length;
1538
+ let startLine = 0;
1539
+ if (totalVisualLines > MAX_VISIBLE_LINES) {
1540
+ if (cursorVisualLine < MAX_VISIBLE_LINES) {
1541
+ startLine = 0;
1542
+ } else {
1543
+ startLine = cursorVisualLine - MAX_VISIBLE_LINES + 1;
1544
+ }
1545
+ }
1546
+ const endLine = Math.min(startLine + MAX_VISIBLE_LINES, totalVisualLines);
1547
+ const visibleVisualLines = visualLines.slice(startLine, endLine);
1548
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", flexGrow: 1 }, startLine > 0 && /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "\u2191 ..."), visibleVisualLines.map((vLine, idx) => {
1549
+ const actualVisualLineIndex = startLine + idx;
1550
+ const lineText = value.slice(vLine.start, vLine.end);
1551
+ const lineStartPos = vLine.start;
1552
+ const isCursorLine = cursorOffset >= vLine.start && cursorOffset <= vLine.end;
1553
+ const cursorCol2 = isCursorLine ? cursorOffset - vLine.start : -1;
1554
+ const isLastLine = actualVisualLineIndex === totalVisualLines - 1;
1555
+ if (!isActive) {
1556
+ if (lineText.length === 0) {
1557
+ return /* @__PURE__ */ React.createElement(Text, { key: idx }, " ");
2034
1558
  }
2035
- // Edge case: cursor at very end of content
2036
- if (cursorOffset === value.length && lines.length > 0) {
2037
- cursorLine = lines.length - 1;
2038
- cursorCol = lines[lines.length - 1].length;
1559
+ return /* @__PURE__ */ React.createElement(Text, { key: idx }, lineText);
1560
+ }
1561
+ const chars = lineText.split("");
1562
+ const renderedChars = chars.map((char, charIdx) => {
1563
+ const absPos = lineStartPos + charIdx;
1564
+ const isSelected = selection && absPos >= Math.min(selection.start, selection.end) && absPos < Math.max(selection.start, selection.end);
1565
+ let activeFileTagEnd = activeFileTagStart !== null ? activeFileTagStart : 0;
1566
+ if (activeFileTagStart !== null) {
1567
+ for (let j = activeFileTagStart; j < value.length && j < cursorOffset; j++) {
1568
+ if (/[\s\n]/.test(value[j])) {
1569
+ break;
1570
+ }
1571
+ activeFileTagEnd = j + 1;
1572
+ }
2039
1573
  }
2040
- // For scrolling, calculate which VISUAL line the cursor is on
2041
- let cursorVisualLine = 0;
2042
- for (let i = 0; i < visualLines.length; i++) {
2043
- if (cursorOffset >= visualLines[i].start && cursorOffset <= visualLines[i].end) {
2044
- cursorVisualLine = i;
2045
- break;
1574
+ const isInActiveFileTag = activeFileTagStart !== null && absPos >= activeFileTagStart && absPos < activeFileTagEnd;
1575
+ let isInConfirmedFileTag = false;
1576
+ if (!commandMode) {
1577
+ const fileTagRegex = /(?:^|[\s\n])(@[^\s@]+)/g;
1578
+ let match;
1579
+ while ((match = fileTagRegex.exec(value)) !== null) {
1580
+ const fullMatch = match[0];
1581
+ const tagContent = match[1];
1582
+ const tagStart = match.index + (fullMatch.length - tagContent.length);
1583
+ const tagEnd = tagStart + tagContent.length;
1584
+ if (activeFileTagStart !== null && tagStart === activeFileTagStart) {
1585
+ continue;
1586
+ }
1587
+ if (absPos >= tagStart && absPos < tagEnd) {
1588
+ isInConfirmedFileTag = true;
1589
+ break;
2046
1590
  }
1591
+ }
2047
1592
  }
2048
- // Handle cursor at very end
2049
- if (cursorOffset === value.length && visualLines.length > 0) {
2050
- cursorVisualLine = visualLines.length - 1;
1593
+ const isCursor = isCursorLine && charIdx === cursorCol2;
1594
+ if (isCursor) {
1595
+ return /* @__PURE__ */ React.createElement(Text, { key: charIdx, inverse: true, color: isSelected ? "yellow" : void 0 }, char);
2051
1596
  }
2052
- // Calculate visible range using VISUAL lines count for proper scrolling
2053
- const totalVisualLines = visualLines.length;
2054
- let startLine = 0;
2055
- if (totalVisualLines > MAX_VISIBLE_LINES) {
2056
- // Use visual line position for scrolling calculation
2057
- if (cursorVisualLine < MAX_VISIBLE_LINES) {
2058
- startLine = 0;
2059
- }
2060
- else {
2061
- startLine = cursorVisualLine - MAX_VISIBLE_LINES + 1;
2062
- }
1597
+ if (isSelected) {
1598
+ return /* @__PURE__ */ React.createElement(Text, { key: charIdx, backgroundColor: "white", color: "black" }, char);
2063
1599
  }
2064
- const endLine = Math.min(startLine + MAX_VISIBLE_LINES, totalVisualLines);
2065
- // Get the text content for each visual line
2066
- const visibleVisualLines = visualLines.slice(startLine, endLine);
2067
- return (React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
2068
- startLine > 0 && React.createElement(Text, { color: "gray" }, "\u2191 ..."),
2069
- visibleVisualLines.map((vLine, idx) => {
2070
- const actualVisualLineIndex = startLine + idx;
2071
- // Extract the text content for this visual line
2072
- const lineText = value.slice(vLine.start, vLine.end);
2073
- const lineStartPos = vLine.start;
2074
- // Determine if cursor is on this visual line
2075
- const isCursorLine = cursorOffset >= vLine.start && cursorOffset <= vLine.end;
2076
- const cursorCol = isCursorLine ? cursorOffset - vLine.start : -1;
2077
- // Is this the last visual line?
2078
- const isLastLine = actualVisualLineIndex === totalVisualLines - 1;
2079
- if (!isActive) {
2080
- if (lineText.length === 0) {
2081
- return React.createElement(Text, { key: idx }, " ");
2082
- }
2083
- return React.createElement(Text, { key: idx }, lineText);
2084
- }
2085
- // Render with selection and cursor
2086
- const chars = lineText.split('');
2087
- const renderedChars = chars.map((char, charIdx) => {
2088
- const absPos = lineStartPos + charIdx;
2089
- const isSelected = selection &&
2090
- absPos >= Math.min(selection.start, selection.end) &&
2091
- absPos < Math.max(selection.start, selection.end);
2092
- // Check if this character is part of an active file tag (being typed after @)
2093
- let activeFileTagEnd = activeFileTagStart !== null ? activeFileTagStart : 0;
2094
- if (activeFileTagStart !== null) {
2095
- for (let j = activeFileTagStart; j < value.length && j < cursorOffset; j++) {
2096
- if (/[\s\n]/.test(value[j])) {
2097
- break;
2098
- }
2099
- activeFileTagEnd = j + 1;
2100
- }
2101
- }
2102
- const isInActiveFileTag = activeFileTagStart !== null &&
2103
- absPos >= activeFileTagStart &&
2104
- absPos < activeFileTagEnd;
2105
- // Check if this character is part of a confirmed file tag
2106
- let isInConfirmedFileTag = false;
2107
- if (!commandMode) {
2108
- const fileTagRegex = /(?:^|[\s\n])(@[^\s@]+)/g;
2109
- let match;
2110
- while ((match = fileTagRegex.exec(value)) !== null) {
2111
- const fullMatch = match[0];
2112
- const tagContent = match[1];
2113
- const tagStart = match.index + (fullMatch.length - tagContent.length);
2114
- const tagEnd = tagStart + tagContent.length;
2115
- if (activeFileTagStart !== null && tagStart === activeFileTagStart) {
2116
- continue;
2117
- }
2118
- if (absPos >= tagStart && absPos < tagEnd) {
2119
- isInConfirmedFileTag = true;
2120
- break;
2121
- }
2122
- }
2123
- }
2124
- const isCursor = isCursorLine && charIdx === cursorCol;
2125
- if (isCursor) {
2126
- return React.createElement(Text, { key: charIdx, inverse: true, color: isSelected ? "yellow" : undefined }, char);
2127
- }
2128
- if (isSelected) {
2129
- return React.createElement(Text, { key: charIdx, backgroundColor: "white", color: "black" }, char);
2130
- }
2131
- // Highlight file tags with cyan color
2132
- if (isInConfirmedFileTag || isInActiveFileTag) {
2133
- return React.createElement(Text, { key: charIdx, color: "#00ccff", bold: true }, char);
2134
- }
2135
- return React.createElement(Text, { key: charIdx }, char);
2136
- });
2137
- // Handle cursor at end of line
2138
- if (isCursorLine && cursorCol === lineText.length) {
2139
- renderedChars.push(React.createElement(Text, { key: "cursor", inverse: true }, " "));
2140
- }
2141
- // Render Autocomplete Ghost Text
2142
- // Only on the last line, if suggestion exists and matches start
2143
- // AI suggestion takes priority over passive suggestion
2144
- const effectiveSuggestion = aiAutocompleteSuggestion || autocompleteSuggestion;
2145
- if (isLastLine && effectiveSuggestion && effectiveSuggestion.startsWith(value)) {
2146
- const suffix = effectiveSuggestion.slice(value.length);
2147
- if (suffix) {
2148
- // Use slightly different color for AI suggestion to differentiate
2149
- const ghostColor = aiAutocompleteSuggestion ? '#888888' : 'gray';
2150
- renderedChars.push(React.createElement(Text, { key: "ghost", color: ghostColor }, suffix));
2151
- }
2152
- }
2153
- if (renderedChars.length === 0) {
2154
- return React.createElement(Text, { key: idx }, " ");
2155
- }
2156
- return React.createElement(Text, { key: idx }, renderedChars);
2157
- }),
2158
- endLine < totalVisualLines && React.createElement(Text, { color: "gray" }, "\u2193 ...")));
2159
- };
2160
- return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: rejectFlash ? "#ff3366" :
2161
- contextLimitReached ? "#ff3366" : // Red when context limit reached
2162
- sessionQuotaExhausted ? "#ff3366" : // Red when quota exhausted
2163
- backgroundMode ? "#9966ff" :
2164
- commandMode ? "#00cc66" :
2165
- "#257aa5ff", paddingX: 1, paddingY: 0, width: "100%" },
2166
- React.createElement(Box, { marginY: 1, justifyContent: "space-between", width: "100%" },
2167
- React.createElement(Box, null,
2168
- subshellContext && subshellContext.type !== 'local' && (React.createElement(Breadcrumbs, { context: subshellContext, stack: subshellContextStack })),
2169
- React.createElement(Text, { color: "#666666" }, "CWD: "),
2170
- React.createElement(Text, { color: "#00ccff", bold: true }, currentDir)),
2171
- React.createElement(Box, null,
2172
- model && !commandMode && !backgroundMode && (React.createElement(Box, { marginRight: 1 },
2173
- React.createElement(Text, { color: "#666666" }, "Model: "),
2174
- React.createElement(Text, { color: "#00ccff" }, model))),
2175
- React.createElement(Box, null,
2176
- React.createElement(Text, { color: "#666666" }, "Mode: "),
2177
- isAutoMode ? (React.createElement(Text, null,
2178
- React.createElement(Text, { color: "#00ccff", bold: true }, "Auto ["),
2179
- detectedIntent === 'command' ? (React.createElement(Text, { color: "#00cc66", bold: true }, "Terminal")) : (React.createElement(Text, { color: "#00ccff" }, "Agent")),
2180
- React.createElement(Text, { color: "#00ccff", bold: true }, "]"))) : backgroundMode ? (React.createElement(Text, { color: "#9966ff", bold: true }, "Background")) : commandMode ? (React.createElement(Text, { color: "#00cc66", bold: true }, "Terminal")) : planMode ? (React.createElement(Text, { color: "#ffaa00", bold: true }, "Plan")) : (React.createElement(Text, { color: "#00ccff" }, "Agent"))))),
2181
- confirmedClipboardFiles.length > 0 && (React.createElement(Box, { marginBottom: 1, flexDirection: "row", flexWrap: "wrap", gap: 1 }, confirmedClipboardFiles.map((file, index) => (React.createElement(Box, { key: file.id, borderStyle: "round", borderColor: "#ff69b4", paddingX: 1 },
2182
- React.createElement(Text, { color: "#ff69b4", bold: true },
2183
- "file_",
2184
- index + 1)))))),
2185
- showQuotaMessage && (React.createElement(Box, { justifyContent: "flex-end", marginBottom: 1 },
2186
- React.createElement(Text, { color: "#ff3366", bold: true },
2187
- "Session quota reached. Try again in ",
2188
- sessionQuotaTimeRemaining))),
2189
- contextLimitReached && (React.createElement(Box, { justifyContent: "flex-end", marginBottom: 1 },
2190
- React.createElement(Text, { color: "#ff3366", bold: true }, "\u26A0\uFE0F Context limit reached. Use /new to start a fresh chat."))),
2191
- React.createElement(Box, { flexDirection: "row", width: "100%" },
2192
- React.createElement(Text, { color: "#666666" }, "> "),
2193
- renderInput()),
2194
- React.createElement(Box, { marginY: 1, justifyContent: "space-between", width: "100%" },
2195
- React.createElement(Text, { color: "#666666", dimColor: true }, isAutoMode ? ('Ctrl+D to switch modes • Ctrl+T to auto-approve') : backgroundMode ? ('Ctrl+D to switch modes • Commands run in background') : commandMode ? ('Ctrl+D to switch modes • Tab for autocomplete') : ('Ctrl+D to switch modes • Ctrl+T to auto-approve')),
2196
- React.createElement(Box, { gap: 1 },
2197
- subAgentCount > 0 && (React.createElement(Text, { color: "#00ccff", bold: true },
2198
- "[sub-agent: ",
2199
- subAgentCount,
2200
- "]")),
2201
- backgroundTaskCount > 0 && (React.createElement(Text, { color: "#9966ff", bold: true },
2202
- "[bkg tasks: ",
2203
- backgroundTaskCount,
2204
- "]")),
2205
- !commandMode && !backgroundMode && planMode && (React.createElement(Text, { color: "#ffaa00", bold: true }, "[PLANNING]")),
2206
- !commandMode && !backgroundMode && autoAcceptMode ? (React.createElement(Text, { color: "#00cc66", bold: true }, "[AUTO-ACCEPT: ON]")) : !commandMode && !backgroundMode ? (React.createElement(Text, { color: "#666666", dimColor: true }, "[AUTO-ACCEPT: OFF]")) : null,
2207
- !commandMode && !backgroundMode && (React.createElement(Box, { marginLeft: 1 },
2208
- React.createElement(ContextWindowIndicator, { currentTokens: currentTokens, maxTokens: maxTokens }))))),
2209
- slashAutocompleteVisible && slashMaxVisibleItems > 0 && (React.createElement(SlashCommandAutocomplete, { commands: slashAutocompleteCommands, selectedIndex: slashAutocompleteSelectedIndex, maxVisibleItems: slashMaxVisibleItems, scrollOffset: slashAutocompleteScrollOffset })),
2210
- fileTagAutocompleteVisible && (React.createElement(FileTagAutocomplete, { files: fileTagSuggestions, selectedIndex: fileTagSelectedIndex }))));
1600
+ if (isInConfirmedFileTag || isInActiveFileTag) {
1601
+ return /* @__PURE__ */ React.createElement(Text, { key: charIdx, color: "#00ccff", bold: true }, char);
1602
+ }
1603
+ return /* @__PURE__ */ React.createElement(Text, { key: charIdx }, char);
1604
+ });
1605
+ if (isCursorLine && cursorCol2 === lineText.length) {
1606
+ renderedChars.push(/* @__PURE__ */ React.createElement(Text, { key: "cursor", inverse: true }, " "));
1607
+ }
1608
+ const effectiveSuggestion = aiAutocompleteSuggestion || autocompleteSuggestion;
1609
+ if (isLastLine && effectiveSuggestion && effectiveSuggestion.startsWith(value)) {
1610
+ const suffix = effectiveSuggestion.slice(value.length);
1611
+ if (suffix) {
1612
+ const ghostColor = aiAutocompleteSuggestion ? "#888888" : "gray";
1613
+ renderedChars.push(/* @__PURE__ */ React.createElement(Text, { key: "ghost", color: ghostColor }, suffix));
1614
+ }
1615
+ }
1616
+ if (renderedChars.length === 0) {
1617
+ return /* @__PURE__ */ React.createElement(Text, { key: idx }, " ");
1618
+ }
1619
+ return /* @__PURE__ */ React.createElement(Text, { key: idx }, renderedChars);
1620
+ }), endLine < totalVisualLines && /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "\u2193 ..."));
1621
+ };
1622
+ return /* @__PURE__ */ React.createElement(
1623
+ Box,
1624
+ {
1625
+ flexDirection: "column",
1626
+ borderStyle: "round",
1627
+ borderColor: rejectFlash ? "#ff3366" : sessionQuotaExhausted ? "#ff3366" : (
1628
+ // Red when quota exhausted
1629
+ backgroundMode ? "#9966ff" : commandMode ? "#00cc66" : "#257aa5ff"
1630
+ ),
1631
+ paddingX: 1,
1632
+ paddingY: 0,
1633
+ width: "100%"
1634
+ },
1635
+ /* @__PURE__ */ React.createElement(Box, { marginY: 1, justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React.createElement(Box, { flexShrink: 1, minWidth: 0, flexDirection: "row" }, subshellContext && subshellContext.type !== "local" && /* @__PURE__ */ React.createElement(Breadcrumbs, { context: subshellContext, stack: subshellContextStack }), /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "CWD: "), /* @__PURE__ */ React.createElement(Text, { color: "#00ccff", bold: true, wrap: "truncate-start" }, currentDir), gitDiffStats && (gitDiffStats.additions > 0 || gitDiffStats.deletions > 0) && /* @__PURE__ */ React.createElement(
1636
+ GitDiffBreadcrumb,
1637
+ {
1638
+ additions: gitDiffStats.additions,
1639
+ deletions: gitDiffStats.deletions
1640
+ }
1641
+ )), /* @__PURE__ */ React.createElement(Box, { flexShrink: 0 }, model && !commandMode && !backgroundMode && /* @__PURE__ */ React.createElement(Box, { marginRight: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "Model: "), /* @__PURE__ */ React.createElement(Text, { color: "#00ccff" }, model)), /* @__PURE__ */ React.createElement(Box, null, showModeIndicator && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "Mode: "), isAutoMode ? /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: "#00ccff", bold: true }, "Auto ["), detectedIntent === "command" ? /* @__PURE__ */ React.createElement(Text, { color: "#00cc66", bold: true }, "Terminal") : /* @__PURE__ */ React.createElement(Text, { color: "#00ccff" }, "Agent"), /* @__PURE__ */ React.createElement(Text, { color: "#00ccff", bold: true }, "]")) : backgroundMode ? /* @__PURE__ */ React.createElement(Text, { color: "#9966ff", bold: true }, "Background") : commandMode ? /* @__PURE__ */ React.createElement(Text, { color: "#00cc66", bold: true }, "Terminal") : planMode ? /* @__PURE__ */ React.createElement(Text, { color: "#ffaa00", bold: true }, "Plan") : /* @__PURE__ */ React.createElement(Text, { color: "#00ccff" }, "Agent"))))),
1642
+ confirmedClipboardFiles.length > 0 && /* @__PURE__ */ React.createElement(Box, { marginBottom: 1, flexDirection: "row", flexWrap: "wrap", gap: 1 }, confirmedClipboardFiles.map((file, index) => /* @__PURE__ */ React.createElement(
1643
+ Box,
1644
+ {
1645
+ key: file.id,
1646
+ borderStyle: "round",
1647
+ borderColor: "#ff69b4",
1648
+ paddingX: 1
1649
+ },
1650
+ /* @__PURE__ */ React.createElement(Text, { color: "#ff69b4", bold: true }, "file_", index + 1)
1651
+ ))),
1652
+ showQuotaMessage && /* @__PURE__ */ React.createElement(Box, { justifyContent: "flex-end", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "#ff3366", bold: true }, "Session quota reached. Try again in ", sessionQuotaTimeRemaining)),
1653
+ /* @__PURE__ */ React.createElement(Box, { flexDirection: "row", width: "100%" }, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "> "), renderInput()),
1654
+ /* @__PURE__ */ React.createElement(Box, { marginY: 1, justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React.createElement(Text, { color: "#666666", dimColor: true }, isAutoMode ? showBottomHints ? "Ctrl+D to switch modes \u2022 Ctrl+T to auto-approve" : "Ctrl+D to switch modes" : backgroundMode ? "Ctrl+D to switch modes \u2022 Commands run in background" : commandMode ? "Ctrl+D to switch modes \u2022 Tab for autocomplete" : showBottomHints ? "Ctrl+D to switch modes \u2022 Ctrl+T to auto-approve" : "Ctrl+D to switch modes"), /* @__PURE__ */ React.createElement(Box, { gap: 1 }, subAgentCount > 0 && /* @__PURE__ */ React.createElement(Text, { color: "#00ccff", bold: true }, "[sub-agent: ", subAgentCount, "]"), backgroundTaskCount > 0 && /* @__PURE__ */ React.createElement(Text, { color: "#9966ff", bold: true }, "[bkg tasks: ", backgroundTaskCount, "]"), !commandMode && !backgroundMode && planMode && /* @__PURE__ */ React.createElement(Text, { color: "#ffaa00", bold: true }, "[PLANNING]"), !commandMode && !backgroundMode && autoAcceptMode ? /* @__PURE__ */ React.createElement(Text, { color: "#00cc66", bold: true }, "[AUTO-ACCEPT: ON]") : !commandMode && !backgroundMode ? /* @__PURE__ */ React.createElement(Text, { color: "#666666", dimColor: true }, "[AUTO-ACCEPT: OFF]") : null, !commandMode && !backgroundMode && /* @__PURE__ */ React.createElement(Box, { marginLeft: 1 }, /* @__PURE__ */ React.createElement(
1655
+ ContextWindowIndicator,
1656
+ {
1657
+ currentTokens,
1658
+ maxTokens
1659
+ }
1660
+ )))),
1661
+ slashAutocompleteVisible && slashMaxVisibleItems > 0 && /* @__PURE__ */ React.createElement(
1662
+ SlashCommandAutocomplete,
1663
+ {
1664
+ commands: slashAutocompleteCommands,
1665
+ selectedIndex: slashAutocompleteSelectedIndex,
1666
+ maxVisibleItems: slashMaxVisibleItems,
1667
+ scrollOffset: slashAutocompleteScrollOffset
1668
+ }
1669
+ ),
1670
+ fileTagAutocompleteVisible && /* @__PURE__ */ React.createElement(
1671
+ FileTagAutocomplete,
1672
+ {
1673
+ files: fileTagSuggestions,
1674
+ selectedIndex: fileTagSelectedIndex
1675
+ }
1676
+ )
1677
+ );
2211
1678
  });
1679
+ export {
1680
+ InputBox
1681
+ };
2212
1682
  //# sourceMappingURL=InputBox.js.map