@nexus-cortex/tui 4.47.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 (409) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +2 -0
  3. package/README.md +13 -0
  4. package/bin/cli-full.js +23 -0
  5. package/bin/cortex-ui.js +26 -0
  6. package/bin/ink-ui.js +108 -0
  7. package/bin/launcher-dev.js +266 -0
  8. package/bin/launcher.js +516 -0
  9. package/dist/commands/agent/InteractiveAgentMenu.d.ts +100 -0
  10. package/dist/commands/agent/InteractiveAgentMenu.d.ts.map +1 -0
  11. package/dist/commands/agent/InteractiveAgentMenu.js +909 -0
  12. package/dist/commands/agent/InteractiveAgentMenu.js.map +1 -0
  13. package/dist/commands/agent/index.d.ts +55 -0
  14. package/dist/commands/agent/index.d.ts.map +1 -0
  15. package/dist/commands/agent/index.js +714 -0
  16. package/dist/commands/agent/index.js.map +1 -0
  17. package/dist/commands/chat/interactive.d.ts +13 -0
  18. package/dist/commands/chat/interactive.d.ts.map +1 -0
  19. package/dist/commands/chat/interactive.js +2055 -0
  20. package/dist/commands/chat/interactive.js.map +1 -0
  21. package/dist/commands/system-message/EditorLauncher.d.ts +65 -0
  22. package/dist/commands/system-message/EditorLauncher.d.ts.map +1 -0
  23. package/dist/commands/system-message/EditorLauncher.js +143 -0
  24. package/dist/commands/system-message/EditorLauncher.js.map +1 -0
  25. package/dist/commands/system-message/InlineEditor.d.ts +23 -0
  26. package/dist/commands/system-message/InlineEditor.d.ts.map +1 -0
  27. package/dist/commands/system-message/InlineEditor.js +225 -0
  28. package/dist/commands/system-message/InlineEditor.js.map +1 -0
  29. package/dist/commands/system-message/InteractiveMenu.d.ts +76 -0
  30. package/dist/commands/system-message/InteractiveMenu.d.ts.map +1 -0
  31. package/dist/commands/system-message/InteractiveMenu.js +979 -0
  32. package/dist/commands/system-message/InteractiveMenu.js.map +1 -0
  33. package/dist/commands/system-message/index.d.ts +56 -0
  34. package/dist/commands/system-message/index.d.ts.map +1 -0
  35. package/dist/commands/system-message/index.js +694 -0
  36. package/dist/commands/system-message/index.js.map +1 -0
  37. package/dist/commands/ui/artifacts.d.ts +14 -0
  38. package/dist/commands/ui/artifacts.d.ts.map +1 -0
  39. package/dist/commands/ui/artifacts.js +22 -0
  40. package/dist/commands/ui/artifacts.js.map +1 -0
  41. package/dist/commands/ui/config.d.ts +12 -0
  42. package/dist/commands/ui/config.d.ts.map +1 -0
  43. package/dist/commands/ui/config.js +26 -0
  44. package/dist/commands/ui/config.js.map +1 -0
  45. package/dist/commands/ui/context.d.ts +16 -0
  46. package/dist/commands/ui/context.d.ts.map +1 -0
  47. package/dist/commands/ui/context.js +28 -0
  48. package/dist/commands/ui/context.js.map +1 -0
  49. package/dist/commands/ui/middleware.d.ts +14 -0
  50. package/dist/commands/ui/middleware.d.ts.map +1 -0
  51. package/dist/commands/ui/middleware.js +22 -0
  52. package/dist/commands/ui/middleware.js.map +1 -0
  53. package/dist/commands/ui/models.d.ts +14 -0
  54. package/dist/commands/ui/models.d.ts.map +1 -0
  55. package/dist/commands/ui/models.js +50 -0
  56. package/dist/commands/ui/models.js.map +1 -0
  57. package/dist/commands/ui/permissions.d.ts +14 -0
  58. package/dist/commands/ui/permissions.d.ts.map +1 -0
  59. package/dist/commands/ui/permissions.js +22 -0
  60. package/dist/commands/ui/permissions.js.map +1 -0
  61. package/dist/commands/ui/sessions.d.ts +13 -0
  62. package/dist/commands/ui/sessions.d.ts.map +1 -0
  63. package/dist/commands/ui/sessions.js +45 -0
  64. package/dist/commands/ui/sessions.js.map +1 -0
  65. package/dist/commands/ui/system-messages.d.ts +5 -0
  66. package/dist/commands/ui/system-messages.d.ts.map +1 -0
  67. package/dist/commands/ui/system-messages.js +19 -0
  68. package/dist/commands/ui/system-messages.js.map +1 -0
  69. package/dist/commands/ui/themes.d.ts +12 -0
  70. package/dist/commands/ui/themes.d.ts.map +1 -0
  71. package/dist/commands/ui/themes.js +40 -0
  72. package/dist/commands/ui/themes.js.map +1 -0
  73. package/dist/commands/ui/tmux.d.ts +5 -0
  74. package/dist/commands/ui/tmux.d.ts.map +1 -0
  75. package/dist/commands/ui/tmux.js +19 -0
  76. package/dist/commands/ui/tmux.js.map +1 -0
  77. package/dist/cortex.d.ts +12 -0
  78. package/dist/cortex.d.ts.map +1 -0
  79. package/dist/cortex.js +320 -0
  80. package/dist/cortex.js.map +1 -0
  81. package/dist/index.d.ts +3 -0
  82. package/dist/index.d.ts.map +1 -0
  83. package/dist/index.js +112 -0
  84. package/dist/index.js.map +1 -0
  85. package/dist/ink-ui/CortexApp.d.ts +28 -0
  86. package/dist/ink-ui/CortexApp.d.ts.map +1 -0
  87. package/dist/ink-ui/CortexApp.js +2291 -0
  88. package/dist/ink-ui/CortexApp.js.map +1 -0
  89. package/dist/ink-ui/SimpleInputBuffer.d.ts +63 -0
  90. package/dist/ink-ui/SimpleInputBuffer.d.ts.map +1 -0
  91. package/dist/ink-ui/SimpleInputBuffer.js +369 -0
  92. package/dist/ink-ui/SimpleInputBuffer.js.map +1 -0
  93. package/dist/ink-ui/commands/slashCommands.d.ts +40 -0
  94. package/dist/ink-ui/commands/slashCommands.d.ts.map +1 -0
  95. package/dist/ink-ui/commands/slashCommands.js +111 -0
  96. package/dist/ink-ui/commands/slashCommands.js.map +1 -0
  97. package/dist/ink-ui/commands/types.d.ts +161 -0
  98. package/dist/ink-ui/commands/types.d.ts.map +1 -0
  99. package/dist/ink-ui/commands/types.js +12 -0
  100. package/dist/ink-ui/commands/types.js.map +1 -0
  101. package/dist/ink-ui/components/ApprovalDialog.d.ts +21 -0
  102. package/dist/ink-ui/components/ApprovalDialog.d.ts.map +1 -0
  103. package/dist/ink-ui/components/ApprovalDialog.js +81 -0
  104. package/dist/ink-ui/components/ApprovalDialog.js.map +1 -0
  105. package/dist/ink-ui/components/CommandSuggestions.d.ts +19 -0
  106. package/dist/ink-ui/components/CommandSuggestions.d.ts.map +1 -0
  107. package/dist/ink-ui/components/CommandSuggestions.js +36 -0
  108. package/dist/ink-ui/components/CommandSuggestions.js.map +1 -0
  109. package/dist/ink-ui/components/DiffPreview.d.ts +60 -0
  110. package/dist/ink-ui/components/DiffPreview.d.ts.map +1 -0
  111. package/dist/ink-ui/components/DiffPreview.js +195 -0
  112. package/dist/ink-ui/components/DiffPreview.js.map +1 -0
  113. package/dist/ink-ui/components/DocumentPreview.d.ts +50 -0
  114. package/dist/ink-ui/components/DocumentPreview.d.ts.map +1 -0
  115. package/dist/ink-ui/components/DocumentPreview.js +92 -0
  116. package/dist/ink-ui/components/DocumentPreview.js.map +1 -0
  117. package/dist/ink-ui/components/MarkdownText.d.ts +27 -0
  118. package/dist/ink-ui/components/MarkdownText.d.ts.map +1 -0
  119. package/dist/ink-ui/components/MarkdownText.js +342 -0
  120. package/dist/ink-ui/components/MarkdownText.js.map +1 -0
  121. package/dist/ink-ui/components/MenuRenderer.d.ts +26 -0
  122. package/dist/ink-ui/components/MenuRenderer.d.ts.map +1 -0
  123. package/dist/ink-ui/components/MenuRenderer.js +264 -0
  124. package/dist/ink-ui/components/MenuRenderer.js.map +1 -0
  125. package/dist/ink-ui/components/ModelPickerDialog.d.ts +33 -0
  126. package/dist/ink-ui/components/ModelPickerDialog.d.ts.map +1 -0
  127. package/dist/ink-ui/components/ModelPickerDialog.js +98 -0
  128. package/dist/ink-ui/components/ModelPickerDialog.js.map +1 -0
  129. package/dist/ink-ui/components/StreamDisplay.d.ts +31 -0
  130. package/dist/ink-ui/components/StreamDisplay.d.ts.map +1 -0
  131. package/dist/ink-ui/components/StreamDisplay.js +573 -0
  132. package/dist/ink-ui/components/StreamDisplay.js.map +1 -0
  133. package/dist/ink-ui/components/SubAgentPanel.d.ts +98 -0
  134. package/dist/ink-ui/components/SubAgentPanel.d.ts.map +1 -0
  135. package/dist/ink-ui/components/SubAgentPanel.js +155 -0
  136. package/dist/ink-ui/components/SubAgentPanel.js.map +1 -0
  137. package/dist/ink-ui/components/ThinkingDisplay.d.ts +46 -0
  138. package/dist/ink-ui/components/ThinkingDisplay.d.ts.map +1 -0
  139. package/dist/ink-ui/components/ThinkingDisplay.js +176 -0
  140. package/dist/ink-ui/components/ThinkingDisplay.js.map +1 -0
  141. package/dist/ink-ui/components/ToolCallDisplay.d.ts +51 -0
  142. package/dist/ink-ui/components/ToolCallDisplay.d.ts.map +1 -0
  143. package/dist/ink-ui/components/ToolCallDisplay.js +378 -0
  144. package/dist/ink-ui/components/ToolCallDisplay.js.map +1 -0
  145. package/dist/ink-ui/components/WritePreview.d.ts +42 -0
  146. package/dist/ink-ui/components/WritePreview.d.ts.map +1 -0
  147. package/dist/ink-ui/components/WritePreview.js +100 -0
  148. package/dist/ink-ui/components/WritePreview.js.map +1 -0
  149. package/dist/ink-ui/contexts/KeypressContext.d.ts +32 -0
  150. package/dist/ink-ui/contexts/KeypressContext.d.ts.map +1 -0
  151. package/dist/ink-ui/contexts/KeypressContext.js +529 -0
  152. package/dist/ink-ui/contexts/KeypressContext.js.map +1 -0
  153. package/dist/ink-ui/contexts/SessionContext.d.ts +46 -0
  154. package/dist/ink-ui/contexts/SessionContext.d.ts.map +1 -0
  155. package/dist/ink-ui/contexts/SessionContext.js +162 -0
  156. package/dist/ink-ui/contexts/SessionContext.js.map +1 -0
  157. package/dist/ink-ui/core-stubs.d.ts +270 -0
  158. package/dist/ink-ui/core-stubs.d.ts.map +1 -0
  159. package/dist/ink-ui/core-stubs.js +276 -0
  160. package/dist/ink-ui/core-stubs.js.map +1 -0
  161. package/dist/ink-ui/cortex-types.d.ts +214 -0
  162. package/dist/ink-ui/cortex-types.d.ts.map +1 -0
  163. package/dist/ink-ui/cortex-types.js +52 -0
  164. package/dist/ink-ui/cortex-types.js.map +1 -0
  165. package/dist/ink-ui/hooks/useCortexStream.d.ts +75 -0
  166. package/dist/ink-ui/hooks/useCortexStream.d.ts.map +1 -0
  167. package/dist/ink-ui/hooks/useCortexStream.js +518 -0
  168. package/dist/ink-ui/hooks/useCortexStream.js.map +1 -0
  169. package/dist/ink-ui/hooks/useFocus.d.ts +11 -0
  170. package/dist/ink-ui/hooks/useFocus.d.ts.map +1 -0
  171. package/dist/ink-ui/hooks/useFocus.js +51 -0
  172. package/dist/ink-ui/hooks/useFocus.js.map +1 -0
  173. package/dist/ink-ui/hooks/useHistoryManager.d.ts +23 -0
  174. package/dist/ink-ui/hooks/useHistoryManager.d.ts.map +1 -0
  175. package/dist/ink-ui/hooks/useHistoryManager.js +63 -0
  176. package/dist/ink-ui/hooks/useHistoryManager.js.map +1 -0
  177. package/dist/ink-ui/hooks/useKeypress.d.ts +18 -0
  178. package/dist/ink-ui/hooks/useKeypress.d.ts.map +1 -0
  179. package/dist/ink-ui/hooks/useKeypress.js +27 -0
  180. package/dist/ink-ui/hooks/useKeypress.js.map +1 -0
  181. package/dist/ink-ui/hooks/useReactApprovalHandler.d.ts +74 -0
  182. package/dist/ink-ui/hooks/useReactApprovalHandler.d.ts.map +1 -0
  183. package/dist/ink-ui/hooks/useReactApprovalHandler.js +83 -0
  184. package/dist/ink-ui/hooks/useReactApprovalHandler.js.map +1 -0
  185. package/dist/ink-ui/hooks/useTerminalSize.d.ts +10 -0
  186. package/dist/ink-ui/hooks/useTerminalSize.d.ts.map +1 -0
  187. package/dist/ink-ui/hooks/useTerminalSize.js +28 -0
  188. package/dist/ink-ui/hooks/useTerminalSize.js.map +1 -0
  189. package/dist/ink-ui/semantic-colors.d.ts +8 -0
  190. package/dist/ink-ui/semantic-colors.d.ts.map +1 -0
  191. package/dist/ink-ui/semantic-colors.js +24 -0
  192. package/dist/ink-ui/semantic-colors.js.map +1 -0
  193. package/dist/ink-ui/state/extensions.d.ts +68 -0
  194. package/dist/ink-ui/state/extensions.d.ts.map +1 -0
  195. package/dist/ink-ui/state/extensions.js +97 -0
  196. package/dist/ink-ui/state/extensions.js.map +1 -0
  197. package/dist/ink-ui/themes/ansi-light.d.ts +8 -0
  198. package/dist/ink-ui/themes/ansi-light.d.ts.map +1 -0
  199. package/dist/ink-ui/themes/ansi-light.js +143 -0
  200. package/dist/ink-ui/themes/ansi-light.js.map +1 -0
  201. package/dist/ink-ui/themes/ansi.d.ts +8 -0
  202. package/dist/ink-ui/themes/ansi.d.ts.map +1 -0
  203. package/dist/ink-ui/themes/ansi.js +153 -0
  204. package/dist/ink-ui/themes/ansi.js.map +1 -0
  205. package/dist/ink-ui/themes/atom-one-dark.d.ts +8 -0
  206. package/dist/ink-ui/themes/atom-one-dark.d.ts.map +1 -0
  207. package/dist/ink-ui/themes/atom-one-dark.js +140 -0
  208. package/dist/ink-ui/themes/atom-one-dark.js.map +1 -0
  209. package/dist/ink-ui/themes/ayu-light.d.ts +8 -0
  210. package/dist/ink-ui/themes/ayu-light.d.ts.map +1 -0
  211. package/dist/ink-ui/themes/ayu-light.js +132 -0
  212. package/dist/ink-ui/themes/ayu-light.js.map +1 -0
  213. package/dist/ink-ui/themes/ayu.d.ts +8 -0
  214. package/dist/ink-ui/themes/ayu.d.ts.map +1 -0
  215. package/dist/ink-ui/themes/ayu.js +106 -0
  216. package/dist/ink-ui/themes/ayu.js.map +1 -0
  217. package/dist/ink-ui/themes/color-utils.d.ts +23 -0
  218. package/dist/ink-ui/themes/color-utils.d.ts.map +1 -0
  219. package/dist/ink-ui/themes/color-utils.js +237 -0
  220. package/dist/ink-ui/themes/color-utils.js.map +1 -0
  221. package/dist/ink-ui/themes/default-light.d.ts +8 -0
  222. package/dist/ink-ui/themes/default-light.d.ts.map +1 -0
  223. package/dist/ink-ui/themes/default-light.js +100 -0
  224. package/dist/ink-ui/themes/default-light.js.map +1 -0
  225. package/dist/ink-ui/themes/default.d.ts +8 -0
  226. package/dist/ink-ui/themes/default.d.ts.map +1 -0
  227. package/dist/ink-ui/themes/default.js +143 -0
  228. package/dist/ink-ui/themes/default.js.map +1 -0
  229. package/dist/ink-ui/themes/dracula.d.ts +8 -0
  230. package/dist/ink-ui/themes/dracula.d.ts.map +1 -0
  231. package/dist/ink-ui/themes/dracula.js +117 -0
  232. package/dist/ink-ui/themes/dracula.js.map +1 -0
  233. package/dist/ink-ui/themes/github-dark.d.ts +8 -0
  234. package/dist/ink-ui/themes/github-dark.d.ts.map +1 -0
  235. package/dist/ink-ui/themes/github-dark.js +140 -0
  236. package/dist/ink-ui/themes/github-dark.js.map +1 -0
  237. package/dist/ink-ui/themes/github-light.d.ts +8 -0
  238. package/dist/ink-ui/themes/github-light.d.ts.map +1 -0
  239. package/dist/ink-ui/themes/github-light.js +142 -0
  240. package/dist/ink-ui/themes/github-light.js.map +1 -0
  241. package/dist/ink-ui/themes/googlecode.d.ts +8 -0
  242. package/dist/ink-ui/themes/googlecode.d.ts.map +1 -0
  243. package/dist/ink-ui/themes/googlecode.js +139 -0
  244. package/dist/ink-ui/themes/googlecode.js.map +1 -0
  245. package/dist/ink-ui/themes/no-color.d.ts +8 -0
  246. package/dist/ink-ui/themes/no-color.d.ts.map +1 -0
  247. package/dist/ink-ui/themes/no-color.js +118 -0
  248. package/dist/ink-ui/themes/no-color.js.map +1 -0
  249. package/dist/ink-ui/themes/semantic-tokens.d.ts +40 -0
  250. package/dist/ink-ui/themes/semantic-tokens.d.ts.map +1 -0
  251. package/dist/ink-ui/themes/semantic-tokens.js +100 -0
  252. package/dist/ink-ui/themes/semantic-tokens.js.map +1 -0
  253. package/dist/ink-ui/themes/shades-of-purple.d.ts +12 -0
  254. package/dist/ink-ui/themes/shades-of-purple.d.ts.map +1 -0
  255. package/dist/ink-ui/themes/shades-of-purple.js +306 -0
  256. package/dist/ink-ui/themes/shades-of-purple.js.map +1 -0
  257. package/dist/ink-ui/themes/theme-manager.d.ts +67 -0
  258. package/dist/ink-ui/themes/theme-manager.d.ts.map +1 -0
  259. package/dist/ink-ui/themes/theme-manager.js +263 -0
  260. package/dist/ink-ui/themes/theme-manager.js.map +1 -0
  261. package/dist/ink-ui/themes/theme.d.ts +134 -0
  262. package/dist/ink-ui/themes/theme.d.ts.map +1 -0
  263. package/dist/ink-ui/themes/theme.js +390 -0
  264. package/dist/ink-ui/themes/theme.js.map +1 -0
  265. package/dist/ink-ui/themes/xcode.d.ts +8 -0
  266. package/dist/ink-ui/themes/xcode.d.ts.map +1 -0
  267. package/dist/ink-ui/themes/xcode.js +147 -0
  268. package/dist/ink-ui/themes/xcode.js.map +1 -0
  269. package/dist/ink-ui/types.d.ts +306 -0
  270. package/dist/ink-ui/types.d.ts.map +1 -0
  271. package/dist/ink-ui/types.js +65 -0
  272. package/dist/ink-ui/types.js.map +1 -0
  273. package/dist/ink-ui/utils/input.d.ts +18 -0
  274. package/dist/ink-ui/utils/input.d.ts.map +1 -0
  275. package/dist/ink-ui/utils/input.js +51 -0
  276. package/dist/ink-ui/utils/input.js.map +1 -0
  277. package/dist/ink-ui/utils/mouse.d.ts +33 -0
  278. package/dist/ink-ui/utils/mouse.d.ts.map +1 -0
  279. package/dist/ink-ui/utils/mouse.js +182 -0
  280. package/dist/ink-ui/utils/mouse.js.map +1 -0
  281. package/dist/services/BuiltinCommandLoader.d.ts +21 -0
  282. package/dist/services/BuiltinCommandLoader.d.ts.map +1 -0
  283. package/dist/services/BuiltinCommandLoader.js +182 -0
  284. package/dist/services/BuiltinCommandLoader.js.map +1 -0
  285. package/dist/services/CommandService.d.ts +39 -0
  286. package/dist/services/CommandService.d.ts.map +1 -0
  287. package/dist/services/CommandService.js +49 -0
  288. package/dist/services/CommandService.js.map +1 -0
  289. package/dist/services/FileCommandLoader.d.ts +16 -0
  290. package/dist/services/FileCommandLoader.d.ts.map +1 -0
  291. package/dist/services/FileCommandLoader.js +19 -0
  292. package/dist/services/FileCommandLoader.js.map +1 -0
  293. package/dist/services/McpPromptLoader.d.ts +16 -0
  294. package/dist/services/McpPromptLoader.d.ts.map +1 -0
  295. package/dist/services/McpPromptLoader.js +19 -0
  296. package/dist/services/McpPromptLoader.js.map +1 -0
  297. package/dist/services/index.d.ts +8 -0
  298. package/dist/services/index.d.ts.map +1 -0
  299. package/dist/services/index.js +8 -0
  300. package/dist/services/index.js.map +1 -0
  301. package/dist/ui/ChalkMenuRenderer.d.ts +22 -0
  302. package/dist/ui/ChalkMenuRenderer.d.ts.map +1 -0
  303. package/dist/ui/ChalkMenuRenderer.js +478 -0
  304. package/dist/ui/ChalkMenuRenderer.js.map +1 -0
  305. package/dist/ui/ChalkModelPicker.d.ts +20 -0
  306. package/dist/ui/ChalkModelPicker.d.ts.map +1 -0
  307. package/dist/ui/ChalkModelPicker.js +363 -0
  308. package/dist/ui/ChalkModelPicker.js.map +1 -0
  309. package/dist/ui/ChalkThemePicker.d.ts +20 -0
  310. package/dist/ui/ChalkThemePicker.d.ts.map +1 -0
  311. package/dist/ui/ChalkThemePicker.js +370 -0
  312. package/dist/ui/ChalkThemePicker.js.map +1 -0
  313. package/dist/ui/InkApprovalDialog.d.ts +8 -0
  314. package/dist/ui/InkApprovalDialog.d.ts.map +1 -0
  315. package/dist/ui/InkApprovalDialog.js +25 -0
  316. package/dist/ui/InkApprovalDialog.js.map +1 -0
  317. package/dist/ui/InkCommandPalette.d.ts +43 -0
  318. package/dist/ui/InkCommandPalette.d.ts.map +1 -0
  319. package/dist/ui/InkCommandPalette.js +105 -0
  320. package/dist/ui/InkCommandPalette.js.map +1 -0
  321. package/dist/ui/InkConfigMenu.d.ts +24 -0
  322. package/dist/ui/InkConfigMenu.d.ts.map +1 -0
  323. package/dist/ui/InkConfigMenu.js +259 -0
  324. package/dist/ui/InkConfigMenu.js.map +1 -0
  325. package/dist/ui/InkHelp.d.ts +25 -0
  326. package/dist/ui/InkHelp.d.ts.map +1 -0
  327. package/dist/ui/InkHelp.js +55 -0
  328. package/dist/ui/InkHelp.js.map +1 -0
  329. package/dist/ui/InkMenuPicker.d.ts +19 -0
  330. package/dist/ui/InkMenuPicker.d.ts.map +1 -0
  331. package/dist/ui/InkMenuPicker.js +33 -0
  332. package/dist/ui/InkMenuPicker.js.map +1 -0
  333. package/dist/ui/InkModelPicker.d.ts +23 -0
  334. package/dist/ui/InkModelPicker.d.ts.map +1 -0
  335. package/dist/ui/InkModelPicker.js +53 -0
  336. package/dist/ui/InkModelPicker.js.map +1 -0
  337. package/dist/ui/PersistentInput.d.ts +91 -0
  338. package/dist/ui/PersistentInput.d.ts.map +1 -0
  339. package/dist/ui/PersistentInput.js +283 -0
  340. package/dist/ui/PersistentInput.js.map +1 -0
  341. package/dist/ui/RawInput.d.ts +27 -0
  342. package/dist/ui/RawInput.d.ts.map +1 -0
  343. package/dist/ui/RawInput.js +731 -0
  344. package/dist/ui/RawInput.js.map +1 -0
  345. package/dist/ui/SlashCommandAutocomplete.d.ts +94 -0
  346. package/dist/ui/SlashCommandAutocomplete.d.ts.map +1 -0
  347. package/dist/ui/SlashCommandAutocomplete.js +274 -0
  348. package/dist/ui/SlashCommandAutocomplete.js.map +1 -0
  349. package/dist/ui/SplashScreen.d.ts +76 -0
  350. package/dist/ui/SplashScreen.d.ts.map +1 -0
  351. package/dist/ui/SplashScreen.js +316 -0
  352. package/dist/ui/SplashScreen.js.map +1 -0
  353. package/dist/ui/components/ArtifactDashboard.d.ts +16 -0
  354. package/dist/ui/components/ArtifactDashboard.d.ts.map +1 -0
  355. package/dist/ui/components/ArtifactDashboard.js +102 -0
  356. package/dist/ui/components/ArtifactDashboard.js.map +1 -0
  357. package/dist/ui/components/ChatInput.d.ts +36 -0
  358. package/dist/ui/components/ChatInput.d.ts.map +1 -0
  359. package/dist/ui/components/ChatInput.js +11 -0
  360. package/dist/ui/components/ChatInput.js.map +1 -0
  361. package/dist/ui/components/ConfigWizard.d.ts +17 -0
  362. package/dist/ui/components/ConfigWizard.d.ts.map +1 -0
  363. package/dist/ui/components/ConfigWizard.js +134 -0
  364. package/dist/ui/components/ConfigWizard.js.map +1 -0
  365. package/dist/ui/components/ContextViewer.d.ts +18 -0
  366. package/dist/ui/components/ContextViewer.d.ts.map +1 -0
  367. package/dist/ui/components/ContextViewer.js +85 -0
  368. package/dist/ui/components/ContextViewer.js.map +1 -0
  369. package/dist/ui/components/MiddlewareDashboard.d.ts +16 -0
  370. package/dist/ui/components/MiddlewareDashboard.d.ts.map +1 -0
  371. package/dist/ui/components/MiddlewareDashboard.js +98 -0
  372. package/dist/ui/components/MiddlewareDashboard.js.map +1 -0
  373. package/dist/ui/components/ModelPicker.d.ts +24 -0
  374. package/dist/ui/components/ModelPicker.d.ts.map +1 -0
  375. package/dist/ui/components/ModelPicker.js +96 -0
  376. package/dist/ui/components/ModelPicker.js.map +1 -0
  377. package/dist/ui/components/MultiLineInput.d.ts +27 -0
  378. package/dist/ui/components/MultiLineInput.d.ts.map +1 -0
  379. package/dist/ui/components/MultiLineInput.js +182 -0
  380. package/dist/ui/components/MultiLineInput.js.map +1 -0
  381. package/dist/ui/components/PermissionsBrowser.d.ts +16 -0
  382. package/dist/ui/components/PermissionsBrowser.d.ts.map +1 -0
  383. package/dist/ui/components/PermissionsBrowser.js +110 -0
  384. package/dist/ui/components/PermissionsBrowser.js.map +1 -0
  385. package/dist/ui/components/SessionBrowser.d.ts +19 -0
  386. package/dist/ui/components/SessionBrowser.d.ts.map +1 -0
  387. package/dist/ui/components/SessionBrowser.js +66 -0
  388. package/dist/ui/components/SessionBrowser.js.map +1 -0
  389. package/dist/ui/components/StatusBar.d.ts +32 -0
  390. package/dist/ui/components/StatusBar.d.ts.map +1 -0
  391. package/dist/ui/components/StatusBar.js +27 -0
  392. package/dist/ui/components/StatusBar.js.map +1 -0
  393. package/dist/ui/components/SystemMessageBrowser.d.ts +11 -0
  394. package/dist/ui/components/SystemMessageBrowser.d.ts.map +1 -0
  395. package/dist/ui/components/SystemMessageBrowser.js +106 -0
  396. package/dist/ui/components/SystemMessageBrowser.js.map +1 -0
  397. package/dist/ui/components/ThemePicker.d.ts +11 -0
  398. package/dist/ui/components/ThemePicker.d.ts.map +1 -0
  399. package/dist/ui/components/ThemePicker.js +62 -0
  400. package/dist/ui/components/ThemePicker.js.map +1 -0
  401. package/dist/ui/components/TmuxBrowser.d.ts +11 -0
  402. package/dist/ui/components/TmuxBrowser.d.ts.map +1 -0
  403. package/dist/ui/components/TmuxBrowser.js +85 -0
  404. package/dist/ui/components/TmuxBrowser.js.map +1 -0
  405. package/dist/ui/components/index.d.ts +22 -0
  406. package/dist/ui/components/index.d.ts.map +1 -0
  407. package/dist/ui/components/index.js +21 -0
  408. package/dist/ui/components/index.js.map +1 -0
  409. package/package.json +70 -0
@@ -0,0 +1,2055 @@
1
+ /**
2
+ * Interactive chat mode with enhanced formatting
3
+ */
4
+ import chalk from 'chalk';
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { OrchestratorClient } from '@nexus-cortex/cli/dist/orchestrator/OrchestratorClient.js';
8
+ import { ThemeManager } from '@nexus-cortex/cli/dist/themes/ThemeManager.js';
9
+ import { ToolFormatter, generateAndParseDiff, parseUnifiedDiff } from '@nexus-cortex/cli/dist/utils/ToolFormatter.js';
10
+ import { MarkdownRenderer } from '@nexus-cortex/cli/dist/utils/MarkdownRenderer.js';
11
+ import { parseSlashCommand } from '@nexus-cortex/cli/dist/commands/slash/SlashCommandParser.js';
12
+ import { commandPalette } from '@nexus-cortex/cli/dist/commands/slash/CommandPalette.js';
13
+ import { slashCommandRegistry } from '@nexus-cortex/cli/dist/commands/slash/SlashCommandRegistry.js';
14
+ import { createSystemMessageCommand } from '../system-message/index.js';
15
+ import { createAgentCommand, listAgents, showAgentInfo } from '../agent/index.js';
16
+ import { renderSplashScreen, renderCompactHeader } from '../../ui/SplashScreen.js';
17
+ import { rawQuestion } from '../../ui/RawInput.js';
18
+ import { createPersistentInput } from '../../ui/PersistentInput.js';
19
+ import { runThemePicker } from '../../ui/ChalkThemePicker.js';
20
+ import { showModelPicker } from '../../ui/InkModelPicker.js';
21
+ import { MentorshipConfigService } from '@nexus-cortex/core';
22
+ import { loadPersistedModelForPlatform } from '@nexus-cortex/cli/dist/themes/colors.js';
23
+ import { showInteractiveMenu } from '../../ui/InkMenuPicker.js';
24
+ import { showCommandPalette } from '../../ui/InkCommandPalette.js';
25
+ import { showHelp } from '../../ui/InkHelp.js';
26
+ import { showApprovalDialog } from '../../ui/InkApprovalDialog.js';
27
+ import * as readline from 'readline';
28
+ // Platform identifier for fuzzycortex CLI
29
+ const PLATFORM = 'fuzzycortex';
30
+ /**
31
+ * Generate diff preview from Edit tool parameters
32
+ * Uses the shared DiffParser from core
33
+ */
34
+ function generateDiffPreview(oldString, newString, filePath) {
35
+ return generateAndParseDiff(oldString, newString, filePath);
36
+ }
37
+ export async function interactiveChat(options) {
38
+ /**
39
+ * Startup cleanup - reset terminal in case previous session crashed
40
+ * This ensures we start with a clean terminal state
41
+ */
42
+ if (process.stdin.isTTY) {
43
+ try {
44
+ process.stdin.setRawMode(false);
45
+ }
46
+ catch {
47
+ // Ignore - stdin may already be in normal mode
48
+ }
49
+ }
50
+ process.stdout.write('\x1b[?25h' + // Show cursor (may have been hidden)
51
+ '\x1b[0m' + // Reset text attributes
52
+ '\x1b[r' // Reset scroll region to full screen
53
+ );
54
+ /**
55
+ * Reset terminal to clean state
56
+ * Called on exit, crash, or signal to prevent terminal pollution
57
+ */
58
+ const resetTerminal = () => {
59
+ // Reset to normal mode (not raw)
60
+ if (process.stdin.isTTY) {
61
+ try {
62
+ process.stdin.setRawMode(false);
63
+ }
64
+ catch {
65
+ // Ignore errors if stdin is already closed
66
+ }
67
+ }
68
+ // Show cursor
69
+ process.stdout.write('\x1b[?25h');
70
+ // Reset all attributes
71
+ process.stdout.write('\x1b[0m');
72
+ // Reset scroll region
73
+ process.stdout.write('\x1b[r');
74
+ // Move to new line
75
+ process.stdout.write('\n');
76
+ };
77
+ // ESC abort tracking - declared early so handlers can access them
78
+ let escPressed = false;
79
+ let isAborting = false;
80
+ // Register cleanup handlers for various exit scenarios
81
+ process.on('exit', resetTerminal);
82
+ process.on('SIGINT', () => {
83
+ resetTerminal();
84
+ process.exit(130);
85
+ });
86
+ process.on('SIGTERM', () => {
87
+ resetTerminal();
88
+ process.exit(143);
89
+ });
90
+ process.on('uncaughtException', (err) => {
91
+ // If we're in the middle of an ESC abort, don't crash - this is expected
92
+ if (isAborting || escPressed) {
93
+ // Log but don't exit during intentional abort
94
+ if (process.env.DEBUG === 'true') {
95
+ console.error('[DEBUG] Suppressed uncaught exception during ESC abort:', err.message);
96
+ }
97
+ return;
98
+ }
99
+ resetTerminal();
100
+ console.error('Uncaught exception:', err);
101
+ process.exit(1);
102
+ });
103
+ process.on('unhandledRejection', (reason) => {
104
+ // If we're in the middle of an ESC abort, don't crash - this is expected
105
+ // The SDK may reject promises after we've aborted the stream
106
+ if (isAborting || escPressed) {
107
+ // Log but don't exit during intentional abort
108
+ if (process.env.DEBUG === 'true') {
109
+ console.error('[DEBUG] Suppressed unhandled rejection during ESC abort:', reason);
110
+ }
111
+ return;
112
+ }
113
+ resetTerminal();
114
+ console.error('Unhandled rejection:', reason);
115
+ process.exit(1);
116
+ });
117
+ const theme = ThemeManager.getExtendedTheme();
118
+ const toolFormatter = new ToolFormatter();
119
+ const markdownRenderer = new MarkdownRenderer();
120
+ // Determine mode: Check environment variable set by launcher
121
+ const envMode = (process.env.CORTEX_MODE);
122
+ const mode = envMode === 'server' ? 'server' : 'direct';
123
+ const serverUrl = options.serverUrl;
124
+ // Create orchestrator client (don't initialize yet - need to set callbacks first)
125
+ // projectPath = user's cwd (where tools resolve files). CORTEX_ROOT is for config only.
126
+ // Priority: CLI arg > persisted model (fuzzycortex) > env var > fallback
127
+ const persistedModel = loadPersistedModelForPlatform(PLATFORM);
128
+ const effectiveModelId = options.model || persistedModel || process.env.DEFAULT_MODEL_ID || 'grok-code-fast-1';
129
+ const client = new OrchestratorClient({
130
+ mode,
131
+ serverUrl,
132
+ defaultModelId: effectiveModelId,
133
+ projectPath: process.env.PROJECT_PATH || process.cwd(),
134
+ debug: process.env.DEBUG === 'true'
135
+ });
136
+ // Note: client.initialize() is called after persistentInput is created
137
+ // so we can wire up the input handler callbacks for approval dialogs
138
+ // DEBUG Mode: Show/hide debug logs
139
+ // Initialize from .env DEBUG setting
140
+ let showDebug = process.env.DEBUG === 'true';
141
+ // PHASE 2.8: Extended Thinking Display
142
+ // Track thinking display state (Tab key toggle)
143
+ let showThinking = false;
144
+ // Track if we're in tool execution mode (for interleaved thinking)
145
+ // Interleaved thinking (during tool calls) is ALWAYS shown
146
+ // Extended thinking (front/rear loaded) respects Tab toggle
147
+ let isInToolExecution = false;
148
+ // Track reasoning effort for OpenAI GPT-5 models
149
+ // Tab toggle switches between 'none' and 'high' for models that support reasoning
150
+ let reasoningEffort = 'none';
151
+ // Track auto-approve and YOLO mode states for status line
152
+ let autoApproveEnabled = false;
153
+ let yoloModeEnabled = false;
154
+ /**
155
+ * Get current status line state for rendering
156
+ */
157
+ const getStatusState = () => {
158
+ const currentModel = client.getCurrentModel();
159
+ return {
160
+ model: currentModel?.id || options.model || process.env.DEFAULT_MODEL_ID || 'default',
161
+ showThinking,
162
+ reasoningEffort,
163
+ supportsReasoning: currentModel?.reasoning?.supported === true,
164
+ autoApprove: autoApproveEnabled,
165
+ yoloMode: yoloModeEnabled,
166
+ };
167
+ };
168
+ // Display compact header on startup (full splash available via /about)
169
+ console.clear();
170
+ console.log(renderCompactHeader({
171
+ model: options.model || process.env.DEFAULT_MODEL_ID || 'default',
172
+ cwd: process.cwd(),
173
+ }));
174
+ // Show debug status inline if enabled
175
+ if (showDebug) {
176
+ console.log(theme.colors.warning(' DEBUG ON') + theme.dimmed(' - use /debug to toggle'));
177
+ }
178
+ // Create readline interface for sub-prompts (e.g., /continue session selection)
179
+ const rl = readline.createInterface({
180
+ input: process.stdin,
181
+ output: process.stdout,
182
+ });
183
+ // Abort controller for cancelling streaming/tool execution (ESC key)
184
+ let currentAbortController = null;
185
+ // Document expansion state for collapsible document previews
186
+ const expandedDocuments = new Set();
187
+ let documentsExpanded = false; // Global toggle for all documents
188
+ let loadedSessionMessages = []; // Store loaded session messages for re-rendering
189
+ /**
190
+ * Toggle handlers for raw input mode
191
+ */
192
+ const onToggleThinking = () => {
193
+ showThinking = !showThinking;
194
+ // Also toggle reasoning effort for models that support it (GPT-5, o-series)
195
+ const currentModel = client.getCurrentModel();
196
+ const supportsReasoning = currentModel?.reasoning?.supported === true;
197
+ if (supportsReasoning) {
198
+ reasoningEffort = showThinking ? 'high' : 'none';
199
+ }
200
+ };
201
+ const onToggleAutoApprove = async () => {
202
+ try {
203
+ const isYoloActive = client.isYoloModeActive();
204
+ if (isYoloActive) {
205
+ // If YOLO is active, disable YOLO AND auto-approve (double off)
206
+ await client.disableYoloMode();
207
+ await client.setApprovalMode(false);
208
+ yoloModeEnabled = false;
209
+ autoApproveEnabled = false;
210
+ }
211
+ else {
212
+ // Normal toggle of auto-approve mode
213
+ const currentMode = await client.getApprovalMode();
214
+ const newMode = !currentMode.autoApproveActions;
215
+ await client.setApprovalMode(newMode);
216
+ autoApproveEnabled = newMode;
217
+ }
218
+ }
219
+ catch {
220
+ // Silently ignore errors during toggle
221
+ }
222
+ };
223
+ const onEscape = () => {
224
+ escPressed = true;
225
+ isAborting = true; // Mark that we're intentionally aborting
226
+ // Abort any ongoing operations (tool execution, API calls)
227
+ if (currentAbortController) {
228
+ currentAbortController.abort();
229
+ }
230
+ };
231
+ const onToggleDocuments = () => {
232
+ documentsExpanded = !documentsExpanded;
233
+ // Toggle all document IDs in or out of the expanded set
234
+ const orchestratorHistory = client.getMessageHistory();
235
+ const allMessages = [...loadedSessionMessages, ...orchestratorHistory];
236
+ if (documentsExpanded) {
237
+ // Collect all document IDs from loaded session messages AND orchestrator history
238
+ for (const msg of allMessages) {
239
+ const content = msg.message?.content;
240
+ if (Array.isArray(content)) {
241
+ content.forEach((block, i) => {
242
+ if (block.type === 'tool_result' && block.metadata?.documentPreview) {
243
+ expandedDocuments.add(`${msg.uuid}-doc-${i}`);
244
+ }
245
+ });
246
+ }
247
+ }
248
+ }
249
+ else {
250
+ // Collapse all
251
+ expandedDocuments.clear();
252
+ }
253
+ // Clear screen and re-render if we have any messages to show
254
+ if (allMessages.length > 0) {
255
+ // Clear terminal
256
+ process.stdout.write('\x1b[2J\x1b[H');
257
+ console.log(theme.dimmed(`Document previews: ${documentsExpanded ? 'expanded' : 'collapsed'}`));
258
+ console.log();
259
+ // Re-render all messages (loaded session + current conversation)
260
+ renderSessionHistory(allMessages);
261
+ }
262
+ };
263
+ /**
264
+ * Render session history messages with document previews
265
+ */
266
+ const renderSessionHistory = (messages) => {
267
+ for (const msg of messages) {
268
+ const msgAny = msg;
269
+ const actualMsg = msgAny.message || msgAny;
270
+ const role = actualMsg.role;
271
+ // Skip system messages
272
+ if (role === 'system')
273
+ continue;
274
+ if (role === 'user') {
275
+ // Check if this message contains ONLY tool results
276
+ const hasOnlyToolResults = Array.isArray(actualMsg.content) &&
277
+ actualMsg.content.every((c) => c.type === 'tool_result') &&
278
+ actualMsg.content.length > 0;
279
+ if (!hasOnlyToolResults) {
280
+ console.log(theme.colors.info('> You: '));
281
+ }
282
+ if (typeof actualMsg.content === 'string') {
283
+ console.log(actualMsg.content);
284
+ }
285
+ else if (Array.isArray(actualMsg.content)) {
286
+ actualMsg.content.forEach((c, contentIdx) => {
287
+ if (c.type === 'text') {
288
+ console.log(c.text);
289
+ }
290
+ else if (c.type === 'tool_result') {
291
+ if (c.is_error) {
292
+ const result = toolFormatter.formatToolResult({ error: c.content });
293
+ process.stdout.write(result);
294
+ }
295
+ else {
296
+ const hasDiff = c.metadata?.diff && c.tool_name?.toLowerCase() === 'edit';
297
+ const hasWritePreview = c.metadata?.writePreview && c.tool_name?.toLowerCase() === 'write';
298
+ const hasDocumentPreview = c.metadata?.documentPreview && c.tool_name?.toLowerCase() === 'write';
299
+ if (hasDiff) {
300
+ const parsedDiff = parseUnifiedDiff(c.metadata.diff);
301
+ if (parsedDiff) {
302
+ const fileStats = c.metadata.fileStats;
303
+ const filePath = fileStats?.path || parsedDiff.file;
304
+ let additions = 0, deletions = 0;
305
+ for (const chunk of parsedDiff.chunks) {
306
+ for (const line of chunk.changes) {
307
+ if (line.type === 'added')
308
+ additions++;
309
+ if (line.type === 'removed')
310
+ deletions++;
311
+ }
312
+ }
313
+ const summary = `${filePath} +${additions}/-${deletions}`;
314
+ const result = toolFormatter.formatToolResult({
315
+ summary: theme.colors.success('✓ ') + summary,
316
+ diff: parsedDiff
317
+ });
318
+ process.stdout.write(result);
319
+ }
320
+ }
321
+ else if (hasDocumentPreview) {
322
+ // Document preview with expand/collapse
323
+ const docPreview = c.metadata.documentPreview;
324
+ const docId = `${msgAny.uuid}-doc-${contentIdx}`;
325
+ const isExpanded = expandedDocuments.has(docId);
326
+ if (!isExpanded) {
327
+ // Collapsed view
328
+ console.log(theme.colors.success('+ ') + chalk.bold(docPreview.filePath) +
329
+ theme.dimmed(` (${docPreview.lineCount} lines, ${docPreview.wordCount} words)`) +
330
+ theme.colors.info(' [Ctrl+E to expand]'));
331
+ }
332
+ else {
333
+ // Expanded view
334
+ console.log(theme.colors.success('- ') + chalk.bold(docPreview.filePath) +
335
+ theme.dimmed(` (${docPreview.lineCount} lines, ${docPreview.wordCount} words)`) +
336
+ theme.colors.info(' [Ctrl+E to collapse]'));
337
+ const absolutePath = path.isAbsolute(docPreview.filePath)
338
+ ? docPreview.filePath
339
+ : path.resolve(process.cwd(), docPreview.filePath);
340
+ try {
341
+ const content = fs.readFileSync(absolutePath, 'utf-8');
342
+ const termWidth = process.stdout.columns || 80;
343
+ const contentWidth = Math.max(termWidth - 6, 40);
344
+ for (const line of content.split('\n')) {
345
+ if (line.length <= contentWidth) {
346
+ console.log(' ' + line);
347
+ }
348
+ else {
349
+ let remaining = line;
350
+ while (remaining.length > contentWidth) {
351
+ const bp = remaining.lastIndexOf(' ', contentWidth);
352
+ const breakPoint = bp > contentWidth * 0.4 ? bp : contentWidth;
353
+ console.log(' ' + remaining.slice(0, breakPoint));
354
+ remaining = remaining.slice(breakPoint).trimStart();
355
+ }
356
+ if (remaining)
357
+ console.log(' ' + remaining);
358
+ }
359
+ }
360
+ console.log(theme.dimmed(' ───────────────────────────────────────'));
361
+ }
362
+ catch {
363
+ console.log(theme.colors.error(' [File not found]'));
364
+ }
365
+ }
366
+ }
367
+ else if (hasWritePreview) {
368
+ // Code file preview with line numbers
369
+ const preview = c.metadata.writePreview;
370
+ const formatBytes = (bytes) => {
371
+ if (bytes < 1024)
372
+ return `${bytes} B`;
373
+ if (bytes < 1024 * 1024)
374
+ return `${(bytes / 1024).toFixed(1)} KB`;
375
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
376
+ };
377
+ console.log(theme.colors.success('+ Created ') + preview.filePath +
378
+ theme.dimmed(` (${preview.lineCount} lines, ${formatBytes(preview.byteSize)})`));
379
+ const lines = preview.content.split('\n').slice(0, 50);
380
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
381
+ const lineNum = String(lineIdx + 1).padStart(4, ' ');
382
+ console.log(theme.dimmed(`${lineNum} │ `) + lines[lineIdx]);
383
+ }
384
+ if (preview.lineCount > 50) {
385
+ console.log(theme.dimmed(` ... ${preview.lineCount - 50} more lines`));
386
+ }
387
+ }
388
+ else {
389
+ const result = toolFormatter.formatToolResult({
390
+ summary: typeof c.content === 'string'
391
+ ? c.content.substring(0, 100) + (c.content.length > 100 ? '...' : '')
392
+ : 'Tool completed successfully'
393
+ });
394
+ process.stdout.write(result);
395
+ }
396
+ }
397
+ }
398
+ });
399
+ }
400
+ console.log();
401
+ }
402
+ else if (role === 'assistant') {
403
+ const modelName = msgAny.model?.id || 'Assistant';
404
+ console.log(theme.colors.primary(`\n ${modelName}:`));
405
+ console.log();
406
+ markdownRenderer.reset();
407
+ if (typeof actualMsg.content === 'string') {
408
+ const rendered = markdownRenderer.processChunk(actualMsg.content);
409
+ const flushed = markdownRenderer.flush();
410
+ process.stdout.write(rendered);
411
+ if (flushed)
412
+ process.stdout.write(flushed);
413
+ }
414
+ else if (Array.isArray(actualMsg.content)) {
415
+ for (const block of actualMsg.content) {
416
+ if (block.type === 'text') {
417
+ const rendered = markdownRenderer.processChunk(block.text);
418
+ const flushed = markdownRenderer.flush();
419
+ process.stdout.write(rendered);
420
+ if (flushed)
421
+ process.stdout.write(flushed);
422
+ }
423
+ else if (block.type === 'tool_use') {
424
+ // Handle both nested (block.toolUse) and direct (block.name/block.input) structures
425
+ const toolName = block.toolUse?.name || block.name || 'Unknown';
426
+ const toolInput = block.toolUse?.input || block.input || {};
427
+ const result = toolFormatter.formatToolCall({
428
+ name: toolName,
429
+ params: toolInput
430
+ });
431
+ process.stdout.write(result);
432
+ }
433
+ else if (block.type === 'thinking') {
434
+ // Skip thinking blocks in display
435
+ continue;
436
+ }
437
+ }
438
+ }
439
+ else {
440
+ // Fallback for unexpected content format
441
+ console.log(JSON.stringify(actualMsg.content));
442
+ }
443
+ console.log();
444
+ }
445
+ }
446
+ };
447
+ // Create persistent input handler for streaming mode
448
+ const persistentInput = createPersistentInput({
449
+ getStatusState,
450
+ theme,
451
+ onToggleThinking,
452
+ onToggleAutoApprove,
453
+ onToggleDocuments,
454
+ onEscape,
455
+ });
456
+ // Now initialize the client
457
+ if (process.env.DEBUG === 'true') {
458
+ console.log(theme.dimmed(`\n... Initializing ${mode} mode...`));
459
+ }
460
+ await client.initialize();
461
+ // Inject Ink-based approval handler (same React component as neoncortex)
462
+ client.setApprovalHandler({
463
+ requestApproval: async (request) => {
464
+ // Pause PersistentInput so Ink owns stdin
465
+ if (persistentInput.isCapturing()) {
466
+ persistentInput.clearThinking();
467
+ persistentInput.pause();
468
+ }
469
+ try {
470
+ // Render diff/file preview before the dialog
471
+ if (request.toolName === 'Edit' && request.toolInput?.old_string && request.toolInput?.new_string) {
472
+ const diffPreview = generateDiffPreview(request.toolInput.old_string, request.toolInput.new_string, request.toolInput.file_path || 'file');
473
+ if (diffPreview) {
474
+ process.stdout.write(toolFormatter.formatDiff(diffPreview, true));
475
+ }
476
+ }
477
+ if (request.toolName === 'Write' && request.toolInput?.content) {
478
+ const content = String(request.toolInput.content);
479
+ const filePath = request.toolInput.file_path || 'file';
480
+ const lines = content.split('\n');
481
+ const fileContent = {
482
+ file: filePath,
483
+ content: content,
484
+ lineCount: lines.length,
485
+ preview: lines.length > 50
486
+ };
487
+ process.stdout.write(toolFormatter.formatFileContent(fileContent));
488
+ }
489
+ const result = await showApprovalDialog(request);
490
+ if (result === 'yolo') {
491
+ await client.setApprovalMode(true);
492
+ console.log('[OK] Operation approved (auto-approve: ON)');
493
+ return true;
494
+ }
495
+ if (result === 'approve') {
496
+ console.log('[OK] Operation approved');
497
+ return true;
498
+ }
499
+ console.log('[--] Operation skipped');
500
+ return false;
501
+ }
502
+ finally {
503
+ // Resume PersistentInput
504
+ if (persistentInput.isCapturing()) {
505
+ persistentInput.resume();
506
+ }
507
+ }
508
+ }
509
+ });
510
+ /**
511
+ * Handle slash commands
512
+ */
513
+ async function handleSlashCommand(parsed, theme, rl, prompt) {
514
+ // Empty command - show interactive command palette
515
+ if (!parsed.command) {
516
+ try {
517
+ const result = await showCommandPalette('/');
518
+ if (result.selected && result.fullPath) {
519
+ // Execute the selected command
520
+ const selectedParsed = parseSlashCommand(result.fullPath);
521
+ await handleSlashCommand(selectedParsed, theme, rl, prompt);
522
+ return;
523
+ }
524
+ }
525
+ catch (error) {
526
+ console.log();
527
+ console.log(theme.errorMessage(`Command palette error: ${error.message}`));
528
+ console.log();
529
+ }
530
+ prompt();
531
+ return;
532
+ }
533
+ // Handle built-in commands
534
+ switch (parsed.command) {
535
+ case '?':
536
+ case 'help':
537
+ if (parsed.subcommand) {
538
+ // Show help for specific command (chalk-based for quick inline display)
539
+ console.log();
540
+ console.log(commandPalette.renderHelp(parsed.subcommand));
541
+ console.log();
542
+ prompt();
543
+ }
544
+ else {
545
+ // Show full interactive help using shared Ink component
546
+ await showHelp();
547
+ prompt();
548
+ }
549
+ return;
550
+ case 'clear':
551
+ console.log();
552
+ console.log(theme.warningMessage('Note: History clearing not yet implemented'));
553
+ console.log(theme.dimmed('[i] In direct mode, restart the CLI to start a fresh session'));
554
+ console.log();
555
+ prompt();
556
+ return;
557
+ case 'exit':
558
+ case 'quit':
559
+ case 'q':
560
+ console.log();
561
+ console.log(theme.roundedBox('Thank you for using Nexus Cortex!\n' +
562
+ 'Your session has been saved.', 'Goodbye'));
563
+ console.log();
564
+ rl.close();
565
+ process.exit(0);
566
+ return;
567
+ case 'debug':
568
+ showDebug = !showDebug;
569
+ client.setDebug(showDebug);
570
+ console.log();
571
+ if (showDebug) {
572
+ console.log(theme.colors.warning('[DEBUG] ON'));
573
+ }
574
+ else {
575
+ console.log(theme.dimmed('[DEBUG] OFF'));
576
+ }
577
+ console.log();
578
+ prompt();
579
+ return;
580
+ case 'yolo':
581
+ try {
582
+ // Check if explicitly disabling with /yolo off
583
+ const isDisabling = parsed.args[0] === 'off';
584
+ const isYoloActive = client.isYoloModeActive();
585
+ if (isDisabling || isYoloActive) {
586
+ // Disable YOLO mode
587
+ await client.disableYoloMode();
588
+ yoloModeEnabled = false;
589
+ autoApproveEnabled = false;
590
+ console.log();
591
+ console.log(theme.successMessage('✓ YOLO mode disabled'));
592
+ console.log(theme.dimmed('Interactive approval restored. Blacklist items will prompt for approval.'));
593
+ console.log();
594
+ }
595
+ else {
596
+ // Enable TRUE YOLO mode - auto-approves ALL (white/gray/blacklist)
597
+ await client.enableYoloMode();
598
+ yoloModeEnabled = true;
599
+ autoApproveEnabled = true;
600
+ console.log();
601
+ console.log(theme.roundedBox(' YOLO MODE ACTIVATED \n\n' +
602
+ 'TRUE auto-approve is now ENABLED.\n' +
603
+ 'ALL tool executions will be auto-approved!\n' +
604
+ '(Bypasses whitelist/graylist/blacklist)\n\n' +
605
+ 'Type /yolo or /yolo off to disable.\n' +
606
+ 'Shift+Tab also disables YOLO mode.', '⚠ WARNING ⚠'));
607
+ console.log();
608
+ }
609
+ }
610
+ catch (error) {
611
+ console.log();
612
+ console.log(theme.errorMessage(`Error: ${error.message}`));
613
+ console.log();
614
+ }
615
+ prompt();
616
+ return;
617
+ case 'thinking':
618
+ showThinking = !showThinking;
619
+ console.log();
620
+ console.log(theme.infoMessage(`Show thinking: ${showThinking ? 'ON' : 'OFF'}`));
621
+ console.log();
622
+ prompt();
623
+ return;
624
+ case 'about':
625
+ console.log(renderSplashScreen());
626
+ prompt();
627
+ return;
628
+ case 'm': // Alias for /model
629
+ case 'model':
630
+ // Handle /model picker command - opens directly without subcommand menu
631
+ if (parsed.subcommand === 'picker' || !parsed.subcommand) {
632
+ try {
633
+ console.log();
634
+ console.log(theme.dimmed('... Loading models...'));
635
+ const models = await client.listModels();
636
+ // Clear loading message
637
+ process.stdout.write('\r' + ' '.repeat(30) + '\r');
638
+ if (!models || models.length === 0) {
639
+ console.log(theme.errorMessage('No models available'));
640
+ prompt();
641
+ return;
642
+ }
643
+ // Convert to ModelDisplayInfo format (matching neoncortex mapping)
644
+ const modelInfos = models.map((m) => ({
645
+ id: m.id,
646
+ displayName: m.displayName || m.name || m.id,
647
+ provider: m.owned_by || m.provider || 'Unknown',
648
+ contextWindow: m.contextWindow,
649
+ inputCost: m.inputCostPer1M ?? m.pricing?.input,
650
+ outputCost: m.outputCostPer1M ?? m.pricing?.output,
651
+ supportsReasoning: m.reasoning?.supported === true,
652
+ supportsVision: m.capabilities?.vision,
653
+ supportsStreaming: m.capabilities?.streaming,
654
+ }));
655
+ const currentModel = client.getCurrentModel();
656
+ const currentModelId = currentModel?.id || '';
657
+ const result = await showModelPicker(modelInfos, currentModelId);
658
+ if (result.selected && result.modelId) {
659
+ console.log();
660
+ console.log(theme.dimmed(`... Switching to ${result.modelId}...`));
661
+ await client.switchModel(result.modelId);
662
+ // Persist is already done in the picker
663
+ console.log(theme.successMessage(`✓ Switched to model: ${theme.colors.highlight(result.modelId)}`));
664
+ console.log(theme.dimmed('[i] New messages will use this model'));
665
+ console.log();
666
+ }
667
+ else {
668
+ console.log(theme.dimmed('\nModel picker cancelled'));
669
+ console.log();
670
+ }
671
+ }
672
+ catch (error) {
673
+ console.log();
674
+ console.log(theme.errorMessage(`Error: ${error.message}`));
675
+ console.log();
676
+ }
677
+ }
678
+ else {
679
+ console.log();
680
+ console.log(theme.warningMessage(`Unknown subcommand: /model ${parsed.subcommand || ''}`));
681
+ console.log(theme.dimmed('[i] Available:'));
682
+ console.log(theme.dimmed(' /model picker - Open interactive model picker'));
683
+ console.log(theme.dimmed(' /model - Open interactive model picker'));
684
+ console.log();
685
+ }
686
+ prompt();
687
+ return;
688
+ case 'session':
689
+ if (parsed.subcommand === 'checkpoint') {
690
+ const checkpointName = parsed.args.join(' ') || `Checkpoint ${new Date().toLocaleString()}`;
691
+ try {
692
+ console.log();
693
+ console.log(theme.dimmed('... Creating checkpoint...'));
694
+ const checkpoint = await client.createCheckpoint(checkpointName);
695
+ // Clear loading message
696
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
697
+ console.log(theme.dimmed('━'.repeat(60)));
698
+ console.log(`${theme.colors.info('▸')} ${theme.colors.primary('Checkpoint Created')}`);
699
+ console.log(theme.dimmed('━'.repeat(60)));
700
+ console.log();
701
+ console.log(` ${theme.colors.secondary('ID:')} ${theme.colors.highlight(checkpoint.id)}`);
702
+ if (checkpoint.name) {
703
+ console.log(` ${theme.colors.secondary('Name:')} ${theme.text(checkpoint.name)}`);
704
+ }
705
+ console.log(` ${theme.colors.secondary('Messages:')} ${theme.dimmed(checkpoint.messageCount || 0)}`);
706
+ console.log();
707
+ console.log(theme.dimmed('━'.repeat(60)));
708
+ console.log(theme.dimmed('[i] Use /session resume <id> to restore this checkpoint'));
709
+ console.log(theme.dimmed('━'.repeat(60)));
710
+ console.log();
711
+ }
712
+ catch (error) {
713
+ console.log();
714
+ console.log(theme.errorMessage(`Error creating checkpoint: ${error.message}`));
715
+ console.log();
716
+ }
717
+ }
718
+ else if (parsed.subcommand === 'list') {
719
+ try {
720
+ console.log();
721
+ console.log(theme.dimmed('... Loading sessions...'));
722
+ const result = await client.listSessions();
723
+ const sessions = result.sessions;
724
+ // Clear loading message
725
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
726
+ console.log(theme.dimmed('━'.repeat(60)));
727
+ console.log(`${theme.colors.info('▸')} ${theme.colors.primary('Sessions')}`);
728
+ console.log(theme.dimmed('━'.repeat(60)));
729
+ console.log();
730
+ if (sessions && sessions.length > 0) {
731
+ sessions.slice(0, 10).forEach((session) => {
732
+ const id = theme.colors.highlight(session.id);
733
+ const name = session.name ? theme.text(session.name) : theme.dimmed('(unnamed)');
734
+ const messages = theme.dimmed(`${session.messageCount || 0} messages`);
735
+ console.log(` ${id} - ${name} ${messages}`);
736
+ });
737
+ if (sessions.length > 10) {
738
+ console.log();
739
+ console.log(theme.dimmed(` ... and ${sessions.length - 10} more`));
740
+ }
741
+ }
742
+ else {
743
+ console.log(theme.dimmed(' No sessions found'));
744
+ }
745
+ console.log();
746
+ console.log(theme.dimmed('━'.repeat(60)));
747
+ console.log();
748
+ }
749
+ catch (error) {
750
+ console.log();
751
+ console.log(theme.errorMessage(`Error loading sessions: ${error.message}`));
752
+ console.log();
753
+ }
754
+ }
755
+ else {
756
+ console.log();
757
+ console.log(theme.warningMessage(`Unknown subcommand: /session ${parsed.subcommand || ''}`));
758
+ console.log(theme.dimmed('[i] Available: /session checkpoint [name], /session list'));
759
+ console.log();
760
+ }
761
+ prompt();
762
+ return;
763
+ case 'cache':
764
+ if (parsed.subcommand === 'metrics') {
765
+ try {
766
+ console.log();
767
+ console.log(theme.dimmed('... Loading cache metrics...'));
768
+ const metrics = await client.getCacheMetrics();
769
+ // Clear loading message
770
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
771
+ console.log(theme.dimmed('━'.repeat(60)));
772
+ console.log(`${theme.colors.info('▸')} ${theme.colors.primary('Cache Metrics')}`);
773
+ console.log(theme.dimmed('━'.repeat(60)));
774
+ console.log();
775
+ // Display metrics
776
+ if (metrics?.metrics) {
777
+ const m = metrics.metrics;
778
+ const hitRate = m.overallCacheHitRate ? `${(m.overallCacheHitRate * 100).toFixed(1)}%` : '0%';
779
+ const costSavings = m.overallCostSavingsRatio ? `${(m.overallCostSavingsRatio * 100).toFixed(1)}%` : '0%';
780
+ console.log(` ${theme.colors.secondary('Total Requests:')} ${theme.dimmed(m.requestCount || 0)}`);
781
+ console.log(` ${theme.colors.secondary('Requests with Cache Hits:')} ${theme.dimmed(m.requestsWithCacheHits || 0)}`);
782
+ console.log();
783
+ console.log(` ${theme.colors.secondary('Total Input Tokens:')} ${theme.dimmed((m.totalInputTokens || 0).toLocaleString())}`);
784
+ console.log(` ${theme.colors.secondary(' Cache Creation:')} ${theme.dimmed((m.totalCacheCreationTokens || 0).toLocaleString())}`);
785
+ console.log(` ${theme.colors.secondary(' Cache Reads:')} ${theme.colors.success((m.totalCacheReadTokens || 0).toLocaleString())}`);
786
+ console.log(` ${theme.colors.secondary(' Uncached:')} ${theme.dimmed((m.totalUncachedInputTokens || 0).toLocaleString())}`);
787
+ console.log();
788
+ console.log(` ${theme.colors.secondary('Cache Hit Rate:')} ${theme.colors.highlight(hitRate)}`);
789
+ console.log(` ${theme.colors.secondary('Est. Cost Savings:')} ${theme.colors.success(costSavings)}`);
790
+ // Show provider breakdown if available
791
+ if (Object.keys(m.byProvider || {}).length > 0) {
792
+ console.log();
793
+ console.log(` ${theme.colors.secondary('By Provider:')}`);
794
+ for (const [provider, pm] of Object.entries(m.byProvider)) {
795
+ const providerHitRate = pm.cacheHitRate ? `${(pm.cacheHitRate * 100).toFixed(1)}%` : '0%';
796
+ console.log(` ${theme.dimmed(provider)}: ${pm.cacheReadTokens.toLocaleString()} cached (${providerHitRate})`);
797
+ }
798
+ }
799
+ }
800
+ else {
801
+ console.log(theme.dimmed(' No cache metrics available'));
802
+ console.log(theme.dimmed(' [i] Make some API requests with caching enabled'));
803
+ }
804
+ console.log();
805
+ console.log(theme.dimmed('━'.repeat(60)));
806
+ console.log();
807
+ }
808
+ catch (error) {
809
+ console.log();
810
+ console.log(theme.errorMessage(`Error loading cache metrics: ${error.message}`));
811
+ console.log();
812
+ }
813
+ }
814
+ else if (parsed.subcommand === 'report') {
815
+ try {
816
+ console.log();
817
+ console.log(theme.dimmed('... Generating cache report...'));
818
+ const report = await client.getCacheReport();
819
+ // Clear loading message
820
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
821
+ console.log(theme.dimmed('━'.repeat(60)));
822
+ console.log(`${theme.colors.info('▸')} ${theme.colors.primary('Cache Report')}`);
823
+ console.log(theme.dimmed('━'.repeat(60)));
824
+ console.log();
825
+ if (report) {
826
+ console.log(theme.text(report));
827
+ }
828
+ else {
829
+ console.log(theme.dimmed(' No report available'));
830
+ }
831
+ console.log();
832
+ console.log(theme.dimmed('━'.repeat(60)));
833
+ console.log();
834
+ }
835
+ catch (error) {
836
+ console.log();
837
+ console.log(theme.errorMessage(`Error generating cache report: ${error.message}`));
838
+ console.log();
839
+ }
840
+ }
841
+ else {
842
+ console.log();
843
+ console.log(theme.warningMessage(`Unknown subcommand: /cache ${parsed.subcommand || ''}`));
844
+ console.log(theme.dimmed('[i] Available: /cache metrics, /cache report'));
845
+ console.log();
846
+ }
847
+ prompt();
848
+ return;
849
+ case 'mcp':
850
+ if (parsed.subcommand === 'list') {
851
+ try {
852
+ console.log();
853
+ console.log(theme.dimmed('... Loading MCP servers...'));
854
+ const result = await client.listMcpServers();
855
+ const servers = result.servers;
856
+ // Clear loading message
857
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
858
+ console.log(theme.dimmed('━'.repeat(60)));
859
+ console.log(`${theme.colors.info('▸')} ${theme.colors.primary('MCP Servers')}`);
860
+ console.log(theme.dimmed('━'.repeat(60)));
861
+ console.log();
862
+ if (servers && servers.length > 0) {
863
+ servers.forEach((server) => {
864
+ const name = theme.colors.highlight(server.name);
865
+ const status = server.enabled
866
+ ? theme.colors.success('enabled')
867
+ : theme.dimmed('disabled');
868
+ const description = server.description
869
+ ? theme.dimmed(` - ${server.description}`)
870
+ : '';
871
+ console.log(` ${name} ${status}${description}`);
872
+ });
873
+ }
874
+ else {
875
+ console.log(theme.dimmed(' No MCP servers configured'));
876
+ }
877
+ console.log();
878
+ console.log(theme.dimmed('━'.repeat(60)));
879
+ console.log(theme.dimmed('[i] Use /mcp enable/disable <name> to toggle servers'));
880
+ console.log(theme.dimmed('━'.repeat(60)));
881
+ console.log();
882
+ }
883
+ catch (error) {
884
+ console.log();
885
+ console.log(theme.errorMessage(`Error loading MCP servers: ${error.message}`));
886
+ console.log();
887
+ }
888
+ }
889
+ else if (parsed.subcommand === 'enable') {
890
+ const serverName = parsed.args[0];
891
+ if (!serverName) {
892
+ console.log();
893
+ console.log(theme.errorMessage('Error: Server name required'));
894
+ console.log(theme.dimmed('[i] Usage: /mcp enable <server-name>'));
895
+ console.log(theme.dimmed('[i] Use /mcp list to see available servers'));
896
+ console.log();
897
+ }
898
+ else {
899
+ try {
900
+ console.log();
901
+ console.log(theme.dimmed(`... Enabling ${serverName}...`));
902
+ await client.enableMCPServer(serverName);
903
+ // Clear loading message
904
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
905
+ console.log(theme.successMessage(`✓ Enabled MCP server: ${theme.colors.highlight(serverName)}`));
906
+ console.log();
907
+ }
908
+ catch (error) {
909
+ console.log();
910
+ console.log(theme.errorMessage(`Error enabling MCP server: ${error.message}`));
911
+ console.log(theme.dimmed('[i] Use /mcp list to see available servers'));
912
+ console.log();
913
+ }
914
+ }
915
+ }
916
+ else if (parsed.subcommand === 'disable') {
917
+ const serverName = parsed.args[0];
918
+ if (!serverName) {
919
+ console.log();
920
+ console.log(theme.errorMessage('Error: Server name required'));
921
+ console.log(theme.dimmed('[i] Usage: /mcp disable <server-name>'));
922
+ console.log(theme.dimmed('[i] Use /mcp list to see available servers'));
923
+ console.log();
924
+ }
925
+ else {
926
+ try {
927
+ console.log();
928
+ console.log(theme.dimmed(`... Disabling ${serverName}...`));
929
+ await client.disableMCPServer(serverName);
930
+ // Clear loading message
931
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
932
+ console.log(theme.successMessage(`✓ Disabled MCP server: ${theme.colors.highlight(serverName)}`));
933
+ console.log();
934
+ }
935
+ catch (error) {
936
+ console.log();
937
+ console.log(theme.errorMessage(`Error disabling MCP server: ${error.message}`));
938
+ console.log(theme.dimmed('[i] Use /mcp list to see available servers'));
939
+ console.log();
940
+ }
941
+ }
942
+ }
943
+ else {
944
+ console.log();
945
+ console.log(theme.warningMessage(`Unknown subcommand: /mcp ${parsed.subcommand || ''}`));
946
+ console.log(theme.dimmed('[i] Available: /mcp list, /mcp enable <name>, /mcp disable <name>'));
947
+ console.log();
948
+ }
949
+ prompt();
950
+ return;
951
+ case 'tools':
952
+ if (parsed.subcommand === 'list') {
953
+ const grouped = parsed.args.includes('--grouped');
954
+ try {
955
+ console.log();
956
+ console.log(theme.dimmed('... Loading tools...'));
957
+ const tools = await client.listTools();
958
+ // Clear loading message
959
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
960
+ console.log(theme.dimmed('━'.repeat(60)));
961
+ console.log(`${theme.colors.info('▸')} ${theme.colors.primary('Available Tools')}`);
962
+ console.log(theme.dimmed('━'.repeat(60)));
963
+ console.log();
964
+ if (tools && tools.length > 0) {
965
+ if (grouped) {
966
+ // Group by category
967
+ const categories = new Map();
968
+ tools.forEach((tool) => {
969
+ const category = tool.category || 'Other';
970
+ if (!categories.has(category)) {
971
+ categories.set(category, []);
972
+ }
973
+ categories.get(category).push(tool);
974
+ });
975
+ categories.forEach((toolList, category) => {
976
+ console.log(` ${theme.colors.secondary(category)}:`);
977
+ toolList.forEach((tool) => {
978
+ const name = theme.colors.highlight(tool.name);
979
+ const description = tool.description
980
+ ? theme.dimmed(` - ${tool.description}`)
981
+ : '';
982
+ console.log(` ${name}${description}`);
983
+ });
984
+ console.log();
985
+ });
986
+ }
987
+ else {
988
+ // Flat list
989
+ tools.forEach((tool) => {
990
+ const name = theme.colors.highlight(tool.name);
991
+ const description = tool.description
992
+ ? theme.dimmed(` - ${tool.description}`)
993
+ : '';
994
+ console.log(` ${name}${description}`);
995
+ });
996
+ }
997
+ }
998
+ else {
999
+ console.log(theme.dimmed(' No tools available'));
1000
+ }
1001
+ console.log();
1002
+ console.log(theme.dimmed('━'.repeat(60)));
1003
+ console.log(theme.dimmed('[i] Use /tools info <tool-name> for details'));
1004
+ console.log(theme.dimmed('━'.repeat(60)));
1005
+ console.log();
1006
+ }
1007
+ catch (error) {
1008
+ console.log();
1009
+ console.log(theme.errorMessage(`Error loading tools: ${error.message}`));
1010
+ console.log();
1011
+ }
1012
+ }
1013
+ else if (parsed.subcommand === 'info') {
1014
+ const toolName = parsed.args[0];
1015
+ if (!toolName) {
1016
+ console.log();
1017
+ console.log(theme.errorMessage('Error: Tool name required'));
1018
+ console.log(theme.dimmed('[i] Usage: /tools info <tool-name>'));
1019
+ console.log(theme.dimmed('[i] Use /tools list to see available tools'));
1020
+ console.log();
1021
+ }
1022
+ else {
1023
+ try {
1024
+ console.log();
1025
+ console.log(theme.dimmed(`... Loading tool info...`));
1026
+ const toolInfo = await client.getToolInfo(toolName);
1027
+ // Clear loading message
1028
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
1029
+ console.log(theme.dimmed('━'.repeat(60)));
1030
+ console.log(`${theme.colors.info('▸')} ${theme.colors.primary('Tool Information')}`);
1031
+ console.log(theme.dimmed('━'.repeat(60)));
1032
+ console.log();
1033
+ if (toolInfo) {
1034
+ console.log(` ${theme.colors.secondary('Name:')} ${theme.colors.highlight(toolInfo.name)}`);
1035
+ if (toolInfo.description) {
1036
+ console.log(` ${theme.colors.secondary('Description:')} ${theme.text(toolInfo.description)}`);
1037
+ }
1038
+ if (toolInfo.category) {
1039
+ console.log(` ${theme.colors.secondary('Category:')} ${theme.dimmed(toolInfo.category)}`);
1040
+ }
1041
+ if (toolInfo.parameters) {
1042
+ console.log();
1043
+ console.log(` ${theme.colors.secondary('Parameters:')}`);
1044
+ console.log(` ${theme.dimmed(JSON.stringify(toolInfo.parameters, null, 2))}`);
1045
+ }
1046
+ }
1047
+ else {
1048
+ console.log(theme.dimmed(' Tool not found'));
1049
+ }
1050
+ console.log();
1051
+ console.log(theme.dimmed('━'.repeat(60)));
1052
+ console.log();
1053
+ }
1054
+ catch (error) {
1055
+ console.log();
1056
+ console.log(theme.errorMessage(`Error loading tool info: ${error.message}`));
1057
+ console.log(theme.dimmed('[i] Use /tools list to see available tools'));
1058
+ console.log();
1059
+ }
1060
+ }
1061
+ }
1062
+ else {
1063
+ console.log();
1064
+ console.log(theme.warningMessage(`Unknown subcommand: /tools ${parsed.subcommand || ''}`));
1065
+ console.log(theme.dimmed('[i] Available: /tools list [--grouped], /tools info <name>'));
1066
+ console.log();
1067
+ }
1068
+ prompt();
1069
+ return;
1070
+ case 'config':
1071
+ if (!parsed.subcommand || (parsed.subcommand === 'set' && !parsed.args[0])) {
1072
+ // /config or /config set (no args) — open interactive menu
1073
+ rl.pause();
1074
+ const { showConfigMenu } = await import('../../ui/InkConfigMenu.js');
1075
+ await showConfigMenu(process.cwd(), (updates) => client.updateRuntimeConfig(updates));
1076
+ rl.resume();
1077
+ }
1078
+ else if (parsed.subcommand === 'get') {
1079
+ const key = parsed.args[0];
1080
+ if (!key) {
1081
+ console.log();
1082
+ console.log(theme.errorMessage('Error: Configuration key required'));
1083
+ console.log(theme.dimmed('[i] Usage: /config get <key>'));
1084
+ console.log(theme.dimmed('[i] Example: /config get ANTHROPIC_API_KEY'));
1085
+ console.log();
1086
+ }
1087
+ else {
1088
+ try {
1089
+ console.log();
1090
+ console.log(theme.dimmed(`... Loading configuration...`));
1091
+ const value = await client.getConfig(key);
1092
+ // Clear loading message
1093
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
1094
+ console.log(theme.dimmed('━'.repeat(60)));
1095
+ console.log(`${theme.colors.info('▸')} ${theme.colors.primary('Configuration')}`);
1096
+ console.log(theme.dimmed('━'.repeat(60)));
1097
+ console.log();
1098
+ console.log(` ${theme.colors.secondary('Key:')} ${theme.colors.highlight(key)}`);
1099
+ const isSensitive = key.toLowerCase().includes('key') ||
1100
+ key.toLowerCase().includes('token') ||
1101
+ key.toLowerCase().includes('secret');
1102
+ const displayValue = isSensitive && value.length > 10
1103
+ ? `${value.substring(0, 10)}...` + theme.dimmed('[masked]')
1104
+ : value;
1105
+ console.log(` ${theme.colors.secondary('Value:')} ${theme.text(displayValue)}`);
1106
+ console.log();
1107
+ console.log(theme.dimmed('━'.repeat(60)));
1108
+ console.log(theme.dimmed('[i] Use /config set <key> <value> to update'));
1109
+ console.log(theme.dimmed('━'.repeat(60)));
1110
+ console.log();
1111
+ }
1112
+ catch (error) {
1113
+ console.log();
1114
+ console.log(theme.errorMessage(`Error: ${error.message}`));
1115
+ console.log(theme.dimmed('[i] In direct mode, use environment variables'));
1116
+ console.log();
1117
+ }
1118
+ }
1119
+ }
1120
+ else if (parsed.subcommand === 'set') {
1121
+ const key = parsed.args[0];
1122
+ const value = parsed.args[1];
1123
+ if (!value) {
1124
+ console.log();
1125
+ console.log(theme.errorMessage('Error: Value required'));
1126
+ console.log(theme.dimmed('[i] Usage: /config set <key> <value>'));
1127
+ console.log();
1128
+ }
1129
+ else {
1130
+ try {
1131
+ console.log();
1132
+ console.log(theme.dimmed(`... Updating configuration...`));
1133
+ await client.setConfig(key, value);
1134
+ // Clear loading message
1135
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
1136
+ const { getRuntimeConfigEntry, isLiveToggleable } = await import('@nexus-cortex/core');
1137
+ const entry = getRuntimeConfigEntry(key);
1138
+ if (entry?.tier === 'config' && entry.mapper) {
1139
+ client.updateRuntimeConfig(entry.mapper(value));
1140
+ }
1141
+ const label = isLiveToggleable(key) ? '(live)' : '(restart required)';
1142
+ console.log(theme.successMessage(`[OK] ${theme.colors.highlight(key)} updated ${label}`));
1143
+ console.log();
1144
+ }
1145
+ catch (error) {
1146
+ console.log();
1147
+ console.log(theme.errorMessage(`Error: ${error.message}`));
1148
+ console.log(theme.dimmed('[i] In direct mode, restart with updated environment variables'));
1149
+ console.log();
1150
+ }
1151
+ }
1152
+ }
1153
+ else if (parsed.subcommand === 'list') {
1154
+ // /config list — show all keys
1155
+ try {
1156
+ const keys = await client.listConfigKeys();
1157
+ console.log();
1158
+ console.log(theme.dimmed('━'.repeat(60)));
1159
+ console.log(`${theme.colors.info('▸')} ${theme.colors.primary('All Configuration Keys')}`);
1160
+ console.log(theme.dimmed('━'.repeat(60)));
1161
+ for (const k of keys) {
1162
+ console.log(` ${k}`);
1163
+ }
1164
+ console.log();
1165
+ console.log(theme.dimmed('[i] /config to open interactive settings browser'));
1166
+ console.log();
1167
+ }
1168
+ catch (error) {
1169
+ console.log();
1170
+ console.log(theme.errorMessage(`Error: ${error.message}`));
1171
+ console.log();
1172
+ }
1173
+ }
1174
+ else {
1175
+ console.log();
1176
+ console.log(theme.warningMessage(`Unknown subcommand: /config ${parsed.subcommand || ''}`));
1177
+ console.log(theme.dimmed('[i] Available: /config, /config get <key>, /config set <key> <value>, /config list'));
1178
+ console.log();
1179
+ }
1180
+ prompt();
1181
+ return;
1182
+ case 'system-message':
1183
+ try {
1184
+ console.log();
1185
+ console.log(theme.dimmed('Opening system message manager...'));
1186
+ console.log(theme.dimmed('[i] Press Q to return to chat'));
1187
+ console.log();
1188
+ // Pause readline to allow Ink to take over
1189
+ rl.pause();
1190
+ // Use current working directory as context root
1191
+ // createSystemMessageCommand will determine if it's a project or global context
1192
+ const contextRoot = process.cwd();
1193
+ // Launch interactive system message manager with orchestrator for AI generation
1194
+ await createSystemMessageCommand(contextRoot, {
1195
+ debug: false,
1196
+ orchestratorClient: client,
1197
+ });
1198
+ // Restore stdin state after Ink unmounts
1199
+ // Ink may have unref'd or altered stdin state - we need to fully restore it
1200
+ // Use setImmediate to ensure Ink's cleanup callbacks have completed
1201
+ await new Promise(resolve => setImmediate(resolve));
1202
+ if (process.stdin.isTTY) {
1203
+ // Reset to a known state before rawQuestion takes over
1204
+ process.stdin.setRawMode(false);
1205
+ process.stdin.ref();
1206
+ process.stdin.resume();
1207
+ }
1208
+ // Resume readline after Ink exits
1209
+ rl.resume();
1210
+ console.log();
1211
+ console.log(theme.dimmed('Returned to chat'));
1212
+ console.log();
1213
+ }
1214
+ catch (error) {
1215
+ console.log();
1216
+ console.log(theme.errorMessage(`Error: ${error.message}`));
1217
+ console.log();
1218
+ }
1219
+ prompt();
1220
+ return;
1221
+ case 'agent':
1222
+ try {
1223
+ const args = parsed.args || [];
1224
+ const subcommand = parsed.subcommand;
1225
+ // Determine project root (go up from packages/cli/dist/commands/chat)
1226
+ const { fileURLToPath: fileURLToPathAgent } = await import('url');
1227
+ const { dirname: dirnameAgent, join: joinAgent } = await import('path');
1228
+ const currentFileAgent = fileURLToPathAgent(import.meta.url);
1229
+ const projectRootAgent = joinAgent(dirnameAgent(currentFileAgent), '..', '..', '..', '..', '..');
1230
+ if (subcommand === 'list') {
1231
+ // List agents non-interactively
1232
+ console.log();
1233
+ await listAgents(projectRootAgent);
1234
+ console.log();
1235
+ }
1236
+ else if (subcommand === 'info' && args.length > 0) {
1237
+ // Show agent info
1238
+ console.log();
1239
+ await showAgentInfo(projectRootAgent, args[0]);
1240
+ console.log();
1241
+ }
1242
+ else {
1243
+ // Open interactive agent manager
1244
+ console.log();
1245
+ console.log(theme.dimmed('Opening agent manager...'));
1246
+ console.log(theme.dimmed('[i] Press Q to return to chat'));
1247
+ console.log();
1248
+ // Pause readline to allow Ink to take over
1249
+ rl.pause();
1250
+ // Get current model for inheritance option
1251
+ const currentModelInfo = client.getCurrentModel();
1252
+ const currentModelId = currentModelInfo?.id || options.model || process.env.DEFAULT_MODEL_ID || 'sonnet';
1253
+ // Launch interactive agent manager with orchestrator for AI generation
1254
+ await createAgentCommand(projectRootAgent, {
1255
+ debug: false,
1256
+ initialAgentName: subcommand || undefined,
1257
+ currentModel: currentModelId,
1258
+ orchestratorClient: client,
1259
+ });
1260
+ // Restore stdin state after Ink unmounts
1261
+ // Ink may have unref'd or altered stdin state - we need to fully restore it
1262
+ // Use setImmediate to ensure Ink's cleanup callbacks have completed
1263
+ await new Promise(resolve => setImmediate(resolve));
1264
+ if (process.stdin.isTTY) {
1265
+ // Reset to a known state before rawQuestion takes over
1266
+ process.stdin.setRawMode(false);
1267
+ process.stdin.ref();
1268
+ process.stdin.resume();
1269
+ }
1270
+ // Resume readline after Ink exits
1271
+ rl.resume();
1272
+ console.log();
1273
+ console.log(theme.dimmed('Returned to chat'));
1274
+ console.log();
1275
+ }
1276
+ }
1277
+ catch (error) {
1278
+ console.log();
1279
+ console.log(theme.errorMessage(`Error: ${error.message}`));
1280
+ console.log();
1281
+ }
1282
+ prompt();
1283
+ return;
1284
+ case 'init':
1285
+ try {
1286
+ console.log();
1287
+ console.log(theme.dimmed('[INIT] Scanning project...'));
1288
+ const { InitCortexContext } = await import('@nexus-cortex/core');
1289
+ const initArgs = parsed.args || [];
1290
+ const isGlobal = initArgs.includes('--global');
1291
+ const depthIndex = initArgs.indexOf('--depth');
1292
+ const depthArg = depthIndex >= 0 ? initArgs[depthIndex + 1] : undefined;
1293
+ const scan = await InitCortexContext.scan(process.cwd(), {
1294
+ scope: isGlobal ? 'global' : 'auto',
1295
+ max_depth: depthArg ? parseInt(depthArg, 10) : undefined,
1296
+ });
1297
+ const initPrompt = InitCortexContext.formatScanAsPrompt(scan);
1298
+ console.log(theme.dimmed('[INIT] Scan complete. Generating CORTEX.md...'));
1299
+ console.log();
1300
+ await processMessage(initPrompt);
1301
+ }
1302
+ catch (error) {
1303
+ console.log();
1304
+ console.log(theme.errorMessage(`Error: ${error.message}`));
1305
+ console.log();
1306
+ prompt();
1307
+ }
1308
+ return;
1309
+ case 'theme':
1310
+ try {
1311
+ console.log();
1312
+ console.log(theme.dimmed('Opening theme picker...'));
1313
+ console.log(theme.dimmed('[i] Use ↑/↓ to navigate, Enter to select, Esc to exit'));
1314
+ console.log();
1315
+ // Pause readline to allow ChalkThemePicker to take over
1316
+ rl.pause();
1317
+ // Run the theme picker
1318
+ await runThemePicker();
1319
+ // Resume readline after theme picker exits
1320
+ rl.resume();
1321
+ console.log();
1322
+ }
1323
+ catch (error) {
1324
+ // Resume readline if there was an error
1325
+ rl.resume();
1326
+ console.log();
1327
+ console.log(theme.errorMessage(`Error: ${error.message}`));
1328
+ console.log();
1329
+ }
1330
+ prompt();
1331
+ return;
1332
+ case 'continue':
1333
+ try {
1334
+ const { resolveContext, JSONLHistoryStore } = await import('@nexus-cortex/core');
1335
+ console.log();
1336
+ console.log(theme.dimmed(' Loading previous sessions...'));
1337
+ console.log();
1338
+ // Use ContextResolver to find the correct storage directory
1339
+ const context = resolveContext({ cwd: process.cwd() });
1340
+ // Create history store
1341
+ const historyStore = new JSONLHistoryStore({
1342
+ baseDir: context.sessionsDir
1343
+ });
1344
+ // List all sessions
1345
+ const sessions = await historyStore.listSessions();
1346
+ if (sessions.length === 0) {
1347
+ console.log(theme.warningMessage('No previous sessions found'));
1348
+ console.log(theme.dimmed('[i] Start chatting to create a session'));
1349
+ console.log();
1350
+ prompt();
1351
+ return;
1352
+ }
1353
+ // Sort by most recent first
1354
+ sessions.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
1355
+ // Display sessions as numbered list
1356
+ console.log();
1357
+ console.log(theme.colors.primary('Available Sessions:'));
1358
+ console.log(theme.dimmed('─'.repeat(80)));
1359
+ const displaySessions = sessions.slice(0, 20);
1360
+ displaySessions.forEach((session, index) => {
1361
+ const sessionId = session.sessionId.slice(0, 8);
1362
+ const date = session.lastModified.toLocaleDateString();
1363
+ const time = session.lastModified.toLocaleTimeString();
1364
+ const size = (session.fileSize / 1024).toFixed(1);
1365
+ const messages = session.messageCount;
1366
+ // Calculate days ago
1367
+ const now = new Date();
1368
+ const diffMs = now.getTime() - session.lastModified.getTime();
1369
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
1370
+ const ageStr = diffDays === 0 ? 'Today' : diffDays === 1 ? '1 day ago' : `${diffDays} days ago`;
1371
+ console.log(` ${theme.colors.info((index + 1).toString().padStart(2))}. ${sessionId} - ${ageStr} (${messages} msgs, ${size} KB) - ${date} ${time}`);
1372
+ });
1373
+ console.log(theme.dimmed('─'.repeat(80)));
1374
+ console.log();
1375
+ // Use existing readline to avoid breaking it
1376
+ rl.question(theme.colors.info('Select session number (or press Enter to cancel): '), async (input) => {
1377
+ const choice = input.trim();
1378
+ if (!choice) {
1379
+ console.log();
1380
+ prompt();
1381
+ return;
1382
+ }
1383
+ const num = parseInt(choice, 10);
1384
+ if (isNaN(num) || num < 1 || num > displaySessions.length) {
1385
+ console.log();
1386
+ console.log(theme.errorMessage('Invalid selection'));
1387
+ console.log();
1388
+ prompt();
1389
+ return;
1390
+ }
1391
+ const selectedSession = displaySessions[num - 1];
1392
+ if (!selectedSession) {
1393
+ console.log();
1394
+ console.log(theme.errorMessage('Invalid selection'));
1395
+ console.log();
1396
+ prompt();
1397
+ return;
1398
+ }
1399
+ const selectedSessionId = selectedSession.sessionId;
1400
+ // Resume selected session
1401
+ console.log();
1402
+ console.log(theme.dimmed(`Resuming session ${selectedSessionId.slice(0, 8)}...`));
1403
+ console.log();
1404
+ // Load messages from the session
1405
+ try {
1406
+ const messages = await historyStore.loadSession(selectedSessionId);
1407
+ if (process.env.DEBUG === 'true') {
1408
+ console.log(theme.dimmed(`DEBUG: Loaded ${messages.length} messages from session`));
1409
+ if (messages.length > 0) {
1410
+ console.log(theme.dimmed(`DEBUG: First message role: ${messages[0].role}`));
1411
+ console.log(theme.dimmed(`DEBUG: Last message role: ${messages[messages.length - 1].role}`));
1412
+ }
1413
+ }
1414
+ console.log(theme.dimmed(`Loaded ${messages.length} messages`));
1415
+ console.log();
1416
+ // Store messages for re-rendering on Ctrl+E toggle
1417
+ loadedSessionMessages = messages;
1418
+ // Display all messages using the shared render function
1419
+ renderSessionHistory(messages);
1420
+ console.log(theme.dimmed('─'.repeat(process.stdout.columns || 80)));
1421
+ console.log();
1422
+ // NOW resume the session in the orchestrator (after displaying messages)
1423
+ console.log(theme.dimmed('Initializing session state...'));
1424
+ await client.resumeSession(selectedSessionId);
1425
+ console.log(theme.successMessage(`Session resumed (${selectedSessionId.slice(0, 8)}) - Continue your conversation below`));
1426
+ console.log();
1427
+ }
1428
+ catch (error) {
1429
+ console.log();
1430
+ console.log(theme.errorMessage(`Failed to browse sessions: ${error.message}`));
1431
+ if (process.env.DEBUG === 'true') {
1432
+ console.log(theme.dimmed('Stack trace:'));
1433
+ console.log(theme.dimmed(error.stack));
1434
+ }
1435
+ console.log();
1436
+ }
1437
+ prompt();
1438
+ });
1439
+ }
1440
+ catch (error) {
1441
+ console.log();
1442
+ console.log(theme.errorMessage(`Failed to load sessions: ${error.message}`));
1443
+ console.log();
1444
+ prompt();
1445
+ }
1446
+ return;
1447
+ case 'resume': {
1448
+ const selector = parsed.subcommand || parsed.args[0];
1449
+ if (!selector) {
1450
+ console.log();
1451
+ console.log(theme.errorMessage('Usage: /resume <session-id>'));
1452
+ console.log(theme.dimmed('[i] Use /continue to browse sessions interactively'));
1453
+ console.log();
1454
+ prompt();
1455
+ return;
1456
+ }
1457
+ try {
1458
+ console.log();
1459
+ console.log(theme.dimmed(`Resuming session ${selector.slice(0, 8)}...`));
1460
+ await client.resumeSession(selector);
1461
+ const { JSONLHistoryStore, resolveContext } = await import('@nexus-cortex/core');
1462
+ const context = resolveContext({ cwd: process.cwd() });
1463
+ const historyStore = new JSONLHistoryStore({ baseDir: context.sessionsDir });
1464
+ const messages = await historyStore.loadSession(selector);
1465
+ loadedSessionMessages = messages;
1466
+ renderSessionHistory(messages);
1467
+ console.log(theme.successMessage(`Session resumed (${selector.slice(0, 8)}) - Continue your conversation below`));
1468
+ console.log();
1469
+ }
1470
+ catch (error) {
1471
+ console.log();
1472
+ console.log(theme.errorMessage(`Failed to resume session: ${error.message}`));
1473
+ console.log();
1474
+ }
1475
+ prompt();
1476
+ return;
1477
+ }
1478
+ case 'mentorship': {
1479
+ // Direct mentorship commands - no multi-step menu BS
1480
+ const projectPath = client.getProjectPath();
1481
+ const mentorshipService = new MentorshipConfigService(projectPath);
1482
+ // /mentorship or /mentorship status - show status immediately
1483
+ if (!parsed.subcommand || parsed.subcommand === 'status') {
1484
+ const summary = mentorshipService.getSummary();
1485
+ const statusColor = summary.status === 'enabled' ? chalk.green : chalk.red;
1486
+ console.log();
1487
+ console.log(` Mentorship: ${statusColor(summary.status.toUpperCase())}`);
1488
+ console.log(` Helper: ${chalk.cyan(summary.helperModel)}`);
1489
+ console.log(` Triggers: ${chalk.dim(summary.triggers.join(', '))}`);
1490
+ console.log();
1491
+ console.log(theme.dimmed(' /mentorship enable|disable|config'));
1492
+ console.log();
1493
+ prompt();
1494
+ return;
1495
+ }
1496
+ if (parsed.subcommand === 'enable') {
1497
+ await mentorshipService.quickEnable();
1498
+ client.updateRuntimeConfig({
1499
+ reactiveMentorship: {
1500
+ enabled: true,
1501
+ triggerOnError: true,
1502
+ errorSeverityThreshold: 'medium',
1503
+ enableKeywords: false,
1504
+ patternDetection: true,
1505
+ },
1506
+ });
1507
+ console.log();
1508
+ console.log(theme.successMessage('Mentorship enabled'));
1509
+ console.log();
1510
+ prompt();
1511
+ return;
1512
+ }
1513
+ if (parsed.subcommand === 'disable') {
1514
+ await mentorshipService.quickDisable();
1515
+ client.updateRuntimeConfig({
1516
+ reactiveMentorship: { enabled: false, triggerOnError: false, errorSeverityThreshold: 'medium', enableKeywords: false },
1517
+ });
1518
+ console.log();
1519
+ console.log(theme.dimmed('Mentorship disabled'));
1520
+ console.log();
1521
+ prompt();
1522
+ return;
1523
+ }
1524
+ // /mentorship config - open interactive menu for power users
1525
+ if (parsed.subcommand === 'config') {
1526
+ try {
1527
+ const menuDef = mentorshipService.getMenuDefinition();
1528
+ const initialValues = {};
1529
+ const config = mentorshipService.getConfig();
1530
+ for (const section of menuDef.sections) {
1531
+ for (const item of section.items) {
1532
+ if (item.key in config) {
1533
+ initialValues[item.key] = config[item.key];
1534
+ }
1535
+ }
1536
+ }
1537
+ console.log();
1538
+ const result = await showInteractiveMenu(menuDef, initialValues, async (key, value) => {
1539
+ const configKey = key.replace('MENTORSHIP_', '').toLowerCase().replace(/_([a-z])/g, (_, c) => c.toUpperCase());
1540
+ await mentorshipService.setConfig({ [configKey]: value });
1541
+ });
1542
+ if (result.action === 'save' && result.hasChanges) {
1543
+ const updates = {};
1544
+ for (const [key, value] of Object.entries(result.changes)) {
1545
+ const configKey = key.replace('MENTORSHIP_', '').toLowerCase().replace(/_([a-z])/g, (_, c) => c.toUpperCase());
1546
+ updates[configKey] = value;
1547
+ }
1548
+ await mentorshipService.setConfig(updates);
1549
+ console.log();
1550
+ console.log(theme.successMessage('Configuration saved'));
1551
+ }
1552
+ else if (result.action === 'reset') {
1553
+ await mentorshipService.resetToDefaults();
1554
+ console.log();
1555
+ console.log(theme.successMessage('Reset to defaults'));
1556
+ }
1557
+ console.log();
1558
+ }
1559
+ catch (error) {
1560
+ console.log();
1561
+ console.log(theme.errorMessage(`Error: ${error.message}`));
1562
+ console.log();
1563
+ }
1564
+ prompt();
1565
+ return;
1566
+ }
1567
+ // Unknown subcommand
1568
+ console.log();
1569
+ console.log(theme.errorMessage(`Unknown: /mentorship ${parsed.subcommand}`));
1570
+ console.log(theme.dimmed(' /mentorship [status|enable|disable|config]'));
1571
+ console.log();
1572
+ prompt();
1573
+ return;
1574
+ }
1575
+ default:
1576
+ // Check if command exists in registry
1577
+ const command = slashCommandRegistry.getCommand(parsed.command);
1578
+ if (command) {
1579
+ console.log();
1580
+ console.log(theme.warningMessage(`Command /${parsed.command} is registered but not yet implemented`));
1581
+ console.log(theme.dimmed(`[i] Coming soon! For now, use the command palette (/) to explore available commands`));
1582
+ console.log();
1583
+ }
1584
+ else {
1585
+ console.log();
1586
+ console.log(theme.errorMessage(`Unknown command: /${parsed.command}`));
1587
+ console.log(theme.dimmed(`[i] Type / to see available commands or /help for more information`));
1588
+ console.log();
1589
+ }
1590
+ prompt();
1591
+ return;
1592
+ }
1593
+ }
1594
+ const prompt = async () => {
1595
+ // Use raw terminal input with persistent status line below
1596
+ const userInput = (await rawQuestion({
1597
+ getStatusState,
1598
+ theme,
1599
+ onToggleThinking,
1600
+ onToggleAutoApprove,
1601
+ onToggleDocuments,
1602
+ onEscape,
1603
+ })).trim();
1604
+ // Handle empty input - just reprompt
1605
+ if (!userInput) {
1606
+ prompt();
1607
+ return;
1608
+ }
1609
+ if (process.env.DEBUG === 'true') {
1610
+ console.log('[DEBUG] Received input:', userInput);
1611
+ }
1612
+ // Parse slash commands
1613
+ const parsed = parseSlashCommand(userInput);
1614
+ if (process.env.DEBUG === 'true') {
1615
+ console.log('[DEBUG] Is command:', parsed.isCommand);
1616
+ }
1617
+ if (parsed.isCommand) {
1618
+ // Handle slash commands
1619
+ await handleSlashCommand(parsed, theme, rl, prompt);
1620
+ return;
1621
+ }
1622
+ if (process.env.DEBUG === 'true') {
1623
+ console.log('[DEBUG] Not a slash command, proceeding to message handling');
1624
+ }
1625
+ // Handle legacy text commands for backwards compatibility
1626
+ if (userInput.toLowerCase() === 'exit') {
1627
+ console.log();
1628
+ console.log(theme.roundedBox('Thank you for using Nexus Cortex!\n' +
1629
+ 'Your session has been saved.', 'Goodbye'));
1630
+ console.log();
1631
+ rl.close();
1632
+ process.exit(0);
1633
+ return;
1634
+ }
1635
+ if (userInput.toLowerCase() === 'clear') {
1636
+ // Note: In direct mode, the orchestrator maintains its own history
1637
+ // This command doesn't actually clear it yet - that would require a new session
1638
+ console.log();
1639
+ console.log(theme.warningMessage('Note: History clearing not yet implemented'));
1640
+ console.log(theme.dimmed('[i] In direct mode, restart the CLI to start a fresh session'));
1641
+ console.log();
1642
+ prompt();
1643
+ return;
1644
+ }
1645
+ if (process.env.DEBUG === 'true') {
1646
+ console.log('[DEBUG] About to enter streaming try block');
1647
+ }
1648
+ // Process the message with persistent input box
1649
+ await processMessage(userInput);
1650
+ // Check for queued messages and process them
1651
+ while (persistentInput.hasQueuedMessages()) {
1652
+ const queued = persistentInput.getQueuedMessages();
1653
+ for (const queuedMessage of queued) {
1654
+ await processMessage(queuedMessage);
1655
+ }
1656
+ }
1657
+ prompt();
1658
+ };
1659
+ /**
1660
+ * Process a single message with streaming and persistent input
1661
+ */
1662
+ async function processMessage(messageText) {
1663
+ // Start capturing input during streaming
1664
+ persistentInput.startCapture();
1665
+ try {
1666
+ // Note: User input was already echoed by rawQuestion's clearInputFrame()
1667
+ // so we don't need to echo it again here
1668
+ // Show thinking indicator
1669
+ persistentInput.showThinking();
1670
+ let thinkingCleared = false;
1671
+ // Create abort controller for this request (allows ESC to interrupt tool execution)
1672
+ currentAbortController = new AbortController();
1673
+ // Stream the message with real-time display
1674
+ const stream = client.streamMessage(messageText, {
1675
+ model: options.model,
1676
+ system: options.system,
1677
+ max_tokens: options.maxTokens,
1678
+ temperature: options.temperature,
1679
+ tools: [], // Enable all tools (empty array = use all default tools)
1680
+ reasoningEffort, // Pass GPT-5 reasoning effort (none, low, medium, high)
1681
+ abortSignal: currentAbortController.signal, // Allow ESC to abort tool execution
1682
+ });
1683
+ // Accumulate response for final display
1684
+ let fullTextContent = '';
1685
+ const toolsUsed = [];
1686
+ let hasStartedThinking = false; // Track if we've shown thinking header
1687
+ let turnUsageData = null;
1688
+ let turnDurationMs = null;
1689
+ // Reset markdown renderer for new message
1690
+ markdownRenderer.reset();
1691
+ // Reset ESC/abort flags before streaming
1692
+ escPressed = false;
1693
+ isAborting = false;
1694
+ // Process streaming chunks with inner try-catch to handle abort errors
1695
+ // The stream iteration can throw when abort() is called, even if the
1696
+ // underlying SDK doesn't properly support AbortSignal
1697
+ let streamingAborted = false;
1698
+ try {
1699
+ for await (const chunk of stream) {
1700
+ // Check if user pressed ESC to abort FIRST (before processing chunk)
1701
+ if (escPressed) {
1702
+ persistentInput.printLine(); // New line after abort message
1703
+ persistentInput.printLine(theme.colors.warning('⏸ Operation halted by user (ESC pressed)'));
1704
+ streamingAborted = true;
1705
+ break; // Exit streaming loop immediately
1706
+ }
1707
+ if (showDebug) {
1708
+ persistentInput.printLine(`[CLI] Received chunk type: ${chunk.type}, delta length: ${chunk.delta?.length || 0}`);
1709
+ }
1710
+ // PHASE 2.8: Interleaved reasoning (Grok, Claude, OpenAI)
1711
+ // content_block_delta with reasoning=true is the model's natural reasoning output.
1712
+ // ALWAYS shown — matches neoncortex behavior. Tab toggle only controls
1713
+ // front/rear-loaded extended thinking (thinking_delta chunks below).
1714
+ if (chunk.type === 'content_block_delta') {
1715
+ const data = chunk.data;
1716
+ if (data.reasoning === true) {
1717
+ if (!thinkingCleared) {
1718
+ persistentInput.clearThinking();
1719
+ thinkingCleared = true;
1720
+ }
1721
+ const thinkingText = chunk.delta || '';
1722
+ if (thinkingText) {
1723
+ persistentInput.writeOutput(chalk.dim(chalk.italic(thinkingText)));
1724
+ }
1725
+ continue;
1726
+ }
1727
+ }
1728
+ // Extended thinking blocks (Gemini <thinking>, Claude <thinking>)
1729
+ // Controlled by Tab toggle — these are front/rear-loaded, not interleaved.
1730
+ if (chunk.type === 'thinking_delta') {
1731
+ if (!thinkingCleared) {
1732
+ persistentInput.clearThinking();
1733
+ thinkingCleared = true;
1734
+ }
1735
+ if (showThinking || isInToolExecution) {
1736
+ if (!hasStartedThinking) {
1737
+ persistentInput.printLine();
1738
+ hasStartedThinking = true;
1739
+ }
1740
+ const thinkingText = chunk.delta || '';
1741
+ if (thinkingText) {
1742
+ persistentInput.writeOutput(chalk.dim(chalk.italic(thinkingText)));
1743
+ }
1744
+ }
1745
+ continue;
1746
+ }
1747
+ // Regular text content (may include mentorship thinking)
1748
+ if (chunk.type === 'text_delta') {
1749
+ // Clear thinking indicator on first content
1750
+ if (!thinkingCleared && chunk.delta) {
1751
+ persistentInput.clearThinking();
1752
+ thinkingCleared = true;
1753
+ }
1754
+ // Reset thinking header flag when text starts (allows multiple thinking blocks)
1755
+ if (hasStartedThinking && chunk.delta) {
1756
+ persistentInput.printLine(); // Add newline after thinking
1757
+ hasStartedThinking = false;
1758
+ }
1759
+ // When we get substantive text after tool execution, mark tool execution as complete
1760
+ // This means any further thinking is extended thinking (respects Tab toggle)
1761
+ if (isInToolExecution && chunk.delta && chunk.delta.trim().length > 0) {
1762
+ isInToolExecution = false;
1763
+ }
1764
+ let text = chunk.delta || '';
1765
+ // DEBUG Mode: Filter out debug/log messages unless showDebug is enabled
1766
+ // Skip chunks that are debug logs (start with [ or contain system markers)
1767
+ const isDebugLog = text.startsWith('[') ||
1768
+ text.includes('[Gateway]') ||
1769
+ text.includes('[Orchestrator') ||
1770
+ text.includes('[XAI Messages API]') ||
1771
+ text.includes('[Phase ') ||
1772
+ text.includes('Validated tool format:') ||
1773
+ text.includes('"name":') && text.includes('"description":') && text.includes('"input_schema":');
1774
+ if (isDebugLog && !showDebug) {
1775
+ continue; // Skip debug output - don't display or accumulate
1776
+ }
1777
+ // If debug mode is ON and this is a debug log, colorize it
1778
+ if (isDebugLog && showDebug) {
1779
+ text = theme.dimmed(text);
1780
+ }
1781
+ // PHASE 2.8: Mentorship Thinking (from @ultrathink)
1782
+ // Parse <thinking> tags in text content
1783
+ if (text.includes('<thinking>') || text.includes('</thinking>')) {
1784
+ if (showThinking) {
1785
+ // Show mentorship thinking with blue bold (emoji optional based on USE_EMOJI setting)
1786
+ const useEmoji = process.env.USE_EMOJI === 'true';
1787
+ const prefix = useEmoji ? ' ' : '[Thinking] ';
1788
+ text = text.replace(/<thinking>([\s\S]*?)<\/thinking>/g, (_match, content) => theme.colors.info(`\n${prefix}${content.trim()}\n`));
1789
+ persistentInput.writeOutput(text);
1790
+ }
1791
+ else {
1792
+ // Hide mentorship thinking if toggle is OFF
1793
+ text = text.replace(/<thinking>[\s\S]*?<\/thinking>/g, '');
1794
+ if (text) {
1795
+ // Process through markdown renderer
1796
+ const formatted = markdownRenderer.processChunk(text);
1797
+ persistentInput.writeOutput(formatted);
1798
+ }
1799
+ }
1800
+ }
1801
+ else {
1802
+ // Regular text with no thinking tags - render as markdown
1803
+ const formatted = markdownRenderer.processChunk(text);
1804
+ persistentInput.writeOutput(formatted);
1805
+ }
1806
+ fullTextContent += chunk.delta || '';
1807
+ }
1808
+ // Tool execution complete
1809
+ if (chunk.type === 'tool_use_complete') {
1810
+ // Mark that we're in tool execution mode (for interleaved thinking)
1811
+ isInToolExecution = true;
1812
+ const toolUse = chunk.toolUse;
1813
+ if (toolUse) {
1814
+ persistentInput.setPhase(`Running ${toolUse.name}`);
1815
+ // Store for summary (backward compat)
1816
+ toolsUsed.push({
1817
+ name: toolUse.name,
1818
+ args: toolUse.input,
1819
+ status: 'success',
1820
+ result: '',
1821
+ duration: 0
1822
+ });
1823
+ // Display tool call with new formatter
1824
+ // Note: Diff/file previews are shown via previewRenderer callback BEFORE
1825
+ // the approval prompt, so we don't need to show them here
1826
+ persistentInput.printLine();
1827
+ const toolCall = toolFormatter.formatToolCall({
1828
+ name: toolUse.name,
1829
+ params: toolUse.input
1830
+ });
1831
+ persistentInput.writeOutput(toolCall);
1832
+ }
1833
+ }
1834
+ // Tool result received (emitted after tool execution)
1835
+ if (chunk.type === 'tool_result') {
1836
+ const toolResult = chunk.toolResult;
1837
+ if (toolResult) {
1838
+ const MAX_RESULT_DISPLAY = 20000;
1839
+ if (toolResult.tool_name === 'Edit') {
1840
+ if (toolResult.is_error) {
1841
+ const result = toolFormatter.formatToolResult({
1842
+ error: toolResult.content
1843
+ });
1844
+ persistentInput.writeOutput(result);
1845
+ }
1846
+ else {
1847
+ const stats = toolResult.metadata?.fileStats;
1848
+ const occurrences = stats?.occurrences || 1;
1849
+ const rawDiff = toolResult.metadata?.diff;
1850
+ if (rawDiff && typeof rawDiff === 'string') {
1851
+ const parsed = parseUnifiedDiff(rawDiff);
1852
+ if (parsed && parsed.chunks.length > 0) {
1853
+ const diffOutput = toolFormatter.formatDiff(parsed);
1854
+ persistentInput.writeOutput(diffOutput);
1855
+ }
1856
+ }
1857
+ const result = toolFormatter.formatToolResult({
1858
+ summary: `Applied (${occurrences} replacement${occurrences > 1 ? 's' : ''})`
1859
+ });
1860
+ persistentInput.writeOutput(result);
1861
+ }
1862
+ }
1863
+ else if (toolResult.tool_name?.toLowerCase() === 'write') {
1864
+ if (toolResult.is_error) {
1865
+ const result = toolFormatter.formatToolResult({
1866
+ error: toolResult.content
1867
+ });
1868
+ persistentInput.writeOutput(result);
1869
+ }
1870
+ else {
1871
+ const writePreview = toolResult.metadata?.writePreview;
1872
+ const docPreview = toolResult.metadata?.documentPreview;
1873
+ if (writePreview?.content) {
1874
+ const filePreview = toolFormatter.formatFileContent({
1875
+ file: writePreview.filePath || 'file',
1876
+ content: writePreview.content,
1877
+ lineCount: writePreview.lineCount || writePreview.content.split('\n').length,
1878
+ preview: writePreview.content.length > MAX_RESULT_DISPLAY,
1879
+ });
1880
+ persistentInput.writeOutput(filePreview);
1881
+ }
1882
+ else if (docPreview) {
1883
+ const wordInfo = docPreview.wordCount ? `, ${docPreview.wordCount} words` : '';
1884
+ const result = toolFormatter.formatToolResult({
1885
+ summary: `Created ${docPreview.filePath} (${docPreview.lineCount} lines${wordInfo})`
1886
+ });
1887
+ persistentInput.writeOutput(result);
1888
+ }
1889
+ else {
1890
+ const stats = toolResult.metadata?.fileStats;
1891
+ const sizeInfo = stats?.size ? ` (${stats.size} bytes)` : '';
1892
+ const result = toolFormatter.formatToolResult({
1893
+ summary: `File created successfully${sizeInfo}`
1894
+ });
1895
+ persistentInput.writeOutput(result);
1896
+ }
1897
+ }
1898
+ }
1899
+ else if (toolResult.tool_name === 'Bash') {
1900
+ if (toolResult.is_error) {
1901
+ const result = toolFormatter.formatToolResult({
1902
+ error: toolResult.content
1903
+ });
1904
+ persistentInput.writeOutput(result);
1905
+ }
1906
+ else {
1907
+ const content = toolResult.content.trim();
1908
+ if (content && content !== '(command completed successfully)') {
1909
+ let displayContent = content;
1910
+ let truncatedNote = '';
1911
+ if (displayContent.length > MAX_RESULT_DISPLAY) {
1912
+ const lines = displayContent.split('\n');
1913
+ const totalLines = lines.length;
1914
+ displayContent = displayContent.substring(0, MAX_RESULT_DISPLAY);
1915
+ const keptLines = displayContent.split('\n').length;
1916
+ truncatedNote = `\n... (${totalLines - keptLines} lines truncated)`;
1917
+ }
1918
+ const result = toolFormatter.formatToolResult({
1919
+ summary: displayContent + truncatedNote
1920
+ });
1921
+ persistentInput.writeOutput(result);
1922
+ }
1923
+ else {
1924
+ const result = toolFormatter.formatToolResult({
1925
+ summary: '(success)'
1926
+ });
1927
+ persistentInput.writeOutput(result);
1928
+ }
1929
+ }
1930
+ }
1931
+ else if (toolResult.tool_name === 'Read' && toolResult.metadata?.fileStats) {
1932
+ const stats = toolResult.metadata.fileStats;
1933
+ const lineInfo = stats.lines ? ` (${stats.lines} lines)` : '';
1934
+ const result = toolFormatter.formatToolResult({
1935
+ summary: `Read ${stats.path}${lineInfo}`
1936
+ });
1937
+ persistentInput.writeOutput(result);
1938
+ }
1939
+ else if (toolResult.is_error) {
1940
+ const result = toolFormatter.formatToolResult({
1941
+ error: toolResult.content
1942
+ });
1943
+ persistentInput.writeOutput(result);
1944
+ }
1945
+ else {
1946
+ let content = toolResult.content;
1947
+ if (content.length > MAX_RESULT_DISPLAY) {
1948
+ const totalLines = content.split('\n').length;
1949
+ content = content.substring(0, MAX_RESULT_DISPLAY);
1950
+ const keptLines = content.split('\n').length;
1951
+ content += `\n... (${totalLines - keptLines} lines truncated)`;
1952
+ }
1953
+ const result = toolFormatter.formatToolResult({
1954
+ summary: content.split('\n')[0] || 'Success',
1955
+ details: content.split('\n').slice(1).join('\n')
1956
+ });
1957
+ persistentInput.writeOutput(result);
1958
+ }
1959
+ }
1960
+ }
1961
+ // Message complete
1962
+ if (chunk.type === 'message_stop') {
1963
+ // Flush any remaining markdown content
1964
+ const flushed = markdownRenderer.flush();
1965
+ if (flushed) {
1966
+ persistentInput.writeOutput(flushed);
1967
+ }
1968
+ persistentInput.printLine(); // Final newline
1969
+ // Capture usage data for turn summary
1970
+ if (chunk.data?.usage) {
1971
+ turnUsageData = chunk.data.usage;
1972
+ }
1973
+ if (chunk.data?.durationMs) {
1974
+ turnDurationMs = chunk.data.durationMs;
1975
+ }
1976
+ }
1977
+ }
1978
+ }
1979
+ catch (streamError) {
1980
+ // Catch errors from the streaming loop (including abort-related errors)
1981
+ // If ESC was pressed, this is expected - handle gracefully
1982
+ if (escPressed || isAborting) {
1983
+ streamingAborted = true;
1984
+ if (process.env.DEBUG === 'true') {
1985
+ console.error('[DEBUG] Stream error during ESC abort (expected):', streamError);
1986
+ }
1987
+ }
1988
+ else {
1989
+ // Re-throw non-abort errors to be handled by outer catch
1990
+ throw streamError;
1991
+ }
1992
+ }
1993
+ // Check if user aborted with ESC key
1994
+ if (escPressed || streamingAborted) {
1995
+ persistentInput.printLine();
1996
+ persistentInput.printLine(theme.colors.warning('Operation was halted. Please provide feedback:'));
1997
+ persistentInput.printLine(theme.dimmed(' • Why did you halt the operation?'));
1998
+ persistentInput.printLine(theme.dimmed(' • What would you like me to do instead?'));
1999
+ persistentInput.printLine(theme.dimmed(' • Is the investigation going off track?'));
2000
+ persistentInput.printLine();
2001
+ escPressed = false;
2002
+ isAborting = false;
2003
+ }
2004
+ // Turn summary line — CC-style: "duration · input/output tokens · tools"
2005
+ const elapsedMs = turnDurationMs || persistentInput.getElapsed();
2006
+ const parts = [];
2007
+ if (elapsedMs > 0) {
2008
+ const s = elapsedMs / 1000;
2009
+ parts.push(s < 10 ? `${s.toFixed(1)}s` : s < 60 ? `${Math.floor(s)}s` : `${Math.floor(s / 60)}m ${Math.floor(s % 60)}s`);
2010
+ }
2011
+ if (turnUsageData && (turnUsageData.inputTokens > 0 || turnUsageData.outputTokens > 0)) {
2012
+ parts.push(`${turnUsageData.inputTokens.toLocaleString()} in / ${turnUsageData.outputTokens.toLocaleString()} out`);
2013
+ }
2014
+ if (toolsUsed.length > 0) {
2015
+ parts.push(`${toolsUsed.length} tool${toolsUsed.length !== 1 ? 's' : ''}`);
2016
+ }
2017
+ if (parts.length > 0) {
2018
+ persistentInput.printLine();
2019
+ persistentInput.printLine(theme.dimmed(parts.join(' · ')));
2020
+ }
2021
+ }
2022
+ catch (error) {
2023
+ // Show error (but be aware it might be abort-related)
2024
+ persistentInput.printLine();
2025
+ // If this is an abort error, show a gentler message
2026
+ if (escPressed || isAborting) {
2027
+ // Abort-related errors - show minimal info
2028
+ persistentInput.printLine(theme.dimmed('(Operation interrupted)'));
2029
+ }
2030
+ else {
2031
+ // Regular error - show full details
2032
+ persistentInput.printLine(theme.errorMessage(error.message));
2033
+ persistentInput.printLine();
2034
+ // Show help for common errors
2035
+ if (error.message?.includes('ECONNREFUSED')) {
2036
+ persistentInput.printLine(theme.infoMessage('Hint: Make sure the server is running'));
2037
+ persistentInput.printLine(theme.dimmed(` Start server: cortex-dev or npm run dev:full`));
2038
+ }
2039
+ else if (error.message?.includes('Streaming not yet supported')) {
2040
+ persistentInput.printLine(theme.infoMessage('Hint: Use direct mode (default) for streaming'));
2041
+ persistentInput.printLine(theme.dimmed(` Remove --server flag to use direct mode`));
2042
+ }
2043
+ }
2044
+ }
2045
+ finally {
2046
+ // Clean up abort state
2047
+ currentAbortController = null;
2048
+ isAborting = false;
2049
+ // Stop capturing and restore normal terminal
2050
+ persistentInput.stopCapture();
2051
+ }
2052
+ }
2053
+ prompt();
2054
+ }
2055
+ //# sourceMappingURL=interactive.js.map