nova-terminal-ai 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (649) hide show
  1. package/README.md +84 -0
  2. package/bin/nova +38 -0
  3. package/bin/nova.js +11 -0
  4. package/dist/commands/SmartCompletion.d.ts +71 -0
  5. package/dist/commands/SmartCompletion.d.ts.map +1 -0
  6. package/dist/commands/SmartCompletion.js +377 -0
  7. package/dist/commands/SmartCompletion.js.map +1 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +5 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/packages/cli/src/commands/SmartCompletion.d.ts +71 -0
  13. package/dist/packages/cli/src/commands/SmartCompletion.d.ts.map +1 -0
  14. package/dist/packages/cli/src/commands/SmartCompletion.js +377 -0
  15. package/dist/packages/cli/src/commands/SmartCompletion.js.map +1 -0
  16. package/dist/packages/cli/src/index.d.ts +2 -0
  17. package/dist/packages/cli/src/index.d.ts.map +1 -0
  18. package/dist/packages/cli/src/index.js +5 -0
  19. package/dist/packages/cli/src/index.js.map +1 -0
  20. package/dist/packages/cli/src/startup/IFlowRepl.d.ts +50 -0
  21. package/dist/packages/cli/src/startup/IFlowRepl.d.ts.map +1 -0
  22. package/dist/packages/cli/src/startup/IFlowRepl.js +178 -0
  23. package/dist/packages/cli/src/startup/IFlowRepl.js.map +1 -0
  24. package/dist/packages/cli/src/startup/InkBasedRepl.d.ts +151 -0
  25. package/dist/packages/cli/src/startup/InkBasedRepl.d.ts.map +1 -0
  26. package/dist/packages/cli/src/startup/InkBasedRepl.js +1415 -0
  27. package/dist/packages/cli/src/startup/InkBasedRepl.js.map +1 -0
  28. package/dist/packages/cli/src/startup/InteractiveRepl.d.ts +141 -0
  29. package/dist/packages/cli/src/startup/InteractiveRepl.d.ts.map +1 -0
  30. package/dist/packages/cli/src/startup/InteractiveRepl.js +2561 -0
  31. package/dist/packages/cli/src/startup/InteractiveRepl.js.map +1 -0
  32. package/dist/packages/cli/src/startup/NovaApp.d.ts +57 -0
  33. package/dist/packages/cli/src/startup/NovaApp.d.ts.map +1 -0
  34. package/dist/packages/cli/src/startup/NovaApp.js +1978 -0
  35. package/dist/packages/cli/src/startup/NovaApp.js.map +1 -0
  36. package/dist/packages/cli/src/startup/index.d.ts +5 -0
  37. package/dist/packages/cli/src/startup/index.d.ts.map +1 -0
  38. package/dist/packages/cli/src/startup/index.js +4 -0
  39. package/dist/packages/cli/src/startup/index.js.map +1 -0
  40. package/dist/packages/cli/src/startup/parseArgs.d.ts +47 -0
  41. package/dist/packages/cli/src/startup/parseArgs.d.ts.map +1 -0
  42. package/dist/packages/cli/src/startup/parseArgs.js +262 -0
  43. package/dist/packages/cli/src/startup/parseArgs.js.map +1 -0
  44. package/dist/packages/cli/src/ui/IFlowDropdown.d.ts +63 -0
  45. package/dist/packages/cli/src/ui/IFlowDropdown.d.ts.map +1 -0
  46. package/dist/packages/cli/src/ui/IFlowDropdown.js +362 -0
  47. package/dist/packages/cli/src/ui/IFlowDropdown.js.map +1 -0
  48. package/dist/packages/cli/src/ui/ModernReplUI.d.ts +55 -0
  49. package/dist/packages/cli/src/ui/ModernReplUI.d.ts.map +1 -0
  50. package/dist/packages/cli/src/ui/ModernReplUI.js +207 -0
  51. package/dist/packages/cli/src/ui/ModernReplUI.js.map +1 -0
  52. package/dist/packages/cli/src/ui/SimpleSelector2.d.ts +28 -0
  53. package/dist/packages/cli/src/ui/SimpleSelector2.d.ts.map +1 -0
  54. package/dist/packages/cli/src/ui/SimpleSelector2.js +181 -0
  55. package/dist/packages/cli/src/ui/SimpleSelector2.js.map +1 -0
  56. package/dist/packages/cli/src/ui/components/ActiveCursor.d.ts +128 -0
  57. package/dist/packages/cli/src/ui/components/ActiveCursor.d.ts.map +1 -0
  58. package/dist/packages/cli/src/ui/components/ActiveCursor.js +273 -0
  59. package/dist/packages/cli/src/ui/components/ActiveCursor.js.map +1 -0
  60. package/dist/packages/cli/src/ui/components/ConfirmDialog.d.ts +51 -0
  61. package/dist/packages/cli/src/ui/components/ConfirmDialog.d.ts.map +1 -0
  62. package/dist/packages/cli/src/ui/components/ConfirmDialog.js +147 -0
  63. package/dist/packages/cli/src/ui/components/ConfirmDialog.js.map +1 -0
  64. package/dist/packages/cli/src/ui/components/ErrorPanel.d.ts +33 -0
  65. package/dist/packages/cli/src/ui/components/ErrorPanel.d.ts.map +1 -0
  66. package/dist/packages/cli/src/ui/components/ErrorPanel.js +309 -0
  67. package/dist/packages/cli/src/ui/components/ErrorPanel.js.map +1 -0
  68. package/dist/packages/cli/src/ui/components/InkAppRunner.d.ts +18 -0
  69. package/dist/packages/cli/src/ui/components/InkAppRunner.d.ts.map +1 -0
  70. package/dist/packages/cli/src/ui/components/InkAppRunner.js +33 -0
  71. package/dist/packages/cli/src/ui/components/InkAppRunner.js.map +1 -0
  72. package/dist/packages/cli/src/ui/components/InkComponents.d.ts +126 -0
  73. package/dist/packages/cli/src/ui/components/InkComponents.d.ts.map +1 -0
  74. package/dist/packages/cli/src/ui/components/InkComponents.js +216 -0
  75. package/dist/packages/cli/src/ui/components/InkComponents.js.map +1 -0
  76. package/dist/packages/cli/src/ui/components/NovaInkApp.d.ts +11 -0
  77. package/dist/packages/cli/src/ui/components/NovaInkApp.d.ts.map +1 -0
  78. package/dist/packages/cli/src/ui/components/NovaInkApp.js +148 -0
  79. package/dist/packages/cli/src/ui/components/NovaInkApp.js.map +1 -0
  80. package/dist/packages/cli/src/ui/components/ProgressBar.d.ts +65 -0
  81. package/dist/packages/cli/src/ui/components/ProgressBar.d.ts.map +1 -0
  82. package/dist/packages/cli/src/ui/components/ProgressBar.js +135 -0
  83. package/dist/packages/cli/src/ui/components/ProgressBar.js.map +1 -0
  84. package/dist/packages/cli/src/ui/components/ProgressIndicator.d.ts +41 -0
  85. package/dist/packages/cli/src/ui/components/ProgressIndicator.d.ts.map +1 -0
  86. package/dist/packages/cli/src/ui/components/ProgressIndicator.js +235 -0
  87. package/dist/packages/cli/src/ui/components/ProgressIndicator.js.map +1 -0
  88. package/dist/packages/cli/src/ui/components/QuickActions.d.ts +36 -0
  89. package/dist/packages/cli/src/ui/components/QuickActions.d.ts.map +1 -0
  90. package/dist/packages/cli/src/ui/components/QuickActions.js +328 -0
  91. package/dist/packages/cli/src/ui/components/QuickActions.js.map +1 -0
  92. package/dist/packages/cli/src/ui/components/SimpleErrorPanel.d.ts +16 -0
  93. package/dist/packages/cli/src/ui/components/SimpleErrorPanel.d.ts.map +1 -0
  94. package/dist/packages/cli/src/ui/components/SimpleErrorPanel.js +193 -0
  95. package/dist/packages/cli/src/ui/components/SimpleErrorPanel.js.map +1 -0
  96. package/dist/packages/cli/src/ui/components/StatusBar.d.ts +30 -0
  97. package/dist/packages/cli/src/ui/components/StatusBar.d.ts.map +1 -0
  98. package/dist/packages/cli/src/ui/components/StatusBar.js +154 -0
  99. package/dist/packages/cli/src/ui/components/StatusBar.js.map +1 -0
  100. package/dist/packages/cli/src/ui/components/ThinkingBlockRenderer.d.ts +109 -0
  101. package/dist/packages/cli/src/ui/components/ThinkingBlockRenderer.d.ts.map +1 -0
  102. package/dist/packages/cli/src/ui/components/ThinkingBlockRenderer.js +335 -0
  103. package/dist/packages/cli/src/ui/components/ThinkingBlockRenderer.js.map +1 -0
  104. package/dist/packages/cli/src/ui/components/ThinkingContentDisplay.d.ts +59 -0
  105. package/dist/packages/cli/src/ui/components/ThinkingContentDisplay.d.ts.map +1 -0
  106. package/dist/packages/cli/src/ui/components/ThinkingContentDisplay.js +172 -0
  107. package/dist/packages/cli/src/ui/components/ThinkingContentDisplay.js.map +1 -0
  108. package/dist/packages/cli/src/ui/components/TodoProgressPanel.d.ts +91 -0
  109. package/dist/packages/cli/src/ui/components/TodoProgressPanel.d.ts.map +1 -0
  110. package/dist/packages/cli/src/ui/components/TodoProgressPanel.js +284 -0
  111. package/dist/packages/cli/src/ui/components/TodoProgressPanel.js.map +1 -0
  112. package/dist/packages/cli/src/ui/components/ToolCallStatusDisplay.d.ts +89 -0
  113. package/dist/packages/cli/src/ui/components/ToolCallStatusDisplay.d.ts.map +1 -0
  114. package/dist/packages/cli/src/ui/components/ToolCallStatusDisplay.js +246 -0
  115. package/dist/packages/cli/src/ui/components/ToolCallStatusDisplay.js.map +1 -0
  116. package/dist/packages/cli/src/ui/components/UserMessageHighlight.d.ts +48 -0
  117. package/dist/packages/cli/src/ui/components/UserMessageHighlight.d.ts.map +1 -0
  118. package/dist/packages/cli/src/ui/components/UserMessageHighlight.js +196 -0
  119. package/dist/packages/cli/src/ui/components/UserMessageHighlight.js.map +1 -0
  120. package/dist/packages/cli/src/ui/components/index.d.ts +17 -0
  121. package/dist/packages/cli/src/ui/components/index.d.ts.map +1 -0
  122. package/dist/packages/cli/src/ui/components/index.js +18 -0
  123. package/dist/packages/cli/src/ui/components/index.js.map +1 -0
  124. package/dist/packages/cli/src/ui/ink-prototype.d.ts +3 -0
  125. package/dist/packages/cli/src/ui/ink-prototype.d.ts.map +1 -0
  126. package/dist/packages/cli/src/ui/ink-prototype.js +160 -0
  127. package/dist/packages/cli/src/ui/ink-prototype.js.map +1 -0
  128. package/dist/packages/cli/src/utils/CliUI.d.ts +163 -0
  129. package/dist/packages/cli/src/utils/CliUI.d.ts.map +1 -0
  130. package/dist/packages/cli/src/utils/CliUI.js +292 -0
  131. package/dist/packages/cli/src/utils/CliUI.js.map +1 -0
  132. package/dist/packages/cli/src/utils/CompletionHelper.d.ts +112 -0
  133. package/dist/packages/cli/src/utils/CompletionHelper.d.ts.map +1 -0
  134. package/dist/packages/cli/src/utils/CompletionHelper.js +304 -0
  135. package/dist/packages/cli/src/utils/CompletionHelper.js.map +1 -0
  136. package/dist/packages/cli/src/utils/EnhancedCompleter.d.ts +107 -0
  137. package/dist/packages/cli/src/utils/EnhancedCompleter.d.ts.map +1 -0
  138. package/dist/packages/cli/src/utils/EnhancedCompleter.js +428 -0
  139. package/dist/packages/cli/src/utils/EnhancedCompleter.js.map +1 -0
  140. package/dist/packages/cli/src/utils/ErrorEnhancer.d.ts +103 -0
  141. package/dist/packages/cli/src/utils/ErrorEnhancer.d.ts.map +1 -0
  142. package/dist/packages/cli/src/utils/ErrorEnhancer.js +350 -0
  143. package/dist/packages/cli/src/utils/ErrorEnhancer.js.map +1 -0
  144. package/dist/packages/cli/src/utils/OutputFormatter.d.ts +65 -0
  145. package/dist/packages/cli/src/utils/OutputFormatter.d.ts.map +1 -0
  146. package/dist/packages/cli/src/utils/OutputFormatter.js +145 -0
  147. package/dist/packages/cli/src/utils/OutputFormatter.js.map +1 -0
  148. package/dist/packages/cli/src/utils/index.d.ts +5 -0
  149. package/dist/packages/cli/src/utils/index.d.ts.map +1 -0
  150. package/dist/packages/cli/src/utils/index.js +8 -0
  151. package/dist/packages/cli/src/utils/index.js.map +1 -0
  152. package/dist/packages/cli/tsconfig.tsbuildinfo +1 -0
  153. package/dist/packages/core/src/agents/AgentOrchestrator.d.ts +147 -0
  154. package/dist/packages/core/src/agents/AgentOrchestrator.d.ts.map +1 -0
  155. package/dist/packages/core/src/agents/AgentOrchestrator.js +358 -0
  156. package/dist/packages/core/src/agents/AgentOrchestrator.js.map +1 -0
  157. package/dist/packages/core/src/agents/index.d.ts +3 -0
  158. package/dist/packages/core/src/agents/index.d.ts.map +1 -0
  159. package/dist/packages/core/src/agents/index.js +5 -0
  160. package/dist/packages/core/src/agents/index.js.map +1 -0
  161. package/dist/packages/core/src/analysis/ProjectAnalyzer.d.ts +95 -0
  162. package/dist/packages/core/src/analysis/ProjectAnalyzer.d.ts.map +1 -0
  163. package/dist/packages/core/src/analysis/ProjectAnalyzer.js +656 -0
  164. package/dist/packages/core/src/analysis/ProjectAnalyzer.js.map +1 -0
  165. package/dist/packages/core/src/audit/AuditLogger.d.ts +140 -0
  166. package/dist/packages/core/src/audit/AuditLogger.d.ts.map +1 -0
  167. package/dist/packages/core/src/audit/AuditLogger.js +357 -0
  168. package/dist/packages/core/src/audit/AuditLogger.js.map +1 -0
  169. package/dist/packages/core/src/audit/index.d.ts +3 -0
  170. package/dist/packages/core/src/audit/index.d.ts.map +1 -0
  171. package/dist/packages/core/src/audit/index.js +5 -0
  172. package/dist/packages/core/src/audit/index.js.map +1 -0
  173. package/dist/packages/core/src/auth/AuthManager.d.ts +38 -0
  174. package/dist/packages/core/src/auth/AuthManager.d.ts.map +1 -0
  175. package/dist/packages/core/src/auth/AuthManager.js +120 -0
  176. package/dist/packages/core/src/auth/AuthManager.js.map +1 -0
  177. package/dist/packages/core/src/auth/index.d.ts +3 -0
  178. package/dist/packages/core/src/auth/index.d.ts.map +1 -0
  179. package/dist/packages/core/src/auth/index.js +2 -0
  180. package/dist/packages/core/src/auth/index.js.map +1 -0
  181. package/dist/packages/core/src/config/ConfigManager.d.ts +47 -0
  182. package/dist/packages/core/src/config/ConfigManager.d.ts.map +1 -0
  183. package/dist/packages/core/src/config/ConfigManager.js +1197 -0
  184. package/dist/packages/core/src/config/ConfigManager.js.map +1 -0
  185. package/dist/packages/core/src/config/index.d.ts +2 -0
  186. package/dist/packages/core/src/config/index.d.ts.map +1 -0
  187. package/dist/packages/core/src/config/index.js +2 -0
  188. package/dist/packages/core/src/config/index.js.map +1 -0
  189. package/dist/packages/core/src/context/ContextBuilder.d.ts +39 -0
  190. package/dist/packages/core/src/context/ContextBuilder.d.ts.map +1 -0
  191. package/dist/packages/core/src/context/ContextBuilder.js +132 -0
  192. package/dist/packages/core/src/context/ContextBuilder.js.map +1 -0
  193. package/dist/packages/core/src/context/ContextCompressor.d.ts +147 -0
  194. package/dist/packages/core/src/context/ContextCompressor.d.ts.map +1 -0
  195. package/dist/packages/core/src/context/ContextCompressor.js +451 -0
  196. package/dist/packages/core/src/context/ContextCompressor.js.map +1 -0
  197. package/dist/packages/core/src/context/LayeredMemoryManager.d.ts +160 -0
  198. package/dist/packages/core/src/context/LayeredMemoryManager.d.ts.map +1 -0
  199. package/dist/packages/core/src/context/LayeredMemoryManager.js +505 -0
  200. package/dist/packages/core/src/context/LayeredMemoryManager.js.map +1 -0
  201. package/dist/packages/core/src/context/MemoryDiscovery.d.ts +33 -0
  202. package/dist/packages/core/src/context/MemoryDiscovery.d.ts.map +1 -0
  203. package/dist/packages/core/src/context/MemoryDiscovery.js +146 -0
  204. package/dist/packages/core/src/context/MemoryDiscovery.js.map +1 -0
  205. package/dist/packages/core/src/context/defaultSystemPrompt.d.ts +12 -0
  206. package/dist/packages/core/src/context/defaultSystemPrompt.d.ts.map +1 -0
  207. package/dist/packages/core/src/context/defaultSystemPrompt.js +32 -0
  208. package/dist/packages/core/src/context/defaultSystemPrompt.js.map +1 -0
  209. package/dist/packages/core/src/context/index.d.ts +9 -0
  210. package/dist/packages/core/src/context/index.d.ts.map +1 -0
  211. package/dist/packages/core/src/context/index.js +5 -0
  212. package/dist/packages/core/src/context/index.js.map +1 -0
  213. package/dist/packages/core/src/extensions/SkillGenerator.d.ts +77 -0
  214. package/dist/packages/core/src/extensions/SkillGenerator.d.ts.map +1 -0
  215. package/dist/packages/core/src/extensions/SkillGenerator.js +323 -0
  216. package/dist/packages/core/src/extensions/SkillGenerator.js.map +1 -0
  217. package/dist/packages/core/src/extensions/SkillInstaller.d.ts +74 -0
  218. package/dist/packages/core/src/extensions/SkillInstaller.d.ts.map +1 -0
  219. package/dist/packages/core/src/extensions/SkillInstaller.js +216 -0
  220. package/dist/packages/core/src/extensions/SkillInstaller.js.map +1 -0
  221. package/dist/packages/core/src/extensions/SkillRegistry.d.ts +99 -0
  222. package/dist/packages/core/src/extensions/SkillRegistry.d.ts.map +1 -0
  223. package/dist/packages/core/src/extensions/SkillRegistry.js +263 -0
  224. package/dist/packages/core/src/extensions/SkillRegistry.js.map +1 -0
  225. package/dist/packages/core/src/extensions/SkillValidator.d.ts +51 -0
  226. package/dist/packages/core/src/extensions/SkillValidator.d.ts.map +1 -0
  227. package/dist/packages/core/src/extensions/SkillValidator.js +465 -0
  228. package/dist/packages/core/src/extensions/SkillValidator.js.map +1 -0
  229. package/dist/packages/core/src/extensions/index.d.ts +11 -0
  230. package/dist/packages/core/src/extensions/index.d.ts.map +1 -0
  231. package/dist/packages/core/src/extensions/index.js +8 -0
  232. package/dist/packages/core/src/extensions/index.js.map +1 -0
  233. package/dist/packages/core/src/index.d.ts +14 -0
  234. package/dist/packages/core/src/index.d.ts.map +1 -0
  235. package/dist/packages/core/src/index.js +30 -0
  236. package/dist/packages/core/src/index.js.map +1 -0
  237. package/dist/packages/core/src/mcp/McpManager.d.ts +94 -0
  238. package/dist/packages/core/src/mcp/McpManager.d.ts.map +1 -0
  239. package/dist/packages/core/src/mcp/McpManager.js +494 -0
  240. package/dist/packages/core/src/mcp/McpManager.js.map +1 -0
  241. package/dist/packages/core/src/mcp/index.d.ts +2 -0
  242. package/dist/packages/core/src/mcp/index.d.ts.map +1 -0
  243. package/dist/packages/core/src/mcp/index.js +3 -0
  244. package/dist/packages/core/src/mcp/index.js.map +1 -0
  245. package/dist/packages/core/src/model/ModelClient.d.ts +40 -0
  246. package/dist/packages/core/src/model/ModelClient.d.ts.map +1 -0
  247. package/dist/packages/core/src/model/ModelClient.js +163 -0
  248. package/dist/packages/core/src/model/ModelClient.js.map +1 -0
  249. package/dist/packages/core/src/model/ModelConnectionTester.d.ts +58 -0
  250. package/dist/packages/core/src/model/ModelConnectionTester.d.ts.map +1 -0
  251. package/dist/packages/core/src/model/ModelConnectionTester.js +311 -0
  252. package/dist/packages/core/src/model/ModelConnectionTester.js.map +1 -0
  253. package/dist/packages/core/src/model/ModelValidator.d.ts +39 -0
  254. package/dist/packages/core/src/model/ModelValidator.d.ts.map +1 -0
  255. package/dist/packages/core/src/model/ModelValidator.js +296 -0
  256. package/dist/packages/core/src/model/ModelValidator.js.map +1 -0
  257. package/dist/packages/core/src/model/index.d.ts +7 -0
  258. package/dist/packages/core/src/model/index.d.ts.map +1 -0
  259. package/dist/packages/core/src/model/index.js +5 -0
  260. package/dist/packages/core/src/model/index.js.map +1 -0
  261. package/dist/packages/core/src/model/providers/AnthropicProvider.d.ts +25 -0
  262. package/dist/packages/core/src/model/providers/AnthropicProvider.d.ts.map +1 -0
  263. package/dist/packages/core/src/model/providers/AnthropicProvider.js +258 -0
  264. package/dist/packages/core/src/model/providers/AnthropicProvider.js.map +1 -0
  265. package/dist/packages/core/src/model/providers/CodingPlanProvider.d.ts +66 -0
  266. package/dist/packages/core/src/model/providers/CodingPlanProvider.d.ts.map +1 -0
  267. package/dist/packages/core/src/model/providers/CodingPlanProvider.js +161 -0
  268. package/dist/packages/core/src/model/providers/CodingPlanProvider.js.map +1 -0
  269. package/dist/packages/core/src/model/providers/OllamaCloudProvider.d.ts +38 -0
  270. package/dist/packages/core/src/model/providers/OllamaCloudProvider.d.ts.map +1 -0
  271. package/dist/packages/core/src/model/providers/OllamaCloudProvider.js +365 -0
  272. package/dist/packages/core/src/model/providers/OllamaCloudProvider.js.map +1 -0
  273. package/dist/packages/core/src/model/providers/OllamaManager.d.ts +72 -0
  274. package/dist/packages/core/src/model/providers/OllamaManager.d.ts.map +1 -0
  275. package/dist/packages/core/src/model/providers/OllamaManager.js +144 -0
  276. package/dist/packages/core/src/model/providers/OllamaManager.js.map +1 -0
  277. package/dist/packages/core/src/model/providers/OllamaProvider.d.ts +23 -0
  278. package/dist/packages/core/src/model/providers/OllamaProvider.d.ts.map +1 -0
  279. package/dist/packages/core/src/model/providers/OllamaProvider.js +56 -0
  280. package/dist/packages/core/src/model/providers/OllamaProvider.js.map +1 -0
  281. package/dist/packages/core/src/model/providers/OpenAICompatibleProvider.d.ts +53 -0
  282. package/dist/packages/core/src/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  283. package/dist/packages/core/src/model/providers/OpenAICompatibleProvider.js +383 -0
  284. package/dist/packages/core/src/model/providers/OpenAICompatibleProvider.js.map +1 -0
  285. package/dist/packages/core/src/model/providers/OpenAIProvider.d.ts +12 -0
  286. package/dist/packages/core/src/model/providers/OpenAIProvider.d.ts.map +1 -0
  287. package/dist/packages/core/src/model/providers/OpenAIProvider.js +19 -0
  288. package/dist/packages/core/src/model/providers/OpenAIProvider.js.map +1 -0
  289. package/dist/packages/core/src/model/providers/index.d.ts +13 -0
  290. package/dist/packages/core/src/model/providers/index.d.ts.map +1 -0
  291. package/dist/packages/core/src/model/providers/index.js +7 -0
  292. package/dist/packages/core/src/model/providers/index.js.map +1 -0
  293. package/dist/packages/core/src/model/types.d.ts +81 -0
  294. package/dist/packages/core/src/model/types.d.ts.map +1 -0
  295. package/dist/packages/core/src/model/types.js +12 -0
  296. package/dist/packages/core/src/model/types.js.map +1 -0
  297. package/dist/packages/core/src/security/ApprovalManager.d.ts +56 -0
  298. package/dist/packages/core/src/security/ApprovalManager.d.ts.map +1 -0
  299. package/dist/packages/core/src/security/ApprovalManager.js +122 -0
  300. package/dist/packages/core/src/security/ApprovalManager.js.map +1 -0
  301. package/dist/packages/core/src/security/FileFilter.d.ts +47 -0
  302. package/dist/packages/core/src/security/FileFilter.d.ts.map +1 -0
  303. package/dist/packages/core/src/security/FileFilter.js +111 -0
  304. package/dist/packages/core/src/security/FileFilter.js.map +1 -0
  305. package/dist/packages/core/src/security/HookExecutor.d.ts +37 -0
  306. package/dist/packages/core/src/security/HookExecutor.d.ts.map +1 -0
  307. package/dist/packages/core/src/security/HookExecutor.js +142 -0
  308. package/dist/packages/core/src/security/HookExecutor.js.map +1 -0
  309. package/dist/packages/core/src/security/SandboxExecutor.d.ts +90 -0
  310. package/dist/packages/core/src/security/SandboxExecutor.d.ts.map +1 -0
  311. package/dist/packages/core/src/security/SandboxExecutor.js +345 -0
  312. package/dist/packages/core/src/security/SandboxExecutor.js.map +1 -0
  313. package/dist/packages/core/src/security/index.d.ts +8 -0
  314. package/dist/packages/core/src/security/index.d.ts.map +1 -0
  315. package/dist/packages/core/src/security/index.js +6 -0
  316. package/dist/packages/core/src/security/index.js.map +1 -0
  317. package/dist/packages/core/src/session/AgentLoop.d.ts +75 -0
  318. package/dist/packages/core/src/session/AgentLoop.d.ts.map +1 -0
  319. package/dist/packages/core/src/session/AgentLoop.js +381 -0
  320. package/dist/packages/core/src/session/AgentLoop.js.map +1 -0
  321. package/dist/packages/core/src/session/SessionManager.d.ts +96 -0
  322. package/dist/packages/core/src/session/SessionManager.d.ts.map +1 -0
  323. package/dist/packages/core/src/session/SessionManager.js +390 -0
  324. package/dist/packages/core/src/session/SessionManager.js.map +1 -0
  325. package/dist/packages/core/src/session/index.d.ts +4 -0
  326. package/dist/packages/core/src/session/index.d.ts.map +1 -0
  327. package/dist/packages/core/src/session/index.js +3 -0
  328. package/dist/packages/core/src/session/index.js.map +1 -0
  329. package/dist/packages/core/src/telemetry/Telemetry.d.ts +25 -0
  330. package/dist/packages/core/src/telemetry/Telemetry.d.ts.map +1 -0
  331. package/dist/packages/core/src/telemetry/Telemetry.js +73 -0
  332. package/dist/packages/core/src/telemetry/Telemetry.js.map +1 -0
  333. package/dist/packages/core/src/telemetry/TelemetryService.d.ts +152 -0
  334. package/dist/packages/core/src/telemetry/TelemetryService.d.ts.map +1 -0
  335. package/dist/packages/core/src/telemetry/TelemetryService.js +410 -0
  336. package/dist/packages/core/src/telemetry/TelemetryService.js.map +1 -0
  337. package/dist/packages/core/src/telemetry/index.d.ts +5 -0
  338. package/dist/packages/core/src/telemetry/index.d.ts.map +1 -0
  339. package/dist/packages/core/src/telemetry/index.js +3 -0
  340. package/dist/packages/core/src/telemetry/index.js.map +1 -0
  341. package/dist/packages/core/src/testing/AutoFixer.d.ts +98 -0
  342. package/dist/packages/core/src/testing/AutoFixer.d.ts.map +1 -0
  343. package/dist/packages/core/src/testing/AutoFixer.js +262 -0
  344. package/dist/packages/core/src/testing/AutoFixer.js.map +1 -0
  345. package/dist/packages/core/src/testing/ErrorAnalyzer.d.ts +74 -0
  346. package/dist/packages/core/src/testing/ErrorAnalyzer.d.ts.map +1 -0
  347. package/dist/packages/core/src/testing/ErrorAnalyzer.js +407 -0
  348. package/dist/packages/core/src/testing/ErrorAnalyzer.js.map +1 -0
  349. package/dist/packages/core/src/testing/TestRunner.d.ts +57 -0
  350. package/dist/packages/core/src/testing/TestRunner.d.ts.map +1 -0
  351. package/dist/packages/core/src/testing/TestRunner.js +205 -0
  352. package/dist/packages/core/src/testing/TestRunner.js.map +1 -0
  353. package/dist/packages/core/src/testing/agent-cli-tests.d.ts +2 -0
  354. package/dist/packages/core/src/testing/agent-cli-tests.d.ts.map +1 -0
  355. package/dist/packages/core/src/testing/agent-cli-tests.js +493 -0
  356. package/dist/packages/core/src/testing/agent-cli-tests.js.map +1 -0
  357. package/dist/packages/core/src/testing/index.d.ts +7 -0
  358. package/dist/packages/core/src/testing/index.d.ts.map +1 -0
  359. package/dist/packages/core/src/testing/index.js +8 -0
  360. package/dist/packages/core/src/testing/index.js.map +1 -0
  361. package/dist/packages/core/src/tools/ToolRegistry.d.ts +72 -0
  362. package/dist/packages/core/src/tools/ToolRegistry.d.ts.map +1 -0
  363. package/dist/packages/core/src/tools/ToolRegistry.js +208 -0
  364. package/dist/packages/core/src/tools/ToolRegistry.js.map +1 -0
  365. package/dist/packages/core/src/tools/impl/EditFileTool.d.ts +3 -0
  366. package/dist/packages/core/src/tools/impl/EditFileTool.d.ts.map +1 -0
  367. package/dist/packages/core/src/tools/impl/EditFileTool.js +76 -0
  368. package/dist/packages/core/src/tools/impl/EditFileTool.js.map +1 -0
  369. package/dist/packages/core/src/tools/impl/FileProcessor.d.ts +85 -0
  370. package/dist/packages/core/src/tools/impl/FileProcessor.d.ts.map +1 -0
  371. package/dist/packages/core/src/tools/impl/FileProcessor.js +512 -0
  372. package/dist/packages/core/src/tools/impl/FileProcessor.js.map +1 -0
  373. package/dist/packages/core/src/tools/impl/FileProcessorTool.d.ts +151 -0
  374. package/dist/packages/core/src/tools/impl/FileProcessorTool.d.ts.map +1 -0
  375. package/dist/packages/core/src/tools/impl/FileProcessorTool.js +100 -0
  376. package/dist/packages/core/src/tools/impl/FileProcessorTool.js.map +1 -0
  377. package/dist/packages/core/src/tools/impl/ImageProcessorTool.d.ts +43 -0
  378. package/dist/packages/core/src/tools/impl/ImageProcessorTool.d.ts.map +1 -0
  379. package/dist/packages/core/src/tools/impl/ImageProcessorTool.js +104 -0
  380. package/dist/packages/core/src/tools/impl/ImageProcessorTool.js.map +1 -0
  381. package/dist/packages/core/src/tools/impl/ListDirectoryTool.d.ts +3 -0
  382. package/dist/packages/core/src/tools/impl/ListDirectoryTool.d.ts.map +1 -0
  383. package/dist/packages/core/src/tools/impl/ListDirectoryTool.js +120 -0
  384. package/dist/packages/core/src/tools/impl/ListDirectoryTool.js.map +1 -0
  385. package/dist/packages/core/src/tools/impl/MemoryTool.d.ts +4 -0
  386. package/dist/packages/core/src/tools/impl/MemoryTool.d.ts.map +1 -0
  387. package/dist/packages/core/src/tools/impl/MemoryTool.js +80 -0
  388. package/dist/packages/core/src/tools/impl/MemoryTool.js.map +1 -0
  389. package/dist/packages/core/src/tools/impl/ReadFileTool.d.ts +3 -0
  390. package/dist/packages/core/src/tools/impl/ReadFileTool.d.ts.map +1 -0
  391. package/dist/packages/core/src/tools/impl/ReadFileTool.js +223 -0
  392. package/dist/packages/core/src/tools/impl/ReadFileTool.js.map +1 -0
  393. package/dist/packages/core/src/tools/impl/SearchContentTool.d.ts +3 -0
  394. package/dist/packages/core/src/tools/impl/SearchContentTool.d.ts.map +1 -0
  395. package/dist/packages/core/src/tools/impl/SearchContentTool.js +65 -0
  396. package/dist/packages/core/src/tools/impl/SearchContentTool.js.map +1 -0
  397. package/dist/packages/core/src/tools/impl/SearchFileTool.d.ts +3 -0
  398. package/dist/packages/core/src/tools/impl/SearchFileTool.d.ts.map +1 -0
  399. package/dist/packages/core/src/tools/impl/SearchFileTool.js +48 -0
  400. package/dist/packages/core/src/tools/impl/SearchFileTool.js.map +1 -0
  401. package/dist/packages/core/src/tools/impl/ShellTool.d.ts +3 -0
  402. package/dist/packages/core/src/tools/impl/ShellTool.d.ts.map +1 -0
  403. package/dist/packages/core/src/tools/impl/ShellTool.js +92 -0
  404. package/dist/packages/core/src/tools/impl/ShellTool.js.map +1 -0
  405. package/dist/packages/core/src/tools/impl/TaskTool.d.ts +51 -0
  406. package/dist/packages/core/src/tools/impl/TaskTool.d.ts.map +1 -0
  407. package/dist/packages/core/src/tools/impl/TaskTool.js +172 -0
  408. package/dist/packages/core/src/tools/impl/TaskTool.js.map +1 -0
  409. package/dist/packages/core/src/tools/impl/TodoTool.d.ts +31 -0
  410. package/dist/packages/core/src/tools/impl/TodoTool.d.ts.map +1 -0
  411. package/dist/packages/core/src/tools/impl/TodoTool.js +102 -0
  412. package/dist/packages/core/src/tools/impl/TodoTool.js.map +1 -0
  413. package/dist/packages/core/src/tools/impl/WebFetchTool.d.ts +3 -0
  414. package/dist/packages/core/src/tools/impl/WebFetchTool.d.ts.map +1 -0
  415. package/dist/packages/core/src/tools/impl/WebFetchTool.js +77 -0
  416. package/dist/packages/core/src/tools/impl/WebFetchTool.js.map +1 -0
  417. package/dist/packages/core/src/tools/impl/WebSearchTool.d.ts +3 -0
  418. package/dist/packages/core/src/tools/impl/WebSearchTool.d.ts.map +1 -0
  419. package/dist/packages/core/src/tools/impl/WebSearchTool.js +67 -0
  420. package/dist/packages/core/src/tools/impl/WebSearchTool.js.map +1 -0
  421. package/dist/packages/core/src/tools/impl/WriteFileTool.d.ts +3 -0
  422. package/dist/packages/core/src/tools/impl/WriteFileTool.d.ts.map +1 -0
  423. package/dist/packages/core/src/tools/impl/WriteFileTool.js +41 -0
  424. package/dist/packages/core/src/tools/impl/WriteFileTool.js.map +1 -0
  425. package/dist/packages/core/src/tools/impl/index.d.ts +14 -0
  426. package/dist/packages/core/src/tools/impl/index.d.ts.map +1 -0
  427. package/dist/packages/core/src/tools/impl/index.js +21 -0
  428. package/dist/packages/core/src/tools/impl/index.js.map +1 -0
  429. package/dist/packages/core/src/tools/index.d.ts +16 -0
  430. package/dist/packages/core/src/tools/index.d.ts.map +1 -0
  431. package/dist/packages/core/src/tools/index.js +21 -0
  432. package/dist/packages/core/src/tools/index.js.map +1 -0
  433. package/dist/packages/core/src/tools/schemas/execution.d.ts +41 -0
  434. package/dist/packages/core/src/tools/schemas/execution.d.ts.map +1 -0
  435. package/dist/packages/core/src/tools/schemas/execution.js +42 -0
  436. package/dist/packages/core/src/tools/schemas/execution.js.map +1 -0
  437. package/dist/packages/core/src/tools/schemas/file.d.ts +113 -0
  438. package/dist/packages/core/src/tools/schemas/file.d.ts.map +1 -0
  439. package/dist/packages/core/src/tools/schemas/file.js +116 -0
  440. package/dist/packages/core/src/tools/schemas/file.js.map +1 -0
  441. package/dist/packages/core/src/tools/schemas/fileProcessorSchema.d.ts +278 -0
  442. package/dist/packages/core/src/tools/schemas/fileProcessorSchema.d.ts.map +1 -0
  443. package/dist/packages/core/src/tools/schemas/fileProcessorSchema.js +61 -0
  444. package/dist/packages/core/src/tools/schemas/fileProcessorSchema.js.map +1 -0
  445. package/dist/packages/core/src/tools/schemas/index.d.ts +8 -0
  446. package/dist/packages/core/src/tools/schemas/index.d.ts.map +1 -0
  447. package/dist/packages/core/src/tools/schemas/index.js +11 -0
  448. package/dist/packages/core/src/tools/schemas/index.js.map +1 -0
  449. package/dist/packages/core/src/tools/schemas/memory.d.ts +50 -0
  450. package/dist/packages/core/src/tools/schemas/memory.d.ts.map +1 -0
  451. package/dist/packages/core/src/tools/schemas/memory.js +51 -0
  452. package/dist/packages/core/src/tools/schemas/memory.js.map +1 -0
  453. package/dist/packages/core/src/tools/schemas/orchestration.d.ts +41 -0
  454. package/dist/packages/core/src/tools/schemas/orchestration.d.ts.map +1 -0
  455. package/dist/packages/core/src/tools/schemas/orchestration.js +44 -0
  456. package/dist/packages/core/src/tools/schemas/orchestration.js.map +1 -0
  457. package/dist/packages/core/src/tools/schemas/search.d.ts +111 -0
  458. package/dist/packages/core/src/tools/schemas/search.d.ts.map +1 -0
  459. package/dist/packages/core/src/tools/schemas/search.js +110 -0
  460. package/dist/packages/core/src/tools/schemas/search.js.map +1 -0
  461. package/dist/packages/core/src/tools/schemas/todo.d.ts +29 -0
  462. package/dist/packages/core/src/tools/schemas/todo.d.ts.map +1 -0
  463. package/dist/packages/core/src/tools/schemas/todo.js +32 -0
  464. package/dist/packages/core/src/tools/schemas/todo.js.map +1 -0
  465. package/dist/packages/core/src/tools/schemas/web.d.ts +84 -0
  466. package/dist/packages/core/src/tools/schemas/web.d.ts.map +1 -0
  467. package/dist/packages/core/src/tools/schemas/web.js +85 -0
  468. package/dist/packages/core/src/tools/schemas/web.js.map +1 -0
  469. package/dist/packages/core/src/types/config.d.ts +212 -0
  470. package/dist/packages/core/src/types/config.d.ts.map +1 -0
  471. package/dist/packages/core/src/types/config.js +5 -0
  472. package/dist/packages/core/src/types/config.js.map +1 -0
  473. package/dist/packages/core/src/types/errors.d.ts +92 -0
  474. package/dist/packages/core/src/types/errors.d.ts.map +1 -0
  475. package/dist/packages/core/src/types/errors.js +172 -0
  476. package/dist/packages/core/src/types/errors.js.map +1 -0
  477. package/dist/packages/core/src/types/index.d.ts +5 -0
  478. package/dist/packages/core/src/types/index.d.ts.map +1 -0
  479. package/dist/packages/core/src/types/index.js +8 -0
  480. package/dist/packages/core/src/types/index.js.map +1 -0
  481. package/dist/packages/core/src/types/session.d.ts +141 -0
  482. package/dist/packages/core/src/types/session.d.ts.map +1 -0
  483. package/dist/packages/core/src/types/session.js +16 -0
  484. package/dist/packages/core/src/types/session.js.map +1 -0
  485. package/dist/packages/core/src/types/tools.d.ts +126 -0
  486. package/dist/packages/core/src/types/tools.d.ts.map +1 -0
  487. package/dist/packages/core/src/types/tools.js +26 -0
  488. package/dist/packages/core/src/types/tools.js.map +1 -0
  489. package/dist/packages/core/src/utils/CheckpointManager.d.ts +100 -0
  490. package/dist/packages/core/src/utils/CheckpointManager.d.ts.map +1 -0
  491. package/dist/packages/core/src/utils/CheckpointManager.js +255 -0
  492. package/dist/packages/core/src/utils/CheckpointManager.js.map +1 -0
  493. package/dist/packages/core/src/utils/Logger.d.ts +29 -0
  494. package/dist/packages/core/src/utils/Logger.d.ts.map +1 -0
  495. package/dist/packages/core/src/utils/Logger.js +77 -0
  496. package/dist/packages/core/src/utils/Logger.js.map +1 -0
  497. package/dist/packages/core/src/utils/RetryManager.d.ts +125 -0
  498. package/dist/packages/core/src/utils/RetryManager.d.ts.map +1 -0
  499. package/dist/packages/core/src/utils/RetryManager.js +348 -0
  500. package/dist/packages/core/src/utils/RetryManager.js.map +1 -0
  501. package/dist/packages/core/src/utils/TokenCounter.d.ts +73 -0
  502. package/dist/packages/core/src/utils/TokenCounter.d.ts.map +1 -0
  503. package/dist/packages/core/src/utils/TokenCounter.js +338 -0
  504. package/dist/packages/core/src/utils/TokenCounter.js.map +1 -0
  505. package/dist/packages/core/src/utils/VectorMemoryStore.d.ts +110 -0
  506. package/dist/packages/core/src/utils/VectorMemoryStore.d.ts.map +1 -0
  507. package/dist/packages/core/src/utils/VectorMemoryStore.js +320 -0
  508. package/dist/packages/core/src/utils/VectorMemoryStore.js.map +1 -0
  509. package/dist/packages/core/src/utils/helpers.d.ts +24 -0
  510. package/dist/packages/core/src/utils/helpers.d.ts.map +1 -0
  511. package/dist/packages/core/src/utils/helpers.js +77 -0
  512. package/dist/packages/core/src/utils/helpers.js.map +1 -0
  513. package/dist/packages/core/src/utils/index.d.ts +11 -0
  514. package/dist/packages/core/src/utils/index.d.ts.map +1 -0
  515. package/dist/packages/core/src/utils/index.js +7 -0
  516. package/dist/packages/core/src/utils/index.js.map +1 -0
  517. package/dist/startup/IFlowRepl.d.ts +50 -0
  518. package/dist/startup/IFlowRepl.d.ts.map +1 -0
  519. package/dist/startup/IFlowRepl.js +178 -0
  520. package/dist/startup/IFlowRepl.js.map +1 -0
  521. package/dist/startup/InkBasedRepl.d.ts +151 -0
  522. package/dist/startup/InkBasedRepl.d.ts.map +1 -0
  523. package/dist/startup/InkBasedRepl.js +1415 -0
  524. package/dist/startup/InkBasedRepl.js.map +1 -0
  525. package/dist/startup/InteractiveRepl.d.ts +141 -0
  526. package/dist/startup/InteractiveRepl.d.ts.map +1 -0
  527. package/dist/startup/InteractiveRepl.js +2561 -0
  528. package/dist/startup/InteractiveRepl.js.map +1 -0
  529. package/dist/startup/NovaApp.d.ts +57 -0
  530. package/dist/startup/NovaApp.d.ts.map +1 -0
  531. package/dist/startup/NovaApp.js +1978 -0
  532. package/dist/startup/NovaApp.js.map +1 -0
  533. package/dist/startup/index.d.ts +5 -0
  534. package/dist/startup/index.d.ts.map +1 -0
  535. package/dist/startup/index.js +4 -0
  536. package/dist/startup/index.js.map +1 -0
  537. package/dist/startup/parseArgs.d.ts +47 -0
  538. package/dist/startup/parseArgs.d.ts.map +1 -0
  539. package/dist/startup/parseArgs.js +262 -0
  540. package/dist/startup/parseArgs.js.map +1 -0
  541. package/dist/ui/IFlowDropdown.d.ts +63 -0
  542. package/dist/ui/IFlowDropdown.d.ts.map +1 -0
  543. package/dist/ui/IFlowDropdown.js +362 -0
  544. package/dist/ui/IFlowDropdown.js.map +1 -0
  545. package/dist/ui/ModernReplUI.d.ts +55 -0
  546. package/dist/ui/ModernReplUI.d.ts.map +1 -0
  547. package/dist/ui/ModernReplUI.js +207 -0
  548. package/dist/ui/ModernReplUI.js.map +1 -0
  549. package/dist/ui/SimpleSelector2.d.ts +28 -0
  550. package/dist/ui/SimpleSelector2.d.ts.map +1 -0
  551. package/dist/ui/SimpleSelector2.js +181 -0
  552. package/dist/ui/SimpleSelector2.js.map +1 -0
  553. package/dist/ui/components/ActiveCursor.d.ts +128 -0
  554. package/dist/ui/components/ActiveCursor.d.ts.map +1 -0
  555. package/dist/ui/components/ActiveCursor.js +273 -0
  556. package/dist/ui/components/ActiveCursor.js.map +1 -0
  557. package/dist/ui/components/ConfirmDialog.d.ts +51 -0
  558. package/dist/ui/components/ConfirmDialog.d.ts.map +1 -0
  559. package/dist/ui/components/ConfirmDialog.js +147 -0
  560. package/dist/ui/components/ConfirmDialog.js.map +1 -0
  561. package/dist/ui/components/ErrorPanel.d.ts +33 -0
  562. package/dist/ui/components/ErrorPanel.d.ts.map +1 -0
  563. package/dist/ui/components/ErrorPanel.js +309 -0
  564. package/dist/ui/components/ErrorPanel.js.map +1 -0
  565. package/dist/ui/components/InkAppRunner.d.ts +18 -0
  566. package/dist/ui/components/InkAppRunner.d.ts.map +1 -0
  567. package/dist/ui/components/InkAppRunner.js +33 -0
  568. package/dist/ui/components/InkAppRunner.js.map +1 -0
  569. package/dist/ui/components/InkComponents.d.ts +126 -0
  570. package/dist/ui/components/InkComponents.d.ts.map +1 -0
  571. package/dist/ui/components/InkComponents.js +216 -0
  572. package/dist/ui/components/InkComponents.js.map +1 -0
  573. package/dist/ui/components/NovaInkApp.d.ts +11 -0
  574. package/dist/ui/components/NovaInkApp.d.ts.map +1 -0
  575. package/dist/ui/components/NovaInkApp.js +148 -0
  576. package/dist/ui/components/NovaInkApp.js.map +1 -0
  577. package/dist/ui/components/ProgressBar.d.ts +65 -0
  578. package/dist/ui/components/ProgressBar.d.ts.map +1 -0
  579. package/dist/ui/components/ProgressBar.js +135 -0
  580. package/dist/ui/components/ProgressBar.js.map +1 -0
  581. package/dist/ui/components/ProgressIndicator.d.ts +41 -0
  582. package/dist/ui/components/ProgressIndicator.d.ts.map +1 -0
  583. package/dist/ui/components/ProgressIndicator.js +235 -0
  584. package/dist/ui/components/ProgressIndicator.js.map +1 -0
  585. package/dist/ui/components/QuickActions.d.ts +36 -0
  586. package/dist/ui/components/QuickActions.d.ts.map +1 -0
  587. package/dist/ui/components/QuickActions.js +328 -0
  588. package/dist/ui/components/QuickActions.js.map +1 -0
  589. package/dist/ui/components/SimpleErrorPanel.d.ts +16 -0
  590. package/dist/ui/components/SimpleErrorPanel.d.ts.map +1 -0
  591. package/dist/ui/components/SimpleErrorPanel.js +193 -0
  592. package/dist/ui/components/SimpleErrorPanel.js.map +1 -0
  593. package/dist/ui/components/StatusBar.d.ts +30 -0
  594. package/dist/ui/components/StatusBar.d.ts.map +1 -0
  595. package/dist/ui/components/StatusBar.js +154 -0
  596. package/dist/ui/components/StatusBar.js.map +1 -0
  597. package/dist/ui/components/ThinkingBlockRenderer.d.ts +109 -0
  598. package/dist/ui/components/ThinkingBlockRenderer.d.ts.map +1 -0
  599. package/dist/ui/components/ThinkingBlockRenderer.js +335 -0
  600. package/dist/ui/components/ThinkingBlockRenderer.js.map +1 -0
  601. package/dist/ui/components/ThinkingContentDisplay.d.ts +59 -0
  602. package/dist/ui/components/ThinkingContentDisplay.d.ts.map +1 -0
  603. package/dist/ui/components/ThinkingContentDisplay.js +172 -0
  604. package/dist/ui/components/ThinkingContentDisplay.js.map +1 -0
  605. package/dist/ui/components/TodoProgressPanel.d.ts +91 -0
  606. package/dist/ui/components/TodoProgressPanel.d.ts.map +1 -0
  607. package/dist/ui/components/TodoProgressPanel.js +284 -0
  608. package/dist/ui/components/TodoProgressPanel.js.map +1 -0
  609. package/dist/ui/components/ToolCallStatusDisplay.d.ts +89 -0
  610. package/dist/ui/components/ToolCallStatusDisplay.d.ts.map +1 -0
  611. package/dist/ui/components/ToolCallStatusDisplay.js +246 -0
  612. package/dist/ui/components/ToolCallStatusDisplay.js.map +1 -0
  613. package/dist/ui/components/UserMessageHighlight.d.ts +48 -0
  614. package/dist/ui/components/UserMessageHighlight.d.ts.map +1 -0
  615. package/dist/ui/components/UserMessageHighlight.js +196 -0
  616. package/dist/ui/components/UserMessageHighlight.js.map +1 -0
  617. package/dist/ui/components/index.d.ts +17 -0
  618. package/dist/ui/components/index.d.ts.map +1 -0
  619. package/dist/ui/components/index.js +18 -0
  620. package/dist/ui/components/index.js.map +1 -0
  621. package/dist/ui/ink-prototype.d.ts +3 -0
  622. package/dist/ui/ink-prototype.d.ts.map +1 -0
  623. package/dist/ui/ink-prototype.js +160 -0
  624. package/dist/ui/ink-prototype.js.map +1 -0
  625. package/dist/utils/CliUI.d.ts +163 -0
  626. package/dist/utils/CliUI.d.ts.map +1 -0
  627. package/dist/utils/CliUI.js +292 -0
  628. package/dist/utils/CliUI.js.map +1 -0
  629. package/dist/utils/CompletionHelper.d.ts +112 -0
  630. package/dist/utils/CompletionHelper.d.ts.map +1 -0
  631. package/dist/utils/CompletionHelper.js +304 -0
  632. package/dist/utils/CompletionHelper.js.map +1 -0
  633. package/dist/utils/EnhancedCompleter.d.ts +107 -0
  634. package/dist/utils/EnhancedCompleter.d.ts.map +1 -0
  635. package/dist/utils/EnhancedCompleter.js +428 -0
  636. package/dist/utils/EnhancedCompleter.js.map +1 -0
  637. package/dist/utils/ErrorEnhancer.d.ts +103 -0
  638. package/dist/utils/ErrorEnhancer.d.ts.map +1 -0
  639. package/dist/utils/ErrorEnhancer.js +350 -0
  640. package/dist/utils/ErrorEnhancer.js.map +1 -0
  641. package/dist/utils/OutputFormatter.d.ts +65 -0
  642. package/dist/utils/OutputFormatter.d.ts.map +1 -0
  643. package/dist/utils/OutputFormatter.js +145 -0
  644. package/dist/utils/OutputFormatter.js.map +1 -0
  645. package/dist/utils/index.d.ts +5 -0
  646. package/dist/utils/index.d.ts.map +1 -0
  647. package/dist/utils/index.js +8 -0
  648. package/dist/utils/index.js.map +1 -0
  649. package/package.json +99 -0
@@ -0,0 +1,2561 @@
1
+ // ============================================================================
2
+ // InteractiveRepl - Interactive read-eval-print loop with rich UX v3
3
+ // Features: multi-line input, @file references, !shell, /init, /memory,
4
+ // /history, session auto-persist, /model switch
5
+ // ============================================================================
6
+ import * as readline from 'node:readline';
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import * as os from 'node:os';
10
+ import { execSync, spawn } from 'node:child_process';
11
+ import chalk from 'chalk';
12
+ import { AgentLoop } from '../../../core/src/session/AgentLoop.js';
13
+ import { buildSystemPrompt } from '../../../core/src/context/defaultSystemPrompt.js';
14
+ import { ThinkingBlockRenderer } from '../ui/components/ThinkingBlockRenderer.js';
15
+ import { TodoProgressPanel } from '../ui/components/TodoProgressPanel.js';
16
+ import { UserMessageHighlight } from '../ui/components/UserMessageHighlight.js';
17
+ import { OllamaManager } from '../../../core/src/model/providers/OllamaManager.js';
18
+ import { EnhancedCompleter } from '../utils/EnhancedCompleter.js';
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ const MODE_LABELS = {
21
+ auto: { label: 'AUTO', color: chalk.green.bold, description: 'Full autonomous - no approval needed', approvalMode: 'yolo' },
22
+ plan: { label: 'PLAN', color: chalk.yellow.bold, description: 'Plan first, then confirm each action', approvalMode: 'plan' },
23
+ ask: { label: 'ASK', color: chalk.cyan.bold, description: 'Answer only, no file changes', approvalMode: 'plan' },
24
+ };
25
+ const MODES = ['auto', 'plan', 'ask'];
26
+ // ============================================================================
27
+ // Box drawing chars & color palette (enhanced UI)
28
+ // ============================================================================
29
+ const BOX = {
30
+ tl: '╭', tr: '╮', bl: '╰', br: '╯',
31
+ h: '─', v: '│',
32
+ ht: '├', htr: '┤', cross: '┼',
33
+ arrow: '›', bullet: '•', check: '✓', crossX: '✗', dot: '·',
34
+ diamond: '◆', star: '★', circle: '○', circleFull: '●',
35
+ spinner: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
36
+ hThick: '━', vThick: '┃',
37
+ arrowRight: '→', arrowLeft: '←', arrowUp: '↑', arrowDown: '↓',
38
+ };
39
+ const C = {
40
+ brand: chalk.hex('#7C3AED').bold,
41
+ brandLight: chalk.hex('#A78BFA'),
42
+ brandDim: chalk.hex('#7C3AED').dim,
43
+ success: chalk.hex('#10B981'),
44
+ successDim: chalk.hex('#10B981').dim,
45
+ warning: chalk.hex('#F59E0B'),
46
+ warningDim: chalk.hex('#F59E0B').dim,
47
+ error: chalk.hex('#EF4444'),
48
+ errorDim: chalk.hex('#EF4444').dim,
49
+ info: chalk.hex('#3B82F6'),
50
+ infoDim: chalk.hex('#3B82F6').dim,
51
+ primary: chalk.white,
52
+ muted: chalk.gray,
53
+ dim: chalk.hex('#6B7280'),
54
+ toolName: chalk.hex('#22D3EE'),
55
+ toolOk: chalk.hex('#6EE7B7'),
56
+ toolErr: chalk.hex('#FCA5A5'),
57
+ turnLine: chalk.hex('#374151').dim,
58
+ accent: chalk.hex('#F472B6'),
59
+ subtle: chalk.hex('#4B5563'),
60
+ };
61
+ // ============================================================================
62
+ // InteractiveRepl
63
+ // ============================================================================
64
+ export class InteractiveRepl {
65
+ modelClient;
66
+ sessionManager;
67
+ toolRegistry;
68
+ approvalManager;
69
+ authManager;
70
+ config;
71
+ configManager;
72
+ cwd;
73
+ contextCompressor;
74
+ mcpManager;
75
+ skillRegistry;
76
+ rl = null;
77
+ currentLoop = null;
78
+ sessionId = null;
79
+ restoreSessionId;
80
+ resizeTimer = null;
81
+ // ---- UX state ----
82
+ mode = 'auto';
83
+ showThinking = true;
84
+ compactMode = true;
85
+ processing = false;
86
+ // ---- Multi-line input state ----
87
+ multilineBuffer = [];
88
+ isMultiline = false;
89
+ // ---- Components ----
90
+ thinkingRenderer;
91
+ todoProgressPanel;
92
+ userMessageHighlight;
93
+ // ---- Streaming state for current task ----
94
+ activeToolCalls = new Map();
95
+ toolCallOrder = [];
96
+ currentTurn = 0;
97
+ spinnerTimer = null;
98
+ spinnerFrame = 0;
99
+ currentTaskTokens = 0;
100
+ _pendingSkillInject = null;
101
+ // ---- Enhanced completer ----
102
+ enhancedCompleter = null;
103
+ inputHistory = [];
104
+ constructor(options) {
105
+ this.modelClient = options.modelClient;
106
+ this.sessionManager = options.sessionManager;
107
+ this.toolRegistry = options.toolRegistry;
108
+ this.approvalManager = options.approvalManager;
109
+ this.authManager = options.authManager;
110
+ this.config = options.config;
111
+ this.configManager = options.configManager;
112
+ this.cwd = options.cwd;
113
+ this.contextCompressor = options.contextCompressor;
114
+ this.mcpManager = options.mcpManager;
115
+ this.skillRegistry = options.skillRegistry;
116
+ this.restoreSessionId = options.restoreSessionId;
117
+ this.thinkingRenderer = new ThinkingBlockRenderer({
118
+ expanded: false,
119
+ maxPreviewLines: 4,
120
+ maxLineLength: 80,
121
+ showStreamingPreview: false,
122
+ });
123
+ // Initialize TODO progress panel for fixed-position task display
124
+ this.todoProgressPanel = new TodoProgressPanel({
125
+ showPriority: true,
126
+ compact: true,
127
+ });
128
+ // Initialize user message highlighter
129
+ this.userMessageHighlight = new UserMessageHighlight({
130
+ highlightColor: 'purple',
131
+ showTimestamp: true,
132
+ });
133
+ // Initialize enhanced completer with empty arrays (will be populated in start())
134
+ this.enhancedCompleter = new EnhancedCompleter({
135
+ configManager: this.configManager,
136
+ cwd: this.cwd,
137
+ history: this.inputHistory,
138
+ skills: [],
139
+ mcpServers: this.mcpManager ? this.mcpManager.listServers().map(s => ({ name: s.name, status: s.connected ? 'connected' : 'disconnected' })) : [],
140
+ });
141
+ }
142
+ // ========================================================================
143
+ // Lifecycle
144
+ // ========================================================================
145
+ async start() {
146
+ this.printBanner();
147
+ // Update skills list now that we're in async context
148
+ if (this.skillRegistry && this.enhancedCompleter) {
149
+ const skills = await this.skillRegistry.list();
150
+ this.enhancedCompleter.updateSkills(skills.map(s => s.metadata.name));
151
+ }
152
+ // Restore or create session
153
+ if (this.restoreSessionId) {
154
+ const existing = this.sessionManager.get(this.restoreSessionId);
155
+ this.sessionId = existing ? this.restoreSessionId : this.createInitialSession();
156
+ if (existing) {
157
+ const msgs = this.sessionManager.getMessages(this.sessionId);
158
+ console.log(C.info(` Restored session: ${String(this.sessionId).slice(0, 8)} — ${msgs.length} messages`));
159
+ }
160
+ }
161
+ else {
162
+ this.sessionId = this.createInitialSession();
163
+ }
164
+ this.rl = readline.createInterface({
165
+ input: process.stdin,
166
+ output: process.stdout,
167
+ prompt: '',
168
+ historySize: 200,
169
+ removeHistoryDuplicates: true,
170
+ });
171
+ this.approvalManager.setHandler(this.handleApproval.bind(this));
172
+ // Cancel running agent loop
173
+ const cancelRunningLoop = () => {
174
+ if (this.currentLoop?.isActive()) {
175
+ this.currentLoop.cancel();
176
+ this.processing = false;
177
+ this.thinkingRenderer.cancel();
178
+ this.stopSpinner();
179
+ // Also persist on cancel
180
+ if (this.sessionId)
181
+ this.sessionManager.persist(this.sessionId);
182
+ process.stdout.write('\n');
183
+ this.printLine('cancelled', 'warning');
184
+ this.printPrompt();
185
+ return true;
186
+ }
187
+ return false;
188
+ };
189
+ // Ctrl+C to cancel running task
190
+ process.on('SIGINT', () => {
191
+ if (!cancelRunningLoop()) {
192
+ console.log(C.muted('\n Use /quit or Ctrl+D to exit'));
193
+ this.printPrompt();
194
+ }
195
+ });
196
+ // Handle terminal resize - redraw banner and status
197
+ let resizeTimer = null;
198
+ process.stdout.on('resize', () => {
199
+ if (!this.processing) {
200
+ // Debounce resize events to avoid flickering
201
+ if (resizeTimer) {
202
+ clearTimeout(resizeTimer);
203
+ }
204
+ resizeTimer = setTimeout(() => {
205
+ console.clear();
206
+ this.printBanner();
207
+ }, 100);
208
+ }
209
+ });
210
+ // Custom input loop with boxed input area
211
+ this.runInputLoop();
212
+ this.rl.on('close', () => {
213
+ if (this.sessionId)
214
+ this.sessionManager.persist(this.sessionId);
215
+ if (this.resizeTimer) {
216
+ clearTimeout(this.resizeTimer);
217
+ this.resizeTimer = null;
218
+ }
219
+ console.log(C.muted('\nGoodbye!'));
220
+ process.exit(0);
221
+ });
222
+ }
223
+ /** Custom input loop with boxed input area */
224
+ async runInputLoop() {
225
+ while (this.rl) {
226
+ // Print input box header
227
+ this.printInputBox();
228
+ // Get user input
229
+ const input = await this.askInput();
230
+ // Close input box
231
+ this.printInputBoxFooter();
232
+ if (!input)
233
+ continue;
234
+ // Process input
235
+ this.processing = true;
236
+ await this.dispatchInput(input.trim());
237
+ this.processing = false;
238
+ }
239
+ }
240
+ /** Ask for input with proper prompt */
241
+ askInput() {
242
+ return new Promise((resolve) => {
243
+ if (!this.rl)
244
+ return resolve('');
245
+ // Set simple prompt inside the box
246
+ this.rl.setPrompt(C.brand(BOX.v) + ' ');
247
+ this.rl.prompt();
248
+ // Tab completion state
249
+ let currentInput = '';
250
+ let completionsShown = false;
251
+ // Enable raw mode for Tab key detection
252
+ const wasRaw = process.stdin.isRaw;
253
+ if (process.stdin.isTTY) {
254
+ process.stdin.setRawMode(true);
255
+ }
256
+ const cleanup = () => {
257
+ if (process.stdin.isTTY) {
258
+ process.stdin.setRawMode(wasRaw ?? false);
259
+ }
260
+ process.stdin.off('data', onKeypress);
261
+ this.rl?.off('line', onLine);
262
+ };
263
+ const onKeypress = (buffer) => {
264
+ const key = buffer.toString();
265
+ // Tab key - show completions
266
+ if (key === '\t') {
267
+ const completions = this.enhancedCompleter?.getCompletions(currentInput) || [];
268
+ if (completions.length > 0) {
269
+ process.stdout.write('\n');
270
+ this.showEnhancedCompletions(completions);
271
+ // Reprint prompt and current input
272
+ process.stdout.write(C.brand(BOX.v) + ' ' + currentInput);
273
+ completionsShown = true;
274
+ }
275
+ }
276
+ // Ctrl+C - cancel
277
+ else if (key === '\x03') {
278
+ cleanup();
279
+ resolve('');
280
+ }
281
+ // Backspace - update currentInput
282
+ else if (key === '\x7f' || key === '\b') {
283
+ if (currentInput.length > 0) {
284
+ currentInput = currentInput.slice(0, -1);
285
+ }
286
+ completionsShown = false;
287
+ }
288
+ // Regular character
289
+ else if (key.length === 1 && key >= ' ' && key <= '~') {
290
+ currentInput += key;
291
+ completionsShown = false;
292
+ }
293
+ };
294
+ const onLine = (line) => {
295
+ cleanup();
296
+ // Multi-line mode: lines ending with \ continue input
297
+ if (line.endsWith('\\')) {
298
+ this.isMultiline = true;
299
+ this.multilineBuffer.push(line.slice(0, -1));
300
+ // Continue reading without resolving
301
+ process.stdout.write(C.dim(' ' + BOX.arrowDown + ' '));
302
+ this.rl?.prompt();
303
+ return;
304
+ }
305
+ if (this.isMultiline) {
306
+ this.multilineBuffer.push(line);
307
+ // Check if empty line ends multiline mode
308
+ if (line.trim() === '') {
309
+ const fullInput = this.multilineBuffer.join('\n').trim();
310
+ this.multilineBuffer = [];
311
+ this.isMultiline = false;
312
+ resolve(fullInput);
313
+ }
314
+ else {
315
+ process.stdout.write(C.dim(' ' + BOX.arrowDown + ' '));
316
+ this.rl?.prompt();
317
+ }
318
+ return;
319
+ }
320
+ resolve(line);
321
+ };
322
+ this.rl.on('line', onLine);
323
+ process.stdin.on('data', onKeypress);
324
+ });
325
+ }
326
+ /** Get all REPL commands for completion */
327
+ getAllReplCommands() {
328
+ return [
329
+ { text: '/help', description: 'Show help' },
330
+ { text: '/quit', description: 'Exit nova' },
331
+ { text: '/clear', description: 'Clear conversation' },
332
+ { text: '/status', description: 'Session info' },
333
+ { text: '/model', description: 'Switch model' },
334
+ { text: '/mode', description: 'Change mode (auto/plan/ask)' },
335
+ { text: '/init', description: 'Generate NOVA.md' },
336
+ { text: '/memory', description: 'Manage memory' },
337
+ { text: '/history', description: 'Session history' },
338
+ { text: '/mcp', description: 'MCP servers' },
339
+ { text: '/skills', description: 'Available skills' },
340
+ { text: '/theme', description: 'Switch color theme' },
341
+ { text: '/checkpoint', description: 'File snapshots' },
342
+ { text: '/image', description: 'Add image to chat' },
343
+ { text: '/ollama', description: 'Ollama status' },
344
+ { text: '/thinking', description: 'Toggle thinking' },
345
+ { text: '/compact', description: 'Toggle compact mode' },
346
+ ];
347
+ }
348
+ /** Get completions for REPL input */
349
+ getReplCompletions(input) {
350
+ const commands = this.getAllReplCommands();
351
+ const partial = input.toLowerCase();
352
+ return commands.filter(cmd => cmd.text.toLowerCase().startsWith(partial));
353
+ }
354
+ /** Display completions */
355
+ showCompletions(completions) {
356
+ const maxLen = Math.max(...completions.map(c => c.text.length));
357
+ for (const c of completions.slice(0, 10)) {
358
+ console.log(` ${C.info(c.text.padEnd(maxLen + 2))} ${C.dim(c.description)}`);
359
+ }
360
+ if (completions.length > 10) {
361
+ console.log(C.dim(` ... and ${completions.length - 10} more`));
362
+ }
363
+ }
364
+ /** Display enhanced completions with type icons */
365
+ showEnhancedCompletions(completions) {
366
+ const typeIcons = {
367
+ command: '⌘',
368
+ model: '🤖',
369
+ file: '📄',
370
+ directory: '📁',
371
+ history: '📜',
372
+ option: '⚙️',
373
+ skill: '🎯',
374
+ mcp: '🔌',
375
+ };
376
+ const typeColors = {
377
+ command: C.info,
378
+ model: C.brand,
379
+ file: C.muted,
380
+ directory: C.success,
381
+ history: C.subtle,
382
+ option: C.warning,
383
+ skill: C.accent,
384
+ mcp: C.toolName,
385
+ };
386
+ const maxLen = Math.max(...completions.map(c => c.displayText.length));
387
+ const limit = Math.min(completions.length, 12);
388
+ for (const c of completions.slice(0, limit)) {
389
+ const icon = typeIcons[c.type] || '•';
390
+ const color = typeColors[c.type] || C.primary;
391
+ const text = c.displayText.padEnd(maxLen + 2);
392
+ console.log(` ${icon} ${color(text)}${C.dim(c.description)}`);
393
+ }
394
+ if (completions.length > limit) {
395
+ console.log(C.dim(` ... and ${completions.length - limit} more`));
396
+ }
397
+ }
398
+ /** Route a single trimmed line to the correct handler */
399
+ async dispatchInput(input) {
400
+ // /command
401
+ if (input.startsWith('/')) {
402
+ await this.handleCommand(input);
403
+ return;
404
+ }
405
+ // !shell command
406
+ if (input.startsWith('!')) {
407
+ await this.handleShellCommand(input.slice(1).trim());
408
+ return;
409
+ }
410
+ // Regular input (may contain @file references)
411
+ await this.processInput(input);
412
+ }
413
+ // ========================================================================
414
+ // @ File reference expansion
415
+ // ========================================================================
416
+ /**
417
+ * Expand @path references in user input.
418
+ * @src/App.tsx → inlines file content
419
+ * @src/components/ → inlines directory listing + all files under 50KB total
420
+ */
421
+ async expandAtReferences(input) {
422
+ // Match @word/path.ext or @path patterns (not email addresses)
423
+ const atPattern = /@([\w./\-\\]+)/g;
424
+ const matches = [...input.matchAll(atPattern)];
425
+ if (matches.length === 0)
426
+ return input;
427
+ let result = input;
428
+ const injections = [];
429
+ for (const match of matches) {
430
+ const refPath = match[1];
431
+ if (!refPath)
432
+ continue;
433
+ const absPath = path.isAbsolute(refPath)
434
+ ? refPath
435
+ : path.resolve(this.cwd, refPath);
436
+ try {
437
+ if (!fs.existsSync(absPath)) {
438
+ injections.push(`[@ ${refPath}: file not found]`);
439
+ continue;
440
+ }
441
+ const stat = fs.statSync(absPath);
442
+ if (stat.isDirectory()) {
443
+ // Directory: list contents and include small files
444
+ const files = this.listDirRecursive(absPath, 3, 30);
445
+ const fileList = files.join('\n');
446
+ let content = `\n\`\`\`\n# Directory: ${refPath}\n${fileList}\n\`\`\`\n`;
447
+ // Include content of small text files (limit to 20 files, 100KB total)
448
+ let totalSize = 0;
449
+ let fileCount = 0;
450
+ for (const f of files) {
451
+ if (fileCount >= 20)
452
+ break;
453
+ const fullPath = path.join(absPath, f);
454
+ try {
455
+ if (!fs.statSync(fullPath).isFile())
456
+ continue;
457
+ const size = fs.statSync(fullPath).size;
458
+ if (size > 50000 || totalSize + size > 100000)
459
+ continue;
460
+ const ext = path.extname(f).slice(1);
461
+ if (!this.isTextFile(ext))
462
+ continue;
463
+ const fileContent = fs.readFileSync(fullPath, 'utf-8');
464
+ content += `\n\`\`\`${ext}\n# ${f}\n${fileContent}\n\`\`\`\n`;
465
+ totalSize += size;
466
+ fileCount++;
467
+ }
468
+ catch { /* skip */ }
469
+ }
470
+ injections.push(content);
471
+ console.log(C.info(` @ ${refPath} → directory (${files.length} files)`));
472
+ }
473
+ else {
474
+ // Single file
475
+ const size = stat.size;
476
+ if (size > 200 * 1024) {
477
+ injections.push(`[@ ${refPath}: file too large (${(size / 1024).toFixed(0)} KB), please be more specific]`);
478
+ console.log(C.warning(` @ ${refPath} → too large (${(size / 1024).toFixed(0)} KB)`));
479
+ continue;
480
+ }
481
+ const ext = path.extname(refPath).slice(1);
482
+ const fileContent = fs.readFileSync(absPath, 'utf-8');
483
+ injections.push(`\n\`\`\`${ext}\n# ${refPath}\n${fileContent}\n\`\`\`\n`);
484
+ console.log(C.info(` @ ${refPath} → ${(size / 1024).toFixed(1)} KB`));
485
+ }
486
+ }
487
+ catch (err) {
488
+ injections.push(`[@ ${refPath}: error reading file — ${err.message}]`);
489
+ }
490
+ }
491
+ // Append all injected content below the original input
492
+ if (injections.length > 0) {
493
+ result = input + '\n\n' + injections.join('\n');
494
+ }
495
+ return result;
496
+ }
497
+ isTextFile(ext) {
498
+ const TEXT_EXTS = new Set([
499
+ 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'json', 'yaml', 'yml', 'toml', 'ini',
500
+ 'md', 'txt', 'html', 'css', 'scss', 'less', 'sh', 'bash', 'zsh', 'fish',
501
+ 'py', 'rb', 'go', 'rs', 'java', 'c', 'cpp', 'h', 'hpp', 'cs', 'php', 'swift',
502
+ 'vue', 'svelte', 'astro', 'graphql', 'sql', 'env', 'gitignore', 'lock',
503
+ ]);
504
+ return TEXT_EXTS.has(ext.toLowerCase());
505
+ }
506
+ listDirRecursive(dir, maxDepth, maxFiles) {
507
+ const results = [];
508
+ const walk = (current, depth, prefix) => {
509
+ if (depth > maxDepth || results.length >= maxFiles)
510
+ return;
511
+ try {
512
+ const entries = fs.readdirSync(current).filter((e) => !e.startsWith('.') && e !== 'node_modules' && e !== 'dist' && e !== '__pycache__');
513
+ for (const e of entries) {
514
+ if (results.length >= maxFiles)
515
+ break;
516
+ const fullPath = path.join(current, e);
517
+ const rel = prefix ? `${prefix}/${e}` : e;
518
+ results.push(rel);
519
+ if (fs.statSync(fullPath).isDirectory()) {
520
+ walk(fullPath, depth + 1, rel);
521
+ }
522
+ }
523
+ }
524
+ catch { /* skip */ }
525
+ };
526
+ walk(dir, 0, '');
527
+ return results;
528
+ }
529
+ // ========================================================================
530
+ // Prompt & Banner
531
+ // ========================================================================
532
+ getPromptText() {
533
+ const modeInfo = MODE_LABELS[this.mode];
534
+ const modelShort = this.modelClient.getModel().split('/').pop() || this.modelClient.getModel();
535
+ if (this.isMultiline) {
536
+ // Multi-line mode indicator
537
+ return C.dim(' ' + BOX.arrowDown + ' ');
538
+ }
539
+ // Compact prompt: [MODE] model ›
540
+ const modeBadge = modeInfo.color(`[${modeInfo.label}]`);
541
+ const modelPart = C.muted(modelShort);
542
+ return `\n${modeBadge} ${modelPart} ${C.brand(BOX.arrowRight)} `;
543
+ }
544
+ printPrompt() {
545
+ if (this.rl) {
546
+ const promptText = this.getPromptText();
547
+ this.rl.setPrompt(promptText);
548
+ this.rl.prompt();
549
+ }
550
+ }
551
+ /** Get terminal width with min/max constraints */
552
+ getTermWidth(min = 40, max = 120) {
553
+ const cols = process.stdout.columns || 80;
554
+ return Math.max(min, Math.min(cols - 4, max));
555
+ }
556
+ /** Print input box frame before prompt */
557
+ printInputBox() {
558
+ const modeInfo = MODE_LABELS[this.mode];
559
+ const modelShort = this.modelClient.getModel().split('/').pop() || this.modelClient.getModel();
560
+ const w = this.getTermWidth(40, 100);
561
+ // Get session stats for context usage
562
+ let contextInfo = '';
563
+ if (this.sessionId) {
564
+ const stats = this.sessionManager.getStats(this.sessionId);
565
+ if (stats) {
566
+ const totalTokens = (stats.totalInputTokens || 0) + (stats.totalOutputTokens || 0);
567
+ const maxContext = this.config.core.maxTokens * 8 || 128000;
568
+ const pct = Math.min(100, Math.round((totalTokens / maxContext) * 100));
569
+ const pctColor = pct > 80 ? C.error : pct > 50 ? C.warning : C.success;
570
+ contextInfo = pctColor(`${pct}%`) + C.dim(' ctx');
571
+ }
572
+ }
573
+ // Input box header
574
+ const modeBadge = modeInfo.color(`[${modeInfo.label}]`);
575
+ const headerText = `${modeBadge} ${C.muted(modelShort)}${contextInfo ? ' ' + contextInfo : ''}`;
576
+ const visibleLen = modeInfo.label.length + 2 + 1 + modelShort.length + (contextInfo ? 8 : 0);
577
+ const headerPadding = Math.max(0, w - visibleLen - 3);
578
+ console.log('');
579
+ console.log(C.brand(BOX.tl) + C.brand(BOX.hThick.repeat(w)) + C.brand(BOX.tr));
580
+ console.log(C.brand(BOX.v) + ' ' + headerText + ' '.repeat(headerPadding) + C.brand(BOX.v));
581
+ console.log(C.brand(BOX.ht) + C.brandDim(BOX.h.repeat(w)) + C.brand(BOX.htr));
582
+ }
583
+ /** Print input box footer after user submits */
584
+ printInputBoxFooter() {
585
+ const w = this.getTermWidth(40, 100);
586
+ console.log(C.brand(BOX.bl) + C.brand(BOX.hThick.repeat(w)) + C.brand(BOX.br));
587
+ }
588
+ /** Print compact status bar after AI response */
589
+ printStatusBar() {
590
+ const modelShort = this.modelClient.getModel().split('/').pop() || this.modelClient.getModel();
591
+ const modeInfo = MODE_LABELS[this.mode];
592
+ let contextInfo = '';
593
+ if (this.sessionId) {
594
+ const stats = this.sessionManager.getStats(this.sessionId);
595
+ if (stats) {
596
+ const totalTokens = (stats.totalInputTokens || 0) + (stats.totalOutputTokens || 0);
597
+ const maxContext = this.config.core.maxTokens * 8 || 128000;
598
+ const pct = Math.min(100, Math.round((totalTokens / maxContext) * 100));
599
+ const pctColor = pct > 80 ? C.error : pct > 50 ? C.warning : C.success;
600
+ contextInfo = pctColor(`${pct}%`);
601
+ }
602
+ }
603
+ const parts = [
604
+ C.muted('Model:') + ' ' + C.primary(modelShort),
605
+ C.muted('Mode:') + ' ' + modeInfo.color(modeInfo.label),
606
+ contextInfo ? C.muted('Context:') + ' ' + contextInfo : '',
607
+ ].filter(Boolean);
608
+ console.log(C.dim(' ' + BOX.h.repeat(4) + ' ') + parts.join(C.dim(' · ')) + C.dim(' ' + BOX.h.repeat(4)));
609
+ }
610
+ printBanner() {
611
+ const modeInfo = MODE_LABELS[this.mode];
612
+ const modelShort = this.modelClient.getModel().split('/').pop() || this.modelClient.getModel();
613
+ const termCols = process.stdout.columns || 80;
614
+ // Use a reasonable max width, but don't exceed terminal width
615
+ const w = Math.min(termCols - 4, 76);
616
+ const hr = C.brandDim(BOX.h.repeat(w));
617
+ const hrThick = C.brand(BOX.hThick.repeat(w));
618
+ const vl = C.brandDim(BOX.v);
619
+ // Simple compact header instead of big ASCII art
620
+ console.log('');
621
+ console.log(C.brand(BOX.tl) + hrThick + C.brand(BOX.tr));
622
+ // Compact logo line
623
+ const logoLine = C.brand(' NOVA ') + C.brandLight('CLI') + C.dim(' · AI-powered terminal assistant');
624
+ const logoPadding = Math.max(0, w - 38); // 38 = visible chars in logoLine (without ANSI codes)
625
+ console.log(vl + logoLine + ' '.repeat(logoPadding) + vl);
626
+ console.log(C.brand(BOX.ht) + hr + C.brand(BOX.htr));
627
+ // Status line 1: Model | Dir
628
+ const modelLabel = C.dim('Model: ');
629
+ const modelVal = C.primary(modelShort);
630
+ const dirLabel = C.dim('Dir: ');
631
+ const dirVal = C.muted(this.cwd.length > 40 ? '...' + this.cwd.slice(-37) : this.cwd);
632
+ const line1 = ` ${modelLabel}${modelVal} ${C.dim(BOX.v)} ${dirLabel}${dirVal}`;
633
+ console.log(vl + line1 + ' '.repeat(Math.max(0, w - 10 - modelShort.length)) + vl);
634
+ // Status line 2: Mode | Session
635
+ const modeLabel = C.dim('Mode: ');
636
+ const modeVal = modeInfo.color(modeInfo.label);
637
+ const sessLabel = C.dim('Session: ');
638
+ const sessionText = this.restoreSessionId ? this.restoreSessionId.slice(0, 8) : 'new';
639
+ const sessVal = C.muted(sessionText);
640
+ const line2 = ` ${modeLabel}${modeVal} ${C.dim(BOX.v)} ${sessLabel}${sessVal}`;
641
+ console.log(vl + line2 + ' '.repeat(Math.max(0, w - 24)) + vl);
642
+ // Status line 3: MCP
643
+ let mcpStatus = C.dim('○');
644
+ let mcpText = 'none';
645
+ if (this.mcpManager) {
646
+ const statuses = this.mcpManager.listServers();
647
+ if (statuses.length > 0) {
648
+ const connected = statuses.filter((s) => s.connected).length;
649
+ const total = statuses.length;
650
+ mcpStatus = connected === total ? C.success(BOX.check) : connected > 0 ? C.warning('◐') : C.error(BOX.crossX);
651
+ mcpText = `${connected}/${total}`;
652
+ }
653
+ }
654
+ const mcpLabel = C.dim('MCP: ');
655
+ const line3 = ` ${mcpLabel}${mcpStatus} ${C.muted(mcpText)}`;
656
+ console.log(vl + line3 + ' '.repeat(Math.max(0, w - 16)) + vl);
657
+ console.log(C.brand(BOX.ht) + hr + C.brand(BOX.htr));
658
+ // Commands help - compact
659
+ const cmdLine = C.dim(' Commands: ') +
660
+ C.primary('/help') + C.dim(', ') +
661
+ C.primary('/mode') + C.dim(', ') +
662
+ C.primary('/model') + C.dim(', ') +
663
+ C.primary('/init') + C.dim(', ') +
664
+ C.primary('/quit');
665
+ console.log(vl + cmdLine + ' '.repeat(Math.max(0, w - 52)) + vl);
666
+ // Shortcuts
667
+ const shortcutLine = C.dim(' Shortcuts: ') +
668
+ C.info('@file') + C.dim(' inject, ') +
669
+ C.info('!cmd') + C.dim(' shell, ') +
670
+ C.info('\\') + C.dim(' multiline');
671
+ console.log(vl + shortcutLine + ' '.repeat(Math.max(0, w - 52)) + vl);
672
+ // Bottom border
673
+ console.log(C.brand(BOX.bl) + hrThick + C.brand(BOX.br));
674
+ console.log('');
675
+ }
676
+ // ========================================================================
677
+ // UI Helpers
678
+ // ========================================================================
679
+ printLine(label, type = 'muted') {
680
+ const colors = { info: C.info, success: C.success, warning: C.warning, error: C.error, muted: C.muted };
681
+ const color = colors[type];
682
+ const w = this.getTermWidth(40, 80);
683
+ const padded = ` ${label} `;
684
+ const left = Math.floor((w - padded.length) / 2);
685
+ const right = w - padded.length - left;
686
+ console.log(C.dim(BOX.h.repeat(left)) + color(padded) + C.dim(BOX.h.repeat(right)));
687
+ }
688
+ startSpinner(msg) {
689
+ this.stopSpinner();
690
+ this.spinnerFrame = 0;
691
+ this.spinnerTimer = setInterval(() => {
692
+ const frame = BOX.spinner[this.spinnerFrame % BOX.spinner.length];
693
+ process.stdout.write(`\r${C.brand(frame)} ${C.muted(msg)}`);
694
+ this.spinnerFrame++;
695
+ }, 80);
696
+ }
697
+ stopSpinner() {
698
+ if (this.spinnerTimer) {
699
+ clearInterval(this.spinnerTimer);
700
+ this.spinnerTimer = null;
701
+ const clearWidth = Math.min(60, (process.stdout.columns || 80) - 1);
702
+ process.stdout.write('\r' + ' '.repeat(clearWidth) + '\r');
703
+ }
704
+ }
705
+ // ========================================================================
706
+ // !shell direct execution
707
+ // ========================================================================
708
+ async handleShellCommand(cmd) {
709
+ if (!cmd) {
710
+ console.log(C.muted(' Usage: !<command> e.g. !ls, !git status, !npm test'));
711
+ return;
712
+ }
713
+ console.log('');
714
+ console.log(C.muted(` $ ${cmd}`));
715
+ const startTime = Date.now();
716
+ try {
717
+ // Use spawn for streaming output
718
+ const isWin = process.platform === 'win32';
719
+ const shell = isWin ? 'powershell.exe' : '/bin/sh';
720
+ const shellArgs = isWin ? ['-Command', cmd] : ['-c', cmd];
721
+ await new Promise((resolve, reject) => {
722
+ const child = spawn(shell, shellArgs, {
723
+ cwd: this.cwd,
724
+ stdio: ['inherit', 'pipe', 'pipe'],
725
+ });
726
+ child.stdout?.on('data', (chunk) => process.stdout.write(C.primary(chunk.toString())));
727
+ child.stderr?.on('data', (chunk) => process.stderr.write(C.warning(chunk.toString())));
728
+ child.on('close', (code) => {
729
+ const duration = ((Date.now() - startTime) / 1000).toFixed(2);
730
+ if (code === 0) {
731
+ console.log('');
732
+ console.log(C.success(` ✓ exit 0`) + C.dim(` (${duration}s)`));
733
+ }
734
+ else {
735
+ console.log('');
736
+ console.log(C.error(` ✗ exit ${code}`) + C.dim(` (${duration}s)`));
737
+ }
738
+ resolve();
739
+ });
740
+ child.on('error', reject);
741
+ });
742
+ }
743
+ catch (err) {
744
+ console.log(C.error(` Error: ${err.message}`));
745
+ }
746
+ }
747
+ // ========================================================================
748
+ // Agent loop processing
749
+ // ========================================================================
750
+ async processInput(input) {
751
+ if (!this.sessionId)
752
+ return;
753
+ this.processing = true;
754
+ this.activeToolCalls.clear();
755
+ this.toolCallOrder = [];
756
+ this.currentTurn = 0;
757
+ this.currentTaskTokens = 0;
758
+ // Clear any previous TODO panel
759
+ this.todoProgressPanel.hide();
760
+ // ESC key listener: cancel running agent loop
761
+ const onEscKey = (buf) => {
762
+ // ESC = \x1b, but arrow keys also start with \x1b[ so we check the raw byte
763
+ if (buf.length === 1 && buf[0] === 0x1b) {
764
+ if (this.currentLoop?.isActive()) {
765
+ this.currentLoop.cancel();
766
+ }
767
+ }
768
+ };
769
+ process.stdin.on('data', onEscKey);
770
+ // Expand @file references
771
+ const expandedInput = await this.expandAtReferences(input);
772
+ // Show user message with highlighted box (distinctive from AI responses)
773
+ this.userMessageHighlight.render(input);
774
+ const modePrefix = this.getModePrefix();
775
+ // Skill injection
776
+ let skillPrefix = '';
777
+ if (this._pendingSkillInject) {
778
+ const skillName = this._pendingSkillInject.metadata.name;
779
+ skillPrefix = `[SKILL: ${skillName}]\n${this._pendingSkillInject.content}\n[/SKILL]\n\n`;
780
+ console.log(C.info(` ⚡ Skill "${skillName}" injected`));
781
+ this._pendingSkillInject = null;
782
+ }
783
+ const fullInput = [modePrefix, skillPrefix, expandedInput].filter(Boolean).join('\n\n');
784
+ try {
785
+ const effectiveApprovalMode = this.getEffectiveApprovalMode();
786
+ // Get model config to check for built-in search capability
787
+ const modelConfigResult = this.configManager.getModelConfig(this.modelClient.getModel());
788
+ const systemPrompt = buildSystemPrompt({
789
+ workingDirectory: this.cwd,
790
+ model: this.modelClient.getModel(),
791
+ approvalMode: effectiveApprovalMode,
792
+ supportsBuiltinSearch: modelConfigResult?.model?.supportsBuiltinSearch,
793
+ });
794
+ const agentLoop = new AgentLoop({
795
+ modelClient: this.modelClient,
796
+ sessionManager: this.sessionManager,
797
+ toolRegistry: this.toolRegistry,
798
+ systemPrompt,
799
+ contextCompressor: this.contextCompressor,
800
+ maxContextTokens: (this.config.core.maxTokens || 16384) * 8,
801
+ onTextDelta: (text) => {
802
+ this.stopSpinner();
803
+ process.stdout.write(text);
804
+ },
805
+ onToolStart: (name, toolCallId, input) => {
806
+ this.stopSpinner();
807
+ if (this.thinkingRenderer.isRendering()) {
808
+ this.thinkingRenderer.cancel();
809
+ process.stdout.write('\n');
810
+ }
811
+ // Summarize tool input for display
812
+ const inputSummary = input ? this.summarizeToolInput(name, input) : '';
813
+ const state = {
814
+ name, toolCallId,
815
+ startTime: Date.now(),
816
+ input: inputSummary, result: '',
817
+ isError: false, isComplete: false,
818
+ lineIndex: this.toolCallOrder.length,
819
+ };
820
+ this.activeToolCalls.set(toolCallId, state);
821
+ this.toolCallOrder.push(toolCallId);
822
+ // Start spinner for this tool
823
+ this.startToolSpinner(state);
824
+ },
825
+ onToolComplete: (name, toolCallId, result) => {
826
+ this.stopSpinner();
827
+ const state = this.activeToolCalls.get(toolCallId);
828
+ if (state) {
829
+ state.result = typeof result.content === 'string' ? result.content : JSON.stringify(result.content);
830
+ state.isError = !!result.isError;
831
+ state.isComplete = true;
832
+ this.printToolLine(state);
833
+ if (name === 'todo' && !result.isError) {
834
+ this.printTodoPanel(state.result);
835
+ }
836
+ }
837
+ },
838
+ onThinkingStart: () => {
839
+ if (!this.showThinking)
840
+ return;
841
+ this.stopSpinner();
842
+ this.thinkingRenderer.start();
843
+ },
844
+ onThinkingDelta: (delta) => {
845
+ if (!this.showThinking)
846
+ return;
847
+ this.thinkingRenderer.append(delta);
848
+ },
849
+ onThinkingEnd: () => {
850
+ if (!this.showThinking)
851
+ return;
852
+ this.thinkingRenderer.complete();
853
+ },
854
+ onApprovalRequired: this.handleApproval.bind(this),
855
+ onTurnStart: (turn) => {
856
+ this.currentTurn = turn;
857
+ this.startSpinner(`turn ${turn} — thinking...`);
858
+ },
859
+ onTurnEnd: () => {
860
+ this.stopSpinner();
861
+ },
862
+ onContextCompress: (orig, result, action) => {
863
+ console.log(C.muted(`\n ${BOX.arrow} context compressed: ${orig} → ${result} tokens (${action})`));
864
+ },
865
+ });
866
+ this.currentLoop = agentLoop;
867
+ const startTime = Date.now();
868
+ const result = await agentLoop.runStream(this.sessionId, fullInput);
869
+ const duration = Date.now() - startTime;
870
+ this.currentLoop = null;
871
+ this.processing = false;
872
+ this.stopSpinner();
873
+ // Auto-persist session after every turn
874
+ this.sessionManager.persist(this.sessionId);
875
+ console.log('');
876
+ const totalTokens = result.totalInputTokens + result.totalOutputTokens;
877
+ // Compact completion summary with icons
878
+ const summaryParts = [
879
+ `${C.success(BOX.check)} ${C.muted(`${result.turnsCompleted} turn${result.turnsCompleted > 1 ? 's' : ''}`)}`,
880
+ `${C.info(BOX.diamond)} ${C.muted(`${totalTokens.toLocaleString()} tok`)}`,
881
+ `${C.accent(BOX.star)} ${C.muted(`${(duration / 1000).toFixed(1)}s`)}`,
882
+ ];
883
+ console.log(C.dim(' ' + BOX.h.repeat(4)) + ' ' +
884
+ C.success('Done') + ' ' +
885
+ summaryParts.join(C.dim(' · ')) + ' ' +
886
+ C.dim(BOX.h.repeat(4)));
887
+ }
888
+ catch (err) {
889
+ this.currentLoop = null;
890
+ this.processing = false;
891
+ this.thinkingRenderer.cancel();
892
+ this.stopSpinner();
893
+ if (err && typeof err === 'object' && 'name' in err && err.name === 'CancelledError') {
894
+ return;
895
+ }
896
+ console.log('');
897
+ this.printLine('error', 'error');
898
+ console.error(C.error(` ${err.message}`));
899
+ }
900
+ finally {
901
+ process.stdin.off('data', onEscKey);
902
+ }
903
+ }
904
+ // ========================================================================
905
+ // Tool call display (collapsible panel)
906
+ // ========================================================================
907
+ /** Summarize tool input into a one-line preview */
908
+ summarizeToolInput(name, input) {
909
+ switch (name) {
910
+ case 'read_file':
911
+ case 'list_directory':
912
+ return String(input.file_path || input.path || input.target_directory || '').replace(/\\/g, '/').split('/').slice(-2).join('/');
913
+ case 'write_file':
914
+ return String(input.file_path || input.path || '').replace(/\\/g, '/').split('/').slice(-2).join('/') + ' (write)';
915
+ case 'edit_file':
916
+ case 'replace_in_file':
917
+ return String(input.file_path || '').replace(/\\/g, '/').split('/').slice(-2).join('/') + ' (edit)';
918
+ case 'execute_command':
919
+ return String(input.command || '').slice(0, 60);
920
+ case 'search_file':
921
+ return String(input.pattern || '') + ' in ' + String(input.path || '.').split('/').pop();
922
+ case 'search_content':
923
+ return '"' + String(input.pattern || '').slice(0, 40) + '"';
924
+ case 'web_search':
925
+ return '"' + String(input.query || '').slice(0, 40) + '"';
926
+ default:
927
+ // Generic: show first meaningful value
928
+ const vals = Object.values(input).filter((v) => typeof v === 'string' && v.length > 0);
929
+ return vals.length > 0 ? vals[0].slice(0, 50) : '';
930
+ }
931
+ }
932
+ /** Start a spinner animation for a running tool */
933
+ startToolSpinner(state) {
934
+ const idx = this.toolCallOrder.indexOf(state.toolCallId) + 1;
935
+ const idxStr = idx.toString().padStart(2, '0');
936
+ const inputPreview = state.input ? ' ' + C.dim(state.input) : '';
937
+ // Print the tool start line
938
+ process.stdout.write('\n' +
939
+ C.dim(' ' + BOX.arrow + ' ') +
940
+ C.toolName(state.name) +
941
+ C.dim(` #${idxStr}`) +
942
+ inputPreview +
943
+ ' ');
944
+ // Start inline spinner
945
+ this.spinnerFrame = 0;
946
+ const chars = BOX.spinner;
947
+ this.spinnerTimer = setInterval(() => {
948
+ const elapsed = Date.now() - state.startTime;
949
+ const elapsedStr = elapsed < 1000 ? `${elapsed}ms` : `${(elapsed / 1000).toFixed(1)}s`;
950
+ const frame = chars[this.spinnerFrame % chars.length];
951
+ process.stdout.write(`\r ${C.dim(frame)} ${C.toolName(state.name)}${C.dim(` #${idxStr}`)}${inputPreview} ${C.dim(elapsedStr)}`);
952
+ this.spinnerFrame++;
953
+ }, 80);
954
+ }
955
+ /** Print a finalized tool line (success or error) */
956
+ printToolLine(state) {
957
+ const duration = Date.now() - state.startTime;
958
+ const durationStr = duration < 1000 ? `${duration}ms` : `${(duration / 1000).toFixed(1)}s`;
959
+ const idx = this.toolCallOrder.indexOf(state.toolCallId) + 1;
960
+ const idxStr = idx.toString().padStart(2, '0');
961
+ const inputPreview = state.input ? ' ' + C.dim(state.input) : '';
962
+ const icon = state.isError ? C.error(BOX.crossX) : C.success(BOX.check);
963
+ const nameColor = state.isError ? C.toolErr : C.toolName;
964
+ // Result preview (one line)
965
+ let resultPreview = '';
966
+ if (state.isError) {
967
+ resultPreview = ' ' + C.error.dim(state.result.slice(0, 60).replace(/\n/g, ' '));
968
+ }
969
+ else if (state.result.length > 0) {
970
+ // Show first line of result as preview
971
+ const firstLine = state.result.split('\n')[0].slice(0, 60);
972
+ resultPreview = ' ' + C.dim(firstLine);
973
+ }
974
+ process.stdout.write('\r' + ' '.repeat(80) + '\r');
975
+ console.log(C.dim(' ' + (state.isError ? BOX.crossX : BOX.check) + ' ') +
976
+ nameColor(state.name) +
977
+ C.dim(` #${idxStr}`) +
978
+ inputPreview +
979
+ ' ' +
980
+ (state.isError ? C.errorDim(durationStr) : C.successDim(durationStr)) +
981
+ resultPreview);
982
+ }
983
+ printTodoPanel(result) {
984
+ if (!result || result === 'No tasks tracked.' || result === 'All tasks cleared.') {
985
+ this.todoProgressPanel.hide();
986
+ return;
987
+ }
988
+ const lines = result.split('\n').filter((l) => l.trim());
989
+ if (lines.length === 0) {
990
+ this.todoProgressPanel.hide();
991
+ return;
992
+ }
993
+ // Parse TODO items from the result string
994
+ const todos = [];
995
+ let idx = 0;
996
+ for (const line of lines) {
997
+ const pendingMatch = line.match(/^○\s+\[pending\s*\]\s+(.+)/);
998
+ const inProgressMatch = line.match(/^◉\s+\[in_progress\s*\]\s+(.+)/);
999
+ const completedMatch = line.match(/^●\s+\[completed\s*\]\s+(.+)/);
1000
+ const failedMatch = line.match(/^✗\s+\[failed\s*\]\s+(.+)/);
1001
+ // Detect priority from task text (high/medium/low keywords)
1002
+ const detectPriority = (text) => {
1003
+ if (/high|critical|urgent|重要/i.test(text))
1004
+ return 'high';
1005
+ if (/low|minor|minor|低/i.test(text))
1006
+ return 'low';
1007
+ if (/medium|normal|中/i.test(text))
1008
+ return 'medium';
1009
+ return undefined;
1010
+ };
1011
+ if (completedMatch) {
1012
+ todos.push({
1013
+ id: String(idx++),
1014
+ task: completedMatch[1].trim(),
1015
+ status: 'completed',
1016
+ priority: detectPriority(completedMatch[1]),
1017
+ });
1018
+ }
1019
+ else if (inProgressMatch) {
1020
+ todos.push({
1021
+ id: String(idx++),
1022
+ task: inProgressMatch[1].trim(),
1023
+ status: 'in_progress',
1024
+ priority: detectPriority(inProgressMatch[1]),
1025
+ });
1026
+ }
1027
+ else if (pendingMatch) {
1028
+ todos.push({
1029
+ id: String(idx++),
1030
+ task: pendingMatch[1].trim(),
1031
+ status: 'pending',
1032
+ priority: detectPriority(pendingMatch[1]),
1033
+ });
1034
+ }
1035
+ else if (failedMatch) {
1036
+ todos.push({
1037
+ id: String(idx++),
1038
+ task: failedMatch[1].trim(),
1039
+ status: 'failed',
1040
+ priority: detectPriority(failedMatch[1]),
1041
+ });
1042
+ }
1043
+ }
1044
+ // Update and show the fixed-position TODO panel
1045
+ if (todos.length > 0) {
1046
+ this.todoProgressPanel.setTodos(todos);
1047
+ this.todoProgressPanel.show();
1048
+ }
1049
+ }
1050
+ renderProgressBar(pct, width) {
1051
+ const filled = Math.round((pct / 100) * width);
1052
+ const empty = width - filled;
1053
+ const bar = C.success('█'.repeat(filled)) + C.dim('░'.repeat(empty));
1054
+ return C.muted('[') + bar + C.muted(']') + C.muted(` ${pct}%`);
1055
+ }
1056
+ // ========================================================================
1057
+ // Mode & toggle operations
1058
+ // ========================================================================
1059
+ cycleMode() {
1060
+ const idx = MODES.indexOf(this.mode);
1061
+ const nextIdx = (idx + 1) % MODES.length;
1062
+ this.mode = MODES[nextIdx] ?? 'auto';
1063
+ const info = MODE_LABELS[this.mode];
1064
+ console.log(C.dim(' ' + BOX.arrowRight) + ' ' +
1065
+ C.muted('Mode: ') + info.color(info.label) + ' ' +
1066
+ C.dim('·') + ' ' + C.muted(info.description));
1067
+ }
1068
+ toggleThinking() {
1069
+ this.showThinking = !this.showThinking;
1070
+ const status = this.showThinking ? C.success('ON') : C.error('OFF');
1071
+ const icon = this.showThinking ? C.success(BOX.check) : C.error(BOX.crossX);
1072
+ console.log(C.dim(` ${icon} Thinking: ${status}`));
1073
+ }
1074
+ toggleCompact() {
1075
+ this.compactMode = !this.compactMode;
1076
+ this.thinkingRenderer.setExpanded(!this.compactMode);
1077
+ const status = this.compactMode ? C.success('compact') : C.info('verbose');
1078
+ console.log(C.dim(` ${BOX.diamond} Display: ${status}`));
1079
+ }
1080
+ // ========================================================================
1081
+ // Commands
1082
+ // ========================================================================
1083
+ async handleCommand(cmd) {
1084
+ const parts = cmd.slice(1).split(/\s+/);
1085
+ const command = parts[0];
1086
+ const arg = parts.slice(1).join(' ');
1087
+ switch (command) {
1088
+ case 'quit':
1089
+ case 'exit':
1090
+ case 'q':
1091
+ if (this.sessionId)
1092
+ this.sessionManager.persist(this.sessionId);
1093
+ console.log(C.muted('Goodbye!'));
1094
+ process.exit(0);
1095
+ case 'help':
1096
+ case 'h':
1097
+ case '?':
1098
+ this.printHelp();
1099
+ break;
1100
+ case 'clear':
1101
+ case 'reset':
1102
+ if (this.sessionId)
1103
+ this.sessionManager.persist(this.sessionId);
1104
+ this.sessionId = this.createInitialSession();
1105
+ console.log(C.muted(' Conversation cleared. New session started.'));
1106
+ break;
1107
+ case 'model':
1108
+ await this.handleModelCommand(arg);
1109
+ break;
1110
+ case 'mode':
1111
+ if (arg && MODES.includes(arg)) {
1112
+ this.mode = arg;
1113
+ const info = MODE_LABELS[this.mode];
1114
+ console.log(C.muted(' Mode: ') + info.color(info.label) + C.muted(` — ${info.description}`));
1115
+ console.log(C.muted(` Approval: `) + C.info(info.approvalMode));
1116
+ }
1117
+ else {
1118
+ this.cycleMode();
1119
+ }
1120
+ break;
1121
+ case 'thinking':
1122
+ this.toggleThinking();
1123
+ break;
1124
+ case 'compact':
1125
+ this.toggleCompact();
1126
+ break;
1127
+ case 'tools': {
1128
+ const tools = this.toolRegistry.getEnabledToolNames();
1129
+ const stats = this.toolRegistry.getStats();
1130
+ console.log(C.muted(` Tools (${stats.enabled}): `) + C.primary(tools.slice(0, 10).join(', ') + (tools.length > 10 ? '...' : '')));
1131
+ break;
1132
+ }
1133
+ case 'mcp':
1134
+ await this.handleMcpCommand(arg);
1135
+ break;
1136
+ case 'ollama':
1137
+ await this.handleOllamaCommand(arg);
1138
+ break;
1139
+ case 'skills':
1140
+ await this.handleSkillsCommand(arg);
1141
+ break;
1142
+ case 'theme':
1143
+ await this.handleThemeCommand(arg);
1144
+ break;
1145
+ case 'image':
1146
+ await this.handleImageCommand(arg);
1147
+ break;
1148
+ case 'checkpoint':
1149
+ await this.handleCheckpointCommand(arg);
1150
+ break;
1151
+ case 'init':
1152
+ await this.handleInitCommand(arg);
1153
+ break;
1154
+ case 'memory':
1155
+ await this.handleMemoryCommand(arg);
1156
+ break;
1157
+ case 'history':
1158
+ await this.handleHistoryCommand(arg);
1159
+ break;
1160
+ case 'compress':
1161
+ await this.handleCompressCommand();
1162
+ break;
1163
+ case 'status':
1164
+ if (this.sessionId) {
1165
+ const stats = this.sessionManager.getStats(this.sessionId);
1166
+ const modeInfo = MODE_LABELS[this.mode];
1167
+ console.log(C.muted(' Session: ') + C.primary(stats?.id?.slice(0, 8) || ''));
1168
+ console.log(C.muted(' Mode: ') + modeInfo.color(modeInfo.label));
1169
+ console.log(C.muted(' Turns: ') + C.primary(String(stats?.turnCount || 0)));
1170
+ console.log(C.muted(' Tokens: ') + C.primary(`${stats?.totalInputTokens || 0} in / ${stats?.totalOutputTokens || 0} out`));
1171
+ console.log(C.muted(' Msgs: ') + C.primary(String(stats?.messageCount || 0)));
1172
+ }
1173
+ break;
1174
+ default:
1175
+ console.log(C.warning(` Unknown command: /${command}. Type /help for help.`));
1176
+ }
1177
+ }
1178
+ // ========================================================================
1179
+ // Helper: Prompt for API key
1180
+ // ========================================================================
1181
+ async promptForApiKey(providerName, providerType) {
1182
+ const readline = await import('node:readline');
1183
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1184
+ const envKey = `${providerName.toUpperCase()}_API_KEY`;
1185
+ console.log('');
1186
+ console.log(C.warning(` No API key found for "${providerName}"`));
1187
+ console.log(C.muted(` You can also set it via: export ${envKey}=<your-key>`));
1188
+ console.log('');
1189
+ const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
1190
+ try {
1191
+ const answer = await question(` Enter ${providerName} API key (or press Enter to skip): `);
1192
+ rl.close();
1193
+ return answer.trim() || null;
1194
+ }
1195
+ catch {
1196
+ rl.close();
1197
+ return null;
1198
+ }
1199
+ }
1200
+ // ========================================================================
1201
+ // /model <id> — switch model at runtime (with interactive selector)
1202
+ // ========================================================================
1203
+ async handleModelCommand(arg) {
1204
+ if (!arg) {
1205
+ // Show interactive model selector
1206
+ await this.showModelSelector();
1207
+ return;
1208
+ }
1209
+ try {
1210
+ this.modelClient.updateOptions({ model: arg });
1211
+ console.log(C.success(` ✓ Switched to model: `) + C.primary(arg));
1212
+ // Save to global config
1213
+ const config = this.configManager.getConfig();
1214
+ config.core.defaultModel = arg;
1215
+ await this.configManager.save(config);
1216
+ // Update session config too
1217
+ if (this.sessionId) {
1218
+ const cfg = this.sessionManager.getConfig(this.sessionId);
1219
+ cfg.model = arg;
1220
+ }
1221
+ }
1222
+ catch (err) {
1223
+ console.log(C.error(` Failed to switch model: ${err.message}`));
1224
+ }
1225
+ }
1226
+ /** Get all available models across all providers */
1227
+ getAvailableModels(showAll = false) {
1228
+ const currentModel = this.modelClient.getModel();
1229
+ const models = [];
1230
+ const config = this.configManager.getConfig();
1231
+ // Collect models from configured providers only
1232
+ for (const [providerKey, providerConfig] of Object.entries(config.models.providers)) {
1233
+ // Check if provider is actually configured
1234
+ const hasCreds = this.authManager?.hasCredentials(providerKey) || false;
1235
+ const providerType = providerConfig.type;
1236
+ const hasBaseUrl = !!providerConfig.baseUrl;
1237
+ // Determine if provider is configured
1238
+ let configured = false;
1239
+ if (providerType === 'ollama') {
1240
+ // Local Ollama - check if baseUrl is configured (default localhost)
1241
+ configured = hasBaseUrl || true; // Always show if type is ollama
1242
+ }
1243
+ else if (providerType === 'ollama-cloud') {
1244
+ // Ollama Cloud - needs baseUrl
1245
+ configured = hasBaseUrl;
1246
+ }
1247
+ else if (providerType === 'custom') {
1248
+ // Custom providers need baseUrl
1249
+ configured = hasBaseUrl;
1250
+ }
1251
+ else if (['anthropic', 'openai', 'google', 'deepseek', 'qwen', 'glm', 'moonshot', 'baichuan', 'minimax', 'yi', 'siliconflow', 'groq', 'mistral', 'together', 'perplexity', 'coding-plan-alibaba', 'github'].includes(providerType)) {
1252
+ // Cloud providers need API keys
1253
+ configured = hasCreds;
1254
+ }
1255
+ else {
1256
+ // Unknown type - check for credentials
1257
+ configured = hasCreds || hasBaseUrl;
1258
+ }
1259
+ // Skip unconfigured providers unless showAll is true
1260
+ if (!showAll && !configured)
1261
+ continue;
1262
+ // Add models from this provider
1263
+ for (const [modelId, modelConfig] of Object.entries(providerConfig.models)) {
1264
+ models.push({
1265
+ id: `${providerKey}/${modelId}`,
1266
+ name: modelConfig.name || modelId,
1267
+ provider: providerKey,
1268
+ configured,
1269
+ providerType: providerType,
1270
+ });
1271
+ }
1272
+ }
1273
+ // Also add common aliases for configured providers
1274
+ if (config.models.aliases) {
1275
+ for (const [alias, targetId] of Object.entries(config.models.aliases)) {
1276
+ // Find which provider this alias belongs to
1277
+ for (const [providerKey, providerConfig] of Object.entries(config.models.providers)) {
1278
+ if (providerConfig.models[targetId]) {
1279
+ const hasCreds = this.authManager?.hasCredentials(providerKey) || false;
1280
+ const providerType = providerConfig.type;
1281
+ const hasBaseUrl = !!providerConfig.baseUrl;
1282
+ let configured = false;
1283
+ if (providerType === 'ollama') {
1284
+ configured = true;
1285
+ }
1286
+ else if (providerType === 'ollama-cloud' || providerType === 'custom') {
1287
+ configured = hasBaseUrl;
1288
+ }
1289
+ else {
1290
+ configured = hasCreds;
1291
+ }
1292
+ if (configured && !models.find(m => m.id === alias)) {
1293
+ models.push({
1294
+ id: alias,
1295
+ name: providerConfig.models[targetId]?.name || alias,
1296
+ provider: providerKey,
1297
+ configured,
1298
+ providerType: providerType,
1299
+ });
1300
+ }
1301
+ break;
1302
+ }
1303
+ }
1304
+ }
1305
+ }
1306
+ return models;
1307
+ }
1308
+ /** Interactive model selector with keyboard navigation - Windows compatible */
1309
+ async showModelSelector() {
1310
+ const models = this.getAvailableModels(false); // Only show configured models
1311
+ const currentModel = this.modelClient.getModel();
1312
+ if (models.length === 0) {
1313
+ console.log(C.warning(' No models available for current provider.'));
1314
+ console.log(C.muted(' Current model: ') + C.primary(currentModel));
1315
+ console.log(C.dim(' Usage: /model <model-id>'));
1316
+ return;
1317
+ }
1318
+ // Sort models, current model first
1319
+ models.sort((a, b) => {
1320
+ if (a.id === currentModel)
1321
+ return -1;
1322
+ if (b.id === currentModel)
1323
+ return 1;
1324
+ return a.id.localeCompare(b.id);
1325
+ });
1326
+ let selectedIndex = models.findIndex(m => m.id === currentModel);
1327
+ if (selectedIndex < 0)
1328
+ selectedIndex = 0;
1329
+ // Render function - clears screen and redraws everything
1330
+ const renderModels = (selected) => {
1331
+ // Clear the terminal for a clean redraw
1332
+ process.stdout.write('\x1b[2J\x1b[H');
1333
+ // Print header
1334
+ console.log('');
1335
+ console.log(C.brand(` ╭──────────────────────────────────────────────────────────╮`));
1336
+ console.log(C.brand(` │ `) + C.primary('Model Selector').padEnd(56) + C.brand(`│`));
1337
+ console.log(C.brand(` │ `) + C.dim(`Current: ${currentModel}`).padEnd(56) + C.brand(`│`));
1338
+ console.log(C.brand(` │ `) + C.muted(`↑↓ Navigate | Enter Select | Esc Cancel`).padEnd(56) + C.brand(`│`));
1339
+ console.log(C.brand(` ├──────────────────────────────────────────────────────────┤`));
1340
+ // Render models
1341
+ for (let i = 0; i < models.length; i++) {
1342
+ const m = models[i];
1343
+ const isSelected = i === selected;
1344
+ const isCurrent = m.id === currentModel;
1345
+ // Configuration status
1346
+ const statusIcon = m.configured ? '✓' : '⚠';
1347
+ const statusColor = m.configured ? C.success : C.warning;
1348
+ const statusText = m.configured ? '' : C.warning(' [needs setup]');
1349
+ // Selection indicator
1350
+ const prefix = isSelected ? C.brand('→ ') : ' ';
1351
+ // Format line
1352
+ let modelDisplay = m.name;
1353
+ if (isCurrent) {
1354
+ modelDisplay = C.success(`${m.name}`) + C.dim(` (${m.id})`);
1355
+ }
1356
+ else if (isSelected) {
1357
+ modelDisplay = C.primary(m.name) + C.dim(` (${m.id})`);
1358
+ }
1359
+ else {
1360
+ modelDisplay = C.muted(m.name) + C.dim(` (${m.id})`);
1361
+ }
1362
+ console.log(C.brand(` │ `) + prefix + statusColor(statusIcon) + ' ' + modelDisplay + statusText + C.brand(`│`));
1363
+ }
1364
+ // Print footer
1365
+ console.log(C.brand(` ╰──────────────────────────────────────────────────────────╯`));
1366
+ };
1367
+ // Initial render
1368
+ renderModels(selectedIndex);
1369
+ // Handle keyboard input
1370
+ return new Promise((resolve) => {
1371
+ // Set stdin to raw mode for key detection
1372
+ const wasRaw = process.stdin.isRaw;
1373
+ if (process.stdin.isTTY) {
1374
+ process.stdin.setRawMode(true);
1375
+ }
1376
+ process.stdin.resume();
1377
+ const cleanup = () => {
1378
+ if (process.stdin.isTTY) {
1379
+ process.stdin.setRawMode(wasRaw ?? false);
1380
+ }
1381
+ process.stdin.off('data', onData);
1382
+ };
1383
+ const onData = (buffer) => {
1384
+ const key = buffer.toString();
1385
+ // Up arrow
1386
+ if (key === '\x1b[A' || key === 'k') {
1387
+ selectedIndex = (selectedIndex - 1 + models.length) % models.length;
1388
+ renderModels(selectedIndex);
1389
+ }
1390
+ // Down arrow
1391
+ else if (key === '\x1b[B' || key === 'j') {
1392
+ selectedIndex = (selectedIndex + 1) % models.length;
1393
+ renderModels(selectedIndex);
1394
+ }
1395
+ // Enter
1396
+ else if (key === '\r' || key === '\n') {
1397
+ cleanup();
1398
+ const selected = models[selectedIndex];
1399
+ // Clear and show result
1400
+ process.stdout.write('\x1b[2J\x1b[H');
1401
+ console.log('');
1402
+ // Check if model is configured
1403
+ const providerName = selected.id.split(':')[0];
1404
+ const hasCreds = this.authManager?.hasCredentials(providerName);
1405
+ const isOllama = providerName === 'ollama' || providerName === 'ollama-cloud';
1406
+ // Handle model switching with proper error handling
1407
+ const switchModel = async () => {
1408
+ try {
1409
+ if (!hasCreds && !isOllama && this.authManager) {
1410
+ // Model not configured - prompt for setup
1411
+ console.log(C.warning(` Model "${selected.name}" is not configured.`));
1412
+ console.log(C.muted(' Please provide API credentials to use this model.'));
1413
+ console.log('');
1414
+ // Prompt for API key
1415
+ const apiKey = await this.promptForApiKey(providerName, providerName);
1416
+ if (apiKey) {
1417
+ await this.authManager.setCredentials({ provider: providerName, apiKey });
1418
+ console.log('');
1419
+ console.log(C.success(` ✓ Configuration saved. Switching to ${selected.name}...`));
1420
+ }
1421
+ else {
1422
+ console.log(C.error(' ✗ Configuration cancelled or failed.'));
1423
+ return;
1424
+ }
1425
+ }
1426
+ else if (selected.id === currentModel) {
1427
+ console.log(C.muted(' Model unchanged: ') + C.primary(currentModel));
1428
+ return;
1429
+ }
1430
+ // Switch model
1431
+ this.modelClient.updateOptions({ model: selected.id });
1432
+ console.log(C.success(` ✓ Switched to: `) + C.primary(selected.id));
1433
+ // Save to global config
1434
+ const config = this.configManager.getConfig();
1435
+ config.core.defaultModel = selected.id;
1436
+ await this.configManager.save(config);
1437
+ if (this.sessionId) {
1438
+ const cfg = this.sessionManager.getConfig(this.sessionId);
1439
+ cfg.model = selected.id;
1440
+ }
1441
+ }
1442
+ catch (err) {
1443
+ console.log(C.error(` Error switching model: ${err.message}`));
1444
+ }
1445
+ };
1446
+ // Execute switch and then resolve
1447
+ switchModel().then(() => resolve()).catch(err => {
1448
+ console.log(C.error(` Error: ${err.message}`));
1449
+ resolve();
1450
+ });
1451
+ }
1452
+ // Escape or Ctrl+C
1453
+ else if (key === '\x1b' || key === '\x03') {
1454
+ cleanup();
1455
+ process.stdout.write('\x1b[2J\x1b[H');
1456
+ console.log('');
1457
+ console.log(C.muted(' Cancelled'));
1458
+ resolve();
1459
+ }
1460
+ // Number key for quick select (1-9)
1461
+ else if (key >= '1' && key <= '9') {
1462
+ const num = parseInt(key, 10) - 1;
1463
+ if (num < models.length) {
1464
+ selectedIndex = num;
1465
+ renderModels(selectedIndex);
1466
+ }
1467
+ }
1468
+ };
1469
+ process.stdin.on('data', onData);
1470
+ });
1471
+ }
1472
+ // ========================================================================
1473
+ // /init — generate NOVA.md project memory file
1474
+ // ========================================================================
1475
+ async handleInitCommand(arg) {
1476
+ const targetDir = arg ? path.resolve(this.cwd, arg) : this.cwd;
1477
+ const novaFile = path.join(targetDir, 'NOVA.md');
1478
+ if (fs.existsSync(novaFile)) {
1479
+ console.log(C.warning(` NOVA.md already exists at: ${novaFile}`));
1480
+ console.log(C.muted(' Use /init --force to regenerate'));
1481
+ if (!arg.includes('--force'))
1482
+ return;
1483
+ }
1484
+ console.log('');
1485
+ console.log(C.brand(' Scanning project structure...'));
1486
+ // Gather project info
1487
+ const scanResult = this.scanProjectForInit(targetDir);
1488
+ const content = this.generateNovaMd(scanResult, targetDir);
1489
+ fs.writeFileSync(novaFile, content, 'utf-8');
1490
+ console.log(C.success(` ✓ NOVA.md created at ${novaFile}`));
1491
+ console.log(C.muted(` This file helps the AI understand your project.`));
1492
+ console.log(C.muted(` Edit it to add custom instructions and context.`));
1493
+ console.log('');
1494
+ console.log(C.dim(' Preview:'));
1495
+ const preview = content.split('\n').slice(0, 20).join('\n');
1496
+ console.log(C.dim(preview));
1497
+ if (content.split('\n').length > 20)
1498
+ console.log(C.dim(' ...'));
1499
+ }
1500
+ scanProjectForInit(dir) {
1501
+ const result = {};
1502
+ // Package.json
1503
+ const pkgPath = path.join(dir, 'package.json');
1504
+ if (fs.existsSync(pkgPath)) {
1505
+ try {
1506
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
1507
+ result.name = pkg.name;
1508
+ result.version = pkg.version;
1509
+ result.description = pkg.description;
1510
+ result.scripts = pkg.scripts;
1511
+ result.dependencies = Object.keys(pkg.dependencies || {}).slice(0, 20);
1512
+ result.devDependencies = Object.keys(pkg.devDependencies || {}).slice(0, 20);
1513
+ result.packageManager = pkg.packageManager;
1514
+ result.type = pkg.type;
1515
+ }
1516
+ catch { /* skip */ }
1517
+ }
1518
+ // Detect project type
1519
+ const indicators = {
1520
+ typescript: fs.existsSync(path.join(dir, 'tsconfig.json')),
1521
+ react: fs.existsSync(path.join(dir, 'src', 'App.tsx')) || fs.existsSync(path.join(dir, 'src', 'App.jsx')),
1522
+ nextjs: fs.existsSync(path.join(dir, 'next.config.js')) || fs.existsSync(path.join(dir, 'next.config.ts')),
1523
+ vite: fs.existsSync(path.join(dir, 'vite.config.ts')) || fs.existsSync(path.join(dir, 'vite.config.js')),
1524
+ monorepo: fs.existsSync(path.join(dir, 'pnpm-workspace.yaml')) || fs.existsSync(path.join(dir, 'turbo.json')),
1525
+ python: fs.existsSync(path.join(dir, 'pyproject.toml')) || fs.existsSync(path.join(dir, 'requirements.txt')),
1526
+ rust: fs.existsSync(path.join(dir, 'Cargo.toml')),
1527
+ go: fs.existsSync(path.join(dir, 'go.mod')),
1528
+ docker: fs.existsSync(path.join(dir, 'Dockerfile')),
1529
+ git: fs.existsSync(path.join(dir, '.git')),
1530
+ };
1531
+ result.indicators = indicators;
1532
+ // Top-level structure
1533
+ try {
1534
+ const entries = fs.readdirSync(dir).filter((e) => !e.startsWith('.') && e !== 'node_modules');
1535
+ result.topLevel = entries;
1536
+ }
1537
+ catch { /* skip */ }
1538
+ // README
1539
+ const readmePath = path.join(dir, 'README.md');
1540
+ if (fs.existsSync(readmePath)) {
1541
+ try {
1542
+ result.readme = fs.readFileSync(readmePath, 'utf-8').slice(0, 1000);
1543
+ }
1544
+ catch { /* skip */ }
1545
+ }
1546
+ // Git remote
1547
+ try {
1548
+ const remote = execSync('git remote get-url origin 2>/dev/null', { cwd: dir, encoding: 'utf-8', timeout: 3000 }).trim();
1549
+ result.gitRemote = remote;
1550
+ }
1551
+ catch { /* skip */ }
1552
+ return result;
1553
+ }
1554
+ generateNovaMd(scan, dir) {
1555
+ const name = scan.name || path.basename(dir);
1556
+ const date = new Date().toISOString().split('T')[0];
1557
+ const indicators = (scan.indicators || {});
1558
+ const tech = [];
1559
+ if (indicators.typescript)
1560
+ tech.push('TypeScript');
1561
+ if (indicators.react)
1562
+ tech.push('React');
1563
+ if (indicators.nextjs)
1564
+ tech.push('Next.js');
1565
+ if (indicators.vite)
1566
+ tech.push('Vite');
1567
+ if (indicators.monorepo)
1568
+ tech.push('Monorepo');
1569
+ if (indicators.python)
1570
+ tech.push('Python');
1571
+ if (indicators.rust)
1572
+ tech.push('Rust');
1573
+ if (indicators.go)
1574
+ tech.push('Go');
1575
+ if (indicators.docker)
1576
+ tech.push('Docker');
1577
+ const scripts = scan.scripts;
1578
+ const scriptLines = scripts ? Object.entries(scripts).map(([k, v]) => `- \`${k}\`: ${v}`).join('\n') : '';
1579
+ const deps = scan.dependencies || [];
1580
+ const devDeps = scan.devDependencies || [];
1581
+ return `# NOVA.md — Project Memory
1582
+
1583
+ > Auto-generated on ${date}. Edit this file to customize AI behavior for this project.
1584
+
1585
+ ## Project Overview
1586
+
1587
+ **Name**: ${name}
1588
+ **Version**: ${scan.version || 'unknown'}
1589
+ **Description**: ${scan.description || '(add a description here)'}
1590
+ **Location**: ${dir}
1591
+ ${scan.gitRemote ? `**Repository**: ${scan.gitRemote}` : ''}
1592
+
1593
+ ## Technology Stack
1594
+
1595
+ ${tech.length > 0 ? tech.map((t) => `- ${t}`).join('\n') : '- (detect automatically)'}
1596
+
1597
+ ## Key Commands
1598
+
1599
+ ${scriptLines || '- (add your build/test/dev commands here)'}
1600
+
1601
+ ## Dependencies
1602
+
1603
+ ${deps.length > 0 ? `Main: ${deps.slice(0, 10).join(', ')}` : ''}
1604
+ ${devDeps.length > 0 ? `Dev: ${devDeps.slice(0, 10).join(', ')}` : ''}
1605
+
1606
+ ## Project Structure
1607
+
1608
+ \`\`\`
1609
+ ${(scan.topLevel || []).slice(0, 20).join('\n')}
1610
+ \`\`\`
1611
+
1612
+ ## Coding Conventions
1613
+
1614
+ <!-- Add your project-specific conventions here -->
1615
+ - (e.g., Use single quotes for strings)
1616
+ - (e.g., Always add JSDoc comments to exported functions)
1617
+ - (e.g., Test files go in __tests__ directories)
1618
+
1619
+ ## Important Notes for AI
1620
+
1621
+ <!-- Add any special instructions, context, or warnings for the AI assistant -->
1622
+ - Working directory: ${dir}
1623
+ - (e.g., Never commit directly to main)
1624
+ - (e.g., Use pnpm, not npm)
1625
+
1626
+ ## File Reference
1627
+
1628
+ <!-- Add paths to key files the AI should know about -->
1629
+ <!-- Example: @src/types/index.ts — Core type definitions -->
1630
+
1631
+ ---
1632
+ *Edit this file to add project-specific context. The AI reads NOVA.md automatically at the start of each session.*
1633
+ `;
1634
+ }
1635
+ // ========================================================================
1636
+ // /memory — manage persistent notes
1637
+ // ========================================================================
1638
+ get memoryFile() {
1639
+ return path.join(os.homedir(), '.nova', 'memory.md');
1640
+ }
1641
+ async handleMemoryCommand(arg) {
1642
+ const parts = arg.trim().split(/\s+/);
1643
+ const sub = parts[0];
1644
+ if (!sub || sub === 'show' || sub === 'list') {
1645
+ // Show memory
1646
+ if (!fs.existsSync(this.memoryFile)) {
1647
+ console.log(C.muted(' No memory file yet. Use /memory add <text> to create entries.'));
1648
+ return;
1649
+ }
1650
+ const content = fs.readFileSync(this.memoryFile, 'utf-8');
1651
+ console.log('');
1652
+ console.log(C.brand(' Nova Memory'));
1653
+ console.log(C.dim(' ' + BOX.h.repeat(58)));
1654
+ content.split('\n').forEach((line) => {
1655
+ if (line.startsWith('## '))
1656
+ console.log(C.brand(' ' + line));
1657
+ else if (line.startsWith('- '))
1658
+ console.log(C.muted(' ' + line));
1659
+ else
1660
+ console.log(C.dim(' ' + line));
1661
+ });
1662
+ return;
1663
+ }
1664
+ if (sub === 'add') {
1665
+ const text = parts.slice(1).join(' ').trim();
1666
+ if (!text) {
1667
+ console.log(C.warning(' Usage: /memory add <text>'));
1668
+ return;
1669
+ }
1670
+ this.ensureDir(path.dirname(this.memoryFile));
1671
+ const timestamp = new Date().toISOString().split('T')[0];
1672
+ const entry = `- [${timestamp}] ${text}\n`;
1673
+ if (!fs.existsSync(this.memoryFile)) {
1674
+ fs.writeFileSync(this.memoryFile, `# Nova Memory\n\n## Notes\n\n${entry}`, 'utf-8');
1675
+ }
1676
+ else {
1677
+ fs.appendFileSync(this.memoryFile, entry, 'utf-8');
1678
+ }
1679
+ console.log(C.success(` ✓ Memory saved: "${text}"`));
1680
+ return;
1681
+ }
1682
+ if (sub === 'clear') {
1683
+ if (fs.existsSync(this.memoryFile)) {
1684
+ fs.writeFileSync(this.memoryFile, '# Nova Memory\n\n', 'utf-8');
1685
+ console.log(C.warning(' Memory cleared.'));
1686
+ }
1687
+ return;
1688
+ }
1689
+ if (sub === 'edit') {
1690
+ const editor = process.env.EDITOR || process.env.VISUAL || (process.platform === 'win32' ? 'notepad' : 'nano');
1691
+ this.ensureDir(path.dirname(this.memoryFile));
1692
+ if (!fs.existsSync(this.memoryFile)) {
1693
+ fs.writeFileSync(this.memoryFile, '# Nova Memory\n\n## Notes\n\n', 'utf-8');
1694
+ }
1695
+ console.log(C.muted(` Opening ${this.memoryFile} in ${editor}...`));
1696
+ try {
1697
+ execSync(`${editor} "${this.memoryFile}"`, { stdio: 'inherit' });
1698
+ }
1699
+ catch {
1700
+ console.log(C.muted(` Memory file: ${this.memoryFile}`));
1701
+ }
1702
+ return;
1703
+ }
1704
+ console.log(C.muted(' Usage: /memory [show|add <text>|clear|edit]'));
1705
+ }
1706
+ // ========================================================================
1707
+ // /history — browse and restore previous sessions
1708
+ // ========================================================================
1709
+ async handleHistoryCommand(arg) {
1710
+ const parts = arg.trim().split(/\s+/);
1711
+ const sub = parts[0];
1712
+ if (!sub || sub === 'list') {
1713
+ const sessions = this.sessionManager.listPersistedSessions(20);
1714
+ if (sessions.length === 0) {
1715
+ console.log(C.muted(' No saved sessions.'));
1716
+ return;
1717
+ }
1718
+ console.log('');
1719
+ console.log(C.brand(' Session History'));
1720
+ console.log(C.dim(' ' + BOX.h.repeat(58)));
1721
+ sessions.forEach((s, idx) => {
1722
+ const date = new Date(s.updatedAt).toLocaleString();
1723
+ const id = s.id.slice(0, 8);
1724
+ const turns = s.turnCount;
1725
+ const tokens = (s.totalInputTokens + s.totalOutputTokens).toLocaleString();
1726
+ const title = (s.title || 'New session').slice(0, 50);
1727
+ const isCurrent = this.sessionId && s.id === String(this.sessionId);
1728
+ const marker = isCurrent ? C.success(' ← current') : '';
1729
+ console.log(` ${C.muted(String(idx + 1).padStart(2) + '.')} ${C.primary(title)}${marker}\n` +
1730
+ ` ${C.dim(id + ' ' + date + ' ' + turns + ' turns ' + tokens + ' tok')}`);
1731
+ });
1732
+ console.log('');
1733
+ console.log(C.dim(' /history restore <n> — switch to session n'));
1734
+ console.log(C.dim(' /history delete <n> — delete session n'));
1735
+ return;
1736
+ }
1737
+ if (sub === 'restore') {
1738
+ const n = parseInt(parts[1], 10);
1739
+ const sessions = this.sessionManager.listPersistedSessions(20);
1740
+ if (isNaN(n) || n < 1 || n > sessions.length) {
1741
+ console.log(C.warning(` Invalid index. Use /history to see sessions.`));
1742
+ return;
1743
+ }
1744
+ // Save current first
1745
+ if (this.sessionId)
1746
+ this.sessionManager.persist(this.sessionId);
1747
+ const target = sessions[n - 1];
1748
+ const restored = this.sessionManager.loadFromDisk(target.id);
1749
+ if (restored) {
1750
+ this.sessionId = restored.id;
1751
+ const msgs = this.sessionManager.getMessages(this.sessionId);
1752
+ console.log(C.success(` ✓ Restored session ${target.id.slice(0, 8)} — ${msgs.length} messages`));
1753
+ console.log(C.muted(` Title: "${target.title}"`));
1754
+ }
1755
+ else {
1756
+ console.log(C.error(' Failed to restore session.'));
1757
+ }
1758
+ return;
1759
+ }
1760
+ if (sub === 'delete') {
1761
+ const n = parseInt(parts[1], 10);
1762
+ const sessions = this.sessionManager.listPersistedSessions(20);
1763
+ if (isNaN(n) || n < 1 || n > sessions.length) {
1764
+ console.log(C.warning(` Invalid index.`));
1765
+ return;
1766
+ }
1767
+ const target = sessions[n - 1];
1768
+ const deleted = this.sessionManager.deletePersisted(target.id);
1769
+ if (deleted) {
1770
+ console.log(C.success(` ✓ Deleted session ${target.id.slice(0, 8)}`));
1771
+ }
1772
+ else {
1773
+ console.log(C.error(' Failed to delete session.'));
1774
+ }
1775
+ return;
1776
+ }
1777
+ console.log(C.muted(' Usage: /history [list|restore <n>|delete <n>]'));
1778
+ }
1779
+ // ========================================================================
1780
+ // /compress — manually trigger context compression
1781
+ // ========================================================================
1782
+ async handleCompressCommand() {
1783
+ if (!this.sessionId || !this.contextCompressor) {
1784
+ console.log(C.warning(' No active session or context compressor not initialized.'));
1785
+ return;
1786
+ }
1787
+ const msgs = this.sessionManager.getMessages(this.sessionId);
1788
+ const before = msgs.length;
1789
+ console.log(C.info(` Compressing context (${before} messages)...`));
1790
+ // Trigger via a lightweight session flush
1791
+ this.sessionManager.persist(this.sessionId);
1792
+ console.log(C.success(` ✓ Context snapshot saved. Session: ${String(this.sessionId).slice(0, 8)}`));
1793
+ }
1794
+ printHelp() {
1795
+ const w = 60;
1796
+ const hr = C.brandDim(BOX.h.repeat(w));
1797
+ const hrThick = C.brand(BOX.hThick.repeat(w));
1798
+ const vl = C.brandDim(BOX.v);
1799
+ const cmd = (name, desc) => ` ${C.info(name.padEnd(18))} ${C.muted(desc)}`;
1800
+ const section = (title) => `\n${vl} ${C.brandLight(BOX.diamond)} ${C.brand(title)}${' '.repeat(w - title.length - 4)}${vl}`;
1801
+ console.log('');
1802
+ console.log(C.brand(BOX.tl) + hrThick + C.brand(BOX.tr));
1803
+ console.log(`${vl} ${C.brand.bold('Nova CLI Commands')}${' '.repeat(w - 18)}${vl}`);
1804
+ console.log(C.brand(BOX.ht) + hr + C.brand(BOX.htr));
1805
+ // Navigation
1806
+ console.log(section('Navigation'));
1807
+ console.log(`${vl}${cmd('/help', 'Show this help')}${' '.repeat(w - 28)}${vl}`);
1808
+ console.log(`${vl}${cmd('/quit', 'Exit (session auto-saved)')}${' '.repeat(w - 38)}${vl}`);
1809
+ console.log(`${vl}${cmd('/clear', 'Clear conversation & start new')}${' '.repeat(w - 38)}${vl}`);
1810
+ // Session
1811
+ console.log(section('Session'));
1812
+ console.log(`${vl}${cmd('/status', 'Show session info & stats')}${' '.repeat(w - 33)}${vl}`);
1813
+ console.log(`${vl}${cmd('/history', 'List previous sessions')}${' '.repeat(w - 30)}${vl}`);
1814
+ console.log(`${vl}${cmd('/history restore', 'Switch to session n')}${' '.repeat(w - 33)}${vl}`);
1815
+ console.log(`${vl}${cmd('/history delete', 'Delete session n')}${' '.repeat(w - 30)}${vl}`);
1816
+ // Model
1817
+ console.log(section('Model'));
1818
+ console.log(`${vl}${cmd('/model', 'Show current model')}${' '.repeat(w - 26)}${vl}`);
1819
+ console.log(`${vl}${cmd('/model <id>', 'Switch model')}${' '.repeat(w - 26)}${vl}`);
1820
+ // Mode
1821
+ const currentMode = MODE_LABELS[this.mode].label;
1822
+ console.log(section(`Mode ${C.dim('(current: ' + currentMode + ')')}`));
1823
+ console.log(`${vl} ${C.info('/mode'.padEnd(18))} ${C.muted('Cycle:')} ${C.success('AUTO')} ${C.dim('→')} ${C.warning('PLAN')} ${C.dim('→')} ${C.info('ASK')}${' '.repeat(w - 46)}${vl}`);
1824
+ console.log(`${vl} ${C.info('/mode auto'.padEnd(18))} ${C.success('AUTO')} ${C.dim('— full autonomous, no approval')}${' '.repeat(w - 49)}${vl}`);
1825
+ console.log(`${vl} ${C.info('/mode plan'.padEnd(18))} ${C.warning('PLAN')} ${C.dim('— confirm before each tool')}${' '.repeat(w - 47)}${vl}`);
1826
+ console.log(`${vl} ${C.info('/mode ask'.padEnd(18))} ${C.info('ASK')} ${C.dim('— read-only, answer only')}${' '.repeat(w - 45)}${vl}`);
1827
+ // Memory
1828
+ console.log(section('Memory'));
1829
+ console.log(`${vl}${cmd('/init', 'Generate NOVA.md project file')}${' '.repeat(w - 36)}${vl}`);
1830
+ console.log(`${vl}${cmd('/memory', 'Show persistent notes')}${' '.repeat(w - 29)}${vl}`);
1831
+ console.log(`${vl}${cmd('/memory add', 'Add a note')}${' '.repeat(w - 22)}${vl}`);
1832
+ // Extensions
1833
+ console.log(section('Extensions'));
1834
+ console.log(`${vl}${cmd('/mcp', 'MCP servers & tools')}${' '.repeat(w - 27)}${vl}`);
1835
+ console.log(`${vl}${cmd('/skills', 'Available skills')}${' '.repeat(w - 26)}${vl}`);
1836
+ console.log(`${vl}${cmd('/skills use', 'Inject skill into next msg')}${' '.repeat(w - 36)}${vl}`);
1837
+ console.log(`${vl}${cmd('/theme', 'Switch color theme')}${' '.repeat(w - 26)}${vl}`);
1838
+ console.log(`${vl}${cmd('/checkpoint', 'File snapshots & rollback')}${' '.repeat(w - 35)}${vl}`);
1839
+ console.log(`${vl}${cmd('/image', 'Add image to chat')}${' '.repeat(w - 26)}${vl}`);
1840
+ // Ollama
1841
+ console.log(section('Ollama (Local Models)'));
1842
+ console.log(`${vl}${cmd('/ollama', 'Show status & models')}${' '.repeat(w - 29)}${vl}`);
1843
+ console.log(`${vl}${cmd('/ollama pull <n>', 'Download a model')}${' '.repeat(w - 32)}${vl}`);
1844
+ console.log(`${vl}${cmd('/ollama list', 'List installed models')}${' '.repeat(w - 33)}${vl}`);
1845
+ // Shortcuts
1846
+ console.log(section('Shortcuts'));
1847
+ console.log(`${vl} ${C.info('@file.ts'.padEnd(18))} ${C.muted('Inject file content')}${' '.repeat(w - 35)}${vl}`);
1848
+ console.log(`${vl} ${C.info('!command'.padEnd(18))} ${C.muted('Run shell command')}${' '.repeat(w - 33)}${vl}`);
1849
+ console.log(`${vl} ${C.info('line\\'.padEnd(18))} ${C.muted('Multi-line input')}${' '.repeat(w - 32)}${vl}`);
1850
+ console.log(C.brand(BOX.bl) + hrThick + C.brand(BOX.br));
1851
+ console.log('');
1852
+ }
1853
+ // ========================================================================
1854
+ // MCP command handler
1855
+ // ========================================================================
1856
+ async handleMcpCommand(subcommand) {
1857
+ if (!this.mcpManager) {
1858
+ console.log(C.warning(' No MCP manager initialized.'));
1859
+ console.log(C.muted(' Add MCP servers to your config (~/.nova/config.yaml):'));
1860
+ console.log('');
1861
+ console.log(C.dim(' mcp:'));
1862
+ console.log(C.dim(' filesystem:'));
1863
+ console.log(C.dim(' command: npx'));
1864
+ console.log(C.dim(' args: [-y, "@modelcontextprotocol/server-filesystem", /path/to/dir]'));
1865
+ return;
1866
+ }
1867
+ const statuses = this.mcpManager.listServers();
1868
+ if (statuses.length === 0) {
1869
+ console.log(C.muted(' No MCP servers configured.'));
1870
+ return;
1871
+ }
1872
+ if (!subcommand || subcommand === 'status') {
1873
+ console.log('');
1874
+ console.log(C.brand(' MCP Servers'));
1875
+ console.log(C.dim(' ' + BOX.h.repeat(58)));
1876
+ for (const s of statuses) {
1877
+ const statusIcon = s.connected ? C.success(BOX.check) : C.error(BOX.cross);
1878
+ const statusStr = s.connected
1879
+ ? C.success('connected')
1880
+ : C.error(`disconnected${s.lastError ? ': ' + s.lastError.slice(0, 40) : ''}`);
1881
+ console.log(` ${statusIcon} ${C.primary(s.name.padEnd(20))} ${statusStr}`);
1882
+ if (s.connected) {
1883
+ console.log(C.dim(` ${s.toolCount} tool${s.toolCount !== 1 ? 's' : ''}`) +
1884
+ (s.resourceCount > 0 ? C.dim(`, ${s.resourceCount} resource${s.resourceCount !== 1 ? 's' : ''}`) : ''));
1885
+ }
1886
+ }
1887
+ const connected = statuses.filter((s) => s.connected).length;
1888
+ console.log('');
1889
+ console.log(C.muted(` ${connected}/${statuses.length} servers connected`));
1890
+ return;
1891
+ }
1892
+ if (subcommand === 'tools') {
1893
+ const allTools = this.toolRegistry.getEnabledToolNames().filter((n) => n.includes('__'));
1894
+ if (allTools.length === 0) {
1895
+ console.log(C.muted(' No MCP tools available.'));
1896
+ return;
1897
+ }
1898
+ console.log('');
1899
+ console.log(C.brand(' MCP Tools'));
1900
+ console.log(C.dim(' ' + BOX.h.repeat(58)));
1901
+ for (const t of allTools) {
1902
+ const [ns, toolName] = t.split('__');
1903
+ console.log(` ${C.info(ns.padEnd(16))} ${C.primary(toolName)}`);
1904
+ }
1905
+ console.log('');
1906
+ console.log(C.muted(` ${allTools.length} MCP tool${allTools.length !== 1 ? 's' : ''} available`));
1907
+ return;
1908
+ }
1909
+ console.log(C.warning(` Unknown MCP subcommand: ${subcommand}`));
1910
+ console.log(C.muted(' Usage: /mcp [status|tools]'));
1911
+ }
1912
+ // ========================================================================
1913
+ // Skills command handler
1914
+ // ========================================================================
1915
+ async handleSkillsCommand(subcommand) {
1916
+ if (!this.skillRegistry) {
1917
+ console.log(C.warning(' Skills system not initialized.'));
1918
+ return;
1919
+ }
1920
+ const parts = (subcommand || '').split(/\s+/).filter(Boolean);
1921
+ const cmd = parts[0];
1922
+ const skillName = parts.slice(1).join(' ');
1923
+ if (!cmd || cmd === 'list') {
1924
+ const skills = await this.skillRegistry.list();
1925
+ if (skills.length === 0) {
1926
+ console.log(C.muted(' No skills found.'));
1927
+ console.log(C.muted(' Add SKILL.md files to ~/.nova/skills/ to create skills.'));
1928
+ console.log(C.dim(' /skills install superpowers — install popular skills'));
1929
+ return;
1930
+ }
1931
+ console.log('');
1932
+ console.log(C.brand(' Available Skills'));
1933
+ console.log(C.dim(' ' + BOX.h.repeat(58)));
1934
+ for (const skill of skills) {
1935
+ const m = skill.metadata;
1936
+ const autoTag = m.autoGenerated ? C.dim(' [auto]') : '';
1937
+ const tags = m.tags.length > 0 ? C.dim(` (${m.tags.slice(0, 3).join(', ')})`) : '';
1938
+ console.log(` ${C.toolName(m.name.padEnd(22))} ${C.muted(m.description.slice(0, 35))}${autoTag}${tags}`);
1939
+ }
1940
+ console.log('');
1941
+ console.log(C.muted(` ${skills.length} skill${skills.length !== 1 ? 's' : ''} available`));
1942
+ console.log(C.dim(' /skills use <name> — inject skill into next message'));
1943
+ console.log(C.dim(' /skills info <name> — show skill details'));
1944
+ console.log(C.dim(' /skills install <repo> — install from GitHub'));
1945
+ return;
1946
+ }
1947
+ // Install skills from GitHub
1948
+ if (cmd === 'install') {
1949
+ await this.handleSkillsInstall(skillName);
1950
+ return;
1951
+ }
1952
+ if (cmd === 'info' && skillName) {
1953
+ const skill = await this.skillRegistry.get(skillName);
1954
+ if (!skill) {
1955
+ console.log(C.error(` Skill "${skillName}" not found.`));
1956
+ return;
1957
+ }
1958
+ const m = skill.metadata;
1959
+ console.log('');
1960
+ console.log(C.brand(' ' + m.name));
1961
+ console.log(C.dim(' ' + BOX.h.repeat(58)));
1962
+ console.log(C.muted(' Description: ') + C.primary(m.description));
1963
+ console.log(C.muted(' Version: ') + C.primary(m.version));
1964
+ if (m.author)
1965
+ console.log(C.muted(' Author: ') + C.primary(m.author));
1966
+ if (m.tags.length > 0)
1967
+ console.log(C.muted(' Tags: ') + C.primary(m.tags.join(', ')));
1968
+ console.log('');
1969
+ const preview = skill.content.split('\n').slice(0, 10).join('\n');
1970
+ console.log(C.dim(preview));
1971
+ if (skill.content.split('\n').length > 10)
1972
+ console.log(C.dim(' ...'));
1973
+ return;
1974
+ }
1975
+ if (cmd === 'use' && skillName) {
1976
+ const skill = await this.skillRegistry.get(skillName);
1977
+ if (!skill) {
1978
+ console.log(C.error(` Skill "${skillName}" not found.`));
1979
+ return;
1980
+ }
1981
+ this._pendingSkillInject = skill;
1982
+ console.log(C.success(` Skill "${skillName}" will be injected into your next message.`));
1983
+ return;
1984
+ }
1985
+ console.log(C.warning(` Unknown skills subcommand.`));
1986
+ console.log(C.muted(' Usage: /skills [list|use <name>|info <name>|install <repo>]'));
1987
+ }
1988
+ // ========================================================================
1989
+ // /skills install — Install skills from GitHub
1990
+ // ========================================================================
1991
+ async handleSkillsInstall(repoArg) {
1992
+ if (!repoArg) {
1993
+ console.log('');
1994
+ console.log(C.brand(' Install Skills from GitHub'));
1995
+ console.log(C.dim(' ' + BOX.h.repeat(58)));
1996
+ console.log(C.muted(' Install skills from GitHub repositories.'));
1997
+ console.log('');
1998
+ console.log(C.info(' Popular repositories:'));
1999
+ console.log(C.dim(' • superpowers — Agentic skills (TDD, debugging, review)'));
2000
+ console.log(C.dim(' • owner/repo — Any GitHub repository'));
2001
+ console.log('');
2002
+ console.log(C.dim(' Usage:'));
2003
+ console.log(C.primary(' /skills install superpowers'));
2004
+ console.log(C.primary(' /skills install obra/superpowers'));
2005
+ console.log(C.primary(' /skills install https://github.com/owner/repo'));
2006
+ return;
2007
+ }
2008
+ // Import installer
2009
+ const { SkillInstaller, POPULAR_SKILL_REPOS } = await import('../../../core/src/extensions/SkillInstaller.js');
2010
+ const installer = new SkillInstaller();
2011
+ // Resolve shorthand
2012
+ const source = POPULAR_SKILL_REPOS[repoArg]?.url || repoArg;
2013
+ console.log(C.muted(` Installing from: ${source}`));
2014
+ console.log('');
2015
+ try {
2016
+ const installed = await installer.install({ source, force: false });
2017
+ if (installed.length === 0) {
2018
+ console.log(C.warning(' No new skills installed.'));
2019
+ console.log(C.dim(' Use --force to overwrite existing skills.'));
2020
+ return;
2021
+ }
2022
+ console.log('');
2023
+ console.log(C.success(` ✓ Installed ${installed.length} skill${installed.length !== 1 ? 's' : ''}:`));
2024
+ for (const skill of installed) {
2025
+ console.log(C.primary(` • ${skill.name}`));
2026
+ }
2027
+ console.log('');
2028
+ console.log(C.dim(' Reload skills with: /skills list'));
2029
+ console.log(C.dim(' Use a skill with: /skills use <name>'));
2030
+ // Reinitialize skill registry
2031
+ if (this.skillRegistry) {
2032
+ await this.skillRegistry.initialize();
2033
+ }
2034
+ }
2035
+ catch (err) {
2036
+ console.log(C.error(` Failed to install: ${err.message}`));
2037
+ console.log(C.dim(' Make sure git is installed and you have internet access.'));
2038
+ }
2039
+ }
2040
+ // ========================================================================
2041
+ // /theme — Switch color theme
2042
+ // ========================================================================
2043
+ async handleThemeCommand(arg) {
2044
+ const themes = {
2045
+ dark: {
2046
+ brand: '#7C3AED',
2047
+ brandLight: '#A78BFA',
2048
+ success: '#10B981',
2049
+ warning: '#F59E0B',
2050
+ error: '#EF4444',
2051
+ info: '#3B82F6',
2052
+ accent: '#F472B6',
2053
+ },
2054
+ light: {
2055
+ brand: '#6366F1',
2056
+ brandLight: '#818CF8',
2057
+ success: '#16A34A',
2058
+ warning: '#D97706',
2059
+ error: '#DC2626',
2060
+ info: '#2563EB',
2061
+ accent: '#EC4899',
2062
+ },
2063
+ neon: {
2064
+ brand: '#FF00FF',
2065
+ brandLight: '#FF66FF',
2066
+ success: '#00FF00',
2067
+ warning: '#FFFF00',
2068
+ error: '#FF0000',
2069
+ info: '#00FFFF',
2070
+ accent: '#FF00AA',
2071
+ },
2072
+ ocean: {
2073
+ brand: '#0891B2',
2074
+ brandLight: '#06B6D4',
2075
+ success: '#10B981',
2076
+ warning: '#F59E0B',
2077
+ error: '#EF4444',
2078
+ info: '#0EA5E9',
2079
+ accent: '#8B5CF6',
2080
+ },
2081
+ };
2082
+ if (!arg) {
2083
+ // Show current theme and available themes
2084
+ console.log('');
2085
+ console.log(C.brand(' Available Themes'));
2086
+ console.log(C.dim(' ' + BOX.h.repeat(58)));
2087
+ Object.keys(themes).forEach((name) => {
2088
+ const isCurrent = name === 'dark'; // Default theme
2089
+ const marker = isCurrent ? C.success('●') : C.dim('○');
2090
+ console.log(` ${marker} ${C.primary(name.padEnd(12))} ${C.dim(name === 'dark' ? '(default)' : '')}`);
2091
+ });
2092
+ console.log('');
2093
+ console.log(C.dim(' /theme <name> — switch theme'));
2094
+ return;
2095
+ }
2096
+ const themeName = arg.toLowerCase();
2097
+ if (!themes[themeName]) {
2098
+ console.log(C.error(` Unknown theme: ${arg}`));
2099
+ console.log(C.muted(` Available: ${Object.keys(themes).join(', ')}`));
2100
+ return;
2101
+ }
2102
+ // Note: In a real implementation, we would:
2103
+ // 1. Save theme preference to ~/.nova/theme.json
2104
+ // 2. Update the C color object dynamically
2105
+ // 3. Redraw the UI with new colors
2106
+ console.log(C.success(` ✓ Theme switched to: ${themeName}`));
2107
+ console.log(C.muted(' Note: Theme will be fully applied after restart'));
2108
+ }
2109
+ // ========================================================================
2110
+ // /image — Add image to conversation
2111
+ // ========================================================================
2112
+ async handleImageCommand(arg) {
2113
+ if (!arg) {
2114
+ console.log(C.error(' Usage: /image <path-or-url> [description]'));
2115
+ console.log(C.muted(' Example: /image ./screenshot.png "Error message"'));
2116
+ console.log(C.muted(' Example: /image https://example.com/chart.png'));
2117
+ return;
2118
+ }
2119
+ const parts = arg.split(/\s+/);
2120
+ const imagePath = parts[0];
2121
+ const description = parts.slice(1).join(' ');
2122
+ try {
2123
+ let imageData;
2124
+ let mediaType;
2125
+ // Handle URL
2126
+ if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) {
2127
+ console.log(C.muted(` Fetching image from URL...`));
2128
+ // URL image support - note: this requires additional implementation
2129
+ // For now, we'll just acknowledge the URL
2130
+ console.log(C.warning(' URL image support is limited in this version'));
2131
+ console.log(C.muted(` Please download the image and use local path instead`));
2132
+ return;
2133
+ }
2134
+ // Handle local file
2135
+ else {
2136
+ const fullPath = require('path').resolve(this.cwd, imagePath);
2137
+ console.log(C.muted(` Reading image: ${fullPath}`));
2138
+ const fs = require('node:fs');
2139
+ if (!fs.existsSync(fullPath)) {
2140
+ console.log(C.error(` File not found: ${imagePath}`));
2141
+ return;
2142
+ }
2143
+ // Read and encode image
2144
+ const imageBuffer = fs.readFileSync(fullPath);
2145
+ imageData = imageBuffer.toString('base64');
2146
+ // Determine media type from extension
2147
+ const ext = require('path').extname(fullPath).toLowerCase();
2148
+ const mimeTypes = {
2149
+ '.png': 'image/png',
2150
+ '.jpg': 'image/jpeg',
2151
+ '.jpeg': 'image/jpeg',
2152
+ '.gif': 'image/gif',
2153
+ '.webp': 'image/webp',
2154
+ '.svg': 'image/svg+xml',
2155
+ '.bmp': 'image/bmp',
2156
+ };
2157
+ mediaType = mimeTypes[ext] || 'image/jpeg';
2158
+ }
2159
+ // Add image to session
2160
+ if (!this.sessionId) {
2161
+ console.log(C.error(' No active session'));
2162
+ return;
2163
+ }
2164
+ // Create image content block
2165
+ const imageContent = {
2166
+ type: 'image',
2167
+ source: {
2168
+ type: 'base64',
2169
+ media_type: mediaType,
2170
+ data: imageData,
2171
+ },
2172
+ };
2173
+ // Add to session messages
2174
+ this.sessionManager.addMessage(this.sessionId, 'user', [
2175
+ { type: 'text', text: description || `Image: ${imagePath}` },
2176
+ imageContent,
2177
+ ]);
2178
+ console.log(C.success(` ✓ Image added to conversation`));
2179
+ console.log(C.muted(` Path: ${imagePath}`));
2180
+ console.log(C.muted(` Size: ${(imageData.length / 1024).toFixed(1)} KB`));
2181
+ console.log(C.muted(` Type: ${mediaType}`));
2182
+ }
2183
+ catch (err) {
2184
+ console.log(C.error(` Failed to add image: ${err.message}`));
2185
+ console.log(C.muted(` Make sure the file is a valid image (PNG, JPG, GIF, etc.)`));
2186
+ }
2187
+ }
2188
+ // ========================================================================
2189
+ // /checkpoint — File snapshot and rollback management
2190
+ // ========================================================================
2191
+ async handleCheckpointCommand(arg) {
2192
+ const { CheckpointManager } = await import('../../../core/src/utils/CheckpointManager.js');
2193
+ const manager = new CheckpointManager(this.cwd, this.config);
2194
+ const parts = arg.split(/\s+/).filter(Boolean);
2195
+ const cmd = parts[0];
2196
+ const subArg = parts.slice(1).join(' ');
2197
+ if (!cmd || cmd === 'list') {
2198
+ const checkpoints = await manager.list();
2199
+ if (checkpoints.length === 0) {
2200
+ console.log(C.muted(' No checkpoints found.'));
2201
+ console.log(C.muted(' Create one with: /checkpoint create <name> [files...]'));
2202
+ return;
2203
+ }
2204
+ console.log('');
2205
+ console.log(C.brand(' Checkpoints'));
2206
+ console.log(C.dim(' ' + BOX.h.repeat(58)));
2207
+ for (const cp of checkpoints.slice(0, 10)) {
2208
+ const date = new Date(cp.timestamp).toLocaleString();
2209
+ const fileCount = cp.files.length;
2210
+ const idShort = cp.id.slice(0, 8);
2211
+ console.log(` ${C.info(idShort)} ${C.primary(cp.name.padEnd(20))} ${C.dim(date)} ${C.muted(`(${fileCount} files)`)}`);
2212
+ }
2213
+ if (checkpoints.length > 10) {
2214
+ console.log(C.dim(` ... and ${checkpoints.length - 10} more`));
2215
+ }
2216
+ console.log('');
2217
+ console.log(C.dim(' /checkpoint create <name> [pattern] — create snapshot'));
2218
+ console.log(C.dim(' /checkpoint restore <id> — restore snapshot'));
2219
+ console.log(C.dim(' /checkpoint diff <id> — show differences'));
2220
+ console.log(C.dim(' /checkpoint delete <id> — delete snapshot'));
2221
+ console.log(C.dim(' /checkpoint stats — show statistics'));
2222
+ return;
2223
+ }
2224
+ if (cmd === 'create') {
2225
+ if (!subArg) {
2226
+ console.log(C.error(' Usage: /checkpoint create <name> [file-pattern]'));
2227
+ console.log(C.muted(' Example: /checkpoint create "before-refactor" "src/**/*.ts"'));
2228
+ return;
2229
+ }
2230
+ const nameMatch = subArg.match(/^"([^"]+)"(?:\s+(.+))?$/);
2231
+ if (!nameMatch) {
2232
+ console.log(C.error(' Invalid format. Use: /checkpoint create "name" [pattern]'));
2233
+ return;
2234
+ }
2235
+ const name = nameMatch[1];
2236
+ const pattern = nameMatch[2] || '**/*';
2237
+ console.log(C.muted(` Creating checkpoint "${name}"...`));
2238
+ try {
2239
+ const checkpoint = await manager.create(name, [pattern], `Created via CLI`);
2240
+ console.log(C.success(` ✓ Checkpoint created: ${checkpoint.id.slice(0, 8)}`));
2241
+ console.log(C.muted(` Files: ${checkpoint.files.length}`));
2242
+ }
2243
+ catch (err) {
2244
+ console.log(C.error(` Failed to create checkpoint: ${err}`));
2245
+ }
2246
+ return;
2247
+ }
2248
+ if (cmd === 'restore') {
2249
+ if (!subArg) {
2250
+ console.log(C.error(' Usage: /checkpoint restore <id>'));
2251
+ return;
2252
+ }
2253
+ const checkpointId = subArg;
2254
+ const checkpoint = await manager.load(checkpointId);
2255
+ if (!checkpoint) {
2256
+ console.log(C.error(` Checkpoint not found: ${checkpointId}`));
2257
+ return;
2258
+ }
2259
+ console.log(C.warning(` ⚠ This will overwrite current files with checkpoint version`));
2260
+ console.log(C.muted(` Checkpoint: ${checkpoint.name}`));
2261
+ console.log(C.muted(` Files: ${checkpoint.files.length}`));
2262
+ console.log(C.muted(` Created: ${new Date(checkpoint.timestamp).toLocaleString()}`));
2263
+ // In a real implementation, we'd use ConfirmDialog here
2264
+ const { ConfirmDialog } = await import('../ui/components/ConfirmDialog.js');
2265
+ const dialog = new ConfirmDialog();
2266
+ const confirmed = await dialog.danger('Restore this checkpoint?');
2267
+ if (!confirmed) {
2268
+ console.log(C.muted(' Restore cancelled.'));
2269
+ return;
2270
+ }
2271
+ try {
2272
+ await manager.restore(checkpointId);
2273
+ console.log(C.success(` ✓ Checkpoint restored successfully`));
2274
+ }
2275
+ catch (err) {
2276
+ console.log(C.error(` Failed to restore checkpoint: ${err}`));
2277
+ }
2278
+ return;
2279
+ }
2280
+ if (cmd === 'diff') {
2281
+ if (!subArg) {
2282
+ console.log(C.error(' Usage: /checkpoint diff <id>'));
2283
+ return;
2284
+ }
2285
+ try {
2286
+ const differences = await manager.diff(subArg);
2287
+ if (differences.length === 0) {
2288
+ console.log(C.muted(' No differences from checkpoint.'));
2289
+ return;
2290
+ }
2291
+ console.log('');
2292
+ console.log(C.brand(' Differences'));
2293
+ console.log(C.dim(' ' + BOX.h.repeat(58)));
2294
+ for (const diff of differences) {
2295
+ const icon = diff.status === 'modified' ? '◉' : diff.status === 'deleted' ? '✗' : '✚';
2296
+ const color = diff.status === 'modified' ? chalk.yellow : diff.status === 'deleted' ? chalk.red : chalk.green;
2297
+ console.log(` ${color(icon)} ${diff.path} ${chalk.dim(`(${diff.status})`)}`);
2298
+ }
2299
+ }
2300
+ catch (err) {
2301
+ console.log(C.error(` Failed to show diff: ${err}`));
2302
+ }
2303
+ return;
2304
+ }
2305
+ if (cmd === 'delete') {
2306
+ if (!subArg) {
2307
+ console.log(C.error(' Usage: /checkpoint delete <id>'));
2308
+ return;
2309
+ }
2310
+ const checkpoint = await manager.load(subArg);
2311
+ if (!checkpoint) {
2312
+ console.log(C.error(` Checkpoint not found: ${subArg}`));
2313
+ return;
2314
+ }
2315
+ console.log(C.warning(` ⚠ Delete checkpoint "${checkpoint.name}"?`));
2316
+ const { ConfirmDialog } = await import('../ui/components/ConfirmDialog.js');
2317
+ const dialog = new ConfirmDialog();
2318
+ const confirmed = await dialog.warning('This cannot be undone');
2319
+ if (!confirmed) {
2320
+ console.log(C.muted(' Delete cancelled.'));
2321
+ return;
2322
+ }
2323
+ const success = await manager.delete(subArg);
2324
+ if (success) {
2325
+ console.log(C.success(` ✓ Checkpoint deleted`));
2326
+ }
2327
+ else {
2328
+ console.log(C.error(` Failed to delete checkpoint`));
2329
+ }
2330
+ return;
2331
+ }
2332
+ if (cmd === 'stats') {
2333
+ const stats = await manager.stats();
2334
+ console.log('');
2335
+ console.log(C.brand(' Checkpoint Statistics'));
2336
+ console.log(C.dim(' ' + BOX.h.repeat(58)));
2337
+ console.log(`${C.muted(' Total:')} ${C.primary(stats.totalCheckpoints)}`);
2338
+ console.log(`${C.muted(' Size:')} ${C.primary(this.formatBytes(stats.totalSize))}`);
2339
+ if (stats.oldest) {
2340
+ console.log(`${C.muted(' Oldest:')} ${C.dim(new Date(stats.oldest).toLocaleDateString())}`);
2341
+ }
2342
+ if (stats.newest) {
2343
+ console.log(`${C.muted(' Newest:')} ${C.dim(new Date(stats.newest).toLocaleDateString())}`);
2344
+ }
2345
+ return;
2346
+ }
2347
+ console.log(C.error(` Unknown checkpoint command: ${cmd}`));
2348
+ console.log(C.muted(' Usage: /checkpoint [list|create|restore|diff|delete|stats]'));
2349
+ }
2350
+ formatBytes(bytes) {
2351
+ if (bytes === 0)
2352
+ return '0 B';
2353
+ const k = 1024;
2354
+ const sizes = ['B', 'KB', 'MB', 'GB'];
2355
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
2356
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
2357
+ }
2358
+ // ========================================================================
2359
+ // /ollama — Ollama status and model management
2360
+ // ========================================================================
2361
+ async handleOllamaCommand(subcommand) {
2362
+ const ollamaCreds = this.authManager?.getCredentials('ollama');
2363
+ const baseUrl = ollamaCreds?.baseUrl || process.env.OLLAMA_HOST || 'http://localhost:11434';
2364
+ const manager = new OllamaManager(baseUrl);
2365
+ const parts = (subcommand || '').split(/\s+/).filter(Boolean);
2366
+ const cmd = parts[0];
2367
+ const arg = parts.slice(1).join(' ');
2368
+ if (!cmd || cmd === 'status') {
2369
+ console.log('');
2370
+ console.log(C.brand(' Ollama Status'));
2371
+ console.log(C.dim(' ' + BOX.h.repeat(58)));
2372
+ const isRunning = await manager.ping();
2373
+ if (isRunning) {
2374
+ try {
2375
+ const version = await manager.version();
2376
+ console.log(C.success(` ${BOX.check} Running`) + C.dim(` (v${version})`));
2377
+ }
2378
+ catch {
2379
+ console.log(C.success(` ${BOX.check} Running`));
2380
+ }
2381
+ console.log(C.muted(` Host: ${baseUrl}`));
2382
+ const models = await manager.listModels();
2383
+ if (models.length > 0) {
2384
+ console.log(C.muted(` Models: ${models.length} installed`));
2385
+ for (const m of models.slice(0, 5)) {
2386
+ const sizeGB = (m.size / 1024 / 1024 / 1024).toFixed(1);
2387
+ console.log(C.dim(` ${BOX.bullet} ${m.name} (${sizeGB} GB)`));
2388
+ }
2389
+ if (models.length > 5) {
2390
+ console.log(C.dim(` ... and ${models.length - 5} more`));
2391
+ }
2392
+ }
2393
+ else {
2394
+ console.log(C.warning(` No models installed`));
2395
+ console.log(C.dim(' Use /ollama pull <model> to download a model'));
2396
+ }
2397
+ }
2398
+ else {
2399
+ console.log(C.error(` ${BOX.crossX} Not running`));
2400
+ console.log(C.muted(` Host: ${baseUrl}`));
2401
+ console.log('');
2402
+ console.log(C.warning(' Start Ollama:'));
2403
+ console.log(C.dim(' ollama serve'));
2404
+ console.log('');
2405
+ console.log(C.warning(' Install Ollama:'));
2406
+ console.log(C.dim(' https://ollama.com'));
2407
+ }
2408
+ console.log('');
2409
+ return;
2410
+ }
2411
+ if (cmd === 'list') {
2412
+ if (!(await manager.ping())) {
2413
+ console.log(C.error(` Ollama is not running at ${baseUrl}`));
2414
+ console.log(C.dim(' Start Ollama first: ollama serve'));
2415
+ return;
2416
+ }
2417
+ const models = await manager.listModels();
2418
+ if (models.length === 0) {
2419
+ console.log(C.muted(' No models installed.'));
2420
+ console.log(C.dim(' Pull one with: /ollama pull <model-name>'));
2421
+ return;
2422
+ }
2423
+ console.log('');
2424
+ console.log(C.brand(' Installed Ollama Models'));
2425
+ console.log(C.dim(' ' + BOX.h.repeat(58)));
2426
+ for (const m of models) {
2427
+ const sizeGB = (m.size / 1024 / 1024 / 1024).toFixed(1);
2428
+ const family = m.details?.family || 'unknown';
2429
+ const params = m.details?.parameter_size || '';
2430
+ console.log(` ${C.toolName(m.name)}`);
2431
+ console.log(C.dim(` ${family} ${params} ${sizeGB} GB`));
2432
+ }
2433
+ console.log('');
2434
+ console.log(C.muted(` ${models.length} model(s)`));
2435
+ return;
2436
+ }
2437
+ if (cmd === 'pull' && arg) {
2438
+ if (!(await manager.ping())) {
2439
+ console.log(C.error(' Ollama is not running.'));
2440
+ console.log(C.dim(' Start Ollama first: ollama serve'));
2441
+ return;
2442
+ }
2443
+ console.log(C.info(` Pulling model: ${arg}`));
2444
+ console.log(C.dim(' This may take a while...'));
2445
+ try {
2446
+ await manager.pullModel(arg, (status) => {
2447
+ process.stdout.write(`\r ${C.muted(status)} `);
2448
+ });
2449
+ console.log('');
2450
+ console.log(C.success(` ${BOX.check} Model "${arg}" pulled successfully`));
2451
+ console.log(C.dim(` Use: /model ${arg}`));
2452
+ }
2453
+ catch (err) {
2454
+ console.log('');
2455
+ console.log(C.error(` Failed to pull model: ${err.message}`));
2456
+ }
2457
+ return;
2458
+ }
2459
+ if (cmd === 'run' && arg) {
2460
+ console.log(C.info(` Running model: ${arg}`));
2461
+ console.log(C.dim(' Note: Use "ollama run" in terminal for interactive session'));
2462
+ console.log(C.dim(' Switching model for Nova CLI...'));
2463
+ try {
2464
+ // Just switch to the model instead of running interactively
2465
+ this.modelClient.updateOptions({ model: arg });
2466
+ console.log(C.success(` ✓ Switched to Ollama model: ${arg}`));
2467
+ // Save to config
2468
+ const config = this.configManager.getConfig();
2469
+ config.core.defaultModel = arg;
2470
+ await this.configManager.save(config);
2471
+ }
2472
+ catch (err) {
2473
+ console.log(C.error(` Failed to switch model: ${err.message}`));
2474
+ }
2475
+ return;
2476
+ }
2477
+ // Unknown subcommand - show help
2478
+ console.log(C.warning(` Unknown subcommand: ${cmd}`));
2479
+ console.log('');
2480
+ console.log(C.muted(' Usage: /ollama [status|list|pull <model>]'));
2481
+ console.log(C.dim(' /ollama — show status and installed models'));
2482
+ console.log(C.dim(' /ollama list — list all installed models'));
2483
+ console.log(C.dim(' /ollama pull <n> — download a model'));
2484
+ }
2485
+ // ========================================================================
2486
+ // Approval handler
2487
+ // ========================================================================
2488
+ async handleApproval(request) {
2489
+ const effectiveMode = this.getEffectiveApprovalMode();
2490
+ if (effectiveMode === 'yolo' || effectiveMode === 'accepting_edits') {
2491
+ return { requestId: request.id, approved: true };
2492
+ }
2493
+ this.stopSpinner();
2494
+ return new Promise((resolve) => {
2495
+ console.log('');
2496
+ console.log(C.warning.bold(' ⚠ Approval Required'));
2497
+ console.log(C.muted(' Tool: ') + C.toolName(request.toolName));
2498
+ console.log(C.muted(' Risk: ') + (request.risk === 'critical' ? C.error(request.risk) :
2499
+ request.risk === 'high' ? C.warning(request.risk) :
2500
+ C.muted(request.risk)));
2501
+ if (request.description) {
2502
+ const desc = request.description.replace(`Tool "${request.toolName}" with input: `, '');
2503
+ const preview = desc.slice(0, 80);
2504
+ console.log(C.muted(' Input: ') + C.dim(preview));
2505
+ }
2506
+ console.log('');
2507
+ this.rl?.question(C.warning(' Allow? [y/N/a(ll)] '), (answer) => {
2508
+ const a = answer.trim().toLowerCase();
2509
+ if (a === 'a' || a === 'all') {
2510
+ // Switch to yolo for remainder of this task
2511
+ this.mode = 'auto';
2512
+ console.log(C.success(' Auto-approved for this task.'));
2513
+ resolve({ requestId: request.id, approved: true });
2514
+ }
2515
+ else {
2516
+ const approved = a === 'y' || a === 'yes';
2517
+ if (!approved)
2518
+ console.log(C.error(' Denied.'));
2519
+ resolve({ requestId: request.id, approved });
2520
+ }
2521
+ });
2522
+ });
2523
+ }
2524
+ // ========================================================================
2525
+ // Helpers
2526
+ // ========================================================================
2527
+ getModePrefix() {
2528
+ switch (this.mode) {
2529
+ case 'plan':
2530
+ return '[PLAN MODE] First analyze and create a step-by-step plan. Wait for confirmation before executing.';
2531
+ case 'ask':
2532
+ return '[ASK MODE] Only answer questions. Do NOT modify files or execute commands.';
2533
+ default:
2534
+ return '';
2535
+ }
2536
+ }
2537
+ getEffectiveApprovalMode() {
2538
+ return MODE_LABELS[this.mode].approvalMode;
2539
+ }
2540
+ createInitialSession() {
2541
+ const session = this.sessionManager.create({
2542
+ workingDirectory: this.cwd,
2543
+ model: this.modelClient.getModel(),
2544
+ maxTokens: this.config.core.maxTokens,
2545
+ temperature: this.config.core.temperature,
2546
+ approvalMode: this.getEffectiveApprovalMode(),
2547
+ streaming: true,
2548
+ maxTurns: this.config.core.maxTurns,
2549
+ });
2550
+ return session.id;
2551
+ }
2552
+ getTimeStr() {
2553
+ return new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
2554
+ }
2555
+ ensureDir(dirPath) {
2556
+ if (!fs.existsSync(dirPath)) {
2557
+ fs.mkdirSync(dirPath, { recursive: true });
2558
+ }
2559
+ }
2560
+ }
2561
+ //# sourceMappingURL=InteractiveRepl.js.map