profclaw 2.0.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (477) hide show
  1. package/.env.example +14 -2
  2. package/CHANGELOG.md +37 -0
  3. package/CONTRIBUTING.md +20 -0
  4. package/Dockerfile +39 -23
  5. package/README.md +174 -40
  6. package/dist/agents/agent-summary.d.ts +44 -0
  7. package/dist/agents/agent-summary.js +176 -0
  8. package/dist/agents/auto-memory.d.ts +59 -0
  9. package/dist/agents/auto-memory.js +310 -0
  10. package/dist/agents/checkpoint-manager.d.ts +76 -0
  11. package/dist/agents/checkpoint-manager.js +156 -0
  12. package/dist/agents/circuit-breaker.d.ts +54 -0
  13. package/dist/agents/circuit-breaker.js +153 -0
  14. package/dist/agents/context-compactor.d.ts +70 -0
  15. package/dist/agents/context-compactor.js +361 -0
  16. package/dist/agents/conversation-branch.d.ts +55 -0
  17. package/dist/agents/conversation-branch.js +115 -0
  18. package/dist/agents/error-recovery.d.ts +44 -0
  19. package/dist/agents/error-recovery.js +197 -0
  20. package/dist/agents/events.d.ts +76 -0
  21. package/dist/agents/events.js +8 -0
  22. package/dist/agents/executor.d.ts +35 -1
  23. package/dist/agents/executor.js +573 -124
  24. package/dist/agents/file-snapshots.d.ts +72 -0
  25. package/dist/agents/file-snapshots.js +182 -0
  26. package/dist/agents/index.d.ts +2 -0
  27. package/dist/agents/orchestrator.d.ts +183 -0
  28. package/dist/agents/orchestrator.js +495 -0
  29. package/dist/agents/permissions.d.ts +63 -0
  30. package/dist/agents/permissions.js +138 -0
  31. package/dist/agents/plan-mode.d.ts +71 -0
  32. package/dist/agents/plan-mode.js +186 -0
  33. package/dist/agents/prompt-suggestions.d.ts +33 -0
  34. package/dist/agents/prompt-suggestions.js +208 -0
  35. package/dist/agents/rate-limit-monitor.d.ts +56 -0
  36. package/dist/agents/rate-limit-monitor.js +224 -0
  37. package/dist/agents/result-store.d.ts +37 -0
  38. package/dist/agents/result-store.js +104 -0
  39. package/dist/agents/session-diff.d.ts +43 -0
  40. package/dist/agents/session-diff.js +258 -0
  41. package/dist/agents/tool-loader.d.ts +65 -0
  42. package/dist/agents/tool-loader.js +273 -0
  43. package/dist/agents/transcript.d.ts +96 -0
  44. package/dist/agents/transcript.js +267 -0
  45. package/dist/agents/worktree-manager.d.ts +80 -0
  46. package/dist/agents/worktree-manager.js +203 -0
  47. package/dist/auth/middleware.js +14 -0
  48. package/dist/chat/agentic-executor.d.ts +7 -0
  49. package/dist/chat/agentic-executor.js +253 -30
  50. package/dist/chat/conversations.d.ts +2 -0
  51. package/dist/chat/conversations.js +8 -0
  52. package/dist/chat/execution/executor.d.ts +8 -0
  53. package/dist/chat/execution/executor.js +157 -103
  54. package/dist/chat/execution/index.js +5 -1
  55. package/dist/chat/execution/tool-router.js +40 -14
  56. package/dist/chat/execution/tools/canvas.d.ts +2 -2
  57. package/dist/chat/execution/tools/code-tools.d.ts +66 -0
  58. package/dist/chat/execution/tools/code-tools.js +353 -0
  59. package/dist/chat/execution/tools/complete-task.d.ts +2 -2
  60. package/dist/chat/execution/tools/cron-tool.d.ts +26 -10
  61. package/dist/chat/execution/tools/cron-tool.js +48 -3
  62. package/dist/chat/execution/tools/decompose-task.d.ts +58 -0
  63. package/dist/chat/execution/tools/decompose-task.js +166 -0
  64. package/dist/chat/execution/tools/exec.d.ts +5 -2
  65. package/dist/chat/execution/tools/exec.js +294 -155
  66. package/dist/chat/execution/tools/feed-tools.d.ts +77 -0
  67. package/dist/chat/execution/tools/feed-tools.js +142 -0
  68. package/dist/chat/execution/tools/file-ops.d.ts +1 -1
  69. package/dist/chat/execution/tools/github.d.ts +2 -2
  70. package/dist/chat/execution/tools/index.d.ts +14 -2
  71. package/dist/chat/execution/tools/index.js +86 -12
  72. package/dist/chat/execution/tools/integrations.d.ts +8 -8
  73. package/dist/chat/execution/tools/multi-patch.d.ts +53 -0
  74. package/dist/chat/execution/tools/multi-patch.js +373 -0
  75. package/dist/chat/execution/tools/openai-image-gen.d.ts +2 -2
  76. package/dist/chat/execution/tools/plan-tools.d.ts +80 -0
  77. package/dist/chat/execution/tools/plan-tools.js +134 -0
  78. package/dist/chat/execution/tools/profclaw-ops.d.ts +6 -6
  79. package/dist/chat/execution/tools/repl-tool.d.ts +46 -0
  80. package/dist/chat/execution/tools/repl-tool.js +207 -0
  81. package/dist/chat/execution/tools/sessions-send.d.ts +2 -2
  82. package/dist/chat/execution/tools/slack-actions.d.ts +2 -2
  83. package/dist/chat/execution/tools/system.d.ts +4 -4
  84. package/dist/chat/execution/tools/todo-tool.d.ts +72 -0
  85. package/dist/chat/execution/tools/todo-tool.js +152 -0
  86. package/dist/chat/execution/tools/tool-search.d.ts +50 -0
  87. package/dist/chat/execution/tools/tool-search.js +155 -0
  88. package/dist/chat/execution/types.d.ts +21 -14
  89. package/dist/chat/execution/workflows/executor.d.ts +11 -0
  90. package/dist/chat/execution/workflows/executor.js +99 -1
  91. package/dist/chat/execution/workflows/types.d.ts +35 -2
  92. package/dist/chat/format/index.d.ts +1 -0
  93. package/dist/chat/format/index.js +1 -0
  94. package/dist/chat/format/markdown-convert.d.ts +23 -0
  95. package/dist/chat/format/markdown-convert.js +149 -0
  96. package/dist/chat/index.d.ts +1 -1
  97. package/dist/chat/index.js +1 -1
  98. package/dist/chat/message-handler.js +58 -6
  99. package/dist/chat/proactive/index.d.ts +62 -0
  100. package/dist/chat/proactive/index.js +147 -0
  101. package/dist/chat/prompt-adapter.js +2 -0
  102. package/dist/chat/providers/irc/index.d.ts +2 -2
  103. package/dist/chat/providers/telegram/index.d.ts +2 -0
  104. package/dist/chat/providers/telegram/index.js +21 -2
  105. package/dist/chat/system-prompts.d.ts +3 -3
  106. package/dist/chat/system-prompts.js +116 -61
  107. package/dist/chat/tools.d.ts +6 -6
  108. package/dist/cli/commands/auth.js +64 -1
  109. package/dist/cli/commands/chat.js +1327 -138
  110. package/dist/cli/commands/doctor.js +222 -16
  111. package/dist/cli/commands/history.d.ts +3 -0
  112. package/dist/cli/commands/history.js +138 -0
  113. package/dist/cli/commands/import.d.ts +44 -0
  114. package/dist/cli/commands/import.js +526 -0
  115. package/dist/cli/commands/init.d.ts +13 -0
  116. package/dist/cli/commands/init.js +381 -0
  117. package/dist/cli/commands/onboard.js +210 -30
  118. package/dist/cli/commands/plan.d.ts +13 -0
  119. package/dist/cli/commands/plan.js +184 -0
  120. package/dist/cli/commands/serve.d.ts +7 -0
  121. package/dist/cli/commands/serve.js +77 -18
  122. package/dist/cli/commands/setup.js +84 -3
  123. package/dist/cli/commands/status.js +54 -0
  124. package/dist/cli/commands/tools.js +13 -13
  125. package/dist/cli/commands/tui.js +26 -1
  126. package/dist/cli/index.js +19 -0
  127. package/dist/cli/ink/App.d.ts +13 -0
  128. package/dist/cli/ink/App.js +6 -0
  129. package/dist/cli/ink/ChatApp.d.ts +60 -0
  130. package/dist/cli/ink/ChatApp.js +325 -0
  131. package/dist/cli/ink/DashboardApp.d.ts +3 -0
  132. package/dist/cli/ink/DashboardApp.js +143 -0
  133. package/dist/cli/ink/components/AgentStatus.d.ts +17 -0
  134. package/dist/cli/ink/components/AgentStatus.js +33 -0
  135. package/dist/cli/ink/components/ChannelList.d.ts +17 -0
  136. package/dist/cli/ink/components/ChannelList.js +33 -0
  137. package/dist/cli/ink/components/ConnectionStatus.d.ts +19 -0
  138. package/dist/cli/ink/components/ConnectionStatus.js +50 -0
  139. package/dist/cli/ink/components/CostBar.d.ts +19 -0
  140. package/dist/cli/ink/components/CostBar.js +35 -0
  141. package/dist/cli/ink/components/HookStatus.d.ts +15 -0
  142. package/dist/cli/ink/components/HookStatus.js +7 -0
  143. package/dist/cli/ink/components/PermissionPrompt.d.ts +21 -0
  144. package/dist/cli/ink/components/PermissionPrompt.js +51 -0
  145. package/dist/cli/ink/components/PlanView.d.ts +23 -0
  146. package/dist/cli/ink/components/PlanView.js +26 -0
  147. package/dist/cli/ink/components/ProviderList.d.ts +16 -0
  148. package/dist/cli/ink/components/ProviderList.js +16 -0
  149. package/dist/cli/ink/components/SessionHeader.d.ts +15 -0
  150. package/dist/cli/ink/components/SessionHeader.js +15 -0
  151. package/dist/cli/ink/components/StreamingMessage.d.ts +20 -0
  152. package/dist/cli/ink/components/StreamingMessage.js +27 -0
  153. package/dist/cli/ink/components/SuggestionBar.d.ts +15 -0
  154. package/dist/cli/ink/components/SuggestionBar.js +28 -0
  155. package/dist/cli/ink/components/SystemHealth.d.ts +20 -0
  156. package/dist/cli/ink/components/SystemHealth.js +39 -0
  157. package/dist/cli/ink/components/TaskList.d.ts +17 -0
  158. package/dist/cli/ink/components/TaskList.js +59 -0
  159. package/dist/cli/ink/components/ToolCall.d.ts +17 -0
  160. package/dist/cli/ink/components/ToolCall.js +43 -0
  161. package/dist/cli/ink/index.d.ts +23 -0
  162. package/dist/cli/ink/index.js +15 -0
  163. package/dist/cli/ink/keybindings.d.ts +35 -0
  164. package/dist/cli/ink/keybindings.js +105 -0
  165. package/dist/cli/ink/markdown-renderer.d.ts +47 -0
  166. package/dist/cli/ink/markdown-renderer.js +380 -0
  167. package/dist/cli/ink/output-styles.d.ts +39 -0
  168. package/dist/cli/ink/output-styles.js +119 -0
  169. package/dist/cli/interactive/auto-serve.d.ts +38 -0
  170. package/dist/cli/interactive/auto-serve.js +209 -0
  171. package/dist/cli/interactive/index.d.ts +25 -0
  172. package/dist/cli/interactive/index.js +23 -0
  173. package/dist/cli/interactive/picker.d.ts +28 -0
  174. package/dist/cli/interactive/picker.js +153 -0
  175. package/dist/cli/interactive/renderer.d.ts +26 -0
  176. package/dist/cli/interactive/renderer.js +555 -0
  177. package/dist/cli/interactive/repl.d.ts +14 -0
  178. package/dist/cli/interactive/repl.js +1516 -0
  179. package/dist/cli/interactive/stream-client.d.ts +59 -0
  180. package/dist/cli/interactive/stream-client.js +506 -0
  181. package/dist/cli/interactive/types.d.ts +121 -0
  182. package/dist/cli/interactive/types.js +10 -0
  183. package/dist/cli/utils/api.d.ts +9 -0
  184. package/dist/cli/utils/api.js +58 -2
  185. package/dist/cron/natural-language.d.ts +54 -0
  186. package/dist/cron/natural-language.js +569 -0
  187. package/dist/cron/scheduler.d.ts +34 -4
  188. package/dist/cron/scheduler.js +141 -9
  189. package/dist/cron/templates.d.ts +1 -1
  190. package/dist/cron/templates.js +219 -0
  191. package/dist/feeds/feed-engine.d.ts +149 -0
  192. package/dist/feeds/feed-engine.js +600 -0
  193. package/dist/feeds/index.d.ts +9 -0
  194. package/dist/feeds/index.js +8 -0
  195. package/dist/gateway/router.js +6 -1
  196. package/dist/hooks/built-in/audit-log.d.ts +11 -0
  197. package/dist/hooks/built-in/audit-log.js +84 -0
  198. package/dist/hooks/built-in/cost-warning.d.ts +10 -0
  199. package/dist/hooks/built-in/cost-warning.js +59 -0
  200. package/dist/hooks/built-in/dangerous-tool.d.ts +32 -0
  201. package/dist/hooks/built-in/dangerous-tool.js +80 -0
  202. package/dist/hooks/built-in/index.d.ts +14 -0
  203. package/dist/hooks/built-in/index.js +20 -0
  204. package/dist/hooks/index.d.ts +3 -0
  205. package/dist/hooks/index.js +6 -0
  206. package/dist/hooks/loader.d.ts +17 -0
  207. package/dist/hooks/loader.js +142 -0
  208. package/dist/hooks/registry.d.ts +62 -0
  209. package/dist/hooks/registry.js +127 -0
  210. package/dist/integrations/web-search.js +6 -3
  211. package/dist/memory/index.d.ts +1 -0
  212. package/dist/memory/index.js +2 -0
  213. package/dist/memory/memory-service.js +14 -13
  214. package/dist/memory/observational.d.ts +62 -0
  215. package/dist/memory/observational.js +396 -0
  216. package/dist/middleware/content-filter.d.ts +22 -0
  217. package/dist/middleware/content-filter.js +69 -0
  218. package/dist/middleware/request-validator.d.ts +159 -0
  219. package/dist/middleware/request-validator.js +93 -0
  220. package/dist/plugins/sdk.d.ts +22 -0
  221. package/dist/plugins/sdk.js +17 -1
  222. package/dist/projects/types.d.ts +10 -10
  223. package/dist/providers/ai-sdk.d.ts +11 -0
  224. package/dist/providers/ai-sdk.js +78 -19
  225. package/dist/providers/core/models.js +67 -1
  226. package/dist/providers/core/types.d.ts +3 -0
  227. package/dist/providers/index.d.ts +1 -0
  228. package/dist/providers/index.js +1 -0
  229. package/dist/providers/smart-router.d.ts +118 -0
  230. package/dist/providers/smart-router.js +685 -0
  231. package/dist/providers/types.d.ts +16 -16
  232. package/dist/routes/agents.js +50 -0
  233. package/dist/routes/auth.js +8 -1
  234. package/dist/routes/chat.js +259 -14
  235. package/dist/routes/costs.js +32 -0
  236. package/dist/routes/cron.js +80 -5
  237. package/dist/routes/feeds.d.ts +10 -0
  238. package/dist/routes/feeds.js +168 -0
  239. package/dist/routes/mcp.js +21 -0
  240. package/dist/routes/memory.js +130 -0
  241. package/dist/routes/skills.js +129 -0
  242. package/dist/routes/teams.d.ts +9 -0
  243. package/dist/routes/teams.js +210 -0
  244. package/dist/routes/tools.js +14 -0
  245. package/dist/routes/users.js +2 -0
  246. package/dist/server/route-loader.js +3 -1
  247. package/dist/server/stream-bridge.d.ts +19 -0
  248. package/dist/server/stream-bridge.js +38 -0
  249. package/dist/server.js +310 -36
  250. package/dist/skills/marketplace.d.ts +87 -0
  251. package/dist/skills/marketplace.js +268 -0
  252. package/dist/storage/index.d.ts +14 -0
  253. package/dist/storage/index.js +64 -3
  254. package/dist/storage/schema.d.ts +4 -4
  255. package/dist/storage/schema.js +1 -1
  256. package/dist/teams/index.d.ts +110 -0
  257. package/dist/teams/index.js +365 -0
  258. package/dist/tickets/types.d.ts +39 -39
  259. package/dist/types/summary.d.ts +5 -5
  260. package/dist/utils/auto-update.d.ts +23 -0
  261. package/dist/utils/auto-update.js +157 -0
  262. package/dist/utils/offline-detect.d.ts +55 -0
  263. package/dist/utils/offline-detect.js +121 -0
  264. package/dist/utils/pid-file.d.ts +36 -0
  265. package/dist/utils/pid-file.js +125 -0
  266. package/dist/utils/prevent-sleep.d.ts +27 -0
  267. package/dist/utils/prevent-sleep.js +95 -0
  268. package/dist/utils/sanitizer.d.ts +57 -0
  269. package/dist/utils/sanitizer.js +174 -0
  270. package/docker-compose.yml +13 -137
  271. package/package.json +31 -6
  272. package/skills/deploy-cloudflare/SKILL.md +62 -0
  273. package/skills/deploy-vercel/SKILL.md +67 -0
  274. package/skills/docker-deploy/SKILL.md +158 -0
  275. package/skills/full-stack-builder/SKILL.md +92 -0
  276. package/skills/humanizer/SKILL.md +488 -0
  277. package/skills/profclaw-assistant/SKILL.md +85 -56
  278. package/ui/dist/assets/ActivityView-Da6HQ8ws.js +1 -0
  279. package/ui/dist/assets/AgentList-H58R0kX3.js +1 -0
  280. package/ui/dist/assets/AnalyticsDashboard-B28AeRsc.js +1 -0
  281. package/ui/dist/assets/AreaChart-DwLzeAQ8.js +1 -0
  282. package/ui/dist/assets/CartesianChart-piKQsTAR.js +36 -0
  283. package/ui/dist/assets/ChatView-Dbj8rxU5.js +10 -0
  284. package/ui/dist/assets/CostsDashboard-C6F5df0F.js +1 -0
  285. package/ui/dist/assets/EstimateSelect-wga0rUKX.js +1 -0
  286. package/ui/dist/assets/FeedDashboard-Ef6FEzEf.js +1 -0
  287. package/ui/dist/assets/FloatingChatbot-0yhkUeCq.js +2 -0
  288. package/ui/dist/assets/InviteCodeManagement-tUXfx4Bc.js +2 -0
  289. package/ui/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  290. package/ui/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  291. package/ui/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  292. package/ui/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  293. package/ui/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  294. package/ui/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  295. package/ui/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  296. package/ui/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  297. package/ui/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  298. package/ui/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  299. package/ui/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  300. package/ui/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  301. package/ui/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  302. package/ui/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  303. package/ui/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  304. package/ui/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  305. package/ui/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  306. package/ui/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  307. package/ui/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  308. package/ui/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  309. package/ui/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  310. package/ui/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  311. package/ui/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  312. package/ui/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  313. package/ui/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  314. package/ui/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  315. package/ui/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  316. package/ui/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  317. package/ui/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  318. package/ui/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  319. package/ui/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  320. package/ui/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  321. package/ui/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  322. package/ui/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  323. package/ui/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  324. package/ui/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  325. package/ui/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  326. package/ui/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  327. package/ui/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  328. package/ui/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  329. package/ui/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  330. package/ui/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  331. package/ui/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  332. package/ui/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  333. package/ui/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  334. package/ui/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  335. package/ui/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  336. package/ui/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  337. package/ui/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  338. package/ui/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  339. package/ui/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  340. package/ui/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  341. package/ui/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  342. package/ui/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  343. package/ui/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  344. package/ui/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  345. package/ui/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  346. package/ui/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  347. package/ui/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  348. package/ui/dist/assets/MarketplacePage-DGrLtolR.js +1 -0
  349. package/ui/dist/assets/{ProjectDetail--gPfvATB.js → ProjectDetail-BgWvA6UP.js} +2 -2
  350. package/ui/dist/assets/ProjectIcon-TDuliqwg.js +1 -0
  351. package/ui/dist/assets/ProjectList-DXgM4187.js +1 -0
  352. package/ui/dist/assets/{Settings-1kaf7VHE.js → Settings-xA8Zhi3p.js} +3 -3
  353. package/ui/dist/assets/StatusIndicator-B8vfFAxg.js +1 -0
  354. package/ui/dist/assets/{SummaryDetail-DV3AmONk.js → SummaryDetail-eedRfTtF.js} +2 -2
  355. package/ui/dist/assets/SummaryList-CLNX2P84.js +1 -0
  356. package/ui/dist/assets/TaskDetail-BtWclNiR.js +1 -0
  357. package/ui/dist/assets/TaskList-JjODAgSc.js +1 -0
  358. package/ui/dist/assets/TeamsPage-CBK5iAyx.js +1 -0
  359. package/ui/dist/assets/TicketBoard-C9aJNNYp.js +1 -0
  360. package/ui/dist/assets/TicketDetail-CV5JmL31.js +1 -0
  361. package/ui/dist/assets/TicketList-68V4lzUz.js +1 -0
  362. package/ui/dist/assets/UserManagement-DKTb6UpE.js +2 -0
  363. package/ui/dist/assets/ViewSwitcher-BvdQht2Y.js +1 -0
  364. package/ui/dist/assets/alert-dialog-O6qBOv_f.js +1 -0
  365. package/ui/dist/assets/{archive-BsLa7brF.js → archive-CX7UvZpf.js} +1 -1
  366. package/ui/dist/assets/badge-BbfnwDJP.js +1 -0
  367. package/ui/dist/assets/{bug-CmZI7jqM.js → bug-3WX0394-.js} +1 -1
  368. package/ui/dist/assets/{calendar-Bf0t2B82.js → calendar-buq77TaS.js} +1 -1
  369. package/ui/dist/assets/{chevron-up-CoUsOvZj.js → chevron-up-4ymsBJRo.js} +1 -1
  370. package/ui/dist/assets/{circle-x-Dgw11LgT.js → circle-x-ByXtwR6Y.js} +1 -1
  371. package/ui/dist/assets/constants-CVdDh7p0.js +1 -0
  372. package/ui/dist/assets/{cpu-DiPMQICV.js → cpu-CiuoCeDY.js} +1 -1
  373. package/ui/dist/assets/{database-BwTfupDi.js → database-BWXSBg5a.js} +1 -1
  374. package/ui/dist/assets/dialog-lDg4NvMV.js +1 -0
  375. package/ui/dist/assets/dollar-sign-Dmijd6DP.js +1 -0
  376. package/ui/dist/assets/{ellipsis-UJx4Xnf6.js → ellipsis-vT-5SVIp.js} +1 -1
  377. package/ui/dist/assets/{ellipsis-vertical-DoULYEKn.js → ellipsis-vertical-B4tAX_bL.js} +1 -1
  378. package/ui/dist/assets/feature-markdown-DBo5Q7uq.js +289 -0
  379. package/ui/dist/assets/{file-code-B2UrJLwi.js → file-code-ChOWdKXf.js} +1 -1
  380. package/ui/dist/assets/{folder-open-BJi577JH.js → folder-open-CkOUbIiP.js} +1 -1
  381. package/ui/dist/assets/{funnel-PkaD_HSR.js → funnel-uZkoSPP-.js} +1 -1
  382. package/ui/dist/assets/{git-branch-C_K30am0.js → git-branch-CwvUxj7C.js} +1 -1
  383. package/ui/dist/assets/{git-commit-horizontal-IT6ecxWQ.js → git-commit-horizontal-lxPzCvhD.js} +1 -1
  384. package/ui/dist/assets/{globe-f-jjCa_g.js → globe-D_WpJdsj.js} +1 -1
  385. package/ui/dist/assets/{hash-DNEv8Elo.js → hash-CA0hJ7vh.js} +1 -1
  386. package/ui/dist/assets/{history-BQSBo_1Q.js → history-CEWMLELs.js} +1 -1
  387. package/ui/dist/assets/inbox-ehtOptTV.js +1 -0
  388. package/ui/dist/assets/{index-nS3EErDg.js → index-C1rhbml4.js} +3 -3
  389. package/ui/dist/assets/index-CR8gfCHv.js +1 -0
  390. package/ui/dist/assets/index-DVXMpWai.js +1 -0
  391. package/ui/dist/assets/index-DaDJMM2v.js +1 -0
  392. package/ui/dist/assets/index-Djdq_0xu.js +1 -0
  393. package/ui/dist/assets/index-M46Gcb8K.css +1 -0
  394. package/ui/dist/assets/index-sEBN7w8J.js +109 -0
  395. package/ui/dist/assets/layers-C6Mhf8Nk.js +1 -0
  396. package/ui/dist/assets/{layout-dashboard-CcKt1hXw.js → layout-dashboard-BRUM0kpT.js} +1 -1
  397. package/ui/dist/assets/{lightbulb-Jhp7FvbU.js → lightbulb-CN2q1O5l.js} +1 -1
  398. package/ui/dist/assets/{link-2-DdO7ostF.js → link-2-CC4E28yJ.js} +1 -1
  399. package/ui/dist/assets/markdown-renderer-C6ENbB0u.js +10 -0
  400. package/ui/dist/assets/markdown-renderer-CfVKi3_s.css +1 -0
  401. package/ui/dist/assets/{message-square-CBgH65X7.js → message-square-Bbb241am.js} +1 -1
  402. package/ui/dist/assets/package-CwT3IzFx.js +1 -0
  403. package/ui/dist/assets/{pencil-CYXCEPQh.js → pencil-CDmiHzoj.js} +1 -1
  404. package/ui/dist/assets/{play-d1ONaH0-.js → play-tjy5jMQX.js} +1 -1
  405. package/ui/dist/assets/popover-CFHZf3pE.js +1 -0
  406. package/ui/dist/assets/progress-BboUHuLf.js +1 -0
  407. package/ui/dist/assets/{rocket-DhvUetsk.js → rocket--8ZHgdXJ.js} +1 -1
  408. package/ui/dist/assets/select-n108rXny.js +1 -0
  409. package/ui/dist/assets/{send-P0r5D7td.js → send-Cu7MR2Qh.js} +1 -1
  410. package/ui/dist/assets/server-DmodxZp-.js +1 -0
  411. package/ui/dist/assets/{settings-2-ocj5uHGx.js → settings-2-CkRNqkxj.js} +1 -1
  412. package/ui/dist/assets/sheet-7aUJkyzU.js +1 -0
  413. package/ui/dist/assets/{shield-check-DfVdE34l.js → shield-check-ViI3nMKE.js} +1 -1
  414. package/ui/dist/assets/{shield-off-8l0J6R-8.js → shield-off-DN3tagkn.js} +1 -1
  415. package/ui/dist/assets/skeleton-C0E1Nfro.js +1 -0
  416. package/ui/dist/assets/{smartphone-77_g3SQn.js → smartphone-5FghiXpA.js} +1 -1
  417. package/ui/dist/assets/{square-Ckzsr3Rn.js → square-DiNSx8K7.js} +1 -1
  418. package/ui/dist/assets/star-DhdFVidS.js +1 -0
  419. package/ui/dist/assets/switch-CIgVbZqQ.js +1 -0
  420. package/ui/dist/assets/table-BC56FVMV.js +1 -0
  421. package/ui/dist/assets/{tag-C35mgoEv.js → tag-CH_vQliw.js} +1 -1
  422. package/ui/dist/assets/textarea-CxDiKFXZ.js +1 -0
  423. package/ui/dist/assets/{timer-DHJBS2w1.js → timer-CdGVTb60.js} +1 -1
  424. package/ui/dist/assets/tooltip-BuW9A5UO.js +1 -0
  425. package/ui/dist/assets/{trash-2-keO5tKPE.js → trash-2-CucZJL0N.js} +1 -1
  426. package/ui/dist/assets/trending-down-BCezC1z2.js +1 -0
  427. package/ui/dist/assets/{trending-up-BBv8KbrY.js → trending-up-C5OYX2k2.js} +1 -1
  428. package/ui/dist/assets/vendor-query-C0p7b53x.js +4 -0
  429. package/ui/dist/assets/vendor-react-Mc5_kfYG.js +12 -0
  430. package/ui/dist/assets/vendor-ui-CXPAUBFv.js +51 -0
  431. package/ui/dist/assets/{wand-sparkles-BusQ52ZK.js → wand-sparkles-BaH6pJnc.js} +1 -1
  432. package/ui/dist/assets/{wrench-02YaOSiK.js → wrench-CjxjP6ok.js} +1 -1
  433. package/ui/dist/index.html +5 -2
  434. package/ui/dist/assets/ActivityView-BeshZXmE.js +0 -1
  435. package/ui/dist/assets/AgentList-QVHeldPi.js +0 -1
  436. package/ui/dist/assets/AnalyticsDashboard-C9R97dXE.js +0 -1
  437. package/ui/dist/assets/AreaChart-B73gaiVQ.js +0 -1
  438. package/ui/dist/assets/CartesianChart-CXYL2R-A.js +0 -36
  439. package/ui/dist/assets/ChatView-qjZErlrp.js +0 -10
  440. package/ui/dist/assets/CostsDashboard-O4DCLy1i.js +0 -1
  441. package/ui/dist/assets/EstimateSelect-1hQf4Osk.js +0 -1
  442. package/ui/dist/assets/FloatingChatbot-BZBzrJxd.js +0 -2
  443. package/ui/dist/assets/InviteCodeManagement-LEoGqYeA.js +0 -2
  444. package/ui/dist/assets/ProjectIcon-Chd_ofsw.js +0 -1
  445. package/ui/dist/assets/ProjectList-BvUfVB6c.js +0 -4
  446. package/ui/dist/assets/StatusIndicator-pRfkNcdV.js +0 -1
  447. package/ui/dist/assets/SummaryList-B6kXiTxL.js +0 -1
  448. package/ui/dist/assets/TaskDetail-CV6CzbOS.js +0 -1
  449. package/ui/dist/assets/TaskList-DL7brQKd.js +0 -1
  450. package/ui/dist/assets/TicketBoard-CrkkFj7C.js +0 -1
  451. package/ui/dist/assets/TicketDetail-hnnYclpD.js +0 -1
  452. package/ui/dist/assets/TicketList-CsDAyQCL.js +0 -1
  453. package/ui/dist/assets/UserManagement-BGFQyTSr.js +0 -2
  454. package/ui/dist/assets/ViewSwitcher-CnL0OqA8.js +0 -1
  455. package/ui/dist/assets/alert-dialog-SKM6Gjz4.js +0 -7
  456. package/ui/dist/assets/badge-CVHH1a-v.js +0 -1
  457. package/ui/dist/assets/brain-6o_RHVpe.js +0 -1
  458. package/ui/dist/assets/constants-Q5Zivl-a.js +0 -1
  459. package/ui/dist/assets/dialog-BUqN9Psj.js +0 -1
  460. package/ui/dist/assets/index-BUmnwPQr.js +0 -1
  461. package/ui/dist/assets/index-BfF5G4Cw.js +0 -1
  462. package/ui/dist/assets/index-C8N375c2.js +0 -1
  463. package/ui/dist/assets/index-CuxnqVVz.css +0 -1
  464. package/ui/dist/assets/index-CzMM3QOk.js +0 -1
  465. package/ui/dist/assets/index-lJlNPFVJ.js +0 -67
  466. package/ui/dist/assets/layers-jFqVrWMD.js +0 -1
  467. package/ui/dist/assets/markdown-renderer-n1ToC4XQ.js +0 -36
  468. package/ui/dist/assets/popover-CrmZgoWu.js +0 -1
  469. package/ui/dist/assets/progress-uv4SPkL5.js +0 -1
  470. package/ui/dist/assets/select-CeQ8AHGy.js +0 -1
  471. package/ui/dist/assets/server-CVHTZB72.js +0 -1
  472. package/ui/dist/assets/sheet-DuQExSho.js +0 -1
  473. package/ui/dist/assets/skeleton-TFiFJ3nB.js +0 -1
  474. package/ui/dist/assets/switch-CAePkd6k.js +0 -1
  475. package/ui/dist/assets/table-ZjAZ7hkt.js +0 -1
  476. package/ui/dist/assets/textarea-DsVPnSgR.js +0 -1
  477. package/ui/dist/assets/tooltip-BYmQ331W.js +0 -1
@@ -0,0 +1,1516 @@
1
+ /**
2
+ * Interactive REPL Engine
3
+ *
4
+ * Claude-like interactive chat with streaming, tool display,
5
+ * slash commands, and session management.
6
+ *
7
+ * Self-contained module - depends only on Node.js builtins,
8
+ * chalk, ora, and the stream-client/renderer siblings.
9
+ *
10
+ * @package profclaw-interactive (future standalone)
11
+ */
12
+ import * as readline from 'node:readline';
13
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+ import { homedir } from 'node:os';
16
+ import chalk from 'chalk';
17
+ import { createRenderer, formatElapsed, formatTokens } from './renderer.js';
18
+ // Module-level state used by slash command handlers and the REPL loop
19
+ let lastAssistantResponse = '';
20
+ let lastUserMessage = '';
21
+ let pendingRetry = '';
22
+ let activeAbort = null;
23
+ let activeSpinnerInterval = null;
24
+ function formatRelativeTime(dateStr) {
25
+ const diff = Date.now() - new Date(dateStr).getTime();
26
+ if (diff < 60000)
27
+ return 'just now';
28
+ if (diff < 3600000)
29
+ return `${Math.floor(diff / 60000)}m ago`;
30
+ if (diff < 86400000)
31
+ return `${Math.floor(diff / 3600000)}h ago`;
32
+ return `${Math.floor(diff / 86400000)}d ago`;
33
+ }
34
+ import { streamChat, createConversation } from './stream-client.js';
35
+ import { ensureServer } from './auto-serve.js';
36
+ import { showPicker } from './picker.js';
37
+ // History file
38
+ const HISTORY_DIR = join(homedir(), '.profclaw');
39
+ const HISTORY_FILE = join(HISTORY_DIR, 'chat_history');
40
+ const SESSION_FILE = join(HISTORY_DIR, 'last_session');
41
+ const MAX_HISTORY = 500;
42
+ function loadHistory() {
43
+ try {
44
+ if (existsSync(HISTORY_FILE)) {
45
+ return readFileSync(HISTORY_FILE, 'utf-8').split('\n').filter(Boolean);
46
+ }
47
+ }
48
+ catch { /* empty */ }
49
+ return [];
50
+ }
51
+ function saveHistory(lines) {
52
+ try {
53
+ if (!existsSync(HISTORY_DIR)) {
54
+ mkdirSync(HISTORY_DIR, { recursive: true });
55
+ }
56
+ writeFileSync(HISTORY_FILE, lines.slice(-MAX_HISTORY).join('\n') + '\n');
57
+ }
58
+ catch { /* empty */ }
59
+ }
60
+ function loadLastSession() {
61
+ try {
62
+ if (existsSync(SESSION_FILE)) {
63
+ const data = readFileSync(SESSION_FILE, 'utf-8').trim();
64
+ if (data)
65
+ return data;
66
+ }
67
+ }
68
+ catch { /* empty */ }
69
+ return null;
70
+ }
71
+ function saveLastSession(conversationId) {
72
+ try {
73
+ if (!existsSync(HISTORY_DIR)) {
74
+ mkdirSync(HISTORY_DIR, { recursive: true });
75
+ }
76
+ writeFileSync(SESSION_FILE, conversationId);
77
+ }
78
+ catch { /* empty */ }
79
+ }
80
+ /** Fetch recent sessions from the server */
81
+ async function fetchSessions(serverUrl, limit = 10) {
82
+ try {
83
+ const response = await fetch(`${serverUrl}/api/chat/conversations/recent?limit=${limit}`, {
84
+ headers: { 'Accept': 'application/json' },
85
+ }).catch(() => null);
86
+ if (!response || !response.ok)
87
+ return [];
88
+ const data = await response.json();
89
+ return data.conversations || [];
90
+ }
91
+ catch {
92
+ return [];
93
+ }
94
+ }
95
+ /** Auto-generate a session title from the first message (fire-and-forget) */
96
+ async function autoTitleSession(serverUrl, conversationId, firstMessage) {
97
+ // Generate a short title from the first message (truncate to ~50 chars)
98
+ const title = firstMessage.length <= 50
99
+ ? firstMessage
100
+ : firstMessage.slice(0, 47).replace(/\s+\S*$/, '') + '...';
101
+ try {
102
+ await fetch(`${serverUrl}/api/chat/conversations/${conversationId}`, {
103
+ method: 'PATCH',
104
+ headers: { 'Content-Type': 'application/json' },
105
+ body: JSON.stringify({ title }),
106
+ }).catch(() => null);
107
+ }
108
+ catch { /* fire-and-forget */ }
109
+ }
110
+ // Banner
111
+ function printBanner(state) {
112
+ const H = '\u2500';
113
+ const DOT = '\u00b7';
114
+ const w = Math.min((process.stdout.columns || 80) - 4, 68);
115
+ const gold = chalk.hex('#F6C453');
116
+ const border = chalk.hex('#3C414B');
117
+ const dim = chalk.dim;
118
+ console.log('');
119
+ console.log(' ' + border(H.repeat(w)));
120
+ console.log(' ' + gold.bold('profClaw'));
121
+ const mode = state.agenticMode ? gold('agentic') : dim('chat');
122
+ const model = dim(state.model || 'default');
123
+ const sessionInfo = state.session ? dim(`resumed ${state.session.conversationId.slice(0, 8)}`) : dim('new session');
124
+ console.log(` ${mode} ${dim(DOT)} ${model} ${dim(DOT)} ${sessionInfo} ${dim(DOT)} ${dim('/help')}`);
125
+ console.log(' ' + border(H.repeat(w)));
126
+ console.log('');
127
+ }
128
+ // Slash Commands
129
+ function buildCommands() {
130
+ return [
131
+ {
132
+ name: 'help',
133
+ aliases: ['?', 'h'],
134
+ description: 'Show available commands',
135
+ handler: (_args, _state) => {
136
+ const d = chalk.dim;
137
+ const c = chalk.cyan;
138
+ console.log('');
139
+ console.log(d(' Chat'));
140
+ console.log(` ${c('/model')} <name> ${d('sonnet, opus, haiku, gpt-4o, llama3.2')}`);
141
+ console.log(` ${c('/agentic')} ${d('toggle agentic mode (tools + multi-step)')}`);
142
+ console.log(` ${c('/effort')} <level> ${d('low, medium, high')}`);
143
+ console.log(` ${c('/thinking')} ${d('toggle thinking display')}`);
144
+ console.log('');
145
+ console.log(d(' Automation'));
146
+ console.log(` ${c('/cron')} [list|add] ${d('manage scheduled jobs')}`);
147
+ console.log(` ${c('/feeds')} [list] ${d('manage RSS feeds')}`);
148
+ console.log('');
149
+ console.log(d(' Tools'));
150
+ console.log(` ${c('/run')} <cmd> ${d('run shell command (!cmd shortcut)')}`);
151
+ console.log(` ${c('/copy')} ${d('copy last response to clipboard')}`);
152
+ console.log(` ${c('/save')} <file> ${d('save last response to file')}`);
153
+ console.log(` ${c('/retry')} [model] ${d('retry last message (optionally with different model)')}`);
154
+ console.log(` ${c('/status')} ${d('server & provider status')}`);
155
+ console.log(` ${c('/providers')} ${d('list AI providers')}`);
156
+ console.log(` ${c('/tools')} ${d('list available tools')}`);
157
+ console.log(` ${c('/skills')} [list|install|search] ${d('manage skills')}`);
158
+ console.log(` ${c('/whoami')} ${d('show current config')}`);
159
+ console.log('');
160
+ console.log(d(' Session'));
161
+ console.log(` ${c('/sessions')} ${d('list recent conversations')}`);
162
+ console.log(` ${c('/resume')} <id> ${d('switch to a previous session')}`);
163
+ console.log(` ${c('/new')} ${d('start fresh session')}`);
164
+ console.log(` ${c('/session')} ${d('show current session info')}`);
165
+ console.log(` ${c('/compact')} ${d('compact history to save tokens')}`);
166
+ console.log(` ${c('/clear')} ${d('clear screen')}`);
167
+ console.log(` ${c('/exit')} ${d('quit')}`);
168
+ console.log('');
169
+ console.log(d(' Ctrl+C clear \u00b7 2x exit \u00b7 Ctrl+D quit'));
170
+ console.log(d(' Alt+Enter newline \u00b7 Ctrl+L clear \u00b7 Ctrl+P sessions \u00b7 Shift+Tab thinking'));
171
+ console.log('');
172
+ },
173
+ },
174
+ {
175
+ name: 'exit',
176
+ aliases: ['quit', 'q'],
177
+ description: 'Exit chat',
178
+ handler: () => {
179
+ // Will be replaced with exitGracefully at runtime
180
+ process.exit(0);
181
+ },
182
+ },
183
+ {
184
+ name: 'clear',
185
+ aliases: ['cls'],
186
+ description: 'Clear screen',
187
+ handler: () => {
188
+ console.clear();
189
+ },
190
+ },
191
+ {
192
+ name: 'new',
193
+ aliases: ['reset'],
194
+ description: 'Start new session',
195
+ handler: async (_args, state) => {
196
+ state.session = null;
197
+ state.sessionTokens = 0;
198
+ state.sessionCost = 0;
199
+ state.messageCount = 0;
200
+ // Clear saved session so next launch also starts fresh
201
+ try {
202
+ const { unlinkSync } = require('fs');
203
+ unlinkSync(join(homedir(), '.profclaw', 'last_session'));
204
+ }
205
+ catch { /* ok */ }
206
+ console.log(chalk.dim(' New session. Next message starts fresh.'));
207
+ },
208
+ },
209
+ {
210
+ name: 'session',
211
+ aliases: ['sess'],
212
+ description: 'Show session info',
213
+ handler: (_args, state) => {
214
+ if (!state.session) {
215
+ console.log(chalk.dim(' No active session. Send a message to start one.'));
216
+ return;
217
+ }
218
+ const d = chalk.dim;
219
+ console.log('');
220
+ console.log(` ${d('id')} ${chalk.cyan(state.session.conversationId.slice(0, 12))}`);
221
+ console.log(` ${d('mode')} ${state.agenticMode ? chalk.hex('#F6C453')('agentic') : 'chat'}`);
222
+ console.log(` ${d('model')} ${state.model || 'default'}`);
223
+ console.log(` ${d('msgs')} ${state.messageCount}`);
224
+ console.log(` ${d('tokens')} ${formatTokens(state.sessionTokens)}`);
225
+ if (state.sessionCost > 0)
226
+ console.log(` ${d('cost')} $${state.sessionCost.toFixed(4)}`);
227
+ console.log('');
228
+ },
229
+ },
230
+ {
231
+ name: 'sessions',
232
+ aliases: ['ls'],
233
+ description: 'Pick a session to resume',
234
+ handler: async (_args, state) => {
235
+ const sessions = await fetchSessions(state.serverUrl, 20);
236
+ if (sessions.length === 0) {
237
+ console.log(chalk.dim(' No sessions found.'));
238
+ return;
239
+ }
240
+ const selected = await showPicker({
241
+ title: 'Sessions',
242
+ filterable: true,
243
+ pageSize: 8,
244
+ items: sessions.map((s) => ({
245
+ id: s.id,
246
+ label: s.title || '(untitled)',
247
+ description: formatRelativeTime(s.createdAt),
248
+ dimLabel: !s.title,
249
+ })),
250
+ });
251
+ if (!selected)
252
+ return; // Cancelled
253
+ const match = sessions.find((s) => s.id === selected);
254
+ if (!match)
255
+ return;
256
+ state.session = {
257
+ conversationId: match.id,
258
+ mode: state.agenticMode ? 'agentic' : 'chat',
259
+ title: match.title,
260
+ totalTokens: 0,
261
+ totalCost: 0,
262
+ messageCount: 0,
263
+ createdAt: new Date(match.createdAt),
264
+ };
265
+ state.sessionTokens = 0;
266
+ state.sessionCost = 0;
267
+ state.messageCount = 0;
268
+ saveLastSession(match.id);
269
+ console.log(chalk.dim(` Resumed: ${match.title || match.id.slice(0, 8)}`));
270
+ },
271
+ },
272
+ {
273
+ name: 'resume',
274
+ aliases: ['switch'],
275
+ description: 'Resume a session (or pick)',
276
+ args: '[session-id]',
277
+ handler: async (args, state) => {
278
+ if (!args[0]) {
279
+ // No arg - show session picker
280
+ const sessions = await fetchSessions(state.serverUrl, 20);
281
+ if (sessions.length === 0) {
282
+ console.log(chalk.dim(' No sessions.'));
283
+ return;
284
+ }
285
+ const selected = await showPicker({
286
+ title: 'Resume Session',
287
+ filterable: true,
288
+ pageSize: 8,
289
+ items: sessions.map((s) => ({
290
+ id: s.id,
291
+ label: s.title || '(untitled)',
292
+ description: formatRelativeTime(s.createdAt),
293
+ dimLabel: !s.title,
294
+ })),
295
+ });
296
+ if (!selected)
297
+ return;
298
+ args = [selected];
299
+ }
300
+ const prefix = args[0];
301
+ const sessions = await fetchSessions(state.serverUrl, 50);
302
+ const match = sessions.find((s) => s.id.startsWith(prefix));
303
+ if (!match) {
304
+ console.log(chalk.dim(` No session matching "${prefix}".`));
305
+ return;
306
+ }
307
+ state.session = {
308
+ conversationId: match.id,
309
+ mode: state.agenticMode ? 'agentic' : 'chat',
310
+ title: match.title,
311
+ totalTokens: 0,
312
+ totalCost: 0,
313
+ messageCount: 0,
314
+ createdAt: new Date(match.createdAt),
315
+ };
316
+ state.sessionTokens = 0;
317
+ state.sessionCost = 0;
318
+ state.messageCount = 0;
319
+ saveLastSession(match.id);
320
+ console.log(chalk.dim(` Resumed: ${match.title || match.id.slice(0, 8)}`));
321
+ },
322
+ },
323
+ {
324
+ name: 'compact',
325
+ aliases: [],
326
+ description: 'Compact conversation history to save tokens',
327
+ handler: async (_args, state) => {
328
+ if (!state.session) {
329
+ console.log(chalk.dim(' No active session.'));
330
+ return;
331
+ }
332
+ try {
333
+ const response = await fetch(`${state.serverUrl}/api/chat/conversations/${state.session.conversationId}/compact`, {
334
+ method: 'POST',
335
+ headers: { 'Content-Type': 'application/json' },
336
+ }).catch(() => null);
337
+ if (response?.ok) {
338
+ console.log(chalk.dim(' History compacted. Older messages summarized to save tokens.'));
339
+ }
340
+ else {
341
+ console.log(chalk.dim(' Compact not available for this conversation.'));
342
+ }
343
+ }
344
+ catch {
345
+ console.log(chalk.dim(' Failed to compact.'));
346
+ }
347
+ },
348
+ },
349
+ {
350
+ name: 'model',
351
+ aliases: ['m'],
352
+ description: 'Pick or set model',
353
+ args: '[name]',
354
+ handler: async (args, state) => {
355
+ if (args.length > 0) {
356
+ state.model = args[0];
357
+ console.log(chalk.green(` Model: ${state.model}`));
358
+ return;
359
+ }
360
+ const selected = await showPicker({
361
+ title: `Model (current: ${state.model || 'default'})`,
362
+ items: [
363
+ { id: 'gpt4o', label: 'GPT-4o', description: 'Azure' },
364
+ { id: 'gpt4o-mini', label: 'GPT-4o Mini', description: 'Azure fast' },
365
+ { id: 'gpt-4.1', label: 'GPT-4.1', description: 'Azure latest' },
366
+ { id: 'sonnet', label: 'Claude Sonnet', description: 'Anthropic' },
367
+ { id: 'opus', label: 'Claude Opus', description: 'Anthropic' },
368
+ { id: 'haiku', label: 'Claude Haiku', description: 'Anthropic fast' },
369
+ { id: 'gemini-2.0-flash', label: 'Gemini Flash', description: 'Google' },
370
+ { id: 'llama3.2', label: 'Llama 3.2', description: 'Ollama local' },
371
+ { id: 'llama-3.3-70b', label: 'Llama 3.3 70B', description: 'Cerebras' },
372
+ ],
373
+ filterable: true,
374
+ pageSize: 9,
375
+ });
376
+ if (selected) {
377
+ state.model = selected;
378
+ console.log(chalk.green(` Model: ${state.model}`));
379
+ }
380
+ },
381
+ },
382
+ {
383
+ name: 'agentic',
384
+ aliases: ['agent', 'a'],
385
+ description: 'Toggle agentic mode',
386
+ handler: (_args, state) => {
387
+ state.agenticMode = !state.agenticMode;
388
+ console.log(chalk.green(` Agentic mode: ${state.agenticMode ? chalk.yellow('enabled') : 'disabled'}`));
389
+ if (state.agenticMode) {
390
+ console.log(chalk.dim(' Agent has access to all tools (web search, file ops, code execution, etc.)'));
391
+ }
392
+ },
393
+ },
394
+ {
395
+ name: 'tools',
396
+ aliases: ['t'],
397
+ description: 'Toggle tool display',
398
+ handler: (_args, state) => {
399
+ state.showTools = !state.showTools;
400
+ console.log(chalk.green(` Tool display: ${state.showTools ? 'shown' : 'hidden'}`));
401
+ },
402
+ },
403
+ {
404
+ name: 'thinking',
405
+ aliases: ['think'],
406
+ description: 'Toggle thinking display',
407
+ handler: (_args, state) => {
408
+ state.showThinking = !state.showThinking;
409
+ console.log(chalk.green(` Thinking: ${state.showThinking ? 'shown' : 'hidden'}`));
410
+ },
411
+ },
412
+ {
413
+ name: 'usage',
414
+ aliases: ['u'],
415
+ description: 'Toggle usage display',
416
+ handler: (_args, state) => {
417
+ state.showUsage = !state.showUsage;
418
+ console.log(chalk.green(` Usage display: ${state.showUsage ? 'shown' : 'hidden'}`));
419
+ },
420
+ },
421
+ {
422
+ name: 'effort',
423
+ aliases: ['e'],
424
+ description: 'Set thinking effort level',
425
+ args: '<low|medium|high>',
426
+ handler: (args, state) => {
427
+ const level = args[0];
428
+ if (!level || !['low', 'medium', 'high'].includes(level)) {
429
+ console.log(chalk.dim(` Current effort: ${state.effort}`));
430
+ console.log(chalk.dim(' Options: low (fast), medium (balanced), high (thorough)'));
431
+ return;
432
+ }
433
+ state.effort = level;
434
+ console.log(chalk.green(` Effort set to: ${state.effort}`));
435
+ },
436
+ },
437
+ {
438
+ name: 'status',
439
+ aliases: ['st'],
440
+ description: 'Server and provider status',
441
+ handler: async (_args, state) => {
442
+ try {
443
+ const [health, providers] = await Promise.all([
444
+ fetch(`${state.serverUrl}/health`).then((r) => r.json()).catch(() => null),
445
+ fetch(`${state.serverUrl}/api/chat/providers`).then((r) => r.json()).catch(() => null),
446
+ ]);
447
+ const d = chalk.dim;
448
+ console.log('');
449
+ if (health) {
450
+ console.log(` ${d('server')} ${chalk.hex('#7DD3A5')('online')} ${d(`v${health.version}`)} ${d(`(${health.mode})`)}`);
451
+ }
452
+ else {
453
+ console.log(` ${d('server')} ${chalk.hex('#F97066')('offline')}`);
454
+ }
455
+ if (providers) {
456
+ const provList = providers.providers;
457
+ const defaultProv = providers.default;
458
+ for (const p of provList || []) {
459
+ const icon = p.healthy ? chalk.hex('#7DD3A5')('\u2713') : chalk.hex('#F97066')('\u2717');
460
+ const isDefault = p.type === defaultProv ? chalk.hex('#F6C453')(' *') : '';
461
+ console.log(` ${d('provider')} ${icon} ${p.type}${isDefault}`);
462
+ }
463
+ }
464
+ console.log('');
465
+ }
466
+ catch {
467
+ console.log(chalk.dim(' Cannot reach server.'));
468
+ }
469
+ },
470
+ },
471
+ {
472
+ name: 'run',
473
+ aliases: ['exec', '!'],
474
+ description: 'Run a shell command',
475
+ args: '<command>',
476
+ handler: async (args, _state) => {
477
+ if (args.length === 0) {
478
+ console.log(chalk.dim(' Usage: /run <command> or !<command>'));
479
+ return;
480
+ }
481
+ const cmd = args.join(' ');
482
+ console.log(chalk.dim(` $ ${cmd}`));
483
+ try {
484
+ const { execSync } = await import('node:child_process');
485
+ const output = execSync(cmd, { encoding: 'utf-8', timeout: 30000, maxBuffer: 100000 });
486
+ if (output.trim())
487
+ console.log(output.trimEnd());
488
+ }
489
+ catch (err) {
490
+ const e = err;
491
+ console.log(chalk.hex('#F97066')(` ${e.stderr?.trim() || e.message || 'Command failed'}`));
492
+ }
493
+ },
494
+ },
495
+ {
496
+ name: 'copy',
497
+ aliases: ['cp'],
498
+ description: 'Copy last response to clipboard',
499
+ handler: async (_args, _state) => {
500
+ // lastResponse is captured from the stream buffer
501
+ try {
502
+ const { execSync } = await import('node:child_process');
503
+ const text = lastAssistantResponse;
504
+ if (!text) {
505
+ console.log(chalk.dim(' No response to copy.'));
506
+ return;
507
+ }
508
+ execSync('pbcopy', { input: text, encoding: 'utf-8' });
509
+ console.log(chalk.dim(' Copied to clipboard.'));
510
+ }
511
+ catch {
512
+ console.log(chalk.dim(' Clipboard not available (pbcopy not found).'));
513
+ }
514
+ },
515
+ },
516
+ {
517
+ name: 'save',
518
+ aliases: [],
519
+ description: 'Save last response to a file',
520
+ args: '<filename>',
521
+ handler: async (args, _state) => {
522
+ if (!args[0]) {
523
+ console.log(chalk.dim(' Usage: /save <filename>'));
524
+ return;
525
+ }
526
+ if (!lastAssistantResponse) {
527
+ console.log(chalk.dim(' No response to save.'));
528
+ return;
529
+ }
530
+ try {
531
+ writeFileSync(args[0], lastAssistantResponse, 'utf-8');
532
+ console.log(chalk.dim(` Saved to ${args[0]}`));
533
+ }
534
+ catch (err) {
535
+ console.log(chalk.dim(` Failed: ${err instanceof Error ? err.message : 'unknown'}`));
536
+ }
537
+ },
538
+ },
539
+ {
540
+ name: 'retry',
541
+ aliases: ['r'],
542
+ description: 'Retry last message (optionally with different model)',
543
+ args: '[model]',
544
+ handler: async (args, state) => {
545
+ if (!lastUserMessage) {
546
+ console.log(chalk.dim(' No message to retry.'));
547
+ return;
548
+ }
549
+ // Optionally switch model for comparison
550
+ const prevModel = state.model;
551
+ if (args[0]) {
552
+ state.model = args[0];
553
+ console.log(chalk.dim(` Retrying with ${state.model} (was: ${prevModel || 'default'})`));
554
+ if (state.lastResponseCache) {
555
+ console.log(chalk.dim(` Previous (${state.lastResponseCache.model}): ${state.lastResponseCache.content.slice(0, 80)}${state.lastResponseCache.content.length > 80 ? '...' : ''}`));
556
+ }
557
+ }
558
+ else {
559
+ console.log(chalk.dim(` Retrying: ${lastUserMessage.slice(0, 50)}...`));
560
+ }
561
+ pendingRetry = lastUserMessage;
562
+ },
563
+ },
564
+ {
565
+ name: 'skills',
566
+ aliases: [],
567
+ description: 'Manage skills',
568
+ args: '[list|install <source>|search <query>]',
569
+ handler: async (args, state) => {
570
+ const sub = args[0] || 'list';
571
+ if (sub === 'list') {
572
+ try {
573
+ const response = await fetch(`${state.serverUrl}/api/skills`).catch(() => null);
574
+ if (!response?.ok) {
575
+ console.log(chalk.dim(' Cannot fetch skills.'));
576
+ return;
577
+ }
578
+ const data = await response.json();
579
+ const skills = data.skills || [];
580
+ const d = chalk.dim;
581
+ console.log('');
582
+ console.log(` ${data.stats?.total || 0} skills loaded, ${data.stats?.eligible || 0} eligible`);
583
+ console.log('');
584
+ const bySource = new Map();
585
+ for (const s of skills) {
586
+ const src = s.source || 'bundled';
587
+ if (!bySource.has(src))
588
+ bySource.set(src, []);
589
+ bySource.get(src).push(s.name);
590
+ }
591
+ for (const [src, names] of bySource) {
592
+ console.log(d(` ${src}: ${names.slice(0, 8).join(', ')}${names.length > 8 ? ` +${names.length - 8} more` : ''}`));
593
+ }
594
+ console.log('');
595
+ }
596
+ catch {
597
+ console.log(chalk.dim(' Cannot fetch skills.'));
598
+ }
599
+ return;
600
+ }
601
+ if (sub === 'install') {
602
+ const source = args.slice(1).join(' ');
603
+ if (!source) {
604
+ console.log(chalk.dim(' Usage: /skills install <github-user/repo> or <url>'));
605
+ console.log(chalk.dim(' Example: /skills install blader/humanizer'));
606
+ return;
607
+ }
608
+ console.log(chalk.dim(` Installing skill from ${source}...`));
609
+ try {
610
+ const response = await fetch(`${state.serverUrl}/api/skills/marketplace/install`, {
611
+ method: 'POST',
612
+ headers: { 'Content-Type': 'application/json' },
613
+ body: JSON.stringify({ source }),
614
+ }).catch(() => null);
615
+ if (!response?.ok) {
616
+ console.log(chalk.hex('#F97066')(' Install failed. Check the source URL.'));
617
+ return;
618
+ }
619
+ const data = await response.json();
620
+ console.log(chalk.hex('#7DD3A5')(` Installed: ${data.data?.skillName || source} v${data.data?.version || '?'}`));
621
+ }
622
+ catch {
623
+ console.log(chalk.hex('#F97066')(' Install failed.'));
624
+ }
625
+ return;
626
+ }
627
+ if (sub === 'search') {
628
+ const query = args.slice(1).join(' ');
629
+ if (!query) {
630
+ console.log(chalk.dim(' Usage: /skills search <query>'));
631
+ return;
632
+ }
633
+ console.log(chalk.dim(` Searching ClawHub for "${query}"...`));
634
+ try {
635
+ const response = await fetch(`${state.serverUrl}/api/skills/marketplace/search?q=${encodeURIComponent(query)}`).catch(() => null);
636
+ if (!response?.ok) {
637
+ console.log(chalk.dim(' No results or registry unavailable.'));
638
+ return;
639
+ }
640
+ const data = await response.json();
641
+ const results = data.data || [];
642
+ if (results.length === 0) {
643
+ console.log(chalk.dim(' No skills found.'));
644
+ return;
645
+ }
646
+ console.log('');
647
+ for (const r of results.slice(0, 10)) {
648
+ console.log(` ${chalk.cyan(r.name)} ${chalk.dim(r.description || '')} ${chalk.dim(`(${r.installs || 0} installs)`)}`);
649
+ }
650
+ console.log('');
651
+ console.log(chalk.dim(' Install: /skills install <name>'));
652
+ }
653
+ catch {
654
+ console.log(chalk.dim(' Search failed.'));
655
+ }
656
+ return;
657
+ }
658
+ console.log(chalk.dim(' Usage: /skills [list|install <source>|search <query>]'));
659
+ },
660
+ },
661
+ {
662
+ name: 'tools',
663
+ aliases: [],
664
+ description: 'List available tools',
665
+ handler: async (_args, state) => {
666
+ try {
667
+ const response = await fetch(`${state.serverUrl}/api/tools`).catch(() => null);
668
+ if (!response?.ok) {
669
+ console.log(chalk.dim(' Cannot fetch tools.'));
670
+ return;
671
+ }
672
+ const data = await response.json();
673
+ const tools = data.tools || [];
674
+ const byCategory = new Map();
675
+ for (const t of tools) {
676
+ const cat = t.category || 'other';
677
+ if (!byCategory.has(cat))
678
+ byCategory.set(cat, []);
679
+ byCategory.get(cat).push(t.name);
680
+ }
681
+ console.log('');
682
+ for (const [cat, names] of byCategory) {
683
+ console.log(chalk.dim(` ${cat}: ${names.join(', ')}`));
684
+ }
685
+ console.log(chalk.dim(`\n ${tools.length} tools total`));
686
+ console.log('');
687
+ }
688
+ catch {
689
+ console.log(chalk.dim(' Cannot fetch tools.'));
690
+ }
691
+ },
692
+ },
693
+ {
694
+ name: 'providers',
695
+ aliases: ['p'],
696
+ description: 'List AI providers',
697
+ handler: async (_args, state) => {
698
+ try {
699
+ const response = await fetch(`${state.serverUrl}/api/chat/providers`).catch(() => null);
700
+ if (!response?.ok) {
701
+ console.log(chalk.dim(' Cannot fetch providers.'));
702
+ return;
703
+ }
704
+ const data = await response.json();
705
+ console.log('');
706
+ for (const p of data.providers) {
707
+ const icon = p.healthy ? chalk.hex('#7DD3A5')('\u2713') : chalk.hex('#F97066')('\u2717');
708
+ const def = p.type === data.default ? chalk.hex('#F6C453')(' (default)') : '';
709
+ console.log(` ${icon} ${p.type}${def}`);
710
+ }
711
+ console.log('');
712
+ }
713
+ catch {
714
+ console.log(chalk.dim(' Cannot fetch providers.'));
715
+ }
716
+ },
717
+ },
718
+ {
719
+ name: 'whoami',
720
+ aliases: [],
721
+ description: 'Show current config',
722
+ handler: (_args, state) => {
723
+ const d = chalk.dim;
724
+ console.log('');
725
+ console.log(` ${d('mode')} ${state.agenticMode ? chalk.hex('#F6C453')('agentic') : 'chat'}`);
726
+ console.log(` ${d('model')} ${state.model || 'default'}`);
727
+ console.log(` ${d('effort')} ${state.effort}`);
728
+ console.log(` ${d('thinking')} ${state.showThinking ? 'shown' : 'hidden'}`);
729
+ console.log(` ${d('tools')} ${state.showTools ? 'shown' : 'hidden'}`);
730
+ console.log(` ${d('usage')} ${state.showUsage ? 'shown' : 'hidden'}`);
731
+ console.log(` ${d('server')} ${state.serverUrl}`);
732
+ if (state.session) {
733
+ console.log(` ${d('session')} ${state.session.conversationId.slice(0, 12)}`);
734
+ console.log(` ${d('messages')} ${state.messageCount}`);
735
+ console.log(` ${d('tokens')} ${formatTokens(state.sessionTokens)}`);
736
+ }
737
+ console.log('');
738
+ },
739
+ },
740
+ {
741
+ name: 'plugins',
742
+ aliases: ['plugin'],
743
+ description: 'Manage plugins',
744
+ args: '[list|load <path>|unload <id>]',
745
+ handler: async (args, state) => {
746
+ const sub = args[0] || 'list';
747
+ if (sub === 'list') {
748
+ try {
749
+ const response = await fetch(`${state.serverUrl}/api/plugins`).catch(() => null);
750
+ if (!response?.ok) {
751
+ console.log(chalk.dim(' No plugins loaded. Plugins require pro mode.'));
752
+ return;
753
+ }
754
+ const data = await response.json();
755
+ const plugins = data.plugins || [];
756
+ if (plugins.length === 0) {
757
+ console.log(chalk.dim(' No plugins loaded.'));
758
+ console.log(chalk.dim(' Install: ~/.profclaw/plugins/<name>/index.js'));
759
+ return;
760
+ }
761
+ console.log('');
762
+ for (const p of plugins) {
763
+ console.log(` ${chalk.cyan(p.name)} v${p.version} ${chalk.dim(`(${p.tools} tools)`)}`);
764
+ }
765
+ console.log('');
766
+ }
767
+ catch {
768
+ console.log(chalk.dim(' Cannot fetch plugins.'));
769
+ }
770
+ return;
771
+ }
772
+ console.log(chalk.dim(' Usage: /plugins [list|load <path>|unload <id>]'));
773
+ },
774
+ },
775
+ {
776
+ name: 'cron',
777
+ aliases: [],
778
+ description: 'List or manage scheduled jobs',
779
+ args: '[list|add|trigger <id>]',
780
+ handler: async (args, state) => {
781
+ const sub = args[0] || 'list';
782
+ if (sub === 'list') {
783
+ try {
784
+ const response = await fetch(`${state.serverUrl}/api/cron`, {
785
+ headers: { 'Accept': 'application/json' },
786
+ }).catch(() => null);
787
+ if (!response || !response.ok) {
788
+ console.log(chalk.dim(' Could not fetch cron jobs. Is the server running?'));
789
+ return;
790
+ }
791
+ const data = await response.json();
792
+ const jobs = data.jobs || [];
793
+ if (jobs.length === 0) {
794
+ console.log(chalk.dim(' No cron jobs configured.'));
795
+ console.log(chalk.dim(' Create one in chat: "Set up a daily AI news digest at 7am and send to my Telegram"'));
796
+ return;
797
+ }
798
+ console.log('');
799
+ console.log(chalk.bold(` ${jobs.length} Cron Jobs:`));
800
+ for (const job of jobs) {
801
+ const statusIcon = job.status === 'active' ? chalk.green('active') : chalk.yellow(job.status);
802
+ const schedule = job.cronExpression || 'event-driven';
803
+ console.log(` ${chalk.cyan(job.name)} (${job.jobType}) - ${schedule} [${statusIcon}]`);
804
+ }
805
+ console.log('');
806
+ }
807
+ catch {
808
+ console.log(chalk.dim(' Could not connect to server.'));
809
+ }
810
+ return;
811
+ }
812
+ if (sub === 'add') {
813
+ console.log(chalk.dim(' To create a cron job, describe what you want in the chat:'));
814
+ console.log(chalk.dim(' Example: "Every weekday at 7am, search for AI news and send a summary to my Telegram"'));
815
+ console.log(chalk.dim(' The agent will use the cron_create tool to set it up.'));
816
+ return;
817
+ }
818
+ console.log(chalk.dim(' Usage: /cron [list|add]'));
819
+ },
820
+ },
821
+ {
822
+ name: 'feeds',
823
+ aliases: ['feed'],
824
+ description: 'Manage RSS feeds',
825
+ args: '[list|add <url>|bundles|poll]',
826
+ handler: async (args, state) => {
827
+ const sub = args[0] || 'list';
828
+ if (sub === 'list') {
829
+ try {
830
+ const response = await fetch(`${state.serverUrl}/api/feeds`, {
831
+ headers: { 'Accept': 'application/json' },
832
+ }).catch(() => null);
833
+ if (!response || !response.ok) {
834
+ console.log(chalk.dim(' Could not fetch feeds. Is the server running?'));
835
+ return;
836
+ }
837
+ const data = await response.json();
838
+ const feeds = data.data || [];
839
+ if (feeds.length === 0) {
840
+ console.log(chalk.dim(' No feeds configured. Install a bundle:'));
841
+ console.log(chalk.dim(' /feeds bundles - List available bundles'));
842
+ console.log(chalk.dim(' Or in chat: "Add the AI news feed bundle"'));
843
+ return;
844
+ }
845
+ console.log('');
846
+ console.log(chalk.bold(` ${feeds.length} Feeds:`));
847
+ for (const feed of feeds) {
848
+ const status = feed.enabled ? chalk.green('on') : chalk.dim('off');
849
+ console.log(` ${chalk.cyan(feed.name)} (${feed.category}) - ${feed.articleCount} articles [${status}]`);
850
+ }
851
+ console.log('');
852
+ }
853
+ catch {
854
+ console.log(chalk.dim(' Could not connect to server.'));
855
+ }
856
+ return;
857
+ }
858
+ if (sub === 'bundles') {
859
+ console.log('');
860
+ console.log(chalk.bold(' Available Feed Bundles:'));
861
+ console.log(` ${chalk.cyan('ai-news')} - OpenAI, Anthropic, HuggingFace, TechCrunch AI, MIT Tech Review`);
862
+ console.log(` ${chalk.cyan('dev-news')} - Hacker News, Lobsters, Dev.to`);
863
+ console.log(` ${chalk.cyan('security')} - Krebs, The Hacker News, Schneier`);
864
+ console.log('');
865
+ console.log(chalk.dim(' Install in chat: "Install the ai-news feed bundle"'));
866
+ return;
867
+ }
868
+ if (sub === 'poll') {
869
+ console.log(chalk.dim(' In chat: "Poll all feeds for new articles"'));
870
+ return;
871
+ }
872
+ if (sub === 'add') {
873
+ const url = args[1];
874
+ if (!url) {
875
+ console.log(chalk.dim(' Usage: /feeds add <url>'));
876
+ console.log(chalk.dim(' Or in chat: "Add this RSS feed: https://example.com/feed.xml"'));
877
+ return;
878
+ }
879
+ console.log(chalk.dim(` In chat: "Add an RSS feed from ${url}"`));
880
+ return;
881
+ }
882
+ console.log(chalk.dim(' Usage: /feeds [list|add <url>|bundles|poll]'));
883
+ },
884
+ },
885
+ ];
886
+ }
887
+ // Main REPL
888
+ export async function startInteractiveREPL(options) {
889
+ const render = createRenderer();
890
+ const commands = buildCommands();
891
+ const history = loadHistory();
892
+ // Multiline input state
893
+ let multilineBuffer = [];
894
+ let isMultiline = false;
895
+ // Initialize state
896
+ const state = {
897
+ session: null,
898
+ model: options.model,
899
+ provider: options.provider,
900
+ agenticMode: options.agentic ?? true, // Always agentic by default - that's the point
901
+ showThinking: options.showThinking ?? false,
902
+ showTools: true,
903
+ showUsage: true,
904
+ effort: options.effort || 'medium',
905
+ isStreaming: false,
906
+ sessionTokens: 0,
907
+ sessionCost: 0,
908
+ messageCount: 0,
909
+ serverUrl: options.server.baseUrl,
910
+ };
911
+ // Resume session: explicit > last saved > new
912
+ const sessionToResume = options.sessionId || loadLastSession();
913
+ if (sessionToResume) {
914
+ state.session = {
915
+ conversationId: sessionToResume,
916
+ mode: state.agenticMode ? 'agentic' : 'chat',
917
+ totalTokens: 0,
918
+ totalCost: 0,
919
+ messageCount: 0,
920
+ createdAt: new Date(),
921
+ };
922
+ }
923
+ // Ensure server is running (auto-start if needed)
924
+ const serverCheck = await ensureServer(options.server.baseUrl, { autoStart: true });
925
+ if (!serverCheck.ok) {
926
+ console.log(chalk.red(' ' + (serverCheck.error || 'Cannot connect to server')));
927
+ console.log('');
928
+ console.log(chalk.dim(' To start manually:'));
929
+ console.log(chalk.dim(' pnpm dev # Development mode (with hot reload)'));
930
+ console.log(chalk.dim(' pnpm start # Production mode'));
931
+ console.log('');
932
+ process.exit(1);
933
+ }
934
+ // State variables for /copy, /save, /retry are at module level
935
+ // Dynamic prompt builder - shows session context inline
936
+ const updatePrompt = () => {
937
+ const parts = [];
938
+ if (state.model)
939
+ parts.push(state.model);
940
+ if (state.sessionTokens > 0)
941
+ parts.push(formatTokens(state.sessionTokens));
942
+ const promptStr = parts.length > 0
943
+ ? chalk.dim(`${parts.join(' \u00b7 ')} > `)
944
+ : chalk.dim('> ');
945
+ rl.setPrompt(promptStr);
946
+ };
947
+ printBanner(state);
948
+ // Load and show recent history for resumed sessions
949
+ if (state.session) {
950
+ try {
951
+ const response = await fetch(`${options.server.baseUrl}/api/chat/conversations/${state.session.conversationId}/messages?limit=6`).catch(() => null);
952
+ if (response?.ok) {
953
+ const data = await response.json();
954
+ const msgs = data.messages || [];
955
+ if (msgs.length > 0) {
956
+ console.log(chalk.dim(' Recent history:'));
957
+ for (const m of msgs.slice(-6)) {
958
+ const role = m.role === 'user' ? chalk.dim(' you: ') : chalk.dim(' ai: ');
959
+ const preview = m.content.slice(0, 60).replace(/\n/g, ' ');
960
+ console.log(`${role}${chalk.dim(preview)}${m.content.length > 60 ? chalk.dim('...') : ''}`);
961
+ }
962
+ console.log('');
963
+ }
964
+ }
965
+ }
966
+ catch { /* skip history load errors */ }
967
+ }
968
+ // Keep stdin flowing so readline doesn't auto-close after async handlers
969
+ if (process.stdin.isPaused()) {
970
+ process.stdin.resume();
971
+ }
972
+ // Build slash command completions with descriptions
973
+ const commandEntries = commands.map((c) => ({
974
+ cmd: `/${c.name}`,
975
+ desc: c.description,
976
+ args: c.args || '',
977
+ }));
978
+ // Tab completer - shows formatted list with descriptions
979
+ const completer = (line) => {
980
+ if (line.startsWith('/')) {
981
+ const prefix = line.toLowerCase();
982
+ const matches = commandEntries.filter((e) => e.cmd.startsWith(prefix));
983
+ if (matches.length === 0) {
984
+ return [commandEntries.map((e) => e.cmd), line];
985
+ }
986
+ if (matches.length === 1) {
987
+ // Exact single match - auto-complete it
988
+ const suffix = matches[0].args ? ' ' : '';
989
+ return [[matches[0].cmd + suffix], line];
990
+ }
991
+ // Multiple matches - show with descriptions
992
+ // Readline displays these as the "options" list
993
+ const display = matches.map((e) => {
994
+ const padded = e.cmd.padEnd(16);
995
+ return `${padded}${e.desc}`;
996
+ });
997
+ // Return just the command names for actual completion
998
+ return [display, line];
999
+ }
1000
+ return [[], line];
1001
+ };
1002
+ // Create readline interface
1003
+ const rl = readline.createInterface({
1004
+ input: process.stdin,
1005
+ output: process.stdout,
1006
+ prompt: chalk.dim('> '),
1007
+ history,
1008
+ historySize: MAX_HISTORY,
1009
+ removeHistoryDuplicates: true,
1010
+ terminal: true,
1011
+ completer,
1012
+ });
1013
+ // Keybindings via keypress events
1014
+ if (process.stdin.isTTY) {
1015
+ let pasteBuffer = '';
1016
+ let pasteTimer = null;
1017
+ process.stdin.on('keypress', (_ch, key) => {
1018
+ if (!key)
1019
+ return;
1020
+ // Alt+Enter: multiline input
1021
+ if (key.name === 'return' && key.meta) {
1022
+ multilineBuffer.push(rl.line);
1023
+ rl.line = '';
1024
+ rl.cursor = 0;
1025
+ process.stdout.write('\n' + chalk.dim(' ... '));
1026
+ isMultiline = true;
1027
+ return;
1028
+ }
1029
+ // Ctrl+L: clear screen (keep prompt)
1030
+ if (key.ctrl && key.name === 'l') {
1031
+ console.clear();
1032
+ printBanner(state);
1033
+ rl.prompt(true);
1034
+ return;
1035
+ }
1036
+ // Ctrl+P: session picker
1037
+ if (key.ctrl && key.name === 'p') {
1038
+ if (!state.isStreaming) {
1039
+ const sessCmd = commands.find((c) => c.name === 'sessions');
1040
+ if (sessCmd) {
1041
+ const result = sessCmd.handler([], state);
1042
+ if (result instanceof Promise)
1043
+ result.then(() => { updatePrompt(); rl.prompt(); });
1044
+ }
1045
+ }
1046
+ return;
1047
+ }
1048
+ // Shift+Tab: toggle thinking display
1049
+ if (key.shift && key.name === 'tab') {
1050
+ state.showThinking = !state.showThinking;
1051
+ process.stdout.write(`\r\x1b[K${chalk.dim(` Thinking: ${state.showThinking ? 'shown' : 'hidden'}`)}\n`);
1052
+ rl.prompt(true);
1053
+ return;
1054
+ }
1055
+ // Paste detection: rapid character input (burst coalescer)
1056
+ if (!key.ctrl && !key.meta && _ch && _ch.length === 1 && _ch >= ' ') {
1057
+ pasteBuffer += _ch;
1058
+ if (pasteTimer)
1059
+ clearTimeout(pasteTimer);
1060
+ pasteTimer = setTimeout(() => {
1061
+ // If we got 5+ chars in a burst, it was a paste
1062
+ if (pasteBuffer.length >= 5 && pasteBuffer.includes('\n')) {
1063
+ // Convert pasted newlines to multiline mode
1064
+ const lines = pasteBuffer.split('\n');
1065
+ if (lines.length > 1) {
1066
+ // First line is already in readline, add rest to multiline buffer
1067
+ for (let i = 0; i < lines.length - 1; i++) {
1068
+ multilineBuffer.push(lines[i]);
1069
+ }
1070
+ isMultiline = true;
1071
+ process.stdout.write(`\n${chalk.dim(` ... (${lines.length} lines pasted)`)}\n`);
1072
+ const lastLine = lines[lines.length - 1];
1073
+ rl.line = lastLine;
1074
+ rl.cursor = lastLine.length;
1075
+ rl.prompt(true);
1076
+ }
1077
+ }
1078
+ pasteBuffer = '';
1079
+ }, 10); // 10ms window for paste detection
1080
+ }
1081
+ });
1082
+ }
1083
+ // Track Ctrl+C for double-tap exit
1084
+ let lastCtrlC = 0;
1085
+ rl.on('SIGINT', () => {
1086
+ const now = Date.now();
1087
+ if (state.isStreaming) {
1088
+ // Cancel stream, spinner, and fetch
1089
+ if (activeAbort) {
1090
+ activeAbort.abort();
1091
+ activeAbort = null;
1092
+ }
1093
+ if (activeSpinnerInterval) {
1094
+ clearInterval(activeSpinnerInterval);
1095
+ activeSpinnerInterval = null;
1096
+ }
1097
+ process.stdout.write('\r\x1b[K'); // Clear spinner line
1098
+ state.isStreaming = false;
1099
+ console.log(chalk.dim(' Cancelled.'));
1100
+ rl.prompt();
1101
+ return;
1102
+ }
1103
+ // Clear multiline state
1104
+ if (isMultiline) {
1105
+ multilineBuffer = [];
1106
+ isMultiline = false;
1107
+ }
1108
+ if (now - lastCtrlC < 1500) {
1109
+ exitGracefully();
1110
+ }
1111
+ lastCtrlC = now;
1112
+ console.log(chalk.dim('\n Ctrl+C again to exit'));
1113
+ rl.prompt();
1114
+ });
1115
+ // Handle Ctrl+D / stream close
1116
+ // Only exit if the user explicitly closed (not if readline auto-closes after async)
1117
+ let userRequestedExit = false;
1118
+ rl.on('close', () => {
1119
+ if (!userRequestedExit) {
1120
+ // Readline closed unexpectedly (e.g., after async handler) - reopen
1121
+ // This happens in Node.js when async handlers are used with readline
1122
+ return;
1123
+ }
1124
+ saveHistory(history);
1125
+ console.log('');
1126
+ process.exit(0);
1127
+ });
1128
+ // Graceful exit helper
1129
+ const exitGracefully = () => {
1130
+ userRequestedExit = true;
1131
+ saveHistory(history);
1132
+ console.log('');
1133
+ rl.close();
1134
+ process.exit(0);
1135
+ };
1136
+ // Override exit command to use graceful exit
1137
+ const exitCmd = commands.find((c) => c.name === 'exit');
1138
+ if (exitCmd) {
1139
+ exitCmd.handler = () => exitGracefully();
1140
+ }
1141
+ // Handle input
1142
+ rl.on('line', async (line) => {
1143
+ let input;
1144
+ if (isMultiline) {
1145
+ multilineBuffer.push(line);
1146
+ input = multilineBuffer.join('\n').trim();
1147
+ multilineBuffer = [];
1148
+ isMultiline = false;
1149
+ }
1150
+ else {
1151
+ input = line.trim();
1152
+ }
1153
+ if (!input) {
1154
+ rl.prompt();
1155
+ return;
1156
+ }
1157
+ // Save to history
1158
+ history.push(input);
1159
+ // Handle slash commands
1160
+ if (input.startsWith('/')) {
1161
+ const [cmdName, ...args] = input.slice(1).split(/\s+/);
1162
+ const cmd = commands.find((c) => c.name === cmdName.toLowerCase() || c.aliases.includes(cmdName.toLowerCase()));
1163
+ if (cmd) {
1164
+ await cmd.handler(args, state);
1165
+ }
1166
+ else {
1167
+ render.error(`Unknown command: /${cmdName}. Type /help for commands.`);
1168
+ }
1169
+ updatePrompt();
1170
+ rl.prompt();
1171
+ return;
1172
+ }
1173
+ // Handle !command shortcut for shell
1174
+ if (input.startsWith('!')) {
1175
+ const shellCmd = input.slice(1).trim();
1176
+ if (shellCmd) {
1177
+ const runCmd = commands.find((c) => c.name === 'run');
1178
+ if (runCmd)
1179
+ await runCmd.handler(shellCmd.split(/\s+/), state);
1180
+ }
1181
+ rl.prompt();
1182
+ return;
1183
+ }
1184
+ // Send message
1185
+ lastUserMessage = input;
1186
+ const messageStartTime = Date.now();
1187
+ const response = await handleMessage(input, state, options, render, rl, history);
1188
+ if (response) {
1189
+ lastAssistantResponse = response;
1190
+ // Cache for /retry comparison
1191
+ state.lastResponseCache = {
1192
+ content: response,
1193
+ model: state.model || 'default',
1194
+ durationMs: Date.now() - messageStartTime,
1195
+ };
1196
+ }
1197
+ // Auto-generate session title after first message
1198
+ if (state.messageCount === 1 && state.session && !state.session.title) {
1199
+ autoTitleSession(state.serverUrl, state.session.conversationId, input).catch(() => { });
1200
+ }
1201
+ // Handle pending retry
1202
+ if (pendingRetry) {
1203
+ const retryMsg = pendingRetry;
1204
+ pendingRetry = '';
1205
+ lastUserMessage = retryMsg;
1206
+ const retryResponse = await handleMessage(retryMsg, state, options, render, rl, history);
1207
+ if (retryResponse)
1208
+ lastAssistantResponse = retryResponse;
1209
+ }
1210
+ updatePrompt();
1211
+ rl.prompt();
1212
+ });
1213
+ // Start
1214
+ updatePrompt();
1215
+ rl.prompt();
1216
+ }
1217
+ async function handleMessage(content, state, options, render, _rl, chatHistory) {
1218
+ let capturedResponse = '';
1219
+ // Ensure we have a session
1220
+ if (!state.session) {
1221
+ const result = await createConversation(options.server, {
1222
+ mode: 'agentic',
1223
+ presetId: 'agentic',
1224
+ });
1225
+ if ('error' in result) {
1226
+ render.error(result.error);
1227
+ return '';
1228
+ }
1229
+ state.session = {
1230
+ conversationId: result.conversationId,
1231
+ mode: state.agenticMode ? 'agentic' : 'chat',
1232
+ totalTokens: 0,
1233
+ totalCost: 0,
1234
+ messageCount: 0,
1235
+ createdAt: new Date(),
1236
+ };
1237
+ // Persist for auto-resume next time
1238
+ saveLastSession(result.conversationId);
1239
+ }
1240
+ // Display user message
1241
+ render.userMessage(content);
1242
+ state.messageCount++;
1243
+ // Start streaming
1244
+ const startTime = Date.now();
1245
+ state.isStreaming = true;
1246
+ state.streamStartTime = startTime;
1247
+ // Simple inline spinner (no ora - it fights readline in TTY)
1248
+ const frames = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f'];
1249
+ let frameIdx = 0;
1250
+ let spinnerStopped = false;
1251
+ const spinnerInterval = setInterval(() => {
1252
+ if (!spinnerStopped) {
1253
+ const elapsed = formatElapsed(Date.now() - startTime);
1254
+ const frame = frames[frameIdx % frames.length];
1255
+ process.stdout.write(`\r${chalk.dim(` ${frame} ${elapsed}`)}\x1b[K`);
1256
+ frameIdx++;
1257
+ }
1258
+ }, 80);
1259
+ activeSpinnerInterval = spinnerInterval;
1260
+ const stopSpinner = () => {
1261
+ if (!spinnerStopped) {
1262
+ spinnerStopped = true;
1263
+ clearInterval(spinnerInterval);
1264
+ process.stdout.write('\r\x1b[K'); // Clear the spinner line
1265
+ }
1266
+ };
1267
+ let assistantStarted = false;
1268
+ let currentThinking = false;
1269
+ try {
1270
+ const abortController = new AbortController();
1271
+ activeAbort = abortController;
1272
+ for await (const event of streamChat(options.server, {
1273
+ conversationId: state.session.conversationId,
1274
+ content,
1275
+ model: state.model,
1276
+ provider: state.provider,
1277
+ agentic: state.agenticMode,
1278
+ showThinking: state.showThinking,
1279
+ effort: state.effort,
1280
+ signal: abortController.signal,
1281
+ })) {
1282
+ if (!state.isStreaming) {
1283
+ abortController.abort();
1284
+ break;
1285
+ }
1286
+ switch (event.type) {
1287
+ case 'content_delta': {
1288
+ if (!assistantStarted) {
1289
+ stopSpinner();
1290
+ render.assistantStart(state.model);
1291
+ assistantStarted = true;
1292
+ }
1293
+ if (currentThinking) {
1294
+ render.thinkingEnd();
1295
+ currentThinking = false;
1296
+ }
1297
+ const chunk = event.data.content;
1298
+ if (chunk) {
1299
+ render.assistantDelta(chunk);
1300
+ capturedResponse += chunk;
1301
+ }
1302
+ break;
1303
+ }
1304
+ case 'thinking_start':
1305
+ case 'thinking:start': {
1306
+ if (!assistantStarted) {
1307
+ stopSpinner();
1308
+ render.assistantStart(state.model);
1309
+ assistantStarted = true;
1310
+ }
1311
+ if (state.showThinking) {
1312
+ render.thinkingStart();
1313
+ currentThinking = true;
1314
+ }
1315
+ break;
1316
+ }
1317
+ case 'thinking_update':
1318
+ case 'thinking:update': {
1319
+ if (state.showThinking && currentThinking) {
1320
+ const chunk = event.data.content;
1321
+ if (chunk)
1322
+ render.thinkingDelta(chunk);
1323
+ }
1324
+ break;
1325
+ }
1326
+ case 'thinking_end': {
1327
+ if (currentThinking) {
1328
+ render.thinkingEnd();
1329
+ currentThinking = false;
1330
+ }
1331
+ break;
1332
+ }
1333
+ case 'tool_call':
1334
+ case 'tool:call': {
1335
+ const toolName = event.data.name || event.data.toolName || 'unknown';
1336
+ const toolArgs = event.data.arguments || event.data.args || {};
1337
+ // Always show tool activity (minimal line, not gated on showTools)
1338
+ stopSpinner();
1339
+ if (!assistantStarted) {
1340
+ render.assistantStart(state.model);
1341
+ assistantStarted = true;
1342
+ }
1343
+ if (state.showTools) {
1344
+ // Verbose: show full tool call with args
1345
+ render.toolCall(toolName, toolArgs);
1346
+ }
1347
+ else {
1348
+ // Minimal: just show what's happening
1349
+ const firstArg = Object.values(toolArgs)[0];
1350
+ const preview = firstArg ? String(firstArg).slice(0, 50) : '';
1351
+ console.log(chalk.dim(` ${chalk.cyan(toolName)} ${preview}`));
1352
+ }
1353
+ break;
1354
+ }
1355
+ case 'tool_result':
1356
+ case 'tool:result': {
1357
+ const resultName = event.data.name || event.data.toolName || 'unknown';
1358
+ const result = event.data.result || event.data.output;
1359
+ const success = event.data.success !== false;
1360
+ const durationMs = event.data.durationMs;
1361
+ if (state.showTools) {
1362
+ render.toolResult(resultName, result, success, durationMs);
1363
+ }
1364
+ else {
1365
+ // Minimal: just show ok/fail
1366
+ const icon = success ? chalk.hex('#7DD3A5')('\u2713') : chalk.hex('#F97066')('\u2717');
1367
+ const dur = durationMs ? chalk.dim(` ${formatElapsed(durationMs)}`) : '';
1368
+ console.log(chalk.dim(` ${icon}${dur}`));
1369
+ }
1370
+ break;
1371
+ }
1372
+ case 'step_start':
1373
+ case 'step:start': {
1374
+ // Show step progress
1375
+ const stepNum = event.data.step;
1376
+ if (stepNum && stepNum > 1) {
1377
+ // Show step indicator for multi-step work
1378
+ process.stdout.write(chalk.dim(` step ${stepNum}...\r`));
1379
+ }
1380
+ break;
1381
+ }
1382
+ case 'step_complete':
1383
+ case 'step:complete': {
1384
+ // Step done - spinner continues for next step
1385
+ break;
1386
+ }
1387
+ case 'thinking:end': {
1388
+ if (currentThinking) {
1389
+ render.thinkingEnd();
1390
+ currentThinking = false;
1391
+ }
1392
+ // In agentic mode, thinking:end.text contains the actual response
1393
+ const responseText = event.data.text;
1394
+ if (responseText) {
1395
+ stopSpinner();
1396
+ if (!assistantStarted) {
1397
+ render.assistantStart(state.model);
1398
+ assistantStarted = true;
1399
+ }
1400
+ // Typewriter effect: emit word-by-word for streaming feel
1401
+ const words = responseText.split(/(\s+)/);
1402
+ for (let wi = 0; wi < words.length; wi++) {
1403
+ render.assistantDelta(words[wi]);
1404
+ if (wi % 4 === 3 && wi < words.length - 1) {
1405
+ await new Promise((r) => setTimeout(r, 12));
1406
+ }
1407
+ }
1408
+ capturedResponse += responseText;
1409
+ }
1410
+ break;
1411
+ }
1412
+ case 'summary': {
1413
+ // Final summary - show if we haven't captured any response yet
1414
+ const summaryText = event.data.summary || event.data.content || event.data.text;
1415
+ if (summaryText && !capturedResponse) {
1416
+ stopSpinner();
1417
+ if (!assistantStarted) {
1418
+ render.assistantStart(state.model);
1419
+ assistantStarted = true;
1420
+ }
1421
+ render.assistantDelta(summaryText);
1422
+ capturedResponse += summaryText;
1423
+ }
1424
+ break;
1425
+ }
1426
+ case 'complete': {
1427
+ stopSpinner();
1428
+ if (assistantStarted) {
1429
+ render.assistantEnd();
1430
+ }
1431
+ // Display usage - data may contain usage directly or nested
1432
+ const rawUsage = event.data.usage;
1433
+ const usage = rawUsage || (event.data.totalTokens ? {
1434
+ promptTokens: event.data.inputTokens || 0,
1435
+ completionTokens: event.data.outputTokens || 0,
1436
+ totalTokens: event.data.totalTokens || 0,
1437
+ cost: event.data.cost,
1438
+ } : undefined);
1439
+ const durationMs = Date.now() - startTime;
1440
+ if (usage && state.showUsage) {
1441
+ render.usage(usage, durationMs);
1442
+ state.sessionTokens += usage.totalTokens || 0;
1443
+ state.sessionCost += usage.cost || 0;
1444
+ }
1445
+ else if (state.showUsage) {
1446
+ console.log(chalk.dim(` ${formatElapsed(durationMs)}`));
1447
+ }
1448
+ // Session footer - running totals
1449
+ if (state.showUsage && state.messageCount > 0) {
1450
+ render.sessionFooter({
1451
+ sessionTokens: state.sessionTokens,
1452
+ sessionCost: state.sessionCost,
1453
+ messageCount: state.messageCount,
1454
+ model: state.model,
1455
+ });
1456
+ }
1457
+ break;
1458
+ }
1459
+ case 'error': {
1460
+ stopSpinner();
1461
+ const errMsg = event.data.message || 'Unknown error';
1462
+ // If streaming failed, fall back to non-streaming
1463
+ if (!assistantStarted && errMsg.includes('No output')) {
1464
+ const { sendMessage } = await import('./stream-client.js');
1465
+ const fallbackResult = await sendMessage(options.server, state.session.conversationId, content, { model: state.model, tools: state.agenticMode });
1466
+ if ('error' in fallbackResult) {
1467
+ render.error(fallbackResult.error);
1468
+ }
1469
+ else {
1470
+ render.assistantStart(fallbackResult.model);
1471
+ render.assistantDelta(fallbackResult.content);
1472
+ render.assistantEnd();
1473
+ }
1474
+ break;
1475
+ }
1476
+ render.error(errMsg);
1477
+ break;
1478
+ }
1479
+ case 'session_start':
1480
+ case 'user_message':
1481
+ // Acknowledgment events - no display needed
1482
+ break;
1483
+ default:
1484
+ // Unknown event type - skip
1485
+ break;
1486
+ }
1487
+ }
1488
+ }
1489
+ catch (error) {
1490
+ stopSpinner();
1491
+ if (error instanceof Error && error.name === 'AbortError') {
1492
+ console.log(chalk.dim('\n Cancelled.'));
1493
+ }
1494
+ else {
1495
+ render.error(error instanceof Error ? error.message : 'Stream failed');
1496
+ }
1497
+ }
1498
+ finally {
1499
+ stopSpinner(); // Always stop spinner before clearing interval to avoid leaked frame writes
1500
+ clearInterval(spinnerInterval);
1501
+ activeSpinnerInterval = null;
1502
+ activeAbort = null;
1503
+ state.isStreaming = false;
1504
+ state.streamStartTime = undefined;
1505
+ // Ensure we end properly
1506
+ if (assistantStarted && !state.isStreaming) {
1507
+ // assistantEnd was called in 'complete' handler
1508
+ }
1509
+ // Save history periodically
1510
+ if (state.messageCount % 5 === 0) {
1511
+ saveHistory(chatHistory);
1512
+ }
1513
+ }
1514
+ return capturedResponse;
1515
+ }
1516
+ //# sourceMappingURL=repl.js.map