@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.
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +13 -0
- package/bin/cli-full.js +23 -0
- package/bin/cortex-ui.js +26 -0
- package/bin/ink-ui.js +108 -0
- package/bin/launcher-dev.js +266 -0
- package/bin/launcher.js +516 -0
- package/dist/commands/agent/InteractiveAgentMenu.d.ts +100 -0
- package/dist/commands/agent/InteractiveAgentMenu.d.ts.map +1 -0
- package/dist/commands/agent/InteractiveAgentMenu.js +909 -0
- package/dist/commands/agent/InteractiveAgentMenu.js.map +1 -0
- package/dist/commands/agent/index.d.ts +55 -0
- package/dist/commands/agent/index.d.ts.map +1 -0
- package/dist/commands/agent/index.js +714 -0
- package/dist/commands/agent/index.js.map +1 -0
- package/dist/commands/chat/interactive.d.ts +13 -0
- package/dist/commands/chat/interactive.d.ts.map +1 -0
- package/dist/commands/chat/interactive.js +2055 -0
- package/dist/commands/chat/interactive.js.map +1 -0
- package/dist/commands/system-message/EditorLauncher.d.ts +65 -0
- package/dist/commands/system-message/EditorLauncher.d.ts.map +1 -0
- package/dist/commands/system-message/EditorLauncher.js +143 -0
- package/dist/commands/system-message/EditorLauncher.js.map +1 -0
- package/dist/commands/system-message/InlineEditor.d.ts +23 -0
- package/dist/commands/system-message/InlineEditor.d.ts.map +1 -0
- package/dist/commands/system-message/InlineEditor.js +225 -0
- package/dist/commands/system-message/InlineEditor.js.map +1 -0
- package/dist/commands/system-message/InteractiveMenu.d.ts +76 -0
- package/dist/commands/system-message/InteractiveMenu.d.ts.map +1 -0
- package/dist/commands/system-message/InteractiveMenu.js +979 -0
- package/dist/commands/system-message/InteractiveMenu.js.map +1 -0
- package/dist/commands/system-message/index.d.ts +56 -0
- package/dist/commands/system-message/index.d.ts.map +1 -0
- package/dist/commands/system-message/index.js +694 -0
- package/dist/commands/system-message/index.js.map +1 -0
- package/dist/commands/ui/artifacts.d.ts +14 -0
- package/dist/commands/ui/artifacts.d.ts.map +1 -0
- package/dist/commands/ui/artifacts.js +22 -0
- package/dist/commands/ui/artifacts.js.map +1 -0
- package/dist/commands/ui/config.d.ts +12 -0
- package/dist/commands/ui/config.d.ts.map +1 -0
- package/dist/commands/ui/config.js +26 -0
- package/dist/commands/ui/config.js.map +1 -0
- package/dist/commands/ui/context.d.ts +16 -0
- package/dist/commands/ui/context.d.ts.map +1 -0
- package/dist/commands/ui/context.js +28 -0
- package/dist/commands/ui/context.js.map +1 -0
- package/dist/commands/ui/middleware.d.ts +14 -0
- package/dist/commands/ui/middleware.d.ts.map +1 -0
- package/dist/commands/ui/middleware.js +22 -0
- package/dist/commands/ui/middleware.js.map +1 -0
- package/dist/commands/ui/models.d.ts +14 -0
- package/dist/commands/ui/models.d.ts.map +1 -0
- package/dist/commands/ui/models.js +50 -0
- package/dist/commands/ui/models.js.map +1 -0
- package/dist/commands/ui/permissions.d.ts +14 -0
- package/dist/commands/ui/permissions.d.ts.map +1 -0
- package/dist/commands/ui/permissions.js +22 -0
- package/dist/commands/ui/permissions.js.map +1 -0
- package/dist/commands/ui/sessions.d.ts +13 -0
- package/dist/commands/ui/sessions.d.ts.map +1 -0
- package/dist/commands/ui/sessions.js +45 -0
- package/dist/commands/ui/sessions.js.map +1 -0
- package/dist/commands/ui/system-messages.d.ts +5 -0
- package/dist/commands/ui/system-messages.d.ts.map +1 -0
- package/dist/commands/ui/system-messages.js +19 -0
- package/dist/commands/ui/system-messages.js.map +1 -0
- package/dist/commands/ui/themes.d.ts +12 -0
- package/dist/commands/ui/themes.d.ts.map +1 -0
- package/dist/commands/ui/themes.js +40 -0
- package/dist/commands/ui/themes.js.map +1 -0
- package/dist/commands/ui/tmux.d.ts +5 -0
- package/dist/commands/ui/tmux.d.ts.map +1 -0
- package/dist/commands/ui/tmux.js +19 -0
- package/dist/commands/ui/tmux.js.map +1 -0
- package/dist/cortex.d.ts +12 -0
- package/dist/cortex.d.ts.map +1 -0
- package/dist/cortex.js +320 -0
- package/dist/cortex.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +112 -0
- package/dist/index.js.map +1 -0
- package/dist/ink-ui/CortexApp.d.ts +28 -0
- package/dist/ink-ui/CortexApp.d.ts.map +1 -0
- package/dist/ink-ui/CortexApp.js +2291 -0
- package/dist/ink-ui/CortexApp.js.map +1 -0
- package/dist/ink-ui/SimpleInputBuffer.d.ts +63 -0
- package/dist/ink-ui/SimpleInputBuffer.d.ts.map +1 -0
- package/dist/ink-ui/SimpleInputBuffer.js +369 -0
- package/dist/ink-ui/SimpleInputBuffer.js.map +1 -0
- package/dist/ink-ui/commands/slashCommands.d.ts +40 -0
- package/dist/ink-ui/commands/slashCommands.d.ts.map +1 -0
- package/dist/ink-ui/commands/slashCommands.js +111 -0
- package/dist/ink-ui/commands/slashCommands.js.map +1 -0
- package/dist/ink-ui/commands/types.d.ts +161 -0
- package/dist/ink-ui/commands/types.d.ts.map +1 -0
- package/dist/ink-ui/commands/types.js +12 -0
- package/dist/ink-ui/commands/types.js.map +1 -0
- package/dist/ink-ui/components/ApprovalDialog.d.ts +21 -0
- package/dist/ink-ui/components/ApprovalDialog.d.ts.map +1 -0
- package/dist/ink-ui/components/ApprovalDialog.js +81 -0
- package/dist/ink-ui/components/ApprovalDialog.js.map +1 -0
- package/dist/ink-ui/components/CommandSuggestions.d.ts +19 -0
- package/dist/ink-ui/components/CommandSuggestions.d.ts.map +1 -0
- package/dist/ink-ui/components/CommandSuggestions.js +36 -0
- package/dist/ink-ui/components/CommandSuggestions.js.map +1 -0
- package/dist/ink-ui/components/DiffPreview.d.ts +60 -0
- package/dist/ink-ui/components/DiffPreview.d.ts.map +1 -0
- package/dist/ink-ui/components/DiffPreview.js +195 -0
- package/dist/ink-ui/components/DiffPreview.js.map +1 -0
- package/dist/ink-ui/components/DocumentPreview.d.ts +50 -0
- package/dist/ink-ui/components/DocumentPreview.d.ts.map +1 -0
- package/dist/ink-ui/components/DocumentPreview.js +92 -0
- package/dist/ink-ui/components/DocumentPreview.js.map +1 -0
- package/dist/ink-ui/components/MarkdownText.d.ts +27 -0
- package/dist/ink-ui/components/MarkdownText.d.ts.map +1 -0
- package/dist/ink-ui/components/MarkdownText.js +342 -0
- package/dist/ink-ui/components/MarkdownText.js.map +1 -0
- package/dist/ink-ui/components/MenuRenderer.d.ts +26 -0
- package/dist/ink-ui/components/MenuRenderer.d.ts.map +1 -0
- package/dist/ink-ui/components/MenuRenderer.js +264 -0
- package/dist/ink-ui/components/MenuRenderer.js.map +1 -0
- package/dist/ink-ui/components/ModelPickerDialog.d.ts +33 -0
- package/dist/ink-ui/components/ModelPickerDialog.d.ts.map +1 -0
- package/dist/ink-ui/components/ModelPickerDialog.js +98 -0
- package/dist/ink-ui/components/ModelPickerDialog.js.map +1 -0
- package/dist/ink-ui/components/StreamDisplay.d.ts +31 -0
- package/dist/ink-ui/components/StreamDisplay.d.ts.map +1 -0
- package/dist/ink-ui/components/StreamDisplay.js +573 -0
- package/dist/ink-ui/components/StreamDisplay.js.map +1 -0
- package/dist/ink-ui/components/SubAgentPanel.d.ts +98 -0
- package/dist/ink-ui/components/SubAgentPanel.d.ts.map +1 -0
- package/dist/ink-ui/components/SubAgentPanel.js +155 -0
- package/dist/ink-ui/components/SubAgentPanel.js.map +1 -0
- package/dist/ink-ui/components/ThinkingDisplay.d.ts +46 -0
- package/dist/ink-ui/components/ThinkingDisplay.d.ts.map +1 -0
- package/dist/ink-ui/components/ThinkingDisplay.js +176 -0
- package/dist/ink-ui/components/ThinkingDisplay.js.map +1 -0
- package/dist/ink-ui/components/ToolCallDisplay.d.ts +51 -0
- package/dist/ink-ui/components/ToolCallDisplay.d.ts.map +1 -0
- package/dist/ink-ui/components/ToolCallDisplay.js +378 -0
- package/dist/ink-ui/components/ToolCallDisplay.js.map +1 -0
- package/dist/ink-ui/components/WritePreview.d.ts +42 -0
- package/dist/ink-ui/components/WritePreview.d.ts.map +1 -0
- package/dist/ink-ui/components/WritePreview.js +100 -0
- package/dist/ink-ui/components/WritePreview.js.map +1 -0
- package/dist/ink-ui/contexts/KeypressContext.d.ts +32 -0
- package/dist/ink-ui/contexts/KeypressContext.d.ts.map +1 -0
- package/dist/ink-ui/contexts/KeypressContext.js +529 -0
- package/dist/ink-ui/contexts/KeypressContext.js.map +1 -0
- package/dist/ink-ui/contexts/SessionContext.d.ts +46 -0
- package/dist/ink-ui/contexts/SessionContext.d.ts.map +1 -0
- package/dist/ink-ui/contexts/SessionContext.js +162 -0
- package/dist/ink-ui/contexts/SessionContext.js.map +1 -0
- package/dist/ink-ui/core-stubs.d.ts +270 -0
- package/dist/ink-ui/core-stubs.d.ts.map +1 -0
- package/dist/ink-ui/core-stubs.js +276 -0
- package/dist/ink-ui/core-stubs.js.map +1 -0
- package/dist/ink-ui/cortex-types.d.ts +214 -0
- package/dist/ink-ui/cortex-types.d.ts.map +1 -0
- package/dist/ink-ui/cortex-types.js +52 -0
- package/dist/ink-ui/cortex-types.js.map +1 -0
- package/dist/ink-ui/hooks/useCortexStream.d.ts +75 -0
- package/dist/ink-ui/hooks/useCortexStream.d.ts.map +1 -0
- package/dist/ink-ui/hooks/useCortexStream.js +518 -0
- package/dist/ink-ui/hooks/useCortexStream.js.map +1 -0
- package/dist/ink-ui/hooks/useFocus.d.ts +11 -0
- package/dist/ink-ui/hooks/useFocus.d.ts.map +1 -0
- package/dist/ink-ui/hooks/useFocus.js +51 -0
- package/dist/ink-ui/hooks/useFocus.js.map +1 -0
- package/dist/ink-ui/hooks/useHistoryManager.d.ts +23 -0
- package/dist/ink-ui/hooks/useHistoryManager.d.ts.map +1 -0
- package/dist/ink-ui/hooks/useHistoryManager.js +63 -0
- package/dist/ink-ui/hooks/useHistoryManager.js.map +1 -0
- package/dist/ink-ui/hooks/useKeypress.d.ts +18 -0
- package/dist/ink-ui/hooks/useKeypress.d.ts.map +1 -0
- package/dist/ink-ui/hooks/useKeypress.js +27 -0
- package/dist/ink-ui/hooks/useKeypress.js.map +1 -0
- package/dist/ink-ui/hooks/useReactApprovalHandler.d.ts +74 -0
- package/dist/ink-ui/hooks/useReactApprovalHandler.d.ts.map +1 -0
- package/dist/ink-ui/hooks/useReactApprovalHandler.js +83 -0
- package/dist/ink-ui/hooks/useReactApprovalHandler.js.map +1 -0
- package/dist/ink-ui/hooks/useTerminalSize.d.ts +10 -0
- package/dist/ink-ui/hooks/useTerminalSize.d.ts.map +1 -0
- package/dist/ink-ui/hooks/useTerminalSize.js +28 -0
- package/dist/ink-ui/hooks/useTerminalSize.js.map +1 -0
- package/dist/ink-ui/semantic-colors.d.ts +8 -0
- package/dist/ink-ui/semantic-colors.d.ts.map +1 -0
- package/dist/ink-ui/semantic-colors.js +24 -0
- package/dist/ink-ui/semantic-colors.js.map +1 -0
- package/dist/ink-ui/state/extensions.d.ts +68 -0
- package/dist/ink-ui/state/extensions.d.ts.map +1 -0
- package/dist/ink-ui/state/extensions.js +97 -0
- package/dist/ink-ui/state/extensions.js.map +1 -0
- package/dist/ink-ui/themes/ansi-light.d.ts +8 -0
- package/dist/ink-ui/themes/ansi-light.d.ts.map +1 -0
- package/dist/ink-ui/themes/ansi-light.js +143 -0
- package/dist/ink-ui/themes/ansi-light.js.map +1 -0
- package/dist/ink-ui/themes/ansi.d.ts +8 -0
- package/dist/ink-ui/themes/ansi.d.ts.map +1 -0
- package/dist/ink-ui/themes/ansi.js +153 -0
- package/dist/ink-ui/themes/ansi.js.map +1 -0
- package/dist/ink-ui/themes/atom-one-dark.d.ts +8 -0
- package/dist/ink-ui/themes/atom-one-dark.d.ts.map +1 -0
- package/dist/ink-ui/themes/atom-one-dark.js +140 -0
- package/dist/ink-ui/themes/atom-one-dark.js.map +1 -0
- package/dist/ink-ui/themes/ayu-light.d.ts +8 -0
- package/dist/ink-ui/themes/ayu-light.d.ts.map +1 -0
- package/dist/ink-ui/themes/ayu-light.js +132 -0
- package/dist/ink-ui/themes/ayu-light.js.map +1 -0
- package/dist/ink-ui/themes/ayu.d.ts +8 -0
- package/dist/ink-ui/themes/ayu.d.ts.map +1 -0
- package/dist/ink-ui/themes/ayu.js +106 -0
- package/dist/ink-ui/themes/ayu.js.map +1 -0
- package/dist/ink-ui/themes/color-utils.d.ts +23 -0
- package/dist/ink-ui/themes/color-utils.d.ts.map +1 -0
- package/dist/ink-ui/themes/color-utils.js +237 -0
- package/dist/ink-ui/themes/color-utils.js.map +1 -0
- package/dist/ink-ui/themes/default-light.d.ts +8 -0
- package/dist/ink-ui/themes/default-light.d.ts.map +1 -0
- package/dist/ink-ui/themes/default-light.js +100 -0
- package/dist/ink-ui/themes/default-light.js.map +1 -0
- package/dist/ink-ui/themes/default.d.ts +8 -0
- package/dist/ink-ui/themes/default.d.ts.map +1 -0
- package/dist/ink-ui/themes/default.js +143 -0
- package/dist/ink-ui/themes/default.js.map +1 -0
- package/dist/ink-ui/themes/dracula.d.ts +8 -0
- package/dist/ink-ui/themes/dracula.d.ts.map +1 -0
- package/dist/ink-ui/themes/dracula.js +117 -0
- package/dist/ink-ui/themes/dracula.js.map +1 -0
- package/dist/ink-ui/themes/github-dark.d.ts +8 -0
- package/dist/ink-ui/themes/github-dark.d.ts.map +1 -0
- package/dist/ink-ui/themes/github-dark.js +140 -0
- package/dist/ink-ui/themes/github-dark.js.map +1 -0
- package/dist/ink-ui/themes/github-light.d.ts +8 -0
- package/dist/ink-ui/themes/github-light.d.ts.map +1 -0
- package/dist/ink-ui/themes/github-light.js +142 -0
- package/dist/ink-ui/themes/github-light.js.map +1 -0
- package/dist/ink-ui/themes/googlecode.d.ts +8 -0
- package/dist/ink-ui/themes/googlecode.d.ts.map +1 -0
- package/dist/ink-ui/themes/googlecode.js +139 -0
- package/dist/ink-ui/themes/googlecode.js.map +1 -0
- package/dist/ink-ui/themes/no-color.d.ts +8 -0
- package/dist/ink-ui/themes/no-color.d.ts.map +1 -0
- package/dist/ink-ui/themes/no-color.js +118 -0
- package/dist/ink-ui/themes/no-color.js.map +1 -0
- package/dist/ink-ui/themes/semantic-tokens.d.ts +40 -0
- package/dist/ink-ui/themes/semantic-tokens.d.ts.map +1 -0
- package/dist/ink-ui/themes/semantic-tokens.js +100 -0
- package/dist/ink-ui/themes/semantic-tokens.js.map +1 -0
- package/dist/ink-ui/themes/shades-of-purple.d.ts +12 -0
- package/dist/ink-ui/themes/shades-of-purple.d.ts.map +1 -0
- package/dist/ink-ui/themes/shades-of-purple.js +306 -0
- package/dist/ink-ui/themes/shades-of-purple.js.map +1 -0
- package/dist/ink-ui/themes/theme-manager.d.ts +67 -0
- package/dist/ink-ui/themes/theme-manager.d.ts.map +1 -0
- package/dist/ink-ui/themes/theme-manager.js +263 -0
- package/dist/ink-ui/themes/theme-manager.js.map +1 -0
- package/dist/ink-ui/themes/theme.d.ts +134 -0
- package/dist/ink-ui/themes/theme.d.ts.map +1 -0
- package/dist/ink-ui/themes/theme.js +390 -0
- package/dist/ink-ui/themes/theme.js.map +1 -0
- package/dist/ink-ui/themes/xcode.d.ts +8 -0
- package/dist/ink-ui/themes/xcode.d.ts.map +1 -0
- package/dist/ink-ui/themes/xcode.js +147 -0
- package/dist/ink-ui/themes/xcode.js.map +1 -0
- package/dist/ink-ui/types.d.ts +306 -0
- package/dist/ink-ui/types.d.ts.map +1 -0
- package/dist/ink-ui/types.js +65 -0
- package/dist/ink-ui/types.js.map +1 -0
- package/dist/ink-ui/utils/input.d.ts +18 -0
- package/dist/ink-ui/utils/input.d.ts.map +1 -0
- package/dist/ink-ui/utils/input.js +51 -0
- package/dist/ink-ui/utils/input.js.map +1 -0
- package/dist/ink-ui/utils/mouse.d.ts +33 -0
- package/dist/ink-ui/utils/mouse.d.ts.map +1 -0
- package/dist/ink-ui/utils/mouse.js +182 -0
- package/dist/ink-ui/utils/mouse.js.map +1 -0
- package/dist/services/BuiltinCommandLoader.d.ts +21 -0
- package/dist/services/BuiltinCommandLoader.d.ts.map +1 -0
- package/dist/services/BuiltinCommandLoader.js +182 -0
- package/dist/services/BuiltinCommandLoader.js.map +1 -0
- package/dist/services/CommandService.d.ts +39 -0
- package/dist/services/CommandService.d.ts.map +1 -0
- package/dist/services/CommandService.js +49 -0
- package/dist/services/CommandService.js.map +1 -0
- package/dist/services/FileCommandLoader.d.ts +16 -0
- package/dist/services/FileCommandLoader.d.ts.map +1 -0
- package/dist/services/FileCommandLoader.js +19 -0
- package/dist/services/FileCommandLoader.js.map +1 -0
- package/dist/services/McpPromptLoader.d.ts +16 -0
- package/dist/services/McpPromptLoader.d.ts.map +1 -0
- package/dist/services/McpPromptLoader.js +19 -0
- package/dist/services/McpPromptLoader.js.map +1 -0
- package/dist/services/index.d.ts +8 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +8 -0
- package/dist/services/index.js.map +1 -0
- package/dist/ui/ChalkMenuRenderer.d.ts +22 -0
- package/dist/ui/ChalkMenuRenderer.d.ts.map +1 -0
- package/dist/ui/ChalkMenuRenderer.js +478 -0
- package/dist/ui/ChalkMenuRenderer.js.map +1 -0
- package/dist/ui/ChalkModelPicker.d.ts +20 -0
- package/dist/ui/ChalkModelPicker.d.ts.map +1 -0
- package/dist/ui/ChalkModelPicker.js +363 -0
- package/dist/ui/ChalkModelPicker.js.map +1 -0
- package/dist/ui/ChalkThemePicker.d.ts +20 -0
- package/dist/ui/ChalkThemePicker.d.ts.map +1 -0
- package/dist/ui/ChalkThemePicker.js +370 -0
- package/dist/ui/ChalkThemePicker.js.map +1 -0
- package/dist/ui/InkApprovalDialog.d.ts +8 -0
- package/dist/ui/InkApprovalDialog.d.ts.map +1 -0
- package/dist/ui/InkApprovalDialog.js +25 -0
- package/dist/ui/InkApprovalDialog.js.map +1 -0
- package/dist/ui/InkCommandPalette.d.ts +43 -0
- package/dist/ui/InkCommandPalette.d.ts.map +1 -0
- package/dist/ui/InkCommandPalette.js +105 -0
- package/dist/ui/InkCommandPalette.js.map +1 -0
- package/dist/ui/InkConfigMenu.d.ts +24 -0
- package/dist/ui/InkConfigMenu.d.ts.map +1 -0
- package/dist/ui/InkConfigMenu.js +259 -0
- package/dist/ui/InkConfigMenu.js.map +1 -0
- package/dist/ui/InkHelp.d.ts +25 -0
- package/dist/ui/InkHelp.d.ts.map +1 -0
- package/dist/ui/InkHelp.js +55 -0
- package/dist/ui/InkHelp.js.map +1 -0
- package/dist/ui/InkMenuPicker.d.ts +19 -0
- package/dist/ui/InkMenuPicker.d.ts.map +1 -0
- package/dist/ui/InkMenuPicker.js +33 -0
- package/dist/ui/InkMenuPicker.js.map +1 -0
- package/dist/ui/InkModelPicker.d.ts +23 -0
- package/dist/ui/InkModelPicker.d.ts.map +1 -0
- package/dist/ui/InkModelPicker.js +53 -0
- package/dist/ui/InkModelPicker.js.map +1 -0
- package/dist/ui/PersistentInput.d.ts +91 -0
- package/dist/ui/PersistentInput.d.ts.map +1 -0
- package/dist/ui/PersistentInput.js +283 -0
- package/dist/ui/PersistentInput.js.map +1 -0
- package/dist/ui/RawInput.d.ts +27 -0
- package/dist/ui/RawInput.d.ts.map +1 -0
- package/dist/ui/RawInput.js +731 -0
- package/dist/ui/RawInput.js.map +1 -0
- package/dist/ui/SlashCommandAutocomplete.d.ts +94 -0
- package/dist/ui/SlashCommandAutocomplete.d.ts.map +1 -0
- package/dist/ui/SlashCommandAutocomplete.js +274 -0
- package/dist/ui/SlashCommandAutocomplete.js.map +1 -0
- package/dist/ui/SplashScreen.d.ts +76 -0
- package/dist/ui/SplashScreen.d.ts.map +1 -0
- package/dist/ui/SplashScreen.js +316 -0
- package/dist/ui/SplashScreen.js.map +1 -0
- package/dist/ui/components/ArtifactDashboard.d.ts +16 -0
- package/dist/ui/components/ArtifactDashboard.d.ts.map +1 -0
- package/dist/ui/components/ArtifactDashboard.js +102 -0
- package/dist/ui/components/ArtifactDashboard.js.map +1 -0
- package/dist/ui/components/ChatInput.d.ts +36 -0
- package/dist/ui/components/ChatInput.d.ts.map +1 -0
- package/dist/ui/components/ChatInput.js +11 -0
- package/dist/ui/components/ChatInput.js.map +1 -0
- package/dist/ui/components/ConfigWizard.d.ts +17 -0
- package/dist/ui/components/ConfigWizard.d.ts.map +1 -0
- package/dist/ui/components/ConfigWizard.js +134 -0
- package/dist/ui/components/ConfigWizard.js.map +1 -0
- package/dist/ui/components/ContextViewer.d.ts +18 -0
- package/dist/ui/components/ContextViewer.d.ts.map +1 -0
- package/dist/ui/components/ContextViewer.js +85 -0
- package/dist/ui/components/ContextViewer.js.map +1 -0
- package/dist/ui/components/MiddlewareDashboard.d.ts +16 -0
- package/dist/ui/components/MiddlewareDashboard.d.ts.map +1 -0
- package/dist/ui/components/MiddlewareDashboard.js +98 -0
- package/dist/ui/components/MiddlewareDashboard.js.map +1 -0
- package/dist/ui/components/ModelPicker.d.ts +24 -0
- package/dist/ui/components/ModelPicker.d.ts.map +1 -0
- package/dist/ui/components/ModelPicker.js +96 -0
- package/dist/ui/components/ModelPicker.js.map +1 -0
- package/dist/ui/components/MultiLineInput.d.ts +27 -0
- package/dist/ui/components/MultiLineInput.d.ts.map +1 -0
- package/dist/ui/components/MultiLineInput.js +182 -0
- package/dist/ui/components/MultiLineInput.js.map +1 -0
- package/dist/ui/components/PermissionsBrowser.d.ts +16 -0
- package/dist/ui/components/PermissionsBrowser.d.ts.map +1 -0
- package/dist/ui/components/PermissionsBrowser.js +110 -0
- package/dist/ui/components/PermissionsBrowser.js.map +1 -0
- package/dist/ui/components/SessionBrowser.d.ts +19 -0
- package/dist/ui/components/SessionBrowser.d.ts.map +1 -0
- package/dist/ui/components/SessionBrowser.js +66 -0
- package/dist/ui/components/SessionBrowser.js.map +1 -0
- package/dist/ui/components/StatusBar.d.ts +32 -0
- package/dist/ui/components/StatusBar.d.ts.map +1 -0
- package/dist/ui/components/StatusBar.js +27 -0
- package/dist/ui/components/StatusBar.js.map +1 -0
- package/dist/ui/components/SystemMessageBrowser.d.ts +11 -0
- package/dist/ui/components/SystemMessageBrowser.d.ts.map +1 -0
- package/dist/ui/components/SystemMessageBrowser.js +106 -0
- package/dist/ui/components/SystemMessageBrowser.js.map +1 -0
- package/dist/ui/components/ThemePicker.d.ts +11 -0
- package/dist/ui/components/ThemePicker.d.ts.map +1 -0
- package/dist/ui/components/ThemePicker.js +62 -0
- package/dist/ui/components/ThemePicker.js.map +1 -0
- package/dist/ui/components/TmuxBrowser.d.ts +11 -0
- package/dist/ui/components/TmuxBrowser.d.ts.map +1 -0
- package/dist/ui/components/TmuxBrowser.js +85 -0
- package/dist/ui/components/TmuxBrowser.js.map +1 -0
- package/dist/ui/components/index.d.ts +22 -0
- package/dist/ui/components/index.d.ts.map +1 -0
- package/dist/ui/components/index.js +21 -0
- package/dist/ui/components/index.js.map +1 -0
- 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
|