@calliopelabs/cli 0.8.20 → 2.0.2

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 (402) hide show
  1. package/dist/agents/agent-config-loader.d.ts +60 -0
  2. package/dist/agents/agent-config-loader.d.ts.map +1 -0
  3. package/dist/agents/agent-config-loader.js +402 -0
  4. package/dist/agents/agent-config-loader.js.map +1 -0
  5. package/dist/agents/agent-config-presets.d.ts +10 -0
  6. package/dist/agents/agent-config-presets.d.ts.map +1 -0
  7. package/dist/agents/agent-config-presets.js +940 -0
  8. package/dist/agents/agent-config-presets.js.map +1 -0
  9. package/dist/agents/agent-config-types.d.ts +145 -0
  10. package/dist/agents/agent-config-types.d.ts.map +1 -0
  11. package/dist/agents/agent-config-types.js +12 -0
  12. package/dist/agents/agent-config-types.js.map +1 -0
  13. package/dist/{agterm → agents}/agent-detection.d.ts +1 -1
  14. package/dist/{agterm → agents}/agent-detection.d.ts.map +1 -1
  15. package/dist/{agterm → agents}/agent-detection.js +21 -5
  16. package/dist/agents/agent-detection.js.map +1 -0
  17. package/dist/agents/aggregator.d.ts +19 -0
  18. package/dist/agents/aggregator.d.ts.map +1 -0
  19. package/dist/agents/aggregator.js +141 -0
  20. package/dist/agents/aggregator.js.map +1 -0
  21. package/dist/{agterm → agents}/cli-backend.d.ts +1 -1
  22. package/dist/{agterm → agents}/cli-backend.d.ts.map +1 -1
  23. package/dist/{agterm → agents}/cli-backend.js +90 -12
  24. package/dist/agents/cli-backend.js.map +1 -0
  25. package/dist/agents/council-types.d.ts +113 -0
  26. package/dist/agents/council-types.d.ts.map +1 -0
  27. package/dist/agents/council-types.js +81 -0
  28. package/dist/agents/council-types.js.map +1 -0
  29. package/dist/agents/council.d.ts +107 -0
  30. package/dist/agents/council.d.ts.map +1 -0
  31. package/dist/agents/council.js +586 -0
  32. package/dist/agents/council.js.map +1 -0
  33. package/dist/agents/decomposer.d.ts +33 -0
  34. package/dist/agents/decomposer.d.ts.map +1 -0
  35. package/dist/agents/decomposer.js +138 -0
  36. package/dist/agents/decomposer.js.map +1 -0
  37. package/dist/agents/dynamic-tools.d.ts +52 -0
  38. package/dist/agents/dynamic-tools.d.ts.map +1 -0
  39. package/dist/agents/dynamic-tools.js +395 -0
  40. package/dist/agents/dynamic-tools.js.map +1 -0
  41. package/dist/agents/index.d.ts +29 -0
  42. package/dist/agents/index.d.ts.map +1 -0
  43. package/dist/agents/index.js +29 -0
  44. package/dist/agents/index.js.map +1 -0
  45. package/dist/agents/installer.d.ts +39 -0
  46. package/dist/agents/installer.d.ts.map +1 -0
  47. package/dist/agents/installer.js +205 -0
  48. package/dist/agents/installer.js.map +1 -0
  49. package/dist/{agterm → agents}/orchestrator.d.ts +7 -2
  50. package/dist/agents/orchestrator.d.ts.map +1 -0
  51. package/dist/{agterm → agents}/orchestrator.js +22 -2
  52. package/dist/agents/orchestrator.js.map +1 -0
  53. package/dist/agents/sdk-backend.d.ts +63 -0
  54. package/dist/agents/sdk-backend.d.ts.map +1 -0
  55. package/dist/agents/sdk-backend.js +489 -0
  56. package/dist/agents/sdk-backend.js.map +1 -0
  57. package/dist/agents/swarm-types.d.ts +83 -0
  58. package/dist/agents/swarm-types.d.ts.map +1 -0
  59. package/dist/agents/swarm-types.js +20 -0
  60. package/dist/agents/swarm-types.js.map +1 -0
  61. package/dist/agents/swarm.d.ts +74 -0
  62. package/dist/agents/swarm.d.ts.map +1 -0
  63. package/dist/agents/swarm.js +307 -0
  64. package/dist/agents/swarm.js.map +1 -0
  65. package/dist/{agterm → agents}/tools.d.ts +7 -5
  66. package/dist/agents/tools.d.ts.map +1 -0
  67. package/dist/agents/tools.js +776 -0
  68. package/dist/agents/tools.js.map +1 -0
  69. package/dist/{agterm → agents}/types.d.ts +14 -2
  70. package/dist/agents/types.d.ts.map +1 -0
  71. package/dist/{agterm → agents}/types.js +2 -2
  72. package/dist/agents/types.js.map +1 -0
  73. package/dist/api-server.d.ts +26 -0
  74. package/dist/api-server.d.ts.map +1 -0
  75. package/dist/api-server.js +230 -0
  76. package/dist/api-server.js.map +1 -0
  77. package/dist/auto-checkpoint.d.ts +35 -0
  78. package/dist/auto-checkpoint.d.ts.map +1 -0
  79. package/dist/auto-checkpoint.js +143 -0
  80. package/dist/auto-checkpoint.js.map +1 -0
  81. package/dist/auto-compressor.d.ts +44 -0
  82. package/dist/auto-compressor.d.ts.map +1 -0
  83. package/dist/auto-compressor.js +145 -0
  84. package/dist/auto-compressor.js.map +1 -0
  85. package/dist/background-jobs.d.ts +45 -0
  86. package/dist/background-jobs.d.ts.map +1 -0
  87. package/dist/background-jobs.js +122 -0
  88. package/dist/background-jobs.js.map +1 -0
  89. package/dist/bin.d.ts +6 -2
  90. package/dist/bin.d.ts.map +1 -1
  91. package/dist/bin.js +127 -24
  92. package/dist/bin.js.map +1 -1
  93. package/dist/checkpoint.d.ts +49 -0
  94. package/dist/checkpoint.d.ts.map +1 -0
  95. package/dist/checkpoint.js +219 -0
  96. package/dist/checkpoint.js.map +1 -0
  97. package/dist/circuit-breaker/breaker.d.ts +80 -0
  98. package/dist/circuit-breaker/breaker.d.ts.map +1 -0
  99. package/dist/circuit-breaker/breaker.js +408 -0
  100. package/dist/circuit-breaker/breaker.js.map +1 -0
  101. package/dist/circuit-breaker/defaults.d.ts +8 -0
  102. package/dist/circuit-breaker/defaults.d.ts.map +1 -0
  103. package/dist/circuit-breaker/defaults.js +35 -0
  104. package/dist/circuit-breaker/defaults.js.map +1 -0
  105. package/dist/circuit-breaker/index.d.ts +9 -0
  106. package/dist/circuit-breaker/index.d.ts.map +1 -0
  107. package/dist/circuit-breaker/index.js +8 -0
  108. package/dist/circuit-breaker/index.js.map +1 -0
  109. package/dist/circuit-breaker/types.d.ts +77 -0
  110. package/dist/circuit-breaker/types.d.ts.map +1 -0
  111. package/dist/circuit-breaker/types.js +8 -0
  112. package/dist/circuit-breaker/types.js.map +1 -0
  113. package/dist/circuit-breaker.d.ts +8 -0
  114. package/dist/circuit-breaker.d.ts.map +1 -0
  115. package/dist/circuit-breaker.js +7 -0
  116. package/dist/circuit-breaker.js.map +1 -0
  117. package/dist/cli/agent.d.ts +9 -0
  118. package/dist/cli/agent.d.ts.map +1 -0
  119. package/dist/cli/agent.js +262 -0
  120. package/dist/cli/agent.js.map +1 -0
  121. package/dist/cli/commands.d.ts +12 -0
  122. package/dist/cli/commands.d.ts.map +1 -0
  123. package/dist/{cli.js → cli/commands.js} +285 -422
  124. package/dist/cli/commands.js.map +1 -0
  125. package/dist/cli/index.d.ts +8 -0
  126. package/dist/cli/index.d.ts.map +1 -0
  127. package/dist/cli/index.js +222 -0
  128. package/dist/cli/index.js.map +1 -0
  129. package/dist/cli/types.d.ts +30 -0
  130. package/dist/cli/types.d.ts.map +1 -0
  131. package/dist/cli/types.js +20 -0
  132. package/dist/cli/types.js.map +1 -0
  133. package/dist/companions.d.ts +54 -0
  134. package/dist/companions.d.ts.map +1 -0
  135. package/dist/companions.js +440 -0
  136. package/dist/companions.js.map +1 -0
  137. package/dist/config.d.ts +23 -1
  138. package/dist/config.d.ts.map +1 -1
  139. package/dist/config.js +95 -22
  140. package/dist/config.js.map +1 -1
  141. package/dist/diff.d.ts +27 -0
  142. package/dist/diff.d.ts.map +1 -1
  143. package/dist/diff.js +415 -10
  144. package/dist/diff.js.map +1 -1
  145. package/dist/errors.d.ts.map +1 -1
  146. package/dist/errors.js +20 -11
  147. package/dist/errors.js.map +1 -1
  148. package/dist/git-status.d.ts +23 -0
  149. package/dist/git-status.d.ts.map +1 -0
  150. package/dist/git-status.js +92 -0
  151. package/dist/git-status.js.map +1 -0
  152. package/dist/headless.d.ts +25 -0
  153. package/dist/headless.d.ts.map +1 -0
  154. package/dist/headless.js +182 -0
  155. package/dist/headless.js.map +1 -0
  156. package/dist/hud/api.d.ts +35 -0
  157. package/dist/hud/api.d.ts.map +1 -0
  158. package/dist/hud/api.js +448 -0
  159. package/dist/hud/api.js.map +1 -0
  160. package/dist/hud/palettes.d.ts +9 -0
  161. package/dist/hud/palettes.d.ts.map +1 -0
  162. package/dist/hud/palettes.js +280 -0
  163. package/dist/hud/palettes.js.map +1 -0
  164. package/dist/hud/skins.d.ts +12 -0
  165. package/dist/hud/skins.d.ts.map +1 -0
  166. package/dist/hud/skins.js +365 -0
  167. package/dist/hud/skins.js.map +1 -0
  168. package/dist/hud/theme-packs/api.d.ts +51 -0
  169. package/dist/hud/theme-packs/api.d.ts.map +1 -0
  170. package/dist/hud/theme-packs/api.js +145 -0
  171. package/dist/hud/theme-packs/api.js.map +1 -0
  172. package/dist/hud/theme-packs/index.d.ts +18 -0
  173. package/dist/hud/theme-packs/index.d.ts.map +1 -0
  174. package/dist/hud/theme-packs/index.js +38 -0
  175. package/dist/hud/theme-packs/index.js.map +1 -0
  176. package/dist/hud/theme-packs/types.d.ts +29 -0
  177. package/dist/hud/theme-packs/types.d.ts.map +1 -0
  178. package/dist/hud/theme-packs/types.js +9 -0
  179. package/dist/hud/theme-packs/types.js.map +1 -0
  180. package/dist/hud/types.d.ts +182 -0
  181. package/dist/hud/types.d.ts.map +1 -0
  182. package/dist/hud/types.js +7 -0
  183. package/dist/hud/types.js.map +1 -0
  184. package/dist/idle-eviction.d.ts +34 -0
  185. package/dist/idle-eviction.d.ts.map +1 -0
  186. package/dist/idle-eviction.js +78 -0
  187. package/dist/idle-eviction.js.map +1 -0
  188. package/dist/index.d.ts +9 -3
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +9 -3
  191. package/dist/index.js.map +1 -1
  192. package/dist/iteration-ledger.d.ts +105 -0
  193. package/dist/iteration-ledger.d.ts.map +1 -0
  194. package/dist/iteration-ledger.js +237 -0
  195. package/dist/iteration-ledger.js.map +1 -0
  196. package/dist/markdown.d.ts.map +1 -1
  197. package/dist/markdown.js +1 -27
  198. package/dist/markdown.js.map +1 -1
  199. package/dist/mcp.d.ts +35 -0
  200. package/dist/mcp.d.ts.map +1 -1
  201. package/dist/mcp.js +291 -7
  202. package/dist/mcp.js.map +1 -1
  203. package/dist/memory.d.ts.map +1 -1
  204. package/dist/memory.js +12 -2
  205. package/dist/memory.js.map +1 -1
  206. package/dist/model-detection.d.ts +5 -0
  207. package/dist/model-detection.d.ts.map +1 -1
  208. package/dist/model-detection.js +278 -10
  209. package/dist/model-detection.js.map +1 -1
  210. package/dist/model-router.d.ts.map +1 -1
  211. package/dist/model-router.js +33 -11
  212. package/dist/model-router.js.map +1 -1
  213. package/dist/plugins.d.ts +8 -0
  214. package/dist/plugins.d.ts.map +1 -1
  215. package/dist/plugins.js +97 -6
  216. package/dist/plugins.js.map +1 -1
  217. package/dist/providers/anthropic.d.ts +10 -0
  218. package/dist/providers/anthropic.d.ts.map +1 -0
  219. package/dist/providers/anthropic.js +221 -0
  220. package/dist/providers/anthropic.js.map +1 -0
  221. package/dist/providers/bedrock.d.ts +17 -0
  222. package/dist/providers/bedrock.d.ts.map +1 -0
  223. package/dist/providers/bedrock.js +574 -0
  224. package/dist/providers/bedrock.js.map +1 -0
  225. package/dist/providers/compat.d.ts +13 -0
  226. package/dist/providers/compat.d.ts.map +1 -0
  227. package/dist/providers/compat.js +202 -0
  228. package/dist/providers/compat.js.map +1 -0
  229. package/dist/providers/google.d.ts +10 -0
  230. package/dist/providers/google.d.ts.map +1 -0
  231. package/dist/providers/google.js +203 -0
  232. package/dist/providers/google.js.map +1 -0
  233. package/dist/providers/index.d.ts +23 -0
  234. package/dist/providers/index.d.ts.map +1 -0
  235. package/dist/providers/index.js +145 -0
  236. package/dist/providers/index.js.map +1 -0
  237. package/dist/providers/ollama.d.ts +17 -0
  238. package/dist/providers/ollama.d.ts.map +1 -0
  239. package/dist/providers/ollama.js +289 -0
  240. package/dist/providers/ollama.js.map +1 -0
  241. package/dist/providers/openai.d.ts +121 -0
  242. package/dist/providers/openai.d.ts.map +1 -0
  243. package/dist/providers/openai.js +485 -0
  244. package/dist/providers/openai.js.map +1 -0
  245. package/dist/providers/types.d.ts +63 -0
  246. package/dist/providers/types.d.ts.map +1 -0
  247. package/dist/providers/types.js +164 -0
  248. package/dist/providers/types.js.map +1 -0
  249. package/dist/sandbox-native.d.ts +59 -0
  250. package/dist/sandbox-native.d.ts.map +1 -0
  251. package/dist/sandbox-native.js +292 -0
  252. package/dist/sandbox-native.js.map +1 -0
  253. package/dist/sandbox.d.ts +2 -2
  254. package/dist/sandbox.d.ts.map +1 -1
  255. package/dist/sandbox.js +59 -13
  256. package/dist/sandbox.js.map +1 -1
  257. package/dist/scope.d.ts +3 -1
  258. package/dist/scope.d.ts.map +1 -1
  259. package/dist/scope.js +13 -1
  260. package/dist/scope.js.map +1 -1
  261. package/dist/session-timeout.d.ts +31 -0
  262. package/dist/session-timeout.d.ts.map +1 -0
  263. package/dist/session-timeout.js +100 -0
  264. package/dist/session-timeout.js.map +1 -0
  265. package/dist/setup.d.ts.map +1 -1
  266. package/dist/setup.js +29 -17
  267. package/dist/setup.js.map +1 -1
  268. package/dist/smart-router.d.ts +73 -0
  269. package/dist/smart-router.d.ts.map +1 -0
  270. package/dist/smart-router.js +332 -0
  271. package/dist/smart-router.js.map +1 -0
  272. package/dist/storage.d.ts +19 -0
  273. package/dist/storage.d.ts.map +1 -1
  274. package/dist/storage.js +164 -1
  275. package/dist/storage.js.map +1 -1
  276. package/dist/streaming.d.ts +4 -0
  277. package/dist/streaming.d.ts.map +1 -1
  278. package/dist/streaming.js +12 -0
  279. package/dist/streaming.js.map +1 -1
  280. package/dist/styles.d.ts +32 -0
  281. package/dist/styles.d.ts.map +1 -1
  282. package/dist/styles.js +91 -0
  283. package/dist/styles.js.map +1 -1
  284. package/dist/summarization.d.ts +1 -1
  285. package/dist/summarization.js +4 -4
  286. package/dist/summarization.js.map +1 -1
  287. package/dist/terminal-image.d.ts +115 -0
  288. package/dist/terminal-image.d.ts.map +1 -0
  289. package/dist/terminal-image.js +766 -0
  290. package/dist/terminal-image.js.map +1 -0
  291. package/dist/terminal-recording.d.ts +55 -0
  292. package/dist/terminal-recording.d.ts.map +1 -0
  293. package/dist/terminal-recording.js +182 -0
  294. package/dist/terminal-recording.js.map +1 -0
  295. package/dist/themes.d.ts +19 -35
  296. package/dist/themes.d.ts.map +1 -1
  297. package/dist/themes.js +101 -210
  298. package/dist/themes.js.map +1 -1
  299. package/dist/tmux.d.ts +35 -0
  300. package/dist/tmux.d.ts.map +1 -0
  301. package/dist/tmux.js +106 -0
  302. package/dist/tmux.js.map +1 -0
  303. package/dist/tools.d.ts +3 -3
  304. package/dist/tools.d.ts.map +1 -1
  305. package/dist/tools.js +587 -45
  306. package/dist/tools.js.map +1 -1
  307. package/dist/trust.d.ts +53 -0
  308. package/dist/trust.d.ts.map +1 -0
  309. package/dist/trust.js +154 -0
  310. package/dist/trust.js.map +1 -0
  311. package/dist/types.d.ts +7 -3
  312. package/dist/types.d.ts.map +1 -1
  313. package/dist/types.js +70 -32
  314. package/dist/types.js.map +1 -1
  315. package/dist/ui/agent.d.ts +61 -0
  316. package/dist/ui/agent.d.ts.map +1 -0
  317. package/dist/ui/agent.js +768 -0
  318. package/dist/ui/agent.js.map +1 -0
  319. package/dist/ui/chat-input.d.ts +32 -0
  320. package/dist/ui/chat-input.d.ts.map +1 -0
  321. package/dist/ui/chat-input.js +355 -0
  322. package/dist/ui/chat-input.js.map +1 -0
  323. package/dist/ui/commands.d.ts +92 -0
  324. package/dist/ui/commands.d.ts.map +1 -0
  325. package/dist/ui/commands.js +3006 -0
  326. package/dist/ui/commands.js.map +1 -0
  327. package/dist/ui/completions.d.ts +22 -0
  328. package/dist/ui/completions.d.ts.map +1 -0
  329. package/dist/ui/completions.js +215 -0
  330. package/dist/ui/completions.js.map +1 -0
  331. package/dist/ui/components.d.ts +38 -0
  332. package/dist/ui/components.d.ts.map +1 -0
  333. package/dist/ui/components.js +422 -0
  334. package/dist/ui/components.js.map +1 -0
  335. package/dist/ui/context.d.ts +12 -0
  336. package/dist/ui/context.d.ts.map +1 -0
  337. package/dist/ui/context.js +102 -0
  338. package/dist/ui/context.js.map +1 -0
  339. package/dist/ui/error-boundary.d.ts +33 -0
  340. package/dist/ui/error-boundary.d.ts.map +1 -0
  341. package/dist/ui/error-boundary.js +94 -0
  342. package/dist/ui/error-boundary.js.map +1 -0
  343. package/dist/ui/frame.d.ts +13 -0
  344. package/dist/ui/frame.d.ts.map +1 -0
  345. package/dist/ui/frame.js +89 -0
  346. package/dist/ui/frame.js.map +1 -0
  347. package/dist/ui/index.d.ts +12 -0
  348. package/dist/ui/index.d.ts.map +1 -0
  349. package/dist/ui/index.js +928 -0
  350. package/dist/ui/index.js.map +1 -0
  351. package/dist/ui/messages.d.ts +19 -0
  352. package/dist/ui/messages.d.ts.map +1 -0
  353. package/dist/ui/messages.js +181 -0
  354. package/dist/ui/messages.js.map +1 -0
  355. package/dist/ui/modals.d.ts +52 -0
  356. package/dist/ui/modals.d.ts.map +1 -0
  357. package/dist/ui/modals.js +204 -0
  358. package/dist/ui/modals.js.map +1 -0
  359. package/dist/ui/pack-picker.d.ts +12 -0
  360. package/dist/ui/pack-picker.d.ts.map +1 -0
  361. package/dist/ui/pack-picker.js +101 -0
  362. package/dist/ui/pack-picker.js.map +1 -0
  363. package/dist/ui/status-bar.d.ts +20 -0
  364. package/dist/ui/status-bar.d.ts.map +1 -0
  365. package/dist/ui/status-bar.js +41 -0
  366. package/dist/ui/status-bar.js.map +1 -0
  367. package/dist/ui/theme-picker.d.ts +24 -0
  368. package/dist/ui/theme-picker.d.ts.map +1 -0
  369. package/dist/ui/theme-picker.js +190 -0
  370. package/dist/ui/theme-picker.js.map +1 -0
  371. package/dist/ui/types.d.ts +62 -0
  372. package/dist/ui/types.d.ts.map +1 -0
  373. package/dist/ui/types.js +7 -0
  374. package/dist/ui/types.js.map +1 -0
  375. package/dist/version-check.d.ts.map +1 -1
  376. package/dist/version-check.js +1 -9
  377. package/dist/version-check.js.map +1 -1
  378. package/package.json +8 -3
  379. package/dist/agterm/agent-detection.js.map +0 -1
  380. package/dist/agterm/cli-backend.js.map +0 -1
  381. package/dist/agterm/index.d.ts +0 -12
  382. package/dist/agterm/index.d.ts.map +0 -1
  383. package/dist/agterm/index.js +0 -15
  384. package/dist/agterm/index.js.map +0 -1
  385. package/dist/agterm/orchestrator.d.ts.map +0 -1
  386. package/dist/agterm/orchestrator.js.map +0 -1
  387. package/dist/agterm/tools.d.ts.map +0 -1
  388. package/dist/agterm/tools.js +0 -278
  389. package/dist/agterm/tools.js.map +0 -1
  390. package/dist/agterm/types.d.ts.map +0 -1
  391. package/dist/agterm/types.js.map +0 -1
  392. package/dist/cli.d.ts +0 -14
  393. package/dist/cli.d.ts.map +0 -1
  394. package/dist/cli.js.map +0 -1
  395. package/dist/providers.d.ts +0 -51
  396. package/dist/providers.d.ts.map +0 -1
  397. package/dist/providers.js +0 -1146
  398. package/dist/providers.js.map +0 -1
  399. package/dist/ui-cli.d.ts +0 -17
  400. package/dist/ui-cli.d.ts.map +0 -1
  401. package/dist/ui-cli.js +0 -3730
  402. package/dist/ui-cli.js.map +0 -1
@@ -0,0 +1,928 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * UI Module - Entry Point
4
+ *
5
+ * TerminalChat (main component), App wrapper, printBanner, startInkCLI.
6
+ * Imports extracted modules and wires state through context bags.
7
+ */
8
+ import React, { useState, useCallback, useRef, useEffect } from 'react';
9
+ import { render, Box, Text, useApp, useStdout } from 'ink';
10
+ import * as fs from 'fs';
11
+ import * as config from '../config.js';
12
+ import { selectProvider } from '../providers/index.js';
13
+ import { getSystemPrompt, DEFAULT_MODELS, supportsVision } from '../types.js';
14
+ import { getVersion } from '../version-check.js';
15
+ import { getModelContextLimit, preWarmModelCache } from '../model-detection.js';
16
+ import { detectComplexity } from '../risk.js';
17
+ import * as storage from '../storage.js';
18
+ import { parseFileReferences, processFilesForMessage, formatFileInfo } from '../files.js';
19
+ import * as memory from '../memory.js';
20
+ import * as hooks from '../hooks.js';
21
+ import { getCurrentSkin, getCurrentPalette, paletteColorize, applySkin, applyPalette } from '../hud/api.js';
22
+ import { renderColoredBanner, renderSplashAnimation, renderTransition, colorFg } from '../terminal-image.js';
23
+ import { HUDFrame } from './frame.js';
24
+ import { getCurrentCompanion, applyCompanion, setEmojiConfig } from '../companions.js';
25
+ import { CircuitBreaker } from '../circuit-breaker.js';
26
+ import { IterationLedger } from '../iteration-ledger.js';
27
+ import { getDefaultSmartRoutingConfig } from '../smart-router.js';
28
+ import * as recording from '../terminal-recording.js';
29
+ import * as sessionTimeout from '../session-timeout.js';
30
+ import * as idleEviction from '../idle-eviction.js';
31
+ import { isTmux, getTmuxInfo } from '../tmux.js';
32
+ import { ErrorBoundary } from './error-boundary.js';
33
+ import { ThinkingDisplay, ProcessingIndicator, StreamingIndicator, StateTransition } from './components.js';
34
+ import { MessageHistory } from './messages.js';
35
+ import { ModelSelector, SessionSelector, UpgradePrompt, ComplexityWarning, SessionResumePrompt, KeybindingsModal, } from './modals.js';
36
+ import { ThemePicker } from './theme-picker.js';
37
+ import { PackPicker } from './pack-picker.js';
38
+ import { applyThemePack, getCurrentPack, getCompanionMode, getThemePack } from '../hud/theme-packs/api.js';
39
+ import { ChatInput } from './chat-input.js';
40
+ import { StatusBar } from './status-bar.js';
41
+ import { handleCommand } from './commands.js';
42
+ import { runAgentImpl, runLoopImpl, validateAndRepairMessagesImpl } from './agent.js';
43
+ // Wire emoji config at module load (breaks circular dep: companions → config)
44
+ setEmojiConfig(config);
45
+ // Module-level state for agterm mode
46
+ let moduleAgtermEnabled = false;
47
+ // Debug logging for flow control issues
48
+ let debugEnabled = process.env.CALLIOPE_DEBUG === '1';
49
+ const debugLog = (label, ...args) => {
50
+ if (debugEnabled) {
51
+ const timestamp = new Date().toISOString().split('T')[1].slice(0, 12);
52
+ console.error(`[${timestamp}] ${label}:`, ...args);
53
+ }
54
+ };
55
+ // ============================================================================
56
+ // Main Chat Component
57
+ // ============================================================================
58
+ function TerminalChat() {
59
+ const { exit } = useApp();
60
+ const { stdout } = useStdout();
61
+ // Reactive terminal width - re-renders on resize via SIGWINCH
62
+ const [width, setWidth] = useState(() => stdout?.columns || 80);
63
+ useEffect(() => {
64
+ const onResize = () => {
65
+ const cols = stdout?.columns || process.stdout.columns || 80;
66
+ setWidth(cols);
67
+ };
68
+ process.stdout.on('resize', onResize);
69
+ return () => { process.stdout.off('resize', onResize); };
70
+ }, [stdout]);
71
+ // Core state
72
+ const [input, setInput] = useState('');
73
+ const [suggestions, setSuggestions] = useState([]);
74
+ const [messages, setMessages] = useState([]);
75
+ const [isProcessing, setIsProcessing] = useState(false);
76
+ const [thinkingState, setThinkingState] = useState(null);
77
+ const [streamingResponse, setStreamingResponse] = useState('');
78
+ const [activityState, setActivityState] = useState(null);
79
+ // State transition tracking
80
+ const prevProcessingState = useRef('idle');
81
+ const [transition, setTransition] = useState(null);
82
+ useEffect(() => {
83
+ const current = isProcessing && thinkingState && !streamingResponse ? 'thinking' :
84
+ isProcessing && streamingResponse ? 'streaming' :
85
+ !isProcessing && prevProcessingState.current !== 'idle' ? 'done' : 'idle';
86
+ if (current !== prevProcessingState.current) {
87
+ const from = prevProcessingState.current;
88
+ // Only show transitions for meaningful state changes
89
+ if (from !== 'idle' || current !== 'idle') {
90
+ setTransition({ from, to: current });
91
+ }
92
+ prevProcessingState.current = current;
93
+ }
94
+ }, [isProcessing, thinkingState, streamingResponse]);
95
+ // Input history for up/down arrow navigation
96
+ const [inputHistory, setInputHistory] = useState([]);
97
+ const [historyIndex, setHistoryIndex] = useState(-1);
98
+ const [savedInput, setSavedInput] = useState(''); // Save current input when navigating
99
+ // Smart suggestions context
100
+ const [hasGitRepo] = useState(() => {
101
+ try {
102
+ return fs.existsSync('.git') || fs.existsSync('../.git');
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ });
108
+ const recentCommands = React.useMemo(() => inputHistory.filter(cmd => cmd.startsWith('/')).slice(-10), [inputHistory]);
109
+ // Clear suggestions when input changes significantly
110
+ const handleInputChange = useCallback((newValue) => {
111
+ setInput(newValue);
112
+ // Clear suggestions if user clears input or submits
113
+ if (!newValue || !newValue.startsWith('/')) {
114
+ setSuggestions([]);
115
+ }
116
+ // Reset history navigation when user types
117
+ setHistoryIndex(-1);
118
+ }, []);
119
+ // Navigate input history
120
+ const navigateHistory = useCallback((direction) => {
121
+ if (inputHistory.length === 0)
122
+ return;
123
+ if (direction === 'up') {
124
+ if (historyIndex === -1) {
125
+ // Save current input before navigating
126
+ setSavedInput(input);
127
+ setHistoryIndex(inputHistory.length - 1);
128
+ setInput(inputHistory[inputHistory.length - 1]);
129
+ }
130
+ else if (historyIndex > 0) {
131
+ setHistoryIndex(historyIndex - 1);
132
+ setInput(inputHistory[historyIndex - 1]);
133
+ }
134
+ }
135
+ else {
136
+ if (historyIndex === -1)
137
+ return;
138
+ if (historyIndex < inputHistory.length - 1) {
139
+ setHistoryIndex(historyIndex + 1);
140
+ setInput(inputHistory[historyIndex + 1]);
141
+ }
142
+ else {
143
+ // Return to saved input
144
+ setHistoryIndex(-1);
145
+ setInput(savedInput);
146
+ }
147
+ }
148
+ }, [inputHistory, historyIndex, input, savedInput]);
149
+ // Add to history when submitting
150
+ const addToHistory = useCallback((value) => {
151
+ if (value.trim() && (inputHistory.length === 0 || inputHistory[inputHistory.length - 1] !== value)) {
152
+ setInputHistory(prev => [...prev.slice(-100), value]); // Keep last 100 entries
153
+ }
154
+ setHistoryIndex(-1);
155
+ setSavedInput('');
156
+ }, [inputHistory]);
157
+ // Config state
158
+ // Use lazy initializers to avoid calling config.get() on every render
159
+ const [provider, setProvider] = useState(() => process.env.CALLIOPE_PROVIDER || config.get('defaultProvider'));
160
+ const [model, setModel] = useState(() => process.env.CALLIOPE_MODEL || config.get('defaultModel'));
161
+ const [persona, setPersona] = useState(() => config.get('persona'));
162
+ const [mode, setMode] = useState('hybrid'); // Default to hybrid mode
163
+ const [confirmMode, setConfirmMode] = useState(true); // Require confirmation for risky ops
164
+ const [layout, setLayout] = useState(() => config.get('layout') || 'response-bottom');
165
+ const [density, setDensity] = useState(() => config.get('density') || 'normal');
166
+ const [collapseSettings, setCollapseSettings] = useState(() => ({
167
+ collapseTools: config.get('collapseTools') ?? false,
168
+ collapseThinking: config.get('collapseThinking') ?? false,
169
+ toolDisplayLimit: config.get('toolDisplayLimit') ?? 0,
170
+ }));
171
+ // Modal state
172
+ const [modalMode, setModalMode] = useState('none');
173
+ const [pendingComplexPrompt, setPendingComplexPrompt] = useState(null);
174
+ const [previousSession, setPreviousSession] = useState(null);
175
+ const [pendingToolCall, setPendingToolCall] = useState(null);
176
+ const [availableModels, setAvailableModels] = useState([]);
177
+ const [availableSessions, setAvailableSessions] = useState([]);
178
+ const [latestVersion, setLatestVersion] = useState(null);
179
+ // Stats
180
+ const [stats, setStats] = useState({
181
+ inputTokens: 0,
182
+ outputTokens: 0,
183
+ cost: 0,
184
+ messageCount: 0,
185
+ });
186
+ const [contextTokens, setContextTokens] = useState(0);
187
+ // Message queue for human-in-the-loop feedback during processing
188
+ const [queuedMessages, setQueuedMessages] = useState([]);
189
+ const queuedMessagesRef = useRef([]); // Ref to avoid stale closure in runAgent
190
+ const [queueInput, setQueueInput] = useState('');
191
+ const [editingQueueIndex, setEditingQueueIndex] = useState(null);
192
+ // Keep ref in sync with state
193
+ useEffect(() => {
194
+ queuedMessagesRef.current = queuedMessages;
195
+ }, [queuedMessages]);
196
+ // Undo/Redo history - stores snapshots of conversation state
197
+ const undoStack = useRef([]);
198
+ const redoStack = useRef([]);
199
+ const MAX_UNDO_HISTORY = 10;
200
+ // Conversation bookmarks
201
+ const [bookmarks, setBookmarks] = useState([]);
202
+ // Prompt templates
203
+ const [templates, setTemplates] = useState([]);
204
+ // Save state before changes (call before modifying messages)
205
+ const saveUndoState = useCallback(() => {
206
+ undoStack.current.push({
207
+ messages: [...messages],
208
+ llmMessages: [...llmMessages.current],
209
+ timestamp: new Date(),
210
+ });
211
+ // Limit stack size
212
+ if (undoStack.current.length > MAX_UNDO_HISTORY) {
213
+ undoStack.current.shift();
214
+ }
215
+ // Clear redo stack on new action
216
+ redoStack.current = [];
217
+ }, [messages]);
218
+ // LLM conversation history
219
+ const llmMessages = useRef([
220
+ { role: 'system', content: getSystemPrompt(persona) }
221
+ ]);
222
+ // Keep system prompt in sync when persona changes (fixes #46 - persona lost on model fallback)
223
+ useEffect(() => {
224
+ const firstMsg = llmMessages.current[0];
225
+ if (firstMsg && firstMsg.role === 'system') {
226
+ // Preserve any appended memory context
227
+ const currentContent = typeof firstMsg.content === 'string' ? firstMsg.content : '';
228
+ const memoryIdx = currentContent.indexOf('\n\n--- Project Context ---\n');
229
+ const memoryPart = memoryIdx >= 0 ? currentContent.slice(memoryIdx) : '';
230
+ llmMessages.current[0] = {
231
+ role: 'system',
232
+ content: getSystemPrompt(persona) + memoryPart,
233
+ };
234
+ }
235
+ }, [persona]);
236
+ // Estimate context tokens (conservative: ~2.5 chars per token + 1.35x overhead)
237
+ const estimateContextTokens = useCallback(() => {
238
+ let chars = 0;
239
+ let msgCount = 0;
240
+ for (const msg of llmMessages.current) {
241
+ msgCount++;
242
+ if (typeof msg.content === 'string') {
243
+ chars += msg.content.length;
244
+ }
245
+ else if (Array.isArray(msg.content)) {
246
+ for (const block of msg.content) {
247
+ if (block.type === 'text') {
248
+ chars += block.text.length;
249
+ }
250
+ else if (block.type === 'image') {
251
+ chars += 1000; // Images count as ~250 tokens
252
+ }
253
+ }
254
+ }
255
+ // Include tool call arguments in estimation
256
+ if (msg.toolCalls) {
257
+ for (const tool of msg.toolCalls) {
258
+ chars += JSON.stringify(tool.arguments || {}).length;
259
+ }
260
+ }
261
+ }
262
+ // Conservative: 2.5 chars/token, 1.35x overhead for formatting/metadata, +50 per message
263
+ return Math.round((chars / 2.5) * 1.35 + msgCount * 50);
264
+ }, []);
265
+ // Session state
266
+ const sessionRef = useRef(null);
267
+ const [autoRoute, setAutoRoute] = useState(false); // Auto model routing
268
+ const [smartRouteActive, setSmartRouteActive] = useState(() => config.get('smartRoutingEnabled') ?? false);
269
+ const [breakerHealth, setBreakerHealth] = useState('ok');
270
+ const ledgerRef = useRef(new IterationLedger());
271
+ const circuitBreakerRef = useRef(config.get('circuitBreakersEnabled') !== false ? (() => {
272
+ const iterTimeSec = config.get('maxIterationTime');
273
+ const cb = new CircuitBreaker();
274
+ if (typeof iterTimeSec === 'number' && iterTimeSec > 0) {
275
+ cb.adjust('wall-clock', { maxIterationDurationMs: iterTimeSec * 1000 });
276
+ }
277
+ // Local/free providers: disable cost breaker, relax token limits
278
+ const prov = config.get('defaultProvider');
279
+ if (prov === 'ollama' || prov === 'litellm') {
280
+ cb.adjust('cost-runaway', { maxSessionCost: 999999, maxCostPerMinute: 999999 });
281
+ cb.adjust('token-burn', { maxTokensPerIteration: 500_000, maxTotalTokens: 20_000_000 });
282
+ }
283
+ return cb;
284
+ })() : null);
285
+ const smartRoutingConfigRef = useRef({
286
+ ...getDefaultSmartRoutingConfig(),
287
+ enabled: config.get('smartRoutingEnabled') ?? false,
288
+ costSensitivity: config.get('smartRoutingCostSensitivity') ?? 0.3,
289
+ });
290
+ const [memoryLoaded, setMemoryLoaded] = useState(false);
291
+ // Agent loop state
292
+ const [loopActive, setLoopActive] = useState(false);
293
+ const [loopPrompt, setLoopPrompt] = useState('');
294
+ const [loopMaxIterations, setLoopMaxIterations] = useState(100);
295
+ const [loopCompletionPromise, setLoopCompletionPromise] = useState();
296
+ const [loopIteration, setLoopIteration] = useState(0);
297
+ const loopCancelledRef = useRef(false);
298
+ // Initialize session and load memory on mount
299
+ useEffect(() => {
300
+ const cwd = process.cwd();
301
+ // Check for existing session with messages
302
+ const existingSessions = storage.listSessions(5);
303
+ const recentSession = existingSessions.find(s => s.projectPath === cwd &&
304
+ s.messageCount > 0 &&
305
+ Date.now() - new Date(s.lastAccessedAt).getTime() < 24 * 60 * 60 * 1000 // Within 24 hours
306
+ );
307
+ if (recentSession && !sessionRef.current) {
308
+ // Offer to resume
309
+ setPreviousSession({
310
+ projectName: recentSession.projectName,
311
+ lastAccessedAt: recentSession.lastAccessedAt,
312
+ messageCount: recentSession.messageCount,
313
+ });
314
+ setModalMode('session-resume');
315
+ }
316
+ const session = storage.getOrCreateSession(cwd);
317
+ sessionRef.current = session;
318
+ // Load memory context into system prompt
319
+ if (!memoryLoaded) {
320
+ const cwdMem = process.cwd();
321
+ const memoryContext = memory.buildMemoryContext(cwdMem);
322
+ if (memoryContext.trim()) {
323
+ // Append memory context to system prompt
324
+ const currentSystem = llmMessages.current[0];
325
+ if (currentSystem && currentSystem.role === 'system') {
326
+ const systemContent = typeof currentSystem.content === 'string'
327
+ ? currentSystem.content
328
+ : '';
329
+ llmMessages.current[0] = {
330
+ role: 'system',
331
+ content: systemContent + '\n\n--- Project Context ---\n' + memoryContext,
332
+ };
333
+ }
334
+ }
335
+ setMemoryLoaded(true);
336
+ // Execute session start hooks
337
+ hooks.executeHooks('session-start', {}).catch((err) => {
338
+ debugLog('hooks', 'session-start hook failed:', err instanceof Error ? err.message : err);
339
+ });
340
+ // Start session recording (audit log) — respects config
341
+ recording.setRecordingEnabled(config.get('recordSessions') !== false);
342
+ recording.setRetentionDays(config.get('recordingRetentionDays') ?? 0);
343
+ recording.startRecording({
344
+ provider: selectProvider(provider),
345
+ model: model || DEFAULT_MODELS[selectProvider(provider)],
346
+ cwd: cwdMem,
347
+ });
348
+ // Configure session timeout (opt-in via config)
349
+ const timeoutMs = config.get('sessionTimeoutMs');
350
+ if (timeoutMs) {
351
+ sessionTimeout.configureTimeout({
352
+ enabled: true,
353
+ idleTimeoutMs: typeof timeoutMs === 'number' ? timeoutMs : 2 * 60 * 60 * 1000,
354
+ });
355
+ sessionTimeout.onTimeout((type) => {
356
+ if (type === 'warning') {
357
+ addMessage('system', `\u23f1\ufe0f Session will timeout in ${sessionTimeout.formatTimeRemaining()}`);
358
+ }
359
+ else {
360
+ addMessage('system', '\ud83d\udeaa Session timeout. Saving and exiting...');
361
+ storage.saveMessageHistory(llmMessages.current);
362
+ }
363
+ });
364
+ }
365
+ // Start idle eviction monitor
366
+ idleEviction.configureEviction({ enabled: true });
367
+ idleEviction.onEviction((action) => {
368
+ if (action === 'auto-save') {
369
+ storage.saveMessageHistory(llmMessages.current);
370
+ }
371
+ });
372
+ // Log tmux context if applicable
373
+ if (isTmux()) {
374
+ const info = getTmuxInfo();
375
+ if (info) {
376
+ debugLog('tmux', `session=${info.session}, windows=${info.windows}, panes=${info.panes}`);
377
+ }
378
+ }
379
+ // Load templates from storage
380
+ const savedTemplates = storage.getTemplates();
381
+ if (savedTemplates.length > 0) {
382
+ setTemplates(savedTemplates.map(t => ({
383
+ name: t.name,
384
+ prompt: t.prompt,
385
+ createdAt: new Date(t.createdAt),
386
+ })));
387
+ }
388
+ // Pre-warm model cache in background for faster model switching
389
+ preWarmModelCache().catch((err) => {
390
+ debugLog('cache', 'model cache pre-warm failed:', err instanceof Error ? err.message : err);
391
+ });
392
+ }
393
+ }, [memoryLoaded]);
394
+ // Derived values
395
+ const actualProvider = selectProvider(provider);
396
+ const actualModel = model || DEFAULT_MODELS[actualProvider];
397
+ const isModalActive = modalMode !== 'none';
398
+ // Add message helper
399
+ const addMessage = useCallback((type, content) => {
400
+ setMessages(prev => [...prev, {
401
+ id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
402
+ type,
403
+ content
404
+ }]);
405
+ // Persist user and assistant messages to storage for session history
406
+ if (type === 'user' || type === 'assistant') {
407
+ storage.addChatMessage({ role: type, content });
408
+ }
409
+ }, []);
410
+ // Handler to edit or delete a queued message
411
+ const handleEditQueuedMessage = useCallback((index, newMsg) => {
412
+ if (newMsg === '') {
413
+ // Delete the message
414
+ setQueuedMessages(prev => prev.filter((_, i) => i !== index));
415
+ addMessage('system', `🗑️ Deleted queued message #${index + 1}`);
416
+ }
417
+ else {
418
+ // Update the message
419
+ setQueuedMessages(prev => prev.map((msg, i) => i === index ? newMsg : msg));
420
+ addMessage('system', `✏️ Updated queued message #${index + 1}`);
421
+ }
422
+ }, [addMessage]);
423
+ // Validate and repair message history
424
+ const validateAndRepairMessages = useCallback(() => {
425
+ return validateAndRepairMessagesImpl({
426
+ llmMessages,
427
+ addMessage,
428
+ debugLog,
429
+ });
430
+ }, [addMessage]);
431
+ // Build agent context
432
+ const buildAgentContext = useCallback(() => ({
433
+ provider,
434
+ model,
435
+ mode,
436
+ confirmMode,
437
+ autoRoute,
438
+ actualProvider,
439
+ actualModel,
440
+ stats,
441
+ agtermEnabled: moduleAgtermEnabled,
442
+ ledger: ledgerRef.current,
443
+ circuitBreaker: circuitBreakerRef.current || undefined,
444
+ smartRouteActive,
445
+ smartRoutingConfig: smartRoutingConfigRef.current,
446
+ setBreakerHealth,
447
+ setStats,
448
+ setStreamingResponse,
449
+ setThinkingState,
450
+ setActivityState,
451
+ setContextTokens,
452
+ setIsProcessing,
453
+ setQueuedMessages,
454
+ setEditingQueueIndex,
455
+ setLoopIteration,
456
+ setLoopActive,
457
+ llmMessages,
458
+ queuedMessagesRef,
459
+ loopCancelledRef,
460
+ sessionRef,
461
+ addMessage,
462
+ estimateContextTokens,
463
+ validateAndRepairMessages,
464
+ debugLog,
465
+ }), [provider, model, mode, confirmMode, autoRoute, smartRouteActive, actualProvider, actualModel, stats, addMessage, estimateContextTokens, validateAndRepairMessages]);
466
+ // Run agent with user prompt
467
+ const runAgent = useCallback(async (content) => {
468
+ const ctx = buildAgentContext();
469
+ await runAgentImpl(ctx, content);
470
+ }, [buildAgentContext]);
471
+ // Agent loop - runs prompt repeatedly until completion promise or max iterations
472
+ const runLoop = useCallback(async (prompt, maxIter, completionPromise) => {
473
+ const ctx = buildAgentContext();
474
+ await runLoopImpl(ctx, prompt, maxIter, completionPromise);
475
+ }, [buildAgentContext]);
476
+ // Build command context
477
+ const buildCommandContext = useCallback(() => ({
478
+ actualProvider,
479
+ actualModel,
480
+ provider,
481
+ model,
482
+ persona,
483
+ mode,
484
+ confirmMode,
485
+ autoRoute,
486
+ layout,
487
+ density,
488
+ collapseSettings,
489
+ messages,
490
+ stats,
491
+ loopActive,
492
+ isProcessing,
493
+ thinkingState,
494
+ streamingResponse,
495
+ queuedMessages,
496
+ bookmarks,
497
+ templates,
498
+ agtermEnabled: moduleAgtermEnabled,
499
+ debugEnabled,
500
+ modalMode,
501
+ circuitBreaker: circuitBreakerRef.current || undefined,
502
+ smartRouteActive,
503
+ smartRoutingConfig: smartRoutingConfigRef.current,
504
+ setProvider,
505
+ setModel,
506
+ setPersona,
507
+ setMode,
508
+ setConfirmMode,
509
+ setAutoRoute,
510
+ setLayout: setLayout,
511
+ setDensity: setDensity,
512
+ setCollapseSettings,
513
+ setMessages,
514
+ setStats,
515
+ setModalMode: setModalMode,
516
+ setPendingComplexPrompt,
517
+ setAvailableModels,
518
+ setAvailableSessions,
519
+ setLatestVersion,
520
+ setLoopActive,
521
+ setLoopPrompt,
522
+ setLoopMaxIterations,
523
+ setLoopCompletionPromise,
524
+ setLoopIteration,
525
+ setIsProcessing,
526
+ setThinkingState,
527
+ setStreamingResponse,
528
+ setQueuedMessages,
529
+ setInput,
530
+ setBookmarks,
531
+ setTemplates,
532
+ setContextTokens,
533
+ setDebugEnabled: (v) => { debugEnabled = v; },
534
+ setSmartRouteActive,
535
+ setBreakerHealth,
536
+ llmMessages,
537
+ undoStack,
538
+ redoStack,
539
+ loopCancelledRef,
540
+ sessionRef,
541
+ addMessage,
542
+ estimateContextTokens,
543
+ saveUndoState,
544
+ runAgent,
545
+ runLoop,
546
+ exit,
547
+ }), [actualProvider, actualModel, provider, model, persona, mode, confirmMode, autoRoute, smartRouteActive,
548
+ layout, density, collapseSettings, messages, stats, loopActive, isProcessing,
549
+ thinkingState, streamingResponse, queuedMessages, bookmarks, templates, modalMode,
550
+ addMessage, estimateContextTokens, saveUndoState, runAgent, runLoop, exit]);
551
+ // Handle slash commands
552
+ const handleCommandWrapped = useCallback(async (cmd) => {
553
+ const ctx = buildCommandContext();
554
+ await handleCommand(cmd, ctx);
555
+ }, [buildCommandContext]);
556
+ // Handle input submission
557
+ const handleSubmit = useCallback(async (value) => {
558
+ const trimmed = value.trim();
559
+ if (!trimmed || isProcessing)
560
+ return;
561
+ // Record activity for timeout/eviction and audit log
562
+ sessionTimeout.recordActivity();
563
+ idleEviction.recordActivity();
564
+ recording.recordEvent('input', trimmed);
565
+ // Add to history for up/down arrow navigation
566
+ addToHistory(trimmed);
567
+ setInput('');
568
+ if (trimmed.startsWith('/')) {
569
+ await handleCommandWrapped(trimmed);
570
+ return;
571
+ }
572
+ // ! prefix executes shell commands directly
573
+ if (trimmed.startsWith('!')) {
574
+ const shellCmd = trimmed.slice(1).trim();
575
+ if (shellCmd) {
576
+ addMessage('system', `$ ${shellCmd}`);
577
+ try {
578
+ const { execSync } = await import('child_process');
579
+ const output = execSync(shellCmd, {
580
+ cwd: process.cwd(),
581
+ encoding: 'utf-8',
582
+ timeout: 30000,
583
+ stdio: ['pipe', 'pipe', 'pipe'],
584
+ maxBuffer: 10 * 1024 * 1024,
585
+ }).trim();
586
+ addMessage('system', output || '(no output)');
587
+ }
588
+ catch (err) {
589
+ const execErr = err;
590
+ addMessage('error', execErr.stderr?.trim() || execErr.message || String(err));
591
+ }
592
+ }
593
+ return;
594
+ }
595
+ // In hybrid mode, check for complex operations
596
+ if (mode === 'hybrid') {
597
+ const complexity = detectComplexity(trimmed);
598
+ if (complexity.isComplex) {
599
+ setPendingComplexPrompt({ prompt: trimmed, complexity });
600
+ setModalMode('complexity-warning');
601
+ return;
602
+ }
603
+ }
604
+ // Save state for undo before modifying conversation
605
+ saveUndoState();
606
+ // Parse file references from input
607
+ const { text: cleanText, files } = parseFileReferences(trimmed, process.cwd());
608
+ // Show user message (with file info if any)
609
+ if (files.length > 0) {
610
+ const fileInfo = formatFileInfo(files);
611
+ addMessage('user', `${cleanText}\n📎 ${fileInfo}`);
612
+ }
613
+ else {
614
+ addMessage('user', trimmed);
615
+ }
616
+ setIsProcessing(true);
617
+ try {
618
+ // Build message content (with file/image support)
619
+ let messageContent;
620
+ if (files.length > 0) {
621
+ const visionSupported = supportsVision(provider, model);
622
+ const { content, warnings } = processFilesForMessage(cleanText || trimmed, files, visionSupported);
623
+ // Show any warnings about files
624
+ for (const warning of warnings) {
625
+ addMessage('system', warning);
626
+ }
627
+ messageContent = content;
628
+ }
629
+ else {
630
+ messageContent = trimmed;
631
+ }
632
+ await runAgent(messageContent);
633
+ }
634
+ finally {
635
+ setIsProcessing(false);
636
+ setThinkingState(null);
637
+ setStreamingResponse('');
638
+ }
639
+ }, [isProcessing, handleCommandWrapped, runAgent, addMessage, provider, model, saveUndoState, addToHistory, mode]);
640
+ // Modal handlers
641
+ const handleModelSelect = useCallback((selectedModel) => {
642
+ setModel(selectedModel);
643
+ addMessage('system', `Model: ${selectedModel}`);
644
+ setModalMode('none');
645
+ setAvailableModels([]);
646
+ }, [addMessage]);
647
+ const handleModalCancel = useCallback(() => {
648
+ setModalMode('none');
649
+ setAvailableModels([]);
650
+ setLatestVersion(null);
651
+ }, []);
652
+ const handleUpgradeConfirm = useCallback(async () => {
653
+ setModalMode('none');
654
+ addMessage('system', 'Upgrading...');
655
+ try {
656
+ const { performUpgrade } = await import('../version-check.js');
657
+ const success = await performUpgrade();
658
+ if (success) {
659
+ addMessage('system', 'Upgrade complete! Restarting...');
660
+ const { spawn } = await import('child_process');
661
+ const child = spawn(process.argv[0], process.argv.slice(1), {
662
+ stdio: 'inherit',
663
+ detached: true,
664
+ });
665
+ child.unref();
666
+ process.exit(0);
667
+ }
668
+ else {
669
+ addMessage('error', 'Upgrade failed. Try: npm install -g @calliopelabs/cli@latest');
670
+ }
671
+ }
672
+ catch (e) {
673
+ addMessage('error', `Upgrade failed: ${e instanceof Error ? e.message : String(e)}`);
674
+ }
675
+ setLatestVersion(null);
676
+ }, [addMessage]);
677
+ // Cycle through modes
678
+ const cycleMode = useCallback(() => {
679
+ setMode(current => {
680
+ const modes = ['plan', 'hybrid', 'work'];
681
+ const idx = modes.indexOf(current);
682
+ const next = modes[(idx + 1) % modes.length];
683
+ return next;
684
+ });
685
+ }, []);
686
+ // Handle Escape key - cancel operation if processing, otherwise show hint
687
+ const handleEscape = useCallback(() => {
688
+ if (isProcessing) {
689
+ // Cancel current operation
690
+ setIsProcessing(false);
691
+ setThinkingState(null);
692
+ setStreamingResponse('');
693
+ setLoopActive(false);
694
+ setEditingQueueIndex(null);
695
+ addMessage('system', '⏹ Operation cancelled. Use /exit to quit.');
696
+ }
697
+ else if (modalMode !== 'none') {
698
+ // Close any open modal
699
+ setModalMode('none');
700
+ setPendingComplexPrompt(null);
701
+ }
702
+ else {
703
+ // Not processing - show hint instead of exiting
704
+ addMessage('system', '💡 Use /exit to quit, or Ctrl+C.');
705
+ }
706
+ }, [isProcessing, modalMode, addMessage]);
707
+ // Handle direct send (Shift+Enter) - interrupts current operation and sends immediately
708
+ const handleDirectSend = useCallback((msg) => {
709
+ // Stop current processing
710
+ setIsProcessing(false);
711
+ setThinkingState(null);
712
+ setStreamingResponse('');
713
+ setEditingQueueIndex(null);
714
+ // Show what happened
715
+ addMessage('system', '⚡ Direct send - interrupting current operation');
716
+ addMessage('user', msg);
717
+ // Start new agent run with this message
718
+ setIsProcessing(true);
719
+ runAgent(msg).finally(() => {
720
+ setIsProcessing(false);
721
+ setThinkingState(null);
722
+ setStreamingResponse('');
723
+ setEditingQueueIndex(null);
724
+ });
725
+ }, [addMessage, runAgent]);
726
+ // Streaming response component (reused across layouts)
727
+ const StreamingResponseBox = streamingResponse ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "cyan", children: ["\u2727 ", getCurrentCompanion().name, ":"] }), streamingResponse.split('\n').map((line, i) => (_jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "\u2502" }), " ", line] }, i))), _jsx(Text, { color: "cyan", children: "\u258C" })] })) : null;
728
+ // Thinking/Processing indicator component with state transitions
729
+ const ProcessingBox = (_jsxs(_Fragment, { children: [transition && (_jsx(StateTransition, { from: transition.from, to: transition.to, onComplete: () => setTransition(null) })), isProcessing && thinkingState && !streamingResponse && _jsx(ThinkingDisplay, { state: thinkingState }), isProcessing && !thinkingState && !streamingResponse && _jsx(ProcessingIndicator, { label: "Waiting for response" }), isProcessing && streamingResponse && _jsx(StreamingIndicator, { activity: activityState ?? undefined })] }));
730
+ // Render based on layout
731
+ return (_jsx(HUDFrame, { width: width, children: _jsxs(Box, { flexDirection: "column", width: width, children: [layout === 'split' && (_jsxs(Box, { flexDirection: "row", width: width, children: [_jsxs(Box, { flexDirection: "column", width: "50%", children: [_jsx(Text, { color: "yellow", dimColor: true, children: "\u2500 Tools \u2500" }), _jsx(MessageHistory, { messages: messages, collapseSettings: collapseSettings }), ProcessingBox] }), _jsxs(Box, { flexDirection: "column", width: "50%", borderStyle: "single", borderLeft: true, borderColor: "gray", children: [_jsx(Text, { color: "cyan", dimColor: true, children: "\u2500 Response \u2500" }), StreamingResponseBox] })] })), layout === 'classic' && (_jsxs(_Fragment, { children: [_jsx(MessageHistory, { messages: messages, collapseSettings: collapseSettings }), ProcessingBox, StreamingResponseBox] })), layout === 'response-top' && (_jsxs(_Fragment, { children: [StreamingResponseBox, _jsx(MessageHistory, { messages: messages, collapseSettings: collapseSettings }), ProcessingBox] })), layout === 'response-bottom' && (_jsxs(_Fragment, { children: [_jsx(MessageHistory, { messages: messages, collapseSettings: collapseSettings }), ProcessingBox, StreamingResponseBox] })), layout === 'zen' && (_jsxs(_Fragment, { children: [_jsx(MessageHistory, { messages: messages.filter(m => m.type === 'user' || m.type === 'assistant'), collapseSettings: { collapseTools: true, collapseThinking: true, toolDisplayLimit: 0 } }), ProcessingBox, StreamingResponseBox] })), layout === 'focus' && (_jsxs(_Fragment, { children: [StreamingResponseBox, ProcessingBox, _jsx(MessageHistory, { messages: messages, collapseSettings: { collapseTools: true, collapseThinking: true, toolDisplayLimit: 3 } })] })), layout === 'dashboard' && (_jsxs(_Fragment, { children: [_jsxs(Text, { dimColor: true, children: [' ', stats.inputTokens ? `tokens: ${stats.inputTokens}/${stats.outputTokens}` : '', stats.cost ? ` | cost: $${stats.cost.toFixed(4)}` : '', model ? ` | ${model}` : '', ` | ${getCurrentCompanion().name}`] }), StreamingResponseBox, ProcessingBox, _jsx(MessageHistory, { messages: messages, collapseSettings: collapseSettings })] })), layout === 'minimal' && (_jsxs(_Fragment, { children: [_jsx(MessageHistory, { messages: messages, collapseSettings: collapseSettings }), ProcessingBox, StreamingResponseBox] })), debugEnabled && (_jsx(Box, { marginY: 0, children: _jsxs(Text, { dimColor: true, children: ["[dbg] proc=", isProcessing ? 'Y' : 'N', " think=", thinkingState ? 'Y' : 'N', " stream=", streamingResponse.length, " mode=", mode, " queue=", queuedMessages.length, " activity=", activityState?.action || 'none'] }) })), modalMode === 'model' && availableModels.length > 0 && (_jsx(ModelSelector, { models: availableModels, onSelect: handleModelSelect, onCancel: handleModalCancel })), modalMode === 'sessions' && (_jsx(SessionSelector, { sessions: availableSessions, onSelect: (session) => {
732
+ // Load history from selected session
733
+ addMessage('system', `Loading session: ${session.projectName}...`);
734
+ addMessage('system', `Session path: ${session.projectPath}\nTo load this session, run calliope from that directory.`);
735
+ setModalMode('none');
736
+ }, onDelete: (session) => {
737
+ if (storage.deleteSession(session.id)) {
738
+ addMessage('system', `🗑️ Deleted session: ${session.projectName}`);
739
+ // Refresh the list
740
+ setAvailableSessions(prev => prev.filter(s => s.id !== session.id));
741
+ }
742
+ else {
743
+ addMessage('error', `Failed to delete session: ${session.projectName}`);
744
+ }
745
+ }, onCancel: handleModalCancel })), modalMode === 'upgrade' && latestVersion && (_jsx(UpgradePrompt, { currentVersion: getVersion(), latestVersion: latestVersion, onConfirm: handleUpgradeConfirm, onCancel: handleModalCancel })), modalMode === 'session-resume' && previousSession && (_jsx(SessionResumePrompt, { session: previousSession, onResume: () => {
746
+ // Load chat history into context
747
+ const history = storage.getChatHistory(20);
748
+ if (history.length > 0) {
749
+ for (const msg of history) {
750
+ if (msg.role === 'user' || msg.role === 'assistant') {
751
+ llmMessages.current.push({
752
+ role: msg.role,
753
+ content: msg.content,
754
+ });
755
+ }
756
+ }
757
+ addMessage('system', `✓ Resumed session with ${history.length} messages loaded`);
758
+ setContextTokens(estimateContextTokens());
759
+ }
760
+ setModalMode('none');
761
+ setPreviousSession(null);
762
+ }, onNew: () => {
763
+ addMessage('system', '✓ Starting fresh session');
764
+ setModalMode('none');
765
+ setPreviousSession(null);
766
+ } })), modalMode === 'complexity-warning' && pendingComplexPrompt && (_jsx(ComplexityWarning, { reason: pendingComplexPrompt.complexity.reason || 'Complex operation detected', prompt: typeof pendingComplexPrompt.prompt === 'string' ? pendingComplexPrompt.prompt : undefined, onProceed: async () => {
767
+ setModalMode('none');
768
+ const prompt = pendingComplexPrompt.prompt;
769
+ setPendingComplexPrompt(null);
770
+ // Proceed with execution
771
+ saveUndoState();
772
+ addMessage('user', typeof prompt === 'string' ? prompt : JSON.stringify(prompt));
773
+ setIsProcessing(true);
774
+ try {
775
+ await runAgent(prompt);
776
+ }
777
+ finally {
778
+ setIsProcessing(false);
779
+ }
780
+ }, onPlan: () => {
781
+ setModalMode('none');
782
+ const prompt = pendingComplexPrompt.prompt;
783
+ setPendingComplexPrompt(null);
784
+ // Switch to plan mode and proceed
785
+ setMode('plan');
786
+ addMessage('system', '📋 Switched to Plan mode - I\'ll describe what I would do without executing.');
787
+ saveUndoState();
788
+ addMessage('user', typeof prompt === 'string' ? prompt : JSON.stringify(prompt));
789
+ setIsProcessing(true);
790
+ runAgent(prompt).finally(() => setIsProcessing(false));
791
+ }, onCancel: () => {
792
+ setModalMode('none');
793
+ setPendingComplexPrompt(null);
794
+ addMessage('system', 'Operation cancelled.');
795
+ } })), modalMode === 'keys' && (_jsx(KeybindingsModal, { onClose: () => setModalMode('none') })), modalMode === 'theme-picker' && (_jsx(ThemePicker, { currentLayout: layout, currentSkin: getCurrentSkin().name, currentPalette: getCurrentPalette().name, currentCompanion: getCurrentCompanion().name, onApply: (selection) => {
796
+ // Apply all selections
797
+ setLayout(selection.layout);
798
+ config.set('layout', selection.layout);
799
+ applySkin(selection.skin);
800
+ config.set('activeSkin', selection.skin);
801
+ applyPalette(selection.palette);
802
+ config.set('activePalette', selection.palette);
803
+ applyCompanion(selection.companion);
804
+ config.set('activeCompanion', selection.companion);
805
+ const changes = [];
806
+ if (selection.layout !== layout)
807
+ changes.push(`layout=${selection.layout}`);
808
+ if (selection.skin !== getCurrentSkin().name)
809
+ changes.push(`skin=${selection.skin}`);
810
+ if (selection.palette !== getCurrentPalette().name)
811
+ changes.push(`palette=${selection.palette}`);
812
+ if (selection.companion !== getCurrentCompanion().name)
813
+ changes.push(`companion=${selection.companion}`);
814
+ addMessage('system', changes.length > 0
815
+ ? `Theme applied: ${changes.join(', ')}`
816
+ : 'Theme unchanged.');
817
+ setModalMode('none');
818
+ }, onCancel: () => setModalMode('none') })), modalMode === 'pack-picker' && (_jsx(PackPicker, { onApply: async (packName) => {
819
+ setModalMode('none');
820
+ // Run transition animation if defined
821
+ const targetPack = getThemePack(packName);
822
+ if (targetPack?.skin.splash?.transition) {
823
+ await renderTransition(targetPack.skin.splash.transition);
824
+ }
825
+ const success = applyThemePack(packName, getCompanionMode());
826
+ if (success) {
827
+ const pack = getCurrentPack();
828
+ config.set('activeThemePack', packName);
829
+ config.set('activeSkin', pack.skin.name);
830
+ config.set('activePalette', pack.palette.name);
831
+ const companion = getCompanionMode() === 'professional'
832
+ ? pack.companions.professional
833
+ : pack.companions.immersive;
834
+ config.set('activeCompanion', companion.name);
835
+ addMessage('system', `Theme pack: ${packName}\n` +
836
+ ` Skin: ${pack.skin.name}, Palette: ${pack.palette.name}, Companion: ${companion.name}\n` +
837
+ ` "${companion.greeting}"`);
838
+ }
839
+ }, onCancel: () => setModalMode('none') })), _jsx(ChatInput, { value: input, onChange: handleInputChange, onSubmit: handleSubmit, onEscape: handleEscape, onCycleMode: cycleMode, disabled: isModalActive, isProcessing: isProcessing, queuedCount: queuedMessages.length, queuedMessages: queuedMessages, editingQueueIndex: editingQueueIndex, onQueueMessage: (msg) => {
840
+ setQueuedMessages(prev => [...prev, msg]);
841
+ addMessage('system', `📨 Queued: "${msg.substring(0, 50)}${msg.length > 50 ? '...' : ''}"`);
842
+ }, onEditQueuedMessage: handleEditQueuedMessage, onSetEditingQueueIndex: setEditingQueueIndex, onDirectSend: handleDirectSend, cwd: process.cwd(), suggestions: suggestions, onSuggestionsChange: setSuggestions, onNavigateHistory: navigateHistory,
843
+ // Smart suggestions context
844
+ currentMode: mode, contextPercentage: Math.round((contextTokens / getModelContextLimit(actualProvider, actualModel)) * 100), recentCommands: recentCommands, hasGitRepo: hasGitRepo }), _jsx(StatusBar, { provider: actualProvider, model: actualModel, mode: mode, stats: stats, contextTokens: contextTokens, breakerHealth: config.get('circuitBreakersEnabled') !== false ? breakerHealth : undefined, smartRouteActive: smartRouteActive, width: width })] }) }));
845
+ }
846
+ // ============================================================================
847
+ // App Wrapper & Entry Point
848
+ // ============================================================================
849
+ function App() {
850
+ const [resetKey, setResetKey] = React.useState(0);
851
+ const handleReset = React.useCallback(() => {
852
+ setResetKey(k => k + 1);
853
+ }, []);
854
+ return (_jsx(ErrorBoundary, { onReset: handleReset, children: _jsx(TerminalChat, {}, resetKey) }));
855
+ }
856
+ // Print banner before Ink takes over (stays fixed at top)
857
+ export async function printBanner() {
858
+ const provider = selectProvider(config.get('defaultProvider'));
859
+ const model = config.get('defaultModel') || DEFAULT_MODELS[provider];
860
+ const skin = getCurrentSkin();
861
+ const dim = '\x1b[2m';
862
+ const reset = '\x1b[0m';
863
+ // Run theme transition on startup if configured
864
+ if (skin.splash?.transition && skin.splash.transition.effect !== 'none') {
865
+ await renderTransition(skin.splash.transition);
866
+ }
867
+ if (skin.banner.style === 'none') {
868
+ // No banner
869
+ }
870
+ else if (skin.splash?.coloredArt && skin.splash.coloredArt.length > 0) {
871
+ // Rich colored banner from splash config
872
+ console.log();
873
+ if (skin.splash.entryAnimation && skin.splash.entryAnimation !== 'none') {
874
+ // Animated splash
875
+ const coloredLines = skin.splash.coloredArt.map(l => colorFg(l.text, l.color));
876
+ await renderSplashAnimation(coloredLines, skin.splash.entryAnimation, skin.splash.animationSpeed ?? 50);
877
+ }
878
+ else {
879
+ // Static colored banner
880
+ const banner = renderColoredBanner(skin.splash.coloredArt, skin.banner.tagline);
881
+ console.log(banner);
882
+ }
883
+ console.log();
884
+ if (skin.banner.tagline && skin.splash.entryAnimation) {
885
+ console.log(`${dim} ${skin.banner.tagline}${reset}`);
886
+ console.log();
887
+ }
888
+ }
889
+ else {
890
+ // Standard banner (existing behavior)
891
+ console.log();
892
+ for (const line of skin.banner.art) {
893
+ if (line.includes('\x1b[')) {
894
+ console.log(line);
895
+ }
896
+ else {
897
+ console.log(paletteColorize(line, 'primary'));
898
+ }
899
+ }
900
+ console.log();
901
+ if (skin.banner.tagline) {
902
+ console.log(`${dim} ${skin.banner.tagline}${reset}`);
903
+ console.log();
904
+ }
905
+ }
906
+ const companion = getCurrentCompanion();
907
+ console.log(`${dim} v${getVersion()} | ${provider}:${model}${reset}`);
908
+ console.log(`${dim} /help for commands | ESC to exit${reset}`);
909
+ if (companion.greeting) {
910
+ console.log(`${dim} ${companion.greeting}${reset}`);
911
+ }
912
+ console.log();
913
+ }
914
+ export async function startInkCLI(options = {}) {
915
+ // Set module-level agterm state
916
+ moduleAgtermEnabled = options.agtermEnabled ?? false;
917
+ // Print banner BEFORE Ink starts - it stays fixed at the top
918
+ await printBanner();
919
+ const { waitUntilExit } = render(_jsx(App, {}), {
920
+ patchConsole: true, // Prevent console.log during session from mixing with Ink
921
+ });
922
+ await waitUntilExit();
923
+ // Session cleanup
924
+ recording.stopRecording();
925
+ sessionTimeout.clearTimers();
926
+ idleEviction.stopMonitor();
927
+ }
928
+ //# sourceMappingURL=index.js.map