@compilr-dev/cli 0.5.0 → 0.5.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 (587) hide show
  1. package/LICENSE +108 -0
  2. package/README.md +237 -69
  3. package/dist/.tsbuildinfo.app +1 -0
  4. package/dist/.tsbuildinfo.data +1 -0
  5. package/dist/.tsbuildinfo.domain +1 -0
  6. package/dist/.tsbuildinfo.foundation +1 -0
  7. package/dist/agent.d.ts +61 -4
  8. package/dist/agent.js +241 -245
  9. package/dist/anchors/index.d.ts +1 -1
  10. package/dist/anchors/index.js +1 -1
  11. package/dist/anchors/project-anchors.d.ts +2 -2
  12. package/dist/anchors/project-anchors.js +1 -1
  13. package/dist/auth/api-client.d.ts +124 -0
  14. package/dist/auth/api-client.js +261 -0
  15. package/dist/auth/index.d.ts +172 -0
  16. package/dist/auth/index.js +545 -0
  17. package/dist/auth/storage.d.ts +52 -0
  18. package/dist/auth/storage.js +118 -0
  19. package/dist/changelog/index.d.ts +16 -0
  20. package/dist/changelog/index.js +24 -0
  21. package/dist/changelog/releases.d.ts +17 -0
  22. package/dist/changelog/releases.js +63 -0
  23. package/dist/commands-v2/handlers/auth.d.ts +10 -0
  24. package/dist/commands-v2/handlers/auth.js +118 -0
  25. package/dist/commands-v2/handlers/background.d.ts +14 -0
  26. package/dist/commands-v2/handlers/background.js +276 -0
  27. package/dist/commands-v2/handlers/context.js +286 -81
  28. package/dist/commands-v2/handlers/core.d.ts +1 -0
  29. package/dist/commands-v2/handlers/core.js +133 -8
  30. package/dist/commands-v2/handlers/debug.js +18 -0
  31. package/dist/commands-v2/handlers/delegations.d.ts +8 -0
  32. package/dist/commands-v2/handlers/delegations.js +29 -0
  33. package/dist/commands-v2/handlers/files.d.ts +8 -0
  34. package/dist/commands-v2/handlers/files.js +162 -0
  35. package/dist/commands-v2/handlers/filter.d.ts +9 -0
  36. package/dist/commands-v2/handlers/filter.js +130 -0
  37. package/dist/commands-v2/handlers/games.d.ts +7 -0
  38. package/dist/commands-v2/handlers/games.js +57 -0
  39. package/dist/commands-v2/handlers/index.d.ts +13 -0
  40. package/dist/commands-v2/handlers/index.js +39 -0
  41. package/dist/commands-v2/handlers/mcp.d.ts +8 -0
  42. package/dist/commands-v2/handlers/mcp.js +39 -0
  43. package/dist/commands-v2/handlers/notifications.d.ts +9 -0
  44. package/dist/commands-v2/handlers/notifications.js +34 -0
  45. package/dist/commands-v2/handlers/project.js +295 -31
  46. package/dist/commands-v2/handlers/reset.d.ts +11 -0
  47. package/dist/commands-v2/handlers/reset.js +118 -0
  48. package/dist/commands-v2/handlers/session.d.ts +161 -0
  49. package/dist/commands-v2/handlers/session.js +805 -0
  50. package/dist/commands-v2/handlers/settings.d.ts +2 -0
  51. package/dist/commands-v2/handlers/settings.js +217 -35
  52. package/dist/commands-v2/handlers/tasks.d.ts +5 -0
  53. package/dist/commands-v2/handlers/tasks.js +36 -0
  54. package/dist/commands-v2/handlers/team.d.ts +9 -0
  55. package/dist/commands-v2/handlers/team.js +549 -0
  56. package/dist/commands-v2/handlers/terminals.d.ts +9 -0
  57. package/dist/commands-v2/handlers/terminals.js +34 -0
  58. package/dist/commands-v2/index.d.ts +3 -2
  59. package/dist/commands-v2/index.js +4 -1
  60. package/dist/commands-v2/registry.d.ts +15 -0
  61. package/dist/commands-v2/registry.js +34 -0
  62. package/dist/commands-v2/types.d.ts +81 -3
  63. package/dist/commands.js +13 -0
  64. package/dist/compilr-diff-companion.vsix +0 -0
  65. package/dist/db/index.js +98 -4
  66. package/dist/db/repositories/document-repository.d.ts +2 -0
  67. package/dist/db/repositories/document-repository.js +6 -1
  68. package/dist/db/repositories/index.d.ts +2 -0
  69. package/dist/db/repositories/index.js +1 -0
  70. package/dist/db/repositories/plan-repository.d.ts +101 -0
  71. package/dist/db/repositories/plan-repository.js +275 -0
  72. package/dist/db/repositories/project-repository.d.ts +6 -0
  73. package/dist/db/repositories/project-repository.js +41 -0
  74. package/dist/db/repositories/work-item-repository.d.ts +15 -0
  75. package/dist/db/repositories/work-item-repository.js +69 -4
  76. package/dist/db/schema.d.ts +40 -3
  77. package/dist/db/schema.js +66 -3
  78. package/dist/episodes/index.d.ts +20 -0
  79. package/dist/episodes/index.js +27 -0
  80. package/dist/episodes/recorder.d.ts +51 -0
  81. package/dist/episodes/recorder.js +195 -0
  82. package/dist/episodes/significant-work.d.ts +21 -0
  83. package/dist/episodes/significant-work.js +56 -0
  84. package/dist/episodes/store.d.ts +38 -0
  85. package/dist/episodes/store.js +199 -0
  86. package/dist/episodes/types.d.ts +35 -0
  87. package/dist/episodes/types.js +6 -0
  88. package/dist/episodes/work-at-risk.d.ts +12 -0
  89. package/dist/episodes/work-at-risk.js +38 -0
  90. package/dist/episodes/work-summary-anchor.d.ts +23 -0
  91. package/dist/episodes/work-summary-anchor.js +73 -0
  92. package/dist/games/coins.d.ts +66 -0
  93. package/dist/games/coins.js +165 -0
  94. package/dist/games/game-base.d.ts +84 -0
  95. package/dist/games/game-base.js +204 -0
  96. package/dist/games/index.d.ts +16 -0
  97. package/dist/games/index.js +49 -0
  98. package/dist/games/scores.d.ts +69 -0
  99. package/dist/games/scores.js +191 -0
  100. package/dist/games/tetris/board.d.ts +59 -0
  101. package/dist/games/tetris/board.js +170 -0
  102. package/dist/games/tetris/index.d.ts +109 -0
  103. package/dist/games/tetris/index.js +610 -0
  104. package/dist/games/tetris/pieces.d.ts +44 -0
  105. package/dist/games/tetris/pieces.js +271 -0
  106. package/dist/games/tetris/renderer.d.ts +26 -0
  107. package/dist/games/tetris/renderer.js +77 -0
  108. package/dist/guide/guide-content.d.ts +23 -0
  109. package/dist/guide/guide-content.js +196 -0
  110. package/dist/guide/index.d.ts +8 -0
  111. package/dist/guide/index.js +7 -0
  112. package/dist/guide/shared-content.d.ts +37 -0
  113. package/dist/guide/shared-content.js +1272 -0
  114. package/dist/guide/tutorial-helpers.d.ts +57 -0
  115. package/dist/guide/tutorial-helpers.js +147 -0
  116. package/dist/handlers/ask-user-handlers.d.ts +32 -0
  117. package/dist/handlers/ask-user-handlers.js +104 -0
  118. package/dist/handlers/delegation-handlers.d.ts +34 -0
  119. package/dist/handlers/delegation-handlers.js +291 -0
  120. package/dist/handlers/permission-handler.d.ts +30 -0
  121. package/dist/handlers/permission-handler.js +205 -0
  122. package/dist/index.d.ts +11 -1
  123. package/dist/index.js +448 -271
  124. package/dist/input-handlers/memory-handler.d.ts +1 -1
  125. package/dist/input-handlers/memory-handler.js +2 -1
  126. package/dist/models/index.d.ts +10 -0
  127. package/dist/models/index.js +12 -0
  128. package/dist/models/model-registry.d.ts +38 -0
  129. package/dist/models/model-registry.js +69 -0
  130. package/dist/models/model-tiers.d.ts +28 -0
  131. package/dist/models/model-tiers.js +71 -0
  132. package/dist/models/model-validation.d.ts +25 -0
  133. package/dist/models/model-validation.js +291 -0
  134. package/dist/models/ollama-models.d.ts +73 -0
  135. package/dist/models/ollama-models.js +178 -0
  136. package/dist/models/provider-types.d.ts +6 -0
  137. package/dist/models/provider-types.js +1 -0
  138. package/dist/models/providers.d.ts +35 -0
  139. package/dist/models/providers.js +58 -0
  140. package/dist/models/types.d.ts +4 -0
  141. package/dist/models/types.js +4 -0
  142. package/dist/multi-agent/activity.d.ts +21 -0
  143. package/dist/multi-agent/activity.js +34 -0
  144. package/dist/multi-agent/agent-selection.d.ts +55 -0
  145. package/dist/multi-agent/agent-selection.js +90 -0
  146. package/dist/multi-agent/artifacts.d.ts +197 -0
  147. package/dist/multi-agent/artifacts.js +379 -0
  148. package/dist/multi-agent/checkpointer.d.ts +138 -0
  149. package/dist/multi-agent/checkpointer.js +471 -0
  150. package/dist/multi-agent/collision-utils.d.ts +16 -0
  151. package/dist/multi-agent/collision-utils.js +28 -0
  152. package/dist/multi-agent/context-resolver.d.ts +97 -0
  153. package/dist/multi-agent/context-resolver.js +316 -0
  154. package/dist/multi-agent/custom-agents.d.ts +83 -0
  155. package/dist/multi-agent/custom-agents.js +227 -0
  156. package/dist/multi-agent/delegation-tracker.d.ts +157 -0
  157. package/dist/multi-agent/delegation-tracker.js +243 -0
  158. package/dist/multi-agent/file-lock-hook.d.ts +29 -0
  159. package/dist/multi-agent/file-lock-hook.js +97 -0
  160. package/dist/multi-agent/file-locks.d.ts +58 -0
  161. package/dist/multi-agent/file-locks.js +194 -0
  162. package/dist/multi-agent/index.d.ts +24 -0
  163. package/dist/multi-agent/index.js +30 -0
  164. package/dist/multi-agent/mention-parser.d.ts +64 -0
  165. package/dist/multi-agent/mention-parser.js +146 -0
  166. package/dist/multi-agent/notification-manager.d.ts +84 -0
  167. package/dist/multi-agent/notification-manager.js +224 -0
  168. package/dist/multi-agent/pending-requests.d.ts +122 -0
  169. package/dist/multi-agent/pending-requests.js +155 -0
  170. package/dist/multi-agent/session-registry.d.ts +139 -0
  171. package/dist/multi-agent/session-registry.js +514 -0
  172. package/dist/multi-agent/shared-context.d.ts +293 -0
  173. package/dist/multi-agent/shared-context.js +671 -0
  174. package/dist/multi-agent/skill-requirements.d.ts +66 -0
  175. package/dist/multi-agent/skill-requirements.js +178 -0
  176. package/dist/multi-agent/task-assignment.d.ts +69 -0
  177. package/dist/multi-agent/task-assignment.js +123 -0
  178. package/dist/multi-agent/task-suggestion.d.ts +31 -0
  179. package/dist/multi-agent/task-suggestion.js +72 -0
  180. package/dist/multi-agent/team-agent.d.ts +201 -0
  181. package/dist/multi-agent/team-agent.js +488 -0
  182. package/dist/multi-agent/team.d.ts +286 -0
  183. package/dist/multi-agent/team.js +610 -0
  184. package/dist/multi-agent/tool-config.d.ts +110 -0
  185. package/dist/multi-agent/tool-config.js +661 -0
  186. package/dist/multi-agent/types.d.ts +211 -0
  187. package/dist/multi-agent/types.js +617 -0
  188. package/dist/prompts/plan-mode-prompt.d.ts +11 -0
  189. package/dist/prompts/plan-mode-prompt.js +95 -0
  190. package/dist/repl-helpers.js +5 -2
  191. package/dist/repl-v2.d.ts +401 -2
  192. package/dist/repl-v2.js +2588 -65
  193. package/dist/session/index.d.ts +6 -0
  194. package/dist/session/index.js +6 -0
  195. package/dist/session/project-session-manager.d.ts +158 -0
  196. package/dist/session/project-session-manager.js +650 -0
  197. package/dist/settings/index.d.ts +133 -13
  198. package/dist/settings/index.js +329 -24
  199. package/dist/settings/mcp-config.d.ts +76 -0
  200. package/dist/settings/mcp-config.js +143 -0
  201. package/dist/settings/paths.d.ts +4 -0
  202. package/dist/settings/paths.js +6 -0
  203. package/dist/shared-handlers.d.ts +62 -0
  204. package/dist/shared-handlers.js +48 -0
  205. package/dist/system-prompt/builder.d.ts +5 -0
  206. package/dist/system-prompt/builder.js +4 -0
  207. package/dist/system-prompt/index.d.ts +18 -0
  208. package/dist/system-prompt/index.js +18 -0
  209. package/dist/system-prompt/modules.d.ts +5 -0
  210. package/dist/system-prompt/modules.js +4 -0
  211. package/dist/tabbed-menu.js +2 -1
  212. package/dist/templates/compilr-md-import.d.ts +16 -0
  213. package/dist/templates/compilr-md-import.js +241 -0
  214. package/dist/templates/compilr-md.js +10 -61
  215. package/dist/templates/config-json.d.ts +1 -25
  216. package/dist/templates/index.d.ts +2 -0
  217. package/dist/templates/index.js +34 -73
  218. package/dist/tool-names.d.ts +113 -0
  219. package/dist/tool-names.js +239 -0
  220. package/dist/tools/ask-user-simple.d.ts +1 -1
  221. package/dist/tools/ask-user-simple.js +2 -1
  222. package/dist/tools/ask-user.d.ts +1 -1
  223. package/dist/tools/ask-user.js +2 -1
  224. package/dist/tools/backlog.d.ts +2 -2
  225. package/dist/tools/backlog.js +1 -1
  226. package/dist/tools/db-tools.d.ts +13 -61
  227. package/dist/tools/db-tools.js +12 -13
  228. package/dist/tools/delegate-background.d.ts +27 -0
  229. package/dist/tools/delegate-background.js +115 -0
  230. package/dist/tools/delegate.d.ts +22 -0
  231. package/dist/tools/delegate.js +97 -0
  232. package/dist/tools/delegation-status.d.ts +16 -0
  233. package/dist/tools/delegation-status.js +128 -0
  234. package/dist/tools/guide-tool.d.ts +12 -0
  235. package/dist/tools/guide-tool.js +59 -0
  236. package/dist/tools/handoff.d.ts +25 -0
  237. package/dist/tools/handoff.js +99 -0
  238. package/dist/tools/meta-tools.d.ts +26 -0
  239. package/dist/tools/meta-tools.js +47 -0
  240. package/dist/tools/platform-adapter.d.ts +35 -0
  241. package/dist/tools/platform-adapter.js +404 -0
  242. package/dist/tools/project-db.d.ts +5 -73
  243. package/dist/tools/project-db.js +5 -336
  244. package/dist/tools.d.ts +67 -2
  245. package/dist/tools.js +240 -48
  246. package/dist/ui/autocomplete-controller.d.ts +42 -0
  247. package/dist/ui/autocomplete-controller.js +384 -0
  248. package/dist/ui/base/index.d.ts +1 -1
  249. package/dist/ui/base/index.js +1 -1
  250. package/dist/ui/base/overlay-base-v2.d.ts +10 -0
  251. package/dist/ui/base/overlay-base-v2.js +14 -0
  252. package/dist/ui/base/render-utils.d.ts +19 -0
  253. package/dist/ui/base/render-utils.js +25 -0
  254. package/dist/ui/base/tabbed-list-overlay-v2.d.ts +16 -1
  255. package/dist/ui/base/tabbed-list-overlay-v2.js +19 -1
  256. package/dist/ui/constants/labels.d.ts +14 -0
  257. package/dist/ui/constants/labels.js +52 -0
  258. package/dist/ui/conversation-store.d.ts +55 -0
  259. package/dist/ui/conversation-store.js +107 -0
  260. package/dist/ui/conversation.js +11 -13
  261. package/dist/ui/diff.d.ts +7 -1
  262. package/dist/ui/diff.js +85 -48
  263. package/dist/ui/ephemeral.js +3 -9
  264. package/dist/ui/file-autocomplete.d.ts +24 -0
  265. package/dist/ui/file-autocomplete.js +56 -0
  266. package/dist/ui/footer-renderer.d.ts +69 -0
  267. package/dist/ui/footer-renderer.js +431 -0
  268. package/dist/ui/footer.d.ts +74 -7
  269. package/dist/ui/footer.js +173 -16
  270. package/dist/ui/input-controller.d.ts +51 -0
  271. package/dist/ui/input-controller.js +176 -0
  272. package/dist/ui/input-prompt.d.ts +19 -0
  273. package/dist/ui/input-prompt.js +206 -14
  274. package/dist/ui/keyboard-handler.d.ts +57 -0
  275. package/dist/ui/keyboard-handler.js +557 -0
  276. package/dist/ui/live-region-facade.d.ts +42 -0
  277. package/dist/ui/live-region-facade.js +205 -0
  278. package/dist/ui/live-region.d.ts +0 -4
  279. package/dist/ui/live-region.js +6 -14
  280. package/dist/ui/mascot/renderer.d.ts +1 -1
  281. package/dist/ui/mascot/renderer.js +37 -2
  282. package/dist/ui/overlay/data/tutorial-content.d.ts +9 -0
  283. package/dist/ui/overlay/data/tutorial-content.js +9 -0
  284. package/dist/ui/overlay/data/tutorial-registry.d.ts +12 -0
  285. package/dist/ui/overlay/data/tutorial-registry.js +116 -0
  286. package/dist/ui/overlay/data/tutorial-types.d.ts +35 -0
  287. package/dist/ui/overlay/data/tutorial-types.js +6 -0
  288. package/dist/ui/overlay/data/tutorials/basics/first-conversation.d.ts +7 -0
  289. package/dist/ui/overlay/data/tutorials/basics/first-conversation.js +220 -0
  290. package/dist/ui/overlay/data/tutorials/basics/first-project.d.ts +7 -0
  291. package/dist/ui/overlay/data/tutorials/basics/first-project.js +284 -0
  292. package/dist/ui/overlay/data/tutorials/basics/navigation.d.ts +8 -0
  293. package/dist/ui/overlay/data/tutorials/basics/navigation.js +22 -0
  294. package/dist/ui/overlay/data/tutorials/basics/welcome.d.ts +7 -0
  295. package/dist/ui/overlay/data/tutorials/basics/welcome.js +174 -0
  296. package/dist/ui/overlay/data/tutorials/config/context-management.d.ts +7 -0
  297. package/dist/ui/overlay/data/tutorials/config/context-management.js +158 -0
  298. package/dist/ui/overlay/data/tutorials/config/mcp-servers.d.ts +8 -0
  299. package/dist/ui/overlay/data/tutorials/config/mcp-servers.js +155 -0
  300. package/dist/ui/overlay/data/tutorials/config/model-selection.d.ts +7 -0
  301. package/dist/ui/overlay/data/tutorials/config/model-selection.js +162 -0
  302. package/dist/ui/overlay/data/tutorials/config/permissions-safety.d.ts +7 -0
  303. package/dist/ui/overlay/data/tutorials/config/permissions-safety.js +163 -0
  304. package/dist/ui/overlay/data/tutorials/config/settings-config.d.ts +7 -0
  305. package/dist/ui/overlay/data/tutorials/config/settings-config.js +166 -0
  306. package/dist/ui/overlay/data/tutorials/planning/arch.d.ts +7 -0
  307. package/dist/ui/overlay/data/tutorials/planning/arch.js +168 -0
  308. package/dist/ui/overlay/data/tutorials/planning/backlog.d.ts +7 -0
  309. package/dist/ui/overlay/data/tutorials/planning/backlog.js +103 -0
  310. package/dist/ui/overlay/data/tutorials/planning/build.d.ts +7 -0
  311. package/dist/ui/overlay/data/tutorials/planning/build.js +173 -0
  312. package/dist/ui/overlay/data/tutorials/planning/design.d.ts +7 -0
  313. package/dist/ui/overlay/data/tutorials/planning/design.js +205 -0
  314. package/dist/ui/overlay/data/tutorials/planning/docs.d.ts +7 -0
  315. package/dist/ui/overlay/data/tutorials/planning/docs.js +143 -0
  316. package/dist/ui/overlay/data/tutorials/planning/prd.d.ts +7 -0
  317. package/dist/ui/overlay/data/tutorials/planning/prd.js +173 -0
  318. package/dist/ui/overlay/data/tutorials/planning/scaffold.d.ts +7 -0
  319. package/dist/ui/overlay/data/tutorials/planning/scaffold.js +164 -0
  320. package/dist/ui/overlay/data/tutorials/planning/sketch.d.ts +7 -0
  321. package/dist/ui/overlay/data/tutorials/planning/sketch.js +58 -0
  322. package/dist/ui/overlay/data/tutorials/projects/anchors.d.ts +7 -0
  323. package/dist/ui/overlay/data/tutorials/projects/anchors.js +248 -0
  324. package/dist/ui/overlay/data/tutorials/projects/import-project.d.ts +7 -0
  325. package/dist/ui/overlay/data/tutorials/projects/import-project.js +172 -0
  326. package/dist/ui/overlay/data/tutorials/projects/managing-projects.d.ts +8 -0
  327. package/dist/ui/overlay/data/tutorials/projects/managing-projects.js +212 -0
  328. package/dist/ui/overlay/data/tutorials/projects/new-project.d.ts +7 -0
  329. package/dist/ui/overlay/data/tutorials/projects/new-project.js +251 -0
  330. package/dist/ui/overlay/data/tutorials/projects/session-management.d.ts +7 -0
  331. package/dist/ui/overlay/data/tutorials/projects/session-management.js +169 -0
  332. package/dist/ui/overlay/data/tutorials/teams/background-execution.d.ts +7 -0
  333. package/dist/ui/overlay/data/tutorials/teams/background-execution.js +171 -0
  334. package/dist/ui/overlay/data/tutorials/teams/multi-terminal.d.ts +8 -0
  335. package/dist/ui/overlay/data/tutorials/teams/multi-terminal.js +147 -0
  336. package/dist/ui/overlay/data/tutorials/teams/task-assignment.d.ts +7 -0
  337. package/dist/ui/overlay/data/tutorials/teams/task-assignment.js +204 -0
  338. package/dist/ui/overlay/data/tutorials/teams/team-overview.d.ts +7 -0
  339. package/dist/ui/overlay/data/tutorials/teams/team-overview.js +165 -0
  340. package/dist/ui/overlay/data/tutorials/teams/working-with-agents.d.ts +7 -0
  341. package/dist/ui/overlay/data/tutorials/teams/working-with-agents.js +172 -0
  342. package/dist/ui/overlay/impl/agents-overlay-v2.js +6 -17
  343. package/dist/ui/overlay/impl/anchors-overlay-v2.js +30 -64
  344. package/dist/ui/overlay/impl/artifact-detail-overlay-v2.d.ts +43 -0
  345. package/dist/ui/overlay/impl/artifact-detail-overlay-v2.js +232 -0
  346. package/dist/ui/overlay/impl/artifact-overlay-v2.d.ts +40 -0
  347. package/dist/ui/overlay/impl/artifact-overlay-v2.js +115 -0
  348. package/dist/ui/overlay/impl/ask-user-overlay-v2.js +2 -5
  349. package/dist/ui/overlay/impl/background-overlay-v2.d.ts +40 -0
  350. package/dist/ui/overlay/impl/background-overlay-v2.js +147 -0
  351. package/dist/ui/overlay/impl/backlog-overlay-v2.d.ts +4 -1
  352. package/dist/ui/overlay/impl/backlog-overlay-v2.js +55 -16
  353. package/dist/ui/overlay/impl/changelog-overlay-v2.d.ts +44 -0
  354. package/dist/ui/overlay/impl/changelog-overlay-v2.js +165 -0
  355. package/dist/ui/overlay/impl/commands-overlay-v2.js +4 -6
  356. package/dist/ui/overlay/impl/config-overlay-v2.d.ts +12 -1
  357. package/dist/ui/overlay/impl/config-overlay-v2.js +164 -100
  358. package/dist/ui/overlay/impl/custom-agent-form-overlay-v2.d.ts +83 -0
  359. package/dist/ui/overlay/impl/custom-agent-form-overlay-v2.js +711 -0
  360. package/dist/ui/overlay/impl/dashboard-overlay-v2.d.ts +2 -0
  361. package/dist/ui/overlay/impl/dashboard-overlay-v2.js +26 -3
  362. package/dist/ui/overlay/impl/delegations-overlay-v2.d.ts +28 -0
  363. package/dist/ui/overlay/impl/delegations-overlay-v2.js +279 -0
  364. package/dist/ui/overlay/impl/docs-overlay-v2.js +12 -9
  365. package/dist/ui/overlay/impl/document-detail-overlay-v2.d.ts +7 -0
  366. package/dist/ui/overlay/impl/document-detail-overlay-v2.js +119 -78
  367. package/dist/ui/overlay/impl/filter-overlay-v2.d.ts +41 -0
  368. package/dist/ui/overlay/impl/filter-overlay-v2.js +110 -0
  369. package/dist/ui/overlay/impl/games-overlay-v2.d.ts +31 -0
  370. package/dist/ui/overlay/impl/games-overlay-v2.js +135 -0
  371. package/dist/ui/overlay/impl/help-overlay-v2.d.ts +26 -3
  372. package/dist/ui/overlay/impl/help-overlay-v2.js +20 -42
  373. package/dist/ui/overlay/impl/login-overlay-v2.d.ts +49 -0
  374. package/dist/ui/overlay/impl/login-overlay-v2.js +277 -0
  375. package/dist/ui/overlay/impl/mcp-overlay-v2.d.ts +63 -0
  376. package/dist/ui/overlay/impl/mcp-overlay-v2.js +907 -0
  377. package/dist/ui/overlay/impl/model-overlay-v2.d.ts +57 -13
  378. package/dist/ui/overlay/impl/model-overlay-v2.js +1086 -61
  379. package/dist/ui/overlay/impl/new-overlay-v2.d.ts +37 -6
  380. package/dist/ui/overlay/impl/new-overlay-v2.js +715 -65
  381. package/dist/ui/overlay/impl/notifications-overlay-v2.d.ts +20 -0
  382. package/dist/ui/overlay/impl/notifications-overlay-v2.js +116 -0
  383. package/dist/ui/overlay/impl/onboarding-wizard-overlay-v2.d.ts +76 -0
  384. package/dist/ui/overlay/impl/onboarding-wizard-overlay-v2.js +728 -0
  385. package/dist/ui/overlay/impl/pending-overlay-v2.d.ts +51 -0
  386. package/dist/ui/overlay/impl/pending-overlay-v2.js +445 -0
  387. package/dist/ui/overlay/impl/permission-overlay-v2.js +5 -5
  388. package/dist/ui/overlay/impl/permissions-overlay-v2.d.ts +85 -0
  389. package/dist/ui/overlay/impl/permissions-overlay-v2.js +820 -0
  390. package/dist/ui/overlay/impl/plan-approval-overlay-v2.d.ts +35 -0
  391. package/dist/ui/overlay/impl/plan-approval-overlay-v2.js +181 -0
  392. package/dist/ui/overlay/impl/project-edit-overlay-v2.d.ts +36 -0
  393. package/dist/ui/overlay/impl/project-edit-overlay-v2.js +195 -0
  394. package/dist/ui/overlay/impl/projects-overlay-v2.d.ts +1 -0
  395. package/dist/ui/overlay/impl/projects-overlay-v2.js +278 -44
  396. package/dist/ui/overlay/impl/reset-overlay-v2.d.ts +39 -0
  397. package/dist/ui/overlay/impl/reset-overlay-v2.js +107 -0
  398. package/dist/ui/overlay/impl/resume-overlay-v2.d.ts +60 -0
  399. package/dist/ui/overlay/impl/resume-overlay-v2.js +414 -0
  400. package/dist/ui/overlay/impl/session-mode-overlay-v2.d.ts +43 -0
  401. package/dist/ui/overlay/impl/session-mode-overlay-v2.js +124 -0
  402. package/dist/ui/overlay/impl/tasks-overlay-v2.d.ts +28 -0
  403. package/dist/ui/overlay/impl/tasks-overlay-v2.js +283 -0
  404. package/dist/ui/overlay/impl/team-overlay-v2.d.ts +86 -0
  405. package/dist/ui/overlay/impl/team-overlay-v2.js +692 -0
  406. package/dist/ui/overlay/impl/terminals-overlay-v2.d.ts +26 -0
  407. package/dist/ui/overlay/impl/terminals-overlay-v2.js +217 -0
  408. package/dist/ui/overlay/impl/tools-overlay-v2.js +3 -7
  409. package/dist/ui/overlay/impl/tutorial-overlay-v2.d.ts +30 -16
  410. package/dist/ui/overlay/impl/tutorial-overlay-v2.js +133 -956
  411. package/dist/ui/overlay/impl/workflow-overlay-v2.d.ts +1 -0
  412. package/dist/ui/overlay/impl/workflow-overlay-v2.js +10 -4
  413. package/dist/ui/overlay/index.d.ts +20 -1
  414. package/dist/ui/overlay/index.js +19 -0
  415. package/dist/ui/overlay/types.d.ts +5 -0
  416. package/dist/ui/overlay-manager.d.ts +43 -0
  417. package/dist/ui/overlay-manager.js +238 -0
  418. package/dist/ui/overlays.js +4 -16
  419. package/dist/ui/permission-overlay.js +6 -5
  420. package/dist/ui/status-bar-controller.d.ts +33 -0
  421. package/dist/ui/status-bar-controller.js +99 -0
  422. package/dist/ui/subagent-renderer.js +3 -19
  423. package/dist/ui/terminal-autocomplete-utils.d.ts +23 -0
  424. package/dist/ui/terminal-autocomplete-utils.js +83 -0
  425. package/dist/ui/terminal-line-builders.d.ts +17 -0
  426. package/dist/ui/terminal-line-builders.js +42 -0
  427. package/dist/ui/terminal-render-item.d.ts +16 -0
  428. package/dist/ui/terminal-render-item.js +267 -0
  429. package/dist/ui/terminal-renderer.d.ts +7 -8
  430. package/dist/ui/terminal-renderer.js +7 -8
  431. package/dist/ui/terminal-types.d.ts +179 -0
  432. package/dist/ui/terminal-types.js +34 -0
  433. package/dist/ui/terminal-ui.d.ts +144 -276
  434. package/dist/ui/terminal-ui.js +384 -1861
  435. package/dist/ui/todo-zone.d.ts +19 -1
  436. package/dist/ui/todo-zone.js +71 -13
  437. package/dist/ui/tool-formatters.js +696 -1
  438. package/dist/ui/turn-metrics.d.ts +56 -0
  439. package/dist/ui/turn-metrics.js +75 -0
  440. package/dist/ui/types.d.ts +28 -0
  441. package/dist/ui/types.js +1 -0
  442. package/dist/ui/vscode-diff-ipc.d.ts +102 -0
  443. package/dist/ui/vscode-diff-ipc.js +385 -0
  444. package/dist/utils/credentials.d.ts +24 -5
  445. package/dist/utils/credentials.js +123 -9
  446. package/dist/utils/format-tokens.d.ts +13 -0
  447. package/dist/utils/format-tokens.js +18 -0
  448. package/dist/utils/git-config.d.ts +26 -0
  449. package/dist/utils/git-config.js +54 -0
  450. package/dist/utils/message-utils.d.ts +61 -0
  451. package/dist/utils/message-utils.js +72 -0
  452. package/dist/utils/model-tiers.d.ts +8 -1
  453. package/dist/utils/model-tiers.js +38 -16
  454. package/dist/utils/open-browser.d.ts +5 -0
  455. package/dist/utils/open-browser.js +32 -0
  456. package/dist/utils/path-safety.js +3 -2
  457. package/dist/utils/project-detection.d.ts +58 -0
  458. package/dist/utils/project-detection.js +424 -0
  459. package/dist/utils/project-memory.js +2 -1
  460. package/dist/utils/project-status.d.ts +2 -2
  461. package/dist/utils/startup-perf.d.ts +18 -0
  462. package/dist/utils/startup-perf.js +60 -0
  463. package/dist/utils/token-tracker.d.ts +62 -0
  464. package/dist/utils/token-tracker.js +150 -0
  465. package/dist/utils/token-types.d.ts +23 -0
  466. package/dist/utils/token-types.js +18 -0
  467. package/dist/utils/types/config-types.d.ts +32 -0
  468. package/dist/utils/types/config-types.js +8 -0
  469. package/dist/utils/update-checker.d.ts +28 -0
  470. package/dist/utils/update-checker.js +106 -0
  471. package/dist/utils/version.d.ts +7 -0
  472. package/dist/utils/version.js +10 -0
  473. package/dist/utils/vscode-detect.d.ts +39 -0
  474. package/dist/utils/vscode-detect.js +137 -0
  475. package/package.json +27 -13
  476. package/dist/commands/handler-types.d.ts +0 -68
  477. package/dist/commands/handler-types.js +0 -8
  478. package/dist/commands/handlers/agent-commands.d.ts +0 -13
  479. package/dist/commands/handlers/agent-commands.js +0 -305
  480. package/dist/commands/handlers/design-commands.d.ts +0 -15
  481. package/dist/commands/handlers/design-commands.js +0 -334
  482. package/dist/commands/handlers/index.d.ts +0 -20
  483. package/dist/commands/handlers/index.js +0 -43
  484. package/dist/commands/handlers/overlay-commands.d.ts +0 -21
  485. package/dist/commands/handlers/overlay-commands.js +0 -287
  486. package/dist/commands/handlers/project-commands.d.ts +0 -11
  487. package/dist/commands/handlers/project-commands.js +0 -167
  488. package/dist/commands/handlers/simple-commands.d.ts +0 -19
  489. package/dist/commands/handlers/simple-commands.js +0 -144
  490. package/dist/commands/registry.d.ts +0 -50
  491. package/dist/commands/registry.js +0 -75
  492. package/dist/index.old.d.ts +0 -7
  493. package/dist/index.old.js +0 -1014
  494. package/dist/repl.d.ts +0 -149
  495. package/dist/repl.js +0 -1151
  496. package/dist/templates/claude-md.d.ts +0 -7
  497. package/dist/templates/claude-md.js +0 -189
  498. package/dist/test-autocomplete.d.ts +0 -7
  499. package/dist/test-autocomplete.js +0 -85
  500. package/dist/test-tabbed-menu.d.ts +0 -7
  501. package/dist/test-tabbed-menu.js +0 -25
  502. package/dist/tool-selector.d.ts +0 -71
  503. package/dist/tool-selector.js +0 -184
  504. package/dist/tools/anchor-tools.d.ts +0 -31
  505. package/dist/tools/anchor-tools.js +0 -255
  506. package/dist/tools/backlog-wrappers.d.ts +0 -54
  507. package/dist/tools/backlog-wrappers.js +0 -338
  508. package/dist/tools/document-db.d.ts +0 -43
  509. package/dist/tools/document-db.js +0 -220
  510. package/dist/tools/workitem-db.d.ts +0 -103
  511. package/dist/tools/workitem-db.js +0 -549
  512. package/dist/ui/agents-overlay-v2.d.ts +0 -43
  513. package/dist/ui/agents-overlay-v2.js +0 -809
  514. package/dist/ui/agents-overlay.d.ts +0 -12
  515. package/dist/ui/agents-overlay.js +0 -863
  516. package/dist/ui/anchors-overlay.d.ts +0 -12
  517. package/dist/ui/anchors-overlay.js +0 -775
  518. package/dist/ui/arch-type-overlay.d.ts +0 -15
  519. package/dist/ui/arch-type-overlay.js +0 -201
  520. package/dist/ui/ask-user-overlay-v2.d.ts +0 -26
  521. package/dist/ui/ask-user-overlay-v2.js +0 -555
  522. package/dist/ui/ask-user-simple-overlay-v2.d.ts +0 -25
  523. package/dist/ui/ask-user-simple-overlay-v2.js +0 -215
  524. package/dist/ui/backlog-overlay.d.ts +0 -32
  525. package/dist/ui/backlog-overlay.js +0 -652
  526. package/dist/ui/commands-overlay-v2.d.ts +0 -33
  527. package/dist/ui/commands-overlay-v2.js +0 -441
  528. package/dist/ui/commands-overlay.d.ts +0 -16
  529. package/dist/ui/commands-overlay.js +0 -439
  530. package/dist/ui/config-overlay.d.ts +0 -35
  531. package/dist/ui/config-overlay.js +0 -707
  532. package/dist/ui/docs-overlay.d.ts +0 -17
  533. package/dist/ui/docs-overlay.js +0 -303
  534. package/dist/ui/footer-v2.d.ts +0 -222
  535. package/dist/ui/footer-v2.js +0 -1349
  536. package/dist/ui/help-overlay-v2.d.ts +0 -34
  537. package/dist/ui/help-overlay-v2.js +0 -309
  538. package/dist/ui/help-overlay.d.ts +0 -16
  539. package/dist/ui/help-overlay.js +0 -316
  540. package/dist/ui/init-overlay-v2.d.ts +0 -34
  541. package/dist/ui/init-overlay-v2.js +0 -600
  542. package/dist/ui/init-overlay.d.ts +0 -34
  543. package/dist/ui/init-overlay.js +0 -604
  544. package/dist/ui/input-prompt-v2.d.ts +0 -180
  545. package/dist/ui/input-prompt-v2.js +0 -999
  546. package/dist/ui/iteration-limit-overlay-v2.d.ts +0 -21
  547. package/dist/ui/iteration-limit-overlay-v2.js +0 -114
  548. package/dist/ui/keys-overlay-v2.d.ts +0 -41
  549. package/dist/ui/keys-overlay-v2.js +0 -248
  550. package/dist/ui/mascot-overlay-v2.d.ts +0 -41
  551. package/dist/ui/mascot-overlay-v2.js +0 -138
  552. package/dist/ui/mascot-overlay.d.ts +0 -21
  553. package/dist/ui/mascot-overlay.js +0 -146
  554. package/dist/ui/model-overlay-v2.d.ts +0 -49
  555. package/dist/ui/model-overlay-v2.js +0 -118
  556. package/dist/ui/model-overlay.d.ts +0 -27
  557. package/dist/ui/model-overlay.js +0 -221
  558. package/dist/ui/model-warning-overlay.d.ts +0 -30
  559. package/dist/ui/model-warning-overlay.js +0 -169
  560. package/dist/ui/new-overlay.d.ts +0 -34
  561. package/dist/ui/new-overlay.js +0 -604
  562. package/dist/ui/overlay/impl/init-overlay-v2.d.ts +0 -77
  563. package/dist/ui/overlay/impl/init-overlay-v2.js +0 -593
  564. package/dist/ui/overlay/overlay-types.d.ts +0 -128
  565. package/dist/ui/overlay/overlay-types.js +0 -22
  566. package/dist/ui/overlays/help-overlay-v2.d.ts +0 -28
  567. package/dist/ui/overlays/help-overlay-v2.js +0 -198
  568. package/dist/ui/overlays/index.d.ts +0 -11
  569. package/dist/ui/overlays/index.js +0 -11
  570. package/dist/ui/permission-overlay-v2.d.ts +0 -36
  571. package/dist/ui/permission-overlay-v2.js +0 -380
  572. package/dist/ui/projects-overlay.d.ts +0 -19
  573. package/dist/ui/projects-overlay.js +0 -484
  574. package/dist/ui/theme-overlay-v2.d.ts +0 -42
  575. package/dist/ui/theme-overlay-v2.js +0 -135
  576. package/dist/ui/theme-overlay.d.ts +0 -24
  577. package/dist/ui/theme-overlay.js +0 -127
  578. package/dist/ui/tools-overlay-v2.d.ts +0 -47
  579. package/dist/ui/tools-overlay-v2.js +0 -218
  580. package/dist/ui/tools-overlay.d.ts +0 -34
  581. package/dist/ui/tools-overlay.js +0 -230
  582. package/dist/ui/tutorial-overlay-v2.d.ts +0 -31
  583. package/dist/ui/tutorial-overlay-v2.js +0 -1035
  584. package/dist/ui/tutorial-overlay.d.ts +0 -11
  585. package/dist/ui/tutorial-overlay.js +0 -1034
  586. package/dist/ui/workflow-overlay.d.ts +0 -22
  587. package/dist/ui/workflow-overlay.js +0 -636
@@ -6,113 +6,1138 @@
6
6
  *
7
7
  * Features:
8
8
  * - Tabs by provider (Claude, OpenAI, Gemini, Ollama)
9
- * - Hot-swap detection (same provider = immediate, different = restart)
9
+ * - Detail view for each model with:
10
+ * - Tier info, provider, model name
11
+ * - Editable model ID
12
+ * - Test model (t), Set active (a), Reset default (d)
10
13
  * - API key status checking
11
14
  * - Persists selection to settings
12
15
  */
13
- import { TabbedListOverlayV2, } from '../../base/index.js';
14
- import { setSetting } from '../../../settings/index.js';
16
+ import { TabbedListOverlayV2, BaseScreen, pushScreen, popScreen, closeOverlay, stay, renderBorder, } from '../../base/index.js';
17
+ import { getSetting, setSetting } from '../../../settings/index.js';
15
18
  import { hasApiKey, settingsProviderToCredentialKey } from '../../../utils/credentials.js';
19
+ import { TIER_INFO, getModelForTier, setTierOverride, clearTierOverride, validateModel, checkApiKeyStatus, getDefaultModelForTier, getModelInfo, getOthersProviders, getModelsForOthersProvider, getModelsForProvider, PROVIDER_METADATA,
20
+ // Ollama auto-detection
21
+ listOllamaModels, } from '../../../models/index.js';
22
+ import { hasApiKey as hasApiKeyForProvider, getMaskedKey, setApiKey, } from '../../../utils/credentials.js';
23
+ import { isCtrlC, isEscape, isClose, isEnter } from '../../base/key-utils.js';
24
+ import * as terminal from '../../terminal.js';
16
25
  // =============================================================================
17
26
  // Constants
18
27
  // =============================================================================
19
28
  const PAGE_SIZE = 10;
29
+ const TIER_WIDTH = 14;
30
+ const LABEL_WIDTH = 14;
31
+ const MODEL_NAME_WIDTH = 28;
32
+ /** Primary providers that show tier-based selection */
33
+ const PRIMARY_PROVIDERS = ['claude', 'openai', 'gemini', 'ollama'];
20
34
  const PROVIDER_TABS = [
21
35
  { id: 'claude', label: 'Claude' },
22
36
  { id: 'openai', label: 'OpenAI' },
23
37
  { id: 'gemini', label: 'Gemini' },
24
- { id: 'ollama', label: 'Ollama' },
25
- ];
26
- const MODEL_OPTIONS = [
27
- // Claude
28
- { id: 'claude-sonnet-4-20250514', name: 'Sonnet 4', description: 'Best for everyday tasks', provider: 'claude' },
29
- { id: 'claude-opus-4-20250514', name: 'Opus 4', description: 'Most capable for complex work', provider: 'claude' },
30
- { id: 'claude-3-5-haiku-20241022', name: 'Haiku 3.5', description: 'Fastest for quick answers', provider: 'claude' },
31
- // OpenAI
32
- { id: 'gpt-4o', name: 'GPT-4o', description: 'Best overall', provider: 'openai' },
33
- { id: 'gpt-4o-mini', name: 'GPT-4o Mini', description: 'Fast and affordable', provider: 'openai' },
34
- // Gemini
35
- { id: 'gemini-2.0-flash', name: 'Gemini 2.0 Flash', description: 'Fast multimodal', provider: 'gemini' },
36
- { id: 'gemini-2.5-flash', name: 'Gemini 2.5 Flash', description: 'Latest stable flash', provider: 'gemini' },
37
- { id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro', description: 'Most capable Gemini', provider: 'gemini' },
38
- // Ollama
39
- { id: 'llama3.2', name: 'Llama 3.2', description: 'Local model', provider: 'ollama' },
40
- { id: 'mistral', name: 'Mistral', description: 'Local model', provider: 'ollama' },
41
- { id: 'codellama', name: 'Code Llama', description: 'Code-focused local model', provider: 'ollama' },
38
+ { id: 'ollama', label: 'Ollama (local)' },
39
+ { id: 'others', label: 'Others' },
42
40
  ];
41
+ /** All model tiers in display order */
42
+ const ALL_TIERS = ['fast', 'balanced', 'powerful'];
43
+ /**
44
+ * Get list items - tier items for primary providers, provider items for Others.
45
+ */
46
+ function getListItems() {
47
+ const items = [];
48
+ // For each primary provider, add 3 tier items
49
+ for (const provider of PRIMARY_PROVIDERS) {
50
+ for (const tier of ALL_TIERS) {
51
+ // Get default model from registry
52
+ const defaultModel = getDefaultModelForTier(provider, tier);
53
+ // Get current model (may have user override)
54
+ const currentModelId = getModelForTier(provider, tier);
55
+ const currentModelInfo = getModelInfo(currentModelId);
56
+ items.push({
57
+ type: 'tier',
58
+ tier,
59
+ provider,
60
+ defaultModelId: defaultModel?.id ?? currentModelId,
61
+ defaultModelName: defaultModel?.displayName ?? currentModelId,
62
+ currentModelId,
63
+ currentModelName: currentModelInfo?.displayName ?? currentModelId,
64
+ });
65
+ }
66
+ }
67
+ // Get providers for "Others" tab
68
+ const providers = getOthersProviders();
69
+ for (const p of providers) {
70
+ items.push({
71
+ type: 'provider',
72
+ metadata: p,
73
+ });
74
+ }
75
+ return items;
76
+ }
77
+ // =============================================================================
78
+ // Key Input Screen (for setting API keys from main list)
79
+ // =============================================================================
80
+ /**
81
+ * Simple screen for entering an API key.
82
+ * Used when pressing 's' from the main model list.
83
+ */
84
+ class KeyInputScreen extends BaseScreen {
85
+ provider;
86
+ providerName;
87
+ styles;
88
+ onKeySaved;
89
+ keyInput = '';
90
+ cursorPos = 0;
91
+ constructor(provider, providerName, styles, onKeySaved) {
92
+ super();
93
+ this.provider = provider;
94
+ this.providerName = providerName;
95
+ this.styles = styles;
96
+ this.onKeySaved = onKeySaved;
97
+ }
98
+ render() {
99
+ const s = this.styles;
100
+ const cols = terminal.getTerminalWidth();
101
+ const border = renderBorder(cols, s);
102
+ const lines = [];
103
+ lines.push(border);
104
+ lines.push(` ${s.primaryBold('Set API Key')}`);
105
+ lines.push(border);
106
+ lines.push('');
107
+ lines.push(` Set API key for ${s.primary(this.providerName)}:`);
108
+ lines.push('');
109
+ // Input field
110
+ const beforeCursor = this.keyInput.slice(0, this.cursorPos);
111
+ const afterCursor = this.keyInput.slice(this.cursorPos);
112
+ const cursor = s.primary('█');
113
+ const placeholder = this.keyInput ? '' : s.muted('Paste or type your API key...');
114
+ lines.push(` > ${beforeCursor}${cursor}${afterCursor}${placeholder}`);
115
+ lines.push('');
116
+ // Help text
117
+ const metadata = PROVIDER_METADATA[this.provider];
118
+ if (metadata.keyUrl) {
119
+ lines.push(` ${s.muted(`Get key: ${metadata.keyUrl}`)}`);
120
+ lines.push('');
121
+ }
122
+ lines.push(border);
123
+ lines.push(` ${s.muted('Enter Save · Esc Cancel')}`);
124
+ lines.push(border);
125
+ return lines;
126
+ }
127
+ handleKey(data) {
128
+ // Ctrl+C closes overlay
129
+ if (isCtrlC(data)) {
130
+ return closeOverlay();
131
+ }
132
+ // Escape goes back
133
+ if (isEscape(data)) {
134
+ return popScreen();
135
+ }
136
+ // Enter saves the key
137
+ if (isEnter(data)) {
138
+ const value = this.keyInput.trim();
139
+ if (value) {
140
+ const metadata = PROVIDER_METADATA[this.provider];
141
+ setApiKey(metadata.credentialKey, value);
142
+ this.onKeySaved();
143
+ }
144
+ return popScreen();
145
+ }
146
+ // Handle text input
147
+ const char = data.toString();
148
+ // Backspace
149
+ if (data[0] === 0x7f || data[0] === 0x08) {
150
+ if (this.cursorPos > 0) {
151
+ this.keyInput = this.keyInput.slice(0, this.cursorPos - 1) + this.keyInput.slice(this.cursorPos);
152
+ this.cursorPos--;
153
+ }
154
+ return stay(true);
155
+ }
156
+ // Left arrow
157
+ if (data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x44) {
158
+ if (this.cursorPos > 0)
159
+ this.cursorPos--;
160
+ return stay(true);
161
+ }
162
+ // Right arrow
163
+ if (data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x43) {
164
+ if (this.cursorPos < this.keyInput.length)
165
+ this.cursorPos++;
166
+ return stay(true);
167
+ }
168
+ // Printable characters
169
+ if (char.length === 1 && char.charCodeAt(0) >= 32 && char.charCodeAt(0) < 127) {
170
+ this.keyInput = this.keyInput.slice(0, this.cursorPos) + char + this.keyInput.slice(this.cursorPos);
171
+ this.cursorPos++;
172
+ return stay(true);
173
+ }
174
+ // Handle paste (multiple characters)
175
+ if (char.length > 1) {
176
+ const printable = char.replace(/[^\x20-\x7e]/g, '');
177
+ this.keyInput = this.keyInput.slice(0, this.cursorPos) + printable + this.keyInput.slice(this.cursorPos);
178
+ this.cursorPos += printable.length;
179
+ return stay(true);
180
+ }
181
+ return stay(false);
182
+ }
183
+ getMinHeight() {
184
+ return 12;
185
+ }
186
+ }
187
+ // =============================================================================
188
+ // Others Tab - Provider Detail Screen (with 3-tier models)
189
+ // =============================================================================
190
+ /**
191
+ * Screen showing a specific "Others" provider's details and 3-tier models.
192
+ */
193
+ class OthersProviderDetailScreen extends BaseScreen {
194
+ provider;
195
+ styles;
196
+ activeModel;
197
+ activeProvider;
198
+ requestRender;
199
+ selectedIndex = 0;
200
+ models;
201
+ editingKey = false;
202
+ keyInput = '';
203
+ keyCursorPos = 0;
204
+ message = null;
205
+ messageType = null;
206
+ constructor(provider, styles, activeModel, activeProvider, requestRender) {
207
+ super();
208
+ this.provider = provider;
209
+ this.styles = styles;
210
+ this.activeModel = activeModel;
211
+ this.activeProvider = activeProvider;
212
+ this.requestRender = requestRender;
213
+ this.models = getModelsForOthersProvider(provider.type);
214
+ }
215
+ render() {
216
+ const s = this.styles;
217
+ const cols = terminal.getTerminalWidth();
218
+ const border = renderBorder(cols, s);
219
+ const lines = [];
220
+ // Header
221
+ lines.push(border);
222
+ lines.push(` ${s.primaryBold(this.provider.displayName)}`);
223
+ lines.push(border);
224
+ lines.push('');
225
+ // Provider info
226
+ const hasKey = hasApiKeyForProvider(this.provider.credentialKey);
227
+ const keyIndicator = hasKey ? s.success('● Set') : s.error('○ Not set');
228
+ const maskedKey = hasKey ? getMaskedKey(this.provider.credentialKey) : null;
229
+ const keyDisplay = maskedKey ? s.muted(` (${maskedKey})`) : '';
230
+ if (this.editingKey) {
231
+ const prefix = s.primary(' > ');
232
+ const label = 'API Key:'.padEnd(12);
233
+ const beforeCursor = this.keyInput.slice(0, this.keyCursorPos);
234
+ const afterCursor = this.keyInput.slice(this.keyCursorPos);
235
+ const cursor = s.primary('█');
236
+ lines.push(`${prefix}${s.muted(label)}${beforeCursor}${cursor}${afterCursor}`);
237
+ }
238
+ else {
239
+ lines.push(` ${s.muted('API Key:'.padEnd(12))}${keyIndicator}${keyDisplay}`);
240
+ }
241
+ lines.push(` ${s.muted('Endpoint:'.padEnd(12))}${s.muted(this.provider.endpoint)}`);
242
+ lines.push(` ${s.muted('Env Var:'.padEnd(12))}${s.muted(this.provider.envVar)}`);
243
+ lines.push('');
244
+ // Models section
245
+ lines.push(` ${s.muted('─── Models ───')}`);
246
+ lines.push('');
247
+ // Tier-first column layout (as per spec)
248
+ lines.push(` ${s.muted('Tier'.padEnd(12))}${s.muted('Model')}`);
249
+ lines.push(` ${s.muted('─'.repeat(40))}`);
250
+ for (let i = 0; i < this.models.length; i++) {
251
+ const model = this.models[i];
252
+ const isSelected = i === this.selectedIndex && !this.editingKey;
253
+ const prefix = isSelected ? s.primary(' > ') : ' ';
254
+ // Check if this model is active
255
+ const isActive = model.id === this.activeModel && this.provider.type === this.activeProvider;
256
+ const tierLabel = model.tier.padEnd(12);
257
+ const modelName = model.displayName;
258
+ const activeBadge = isActive ? s.success(' (active)') : '';
259
+ const tierStyle = isSelected ? s.primary : (str) => str;
260
+ const nameStyle = isSelected ? s.primary : (str) => str;
261
+ lines.push(`${prefix}${tierStyle(tierLabel)}${nameStyle(modelName)}${activeBadge}`);
262
+ }
263
+ lines.push('');
264
+ // Message display
265
+ if (this.message) {
266
+ const messageStyle = this.messageType === 'error'
267
+ ? s.error
268
+ : this.messageType === 'success'
269
+ ? s.success
270
+ : s.muted;
271
+ lines.push(` ${messageStyle(this.message)}`);
272
+ lines.push('');
273
+ }
274
+ // Footer
275
+ lines.push(border);
276
+ if (this.editingKey) {
277
+ lines.push(` ${s.muted('Enter Save · Esc Cancel')}`);
278
+ }
279
+ else {
280
+ lines.push(` ${s.muted('↑↓ Select · Enter Details · s Set Key · Esc Back')}`);
281
+ }
282
+ lines.push(border);
283
+ return lines;
284
+ }
285
+ handleKey(data) {
286
+ // Clear message on any key
287
+ if (this.message && !this.editingKey) {
288
+ this.message = null;
289
+ this.messageType = null;
290
+ }
291
+ if (this.editingKey) {
292
+ return this.handleKeyEdit(data);
293
+ }
294
+ // Ctrl+C closes overlay
295
+ if (isCtrlC(data)) {
296
+ return closeOverlay();
297
+ }
298
+ // Esc goes back
299
+ if (isEscape(data) || isClose(data)) {
300
+ return popScreen();
301
+ }
302
+ // Check for arrow keys
303
+ if (data[0] === 0x1b && data[1] === 0x5b) {
304
+ // Up arrow
305
+ if (data[2] === 0x41) {
306
+ if (this.selectedIndex > 0) {
307
+ this.selectedIndex--;
308
+ }
309
+ return stay(true);
310
+ }
311
+ // Down arrow
312
+ if (data[2] === 0x42) {
313
+ if (this.selectedIndex < this.models.length - 1) {
314
+ this.selectedIndex++;
315
+ }
316
+ return stay(true);
317
+ }
318
+ }
319
+ const char = data.toString();
320
+ // j/k vim navigation
321
+ if (char === 'j' && this.selectedIndex < this.models.length - 1) {
322
+ this.selectedIndex++;
323
+ return stay(true);
324
+ }
325
+ if (char === 'k' && this.selectedIndex > 0) {
326
+ this.selectedIndex--;
327
+ return stay(true);
328
+ }
329
+ // 's' - set API key
330
+ if (char === 's') {
331
+ this.editingKey = true;
332
+ this.keyInput = '';
333
+ this.keyCursorPos = 0;
334
+ return stay(true);
335
+ }
336
+ // Enter - open model detail screen (consistent with other provider tabs)
337
+ if (isEnter(data)) {
338
+ const selectedModel = this.models[this.selectedIndex];
339
+ // Convert OthersProviderModel to TierItem for detail screen
340
+ const tierItem = {
341
+ type: 'tier',
342
+ tier: selectedModel.tier,
343
+ provider: this.provider.type,
344
+ defaultModelId: selectedModel.id,
345
+ defaultModelName: selectedModel.displayName,
346
+ currentModelId: getModelForTier(this.provider.type, selectedModel.tier),
347
+ currentModelName: selectedModel.displayName,
348
+ };
349
+ return pushScreen(new TierDetailScreen(tierItem, this.styles, this.activeModel, this.activeProvider, { status: 'offline', availableModels: new Set() }, // Others providers don't use Ollama state
350
+ this.requestRender));
351
+ }
352
+ return stay(false);
353
+ }
354
+ handleKeyEdit(data) {
355
+ // Escape - cancel edit
356
+ if (isEscape(data) || isCtrlC(data)) {
357
+ this.editingKey = false;
358
+ this.keyInput = '';
359
+ this.keyCursorPos = 0;
360
+ return stay(true);
361
+ }
362
+ // Enter - save key
363
+ if (isEnter(data)) {
364
+ const key = this.keyInput.trim();
365
+ if (key) {
366
+ // Import setApiKey dynamically to avoid circular deps
367
+ import('../../../utils/credentials.js').then(({ setApiKey }) => {
368
+ setApiKey(this.provider.credentialKey, key);
369
+ this.message = 'API key saved';
370
+ this.messageType = 'success';
371
+ this.requestRender();
372
+ }).catch(() => {
373
+ this.message = 'Failed to save API key';
374
+ this.messageType = 'error';
375
+ this.requestRender();
376
+ });
377
+ }
378
+ this.editingKey = false;
379
+ this.keyInput = '';
380
+ this.keyCursorPos = 0;
381
+ return stay(true);
382
+ }
383
+ // Left arrow
384
+ if (data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x44) {
385
+ if (this.keyCursorPos > 0) {
386
+ this.keyCursorPos--;
387
+ }
388
+ return stay(true);
389
+ }
390
+ // Right arrow
391
+ if (data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x43) {
392
+ if (this.keyCursorPos < this.keyInput.length) {
393
+ this.keyCursorPos++;
394
+ }
395
+ return stay(true);
396
+ }
397
+ // Backspace
398
+ if (data[0] === 127 || data[0] === 8) {
399
+ if (this.keyCursorPos > 0) {
400
+ const before = this.keyInput.slice(0, this.keyCursorPos - 1);
401
+ const after = this.keyInput.slice(this.keyCursorPos);
402
+ this.keyInput = before + after;
403
+ this.keyCursorPos--;
404
+ }
405
+ return stay(true);
406
+ }
407
+ // Printable character
408
+ const char = data.toString();
409
+ if (char.length === 1 && char.charCodeAt(0) >= 32 && char.charCodeAt(0) < 127) {
410
+ const before = this.keyInput.slice(0, this.keyCursorPos);
411
+ const after = this.keyInput.slice(this.keyCursorPos);
412
+ this.keyInput = before + char + after;
413
+ this.keyCursorPos++;
414
+ return stay(true);
415
+ }
416
+ return stay(false);
417
+ }
418
+ getMinHeight() {
419
+ return 22;
420
+ }
421
+ }
422
+ const DEFAULT_OLLAMA_URL = 'http://localhost:11434';
423
+ class TierDetailScreen extends BaseScreen {
424
+ tierItem;
425
+ styles;
426
+ state;
427
+ currentModelId;
428
+ currentOllamaUrl;
429
+ activeModel;
430
+ activeProvider;
431
+ ollamaState;
432
+ requestRenderFn;
433
+ /** Available models for cycling */
434
+ availableModels;
435
+ constructor(tierItem, styles, activeModel, activeProvider, ollamaState, requestRender) {
436
+ super();
437
+ this.tierItem = tierItem;
438
+ this.styles = styles;
439
+ this.activeModel = activeModel;
440
+ this.activeProvider = activeProvider;
441
+ this.ollamaState = ollamaState;
442
+ this.requestRenderFn = requestRender;
443
+ this.currentModelId = tierItem.currentModelId;
444
+ const settingUrl = getSetting('ollamaBaseUrl');
445
+ this.currentOllamaUrl = typeof settingUrl === 'string' ? settingUrl : DEFAULT_OLLAMA_URL;
446
+ this.state = {
447
+ editMode: false,
448
+ // For Ollama, start with URL field; otherwise just model
449
+ selectedField: tierItem.provider === 'ollama' ? 'url' : 'model',
450
+ editValue: '',
451
+ cursorPos: 0,
452
+ testing: false,
453
+ message: null,
454
+ messageType: null,
455
+ };
456
+ // Build available models list for cycling
457
+ this.availableModels = this.buildAvailableModels();
458
+ }
459
+ /**
460
+ * Build list of available models for this tier from registry.
461
+ * For Ollama: also includes locally installed models.
462
+ */
463
+ buildAvailableModels() {
464
+ const models = [];
465
+ const registryModels = getModelsForProvider(this.tierItem.provider);
466
+ // Add registry models (supported ones)
467
+ for (const model of registryModels) {
468
+ if (model.status !== 'supported')
469
+ continue;
470
+ const isDefault = model.id === this.tierItem.defaultModelId;
471
+ // For Ollama: check if locally available
472
+ let isLocal;
473
+ if (this.tierItem.provider === 'ollama') {
474
+ const modelName = model.id.toLowerCase();
475
+ const baseName = modelName.split(':')[0];
476
+ isLocal = this.ollamaState.availableModels.has(modelName) ||
477
+ this.ollamaState.availableModels.has(baseName);
478
+ }
479
+ models.push({
480
+ id: model.id,
481
+ displayName: model.displayName,
482
+ isDefault,
483
+ isLocal,
484
+ });
485
+ }
486
+ // For Ollama: add any local models not in registry
487
+ if (this.tierItem.provider === 'ollama') {
488
+ const registryIds = new Set(models.map(m => m.id.toLowerCase()));
489
+ for (const localModel of this.ollamaState.availableModels) {
490
+ // Skip base names (we only want full model:tag names)
491
+ if (!localModel.includes(':') && this.ollamaState.availableModels.has(localModel + ':latest')) {
492
+ continue;
493
+ }
494
+ if (!registryIds.has(localModel)) {
495
+ models.push({
496
+ id: localModel,
497
+ displayName: localModel,
498
+ isDefault: false,
499
+ isLocal: true,
500
+ });
501
+ }
502
+ }
503
+ }
504
+ // Sort: default first, then by name
505
+ models.sort((a, b) => {
506
+ if (a.isDefault && !b.isDefault)
507
+ return -1;
508
+ if (!a.isDefault && b.isDefault)
509
+ return 1;
510
+ return a.displayName.localeCompare(b.displayName);
511
+ });
512
+ return models;
513
+ }
514
+ /**
515
+ * Get current model index in available models list.
516
+ * Returns -1 if current model is custom (not in list).
517
+ */
518
+ getCurrentModelIndex() {
519
+ return this.availableModels.findIndex(m => m.id === this.currentModelId);
520
+ }
521
+ /**
522
+ * Cycle to next available model.
523
+ */
524
+ cycleToNextModel() {
525
+ if (this.availableModels.length === 0)
526
+ return;
527
+ const currentIndex = this.getCurrentModelIndex();
528
+ const nextIndex = (currentIndex + 1) % this.availableModels.length;
529
+ const nextModel = this.availableModels[nextIndex];
530
+ this.currentModelId = nextModel.id;
531
+ // Persist the change
532
+ if (nextModel.isDefault) {
533
+ clearTierOverride(this.tierItem.provider, this.tierItem.tier);
534
+ }
535
+ else {
536
+ setTierOverride(this.tierItem.provider, this.tierItem.tier, nextModel.id);
537
+ }
538
+ }
539
+ /**
540
+ * Cycle to previous available model.
541
+ */
542
+ cycleToPrevModel() {
543
+ if (this.availableModels.length === 0)
544
+ return;
545
+ const currentIndex = this.getCurrentModelIndex();
546
+ const prevIndex = currentIndex <= 0
547
+ ? this.availableModels.length - 1
548
+ : currentIndex - 1;
549
+ const prevModel = this.availableModels[prevIndex];
550
+ this.currentModelId = prevModel.id;
551
+ // Persist the change
552
+ if (prevModel.isDefault) {
553
+ clearTierOverride(this.tierItem.provider, this.tierItem.tier);
554
+ }
555
+ else {
556
+ setTierOverride(this.tierItem.provider, this.tierItem.tier, prevModel.id);
557
+ }
558
+ }
559
+ render() {
560
+ const s = this.styles;
561
+ const cols = terminal.getTerminalWidth();
562
+ const border = renderBorder(cols, s);
563
+ const lines = [];
564
+ // Header
565
+ lines.push(border);
566
+ lines.push(` ${s.primaryBold('Tier Configuration')}`);
567
+ lines.push(border);
568
+ lines.push('');
569
+ // Provider
570
+ const providerLabel = this.tierItem.provider.charAt(0).toUpperCase() + this.tierItem.provider.slice(1);
571
+ lines.push(` ${s.muted('Provider:'.padEnd(LABEL_WIDTH))}${providerLabel}`);
572
+ // Tier
573
+ const tierInfo = TIER_INFO[this.tierItem.tier];
574
+ lines.push(` ${s.muted('Tier:'.padEnd(LABEL_WIDTH))}${tierInfo.label} ${s.muted(`- ${tierInfo.shortDescription}`)}`);
575
+ lines.push('');
576
+ // API Key Status (not shown for Ollama - no auth needed)
577
+ if (this.tierItem.provider !== 'ollama') {
578
+ const keyStatus = checkApiKeyStatus(this.tierItem.provider);
579
+ const keyIndicator = keyStatus.hasKey ? s.success('● Set') : s.error('○ Not set');
580
+ const keyHint = !keyStatus.hasKey && keyStatus.hint ? s.warning(` (${keyStatus.hint})`) : '';
581
+ lines.push(` ${s.muted('API Key:'.padEnd(LABEL_WIDTH))}${keyIndicator}${keyHint}`);
582
+ lines.push('');
583
+ }
584
+ // Ollama Server URL (only for Ollama provider)
585
+ if (this.tierItem.provider === 'ollama') {
586
+ const isUrlSelected = this.state.editMode && this.state.selectedField === 'url';
587
+ const sectionHeader = isUrlSelected ? s.primary('─── Server URL ───') : s.muted('─── Server URL ───');
588
+ lines.push(` ${sectionHeader}`);
589
+ lines.push('');
590
+ // Default URL first (consistent order)
591
+ lines.push(` ${s.muted('Default:'.padEnd(LABEL_WIDTH))}${s.muted(DEFAULT_OLLAMA_URL)}`);
592
+ // Current URL (editable)
593
+ const isDefaultUrl = this.currentOllamaUrl === DEFAULT_OLLAMA_URL;
594
+ if (isUrlSelected) {
595
+ const prefix = s.primary(' > ');
596
+ const beforeCursor = this.state.editValue.slice(0, this.state.cursorPos);
597
+ const afterCursor = this.state.editValue.slice(this.state.cursorPos);
598
+ const cursor = s.primary('█');
599
+ lines.push(`${prefix}${s.muted('Current:'.padEnd(LABEL_WIDTH - 3))}${beforeCursor}${cursor}${afterCursor}`);
600
+ }
601
+ else {
602
+ const urlStyle = isDefaultUrl ? (str) => str : s.success;
603
+ const urlBadge = isDefaultUrl ? '' : s.success(' (custom)');
604
+ lines.push(` ${s.muted('Current:'.padEnd(LABEL_WIDTH))}${urlStyle(this.currentOllamaUrl)}${urlBadge}`);
605
+ }
606
+ lines.push('');
607
+ }
608
+ // Model ID section
609
+ const isModelSelected = this.state.editMode && this.state.selectedField === 'model';
610
+ const modelHeader = isModelSelected ? s.primary('─── Model ID ───') : s.muted('─── Model ID ───');
611
+ lines.push(` ${modelHeader}`);
612
+ lines.push('');
613
+ // Default Model ID first (consistent order)
614
+ lines.push(` ${s.muted('Default:'.padEnd(LABEL_WIDTH))}${s.muted(this.tierItem.defaultModelId)}`);
615
+ // Current Model ID (with cycling indicator)
616
+ const isOverride = this.currentModelId !== this.tierItem.defaultModelId;
617
+ const currentIndex = this.getCurrentModelIndex();
618
+ const isCustomModel = currentIndex === -1;
619
+ if (isModelSelected) {
620
+ // Edit mode - show text input
621
+ const prefix = s.primary(' > ');
622
+ const beforeCursor = this.state.editValue.slice(0, this.state.cursorPos);
623
+ const afterCursor = this.state.editValue.slice(this.state.cursorPos);
624
+ const cursor = s.primary('█');
625
+ lines.push(`${prefix}${s.muted('Current:'.padEnd(LABEL_WIDTH - 3))}${beforeCursor}${cursor}${afterCursor}`);
626
+ }
627
+ else {
628
+ // Normal mode - show current with cycling info
629
+ const currentStyle = isOverride ? s.success : (str) => str;
630
+ // Build badges
631
+ const badges = [];
632
+ if (isOverride && !isCustomModel)
633
+ badges.push(s.success('custom'));
634
+ if (isCustomModel)
635
+ badges.push(s.warning('manual'));
636
+ // Cycling indicator: (2/5) or (manual)
637
+ let cycleIndicator = '';
638
+ if (this.availableModels.length > 0) {
639
+ if (isCustomModel) {
640
+ cycleIndicator = s.muted(` [manual]`);
641
+ }
642
+ else {
643
+ cycleIndicator = s.muted(` [${String(currentIndex + 1)}/${String(this.availableModels.length)}]`);
644
+ }
645
+ }
646
+ // Get display name for current model
647
+ const currentModelInfo = this.availableModels.find(m => m.id === this.currentModelId);
648
+ const displayName = currentModelInfo?.displayName ?? this.currentModelId;
649
+ // For Ollama: show local availability
650
+ let localBadge = '';
651
+ if (this.tierItem.provider === 'ollama' && currentModelInfo) {
652
+ localBadge = currentModelInfo.isLocal ? s.success(' ●') : s.warning(' ○');
653
+ }
654
+ lines.push(` ${s.muted('Current:'.padEnd(LABEL_WIDTH))}${currentStyle(displayName)}${localBadge}${cycleIndicator}`);
655
+ }
656
+ lines.push('');
657
+ // Active status
658
+ const isActive = this.currentModelId === this.activeModel && this.tierItem.provider === this.activeProvider;
659
+ if (isActive) {
660
+ lines.push(` ${s.success('✓ This is the active model')}`);
661
+ }
662
+ lines.push('');
663
+ // Message display
664
+ if (this.state.message) {
665
+ const messageStyle = this.state.messageType === 'error'
666
+ ? s.error
667
+ : this.state.messageType === 'success'
668
+ ? s.success
669
+ : s.muted;
670
+ lines.push(` ${messageStyle(this.state.message)}`);
671
+ lines.push('');
672
+ }
673
+ // Footer
674
+ lines.push(border);
675
+ if (this.state.testing) {
676
+ lines.push(` ${s.warning('Testing model...')}`);
677
+ }
678
+ else if (this.state.editMode) {
679
+ const arrowHint = this.tierItem.provider === 'ollama' ? '↑↓ Switch field · ' : '';
680
+ lines.push(` ${s.muted(`${arrowHint}Enter Save · Esc Cancel · Clear = Reset to default`)}`);
681
+ }
682
+ else {
683
+ // Show cycling hint if multiple models available
684
+ const cycleHint = this.availableModels.length > 1 ? '←→/Enter Cycle · ' : '';
685
+ lines.push(` ${s.muted(`${cycleHint}e Manual · t Test · a Set Active · d Reset · Esc Back`)}`);
686
+ }
687
+ lines.push(border);
688
+ return lines;
689
+ }
690
+ handleKey(data) {
691
+ // Clear message on any key (except while testing)
692
+ if (this.state.message && !this.state.testing) {
693
+ this.state.message = null;
694
+ this.state.messageType = null;
695
+ }
696
+ if (this.state.testing) {
697
+ // Ignore keys while testing
698
+ return stay(false);
699
+ }
700
+ if (this.state.editMode) {
701
+ return this.handleEditKey(data);
702
+ }
703
+ return this.handleNavigateKey(data);
704
+ }
705
+ handleNavigateKey(data) {
706
+ // Ctrl+C closes overlay completely
707
+ if (isCtrlC(data)) {
708
+ return closeOverlay();
709
+ }
710
+ // Esc or q goes back to list
711
+ if (isEscape(data) || isClose(data)) {
712
+ return popScreen();
713
+ }
714
+ // Arrow keys for cycling through models
715
+ if (data[0] === 0x1b && data[1] === 0x5b) {
716
+ // Right arrow - cycle to next model
717
+ if (data[2] === 0x43) {
718
+ this.cycleToNextModel();
719
+ return stay(true);
720
+ }
721
+ // Left arrow - cycle to previous model
722
+ if (data[2] === 0x44) {
723
+ this.cycleToPrevModel();
724
+ return stay(true);
725
+ }
726
+ }
727
+ const char = data.toString();
728
+ // Enter/Space - cycle to next model (like config overlay)
729
+ if (isEnter(data) || char === ' ') {
730
+ if (this.availableModels.length > 1) {
731
+ this.cycleToNextModel();
732
+ return stay(true);
733
+ }
734
+ return stay(false);
735
+ }
736
+ // 'e' - enter manual edit mode
737
+ if (char === 'e') {
738
+ this.state.editMode = true;
739
+ // For Ollama, start with URL field; otherwise just model
740
+ if (this.tierItem.provider === 'ollama') {
741
+ this.state.selectedField = 'url';
742
+ this.state.editValue = this.currentOllamaUrl;
743
+ this.state.cursorPos = this.currentOllamaUrl.length;
744
+ }
745
+ else {
746
+ this.state.selectedField = 'model';
747
+ this.state.editValue = this.currentModelId;
748
+ this.state.cursorPos = this.currentModelId.length;
749
+ }
750
+ return stay(true);
751
+ }
752
+ // 't' - test model
753
+ if (char === 't') {
754
+ // Start async test (fire-and-forget)
755
+ void this.testModel();
756
+ return stay(true);
757
+ }
758
+ // 'a' - set as active
759
+ if (char === 'a') {
760
+ setSetting('defaultModel', this.currentModelId);
761
+ setSetting('defaultProvider', this.tierItem.provider);
762
+ const tierInfo = TIER_INFO[this.tierItem.tier];
763
+ this.state.message = `Set ${tierInfo.label} tier (${this.currentModelId}) as active`;
764
+ this.state.messageType = 'success';
765
+ return stay(true);
766
+ }
767
+ // 'd' - reset to default
768
+ if (char === 'd') {
769
+ if (this.currentModelId !== this.tierItem.defaultModelId) {
770
+ clearTierOverride(this.tierItem.provider, this.tierItem.tier);
771
+ this.currentModelId = this.tierItem.defaultModelId;
772
+ this.state.message = 'Reset to default';
773
+ this.state.messageType = 'info';
774
+ }
775
+ else {
776
+ this.state.message = 'Already using default';
777
+ this.state.messageType = 'info';
778
+ }
779
+ return stay(true);
780
+ }
781
+ return stay(false);
782
+ }
783
+ handleEditKey(data) {
784
+ // Escape - cancel edit
785
+ if (isEscape(data) || isCtrlC(data)) {
786
+ this.state.editMode = false;
787
+ this.state.editValue = '';
788
+ this.state.cursorPos = 0;
789
+ return stay(true);
790
+ }
791
+ // Check for escape sequences (arrow keys)
792
+ if (data[0] === 0x1b && data[1] === 0x5b) {
793
+ // Left arrow - move cursor left
794
+ if (data[2] === 0x44) {
795
+ if (this.state.cursorPos > 0) {
796
+ this.state.cursorPos--;
797
+ }
798
+ return stay(true);
799
+ }
800
+ // Right arrow - move cursor right
801
+ if (data[2] === 0x43) {
802
+ if (this.state.cursorPos < this.state.editValue.length) {
803
+ this.state.cursorPos++;
804
+ }
805
+ return stay(true);
806
+ }
807
+ // Up/Down arrows - switch between fields (only for Ollama)
808
+ if (this.tierItem.provider === 'ollama') {
809
+ // Up arrow
810
+ if (data[2] === 0x41) {
811
+ if (this.state.selectedField === 'model') {
812
+ this.state.selectedField = 'url';
813
+ this.state.editValue = this.currentOllamaUrl;
814
+ this.state.cursorPos = this.currentOllamaUrl.length;
815
+ }
816
+ return stay(true);
817
+ }
818
+ // Down arrow
819
+ if (data[2] === 0x42) {
820
+ if (this.state.selectedField === 'url') {
821
+ this.state.selectedField = 'model';
822
+ this.state.editValue = this.currentModelId;
823
+ this.state.cursorPos = this.currentModelId.length;
824
+ }
825
+ return stay(true);
826
+ }
827
+ }
828
+ }
829
+ // Enter - save current field
830
+ if (isEnter(data)) {
831
+ const newValue = this.state.editValue.trim();
832
+ if (this.state.selectedField === 'model') {
833
+ // Editing model ID
834
+ if (!newValue) {
835
+ // Empty = reset to default
836
+ clearTierOverride(this.tierItem.provider, this.tierItem.tier);
837
+ this.currentModelId = this.tierItem.defaultModelId;
838
+ this.state.message = 'Reset to default model';
839
+ this.state.messageType = 'info';
840
+ }
841
+ else if (newValue !== this.currentModelId) {
842
+ // Save new value
843
+ setTierOverride(this.tierItem.provider, this.tierItem.tier, newValue);
844
+ this.currentModelId = newValue;
845
+ this.state.message = `Saved model. Press 't' to test.`;
846
+ this.state.messageType = 'info';
847
+ }
848
+ }
849
+ else {
850
+ // Editing Ollama URL (selectedField === 'url')
851
+ if (!newValue || newValue === DEFAULT_OLLAMA_URL) {
852
+ // Empty or default = clear override
853
+ setSetting('ollamaBaseUrl', undefined);
854
+ this.currentOllamaUrl = DEFAULT_OLLAMA_URL;
855
+ this.state.message = 'Reset to default URL';
856
+ this.state.messageType = 'info';
857
+ }
858
+ else if (newValue !== this.currentOllamaUrl) {
859
+ // Save new URL
860
+ setSetting('ollamaBaseUrl', newValue);
861
+ this.currentOllamaUrl = newValue;
862
+ this.state.message = `Saved URL. Press 't' to test.`;
863
+ this.state.messageType = 'info';
864
+ }
865
+ }
866
+ this.state.editMode = false;
867
+ this.state.editValue = '';
868
+ this.state.cursorPos = 0;
869
+ return stay(true);
870
+ }
871
+ // Backspace - delete char before cursor
872
+ if (data[0] === 127 || data[0] === 8) {
873
+ if (this.state.cursorPos > 0) {
874
+ const before = this.state.editValue.slice(0, this.state.cursorPos - 1);
875
+ const after = this.state.editValue.slice(this.state.cursorPos);
876
+ this.state.editValue = before + after;
877
+ this.state.cursorPos--;
878
+ }
879
+ return stay(true);
880
+ }
881
+ // Printable character - insert at cursor position
882
+ const char = data.toString();
883
+ if (char.length === 1 && char.charCodeAt(0) >= 32 && char.charCodeAt(0) < 127) {
884
+ const before = this.state.editValue.slice(0, this.state.cursorPos);
885
+ const after = this.state.editValue.slice(this.state.cursorPos);
886
+ this.state.editValue = before + char + after;
887
+ this.state.cursorPos++;
888
+ return stay(true);
889
+ }
890
+ return stay(false);
891
+ }
892
+ async testModel() {
893
+ this.state.testing = true;
894
+ this.state.message = `Testing ${this.currentModelId}...`;
895
+ this.state.messageType = 'info';
896
+ try {
897
+ const result = await validateModel(this.tierItem.provider, this.currentModelId);
898
+ this.state.testing = false;
899
+ if (result.valid) {
900
+ this.state.message = `✓ Model '${this.currentModelId}' is valid and working`;
901
+ this.state.messageType = 'success';
902
+ }
903
+ else {
904
+ this.state.message = result.error || 'Model validation failed';
905
+ this.state.messageType = 'error';
906
+ }
907
+ }
908
+ catch (error) {
909
+ this.state.testing = false;
910
+ this.state.message = `Error: ${error instanceof Error ? error.message : 'Unknown error'}`;
911
+ this.state.messageType = 'error';
912
+ }
913
+ // Trigger re-render after async operation
914
+ this.requestRenderFn();
915
+ }
916
+ getMinHeight() {
917
+ // Ollama needs more height for the URL section
918
+ return this.tierItem.provider === 'ollama' ? 26 : 22;
919
+ }
920
+ }
43
921
  // =============================================================================
44
922
  // Model Overlay V2 Class
45
923
  // =============================================================================
46
924
  /**
47
925
  * Model selection overlay using the new Overlay interface.
48
926
  * Displays available models in a tabbed list by provider.
927
+ * Others tab shows providers; selecting a provider shows its models.
49
928
  */
50
929
  export class ModelOverlayV2 extends TabbedListOverlayV2 {
51
930
  type = 'inline';
52
931
  id = 'model-overlay-v2';
53
- currentModel;
54
- currentProvider;
932
+ activeModel;
933
+ activeProvider;
934
+ /** Ollama auto-detection state */
935
+ ollamaState = {
936
+ status: 'loading',
937
+ availableModels: new Set(),
938
+ };
939
+ minHeight = 18;
55
940
  constructor(options = {}) {
56
- const currentModel = options.currentModel ?? 'claude-sonnet-4-20250514';
57
- const currentProvider = options.currentProvider ?? 'claude';
941
+ const activeModel = options.currentModel ?? 'claude-sonnet-4-5-20250929';
942
+ const activeProvider = options.currentProvider ?? 'claude';
58
943
  // Find the initial tab based on current provider
59
- const initialTabIndex = PROVIDER_TABS.findIndex((t) => t.id === currentProvider);
944
+ const initialTabIndex = PROVIDER_TABS.findIndex((t) => t.id === activeProvider);
945
+ // Get list items - tier items for primary providers, provider items for Others
946
+ const listItems = getListItems();
60
947
  super({
61
948
  title: 'Select Model',
62
949
  tabs: PROVIDER_TABS,
63
- items: MODEL_OPTIONS,
950
+ items: listItems,
64
951
  pageSize: PAGE_SIZE,
65
- filterByTab: (item, tabId) => item.provider === tabId,
66
- getSearchText: (item) => `${item.name} ${item.description} ${item.id}`,
952
+ filterByTab: (item, tabId) => {
953
+ if (tabId === 'others') {
954
+ // "Others" tab shows provider items
955
+ return item.type === 'provider';
956
+ }
957
+ // Primary provider tabs show tier items for that provider
958
+ return item.type === 'tier' && item.provider === tabId;
959
+ },
960
+ getSearchText: (item) => {
961
+ if (item.type === 'provider') {
962
+ return `${item.metadata.displayName} ${item.metadata.description}`;
963
+ }
964
+ // TierItem
965
+ const tierInfo = TIER_INFO[item.tier];
966
+ return `${tierInfo.label} ${item.currentModelName} ${item.currentModelId}`;
967
+ },
67
968
  renderItem: (item, isSelected, styles) => {
68
- const isCurrent = item.id === currentModel;
69
969
  const prefix = isSelected ? styles.primary(' > ') : ' ';
70
- // Check API key status for this provider
71
- const providerType = item.provider;
72
- const credKey = settingsProviderToCredentialKey(providerType);
73
- const hasKey = item.provider === 'ollama' || hasApiKey(credKey);
74
- const keyIndicator = hasKey ? styles.success('●') : styles.error('○');
75
- const name = isSelected
76
- ? styles.primary(item.name.padEnd(18))
77
- : styles.muted(item.name.padEnd(18));
78
- const desc = styles.muted(item.description);
79
- const currentBadge = isCurrent ? styles.success(' (current)') : '';
80
- return `${prefix}${keyIndicator} ${name}${desc}${currentBadge}`;
970
+ if (item.type === 'provider') {
971
+ // Render provider item for Others tab
972
+ const provider = item.metadata;
973
+ const hasKey = hasApiKeyForProvider(provider.credentialKey);
974
+ const keyIndicator = hasKey ? styles.success('●') : styles.error('○');
975
+ const keyStatus = hasKey ? styles.muted('Configured') : styles.muted('Not configured');
976
+ const name = isSelected
977
+ ? styles.primary(provider.displayName.padEnd(20))
978
+ : provider.displayName.padEnd(20);
979
+ const desc = styles.muted(provider.description.substring(0, 30));
980
+ return `${prefix}${keyIndicator} ${name}${desc} ${keyStatus}`;
981
+ }
982
+ // Render tier item (tier-centric view)
983
+ const tierInfo = TIER_INFO[item.tier];
984
+ const isCustom = item.currentModelId !== item.defaultModelId;
985
+ // Check if this tier is active
986
+ const isActive = item.currentModelId === activeModel && item.provider === activeProvider;
987
+ // For Ollama, show availability status; for others, show API key status
988
+ let statusIndicator;
989
+ if (item.provider === 'ollama') {
990
+ const isLocallyAvailable = this.ollamaState.availableModels.has(item.currentModelId.toLowerCase()) ||
991
+ this.ollamaState.availableModels.has(item.currentModelId.split(':')[0].toLowerCase());
992
+ statusIndicator = isLocallyAvailable ? styles.success('●') : styles.warning('○');
993
+ }
994
+ else {
995
+ const credKey = settingsProviderToCredentialKey(item.provider);
996
+ const hasKey = hasApiKey(credKey);
997
+ statusIndicator = hasKey ? styles.success('●') : styles.error('○');
998
+ }
999
+ // Format: Tier | Model Name | Badges
1000
+ const tierLabel = isSelected
1001
+ ? styles.primary(tierInfo.label.padEnd(TIER_WIDTH))
1002
+ : tierInfo.label.padEnd(TIER_WIDTH);
1003
+ const modelName = item.currentModelName.padEnd(MODEL_NAME_WIDTH);
1004
+ const activeBadge = isActive ? styles.success(' (active)') : '';
1005
+ const customBadge = isCustom && !isActive ? styles.warning(' *') : '';
1006
+ return `${prefix}${statusIndicator} ${tierLabel}${modelName}${activeBadge}${customBadge}`;
81
1007
  },
82
1008
  footerHints: (searchMode) => {
83
1009
  if (searchMode) {
84
- return 'Type to filter · ↑↓/jk Navigate · Enter Select · Esc Exit search';
1010
+ return 'Type to filter · ↑↓/jk Navigate · Enter Details · Esc Exit search';
85
1011
  }
86
- return 'Tab Provider · ↑↓/jk · / Search · Enter Select · q/Esc Close';
1012
+ const currentTabId = PROVIDER_TABS[this.state.currentTab]?.id;
1013
+ // "Others" tab doesn't have inline key setting (handled in provider detail)
1014
+ if (currentTabId === 'others') {
1015
+ return 'Tab · ↑↓ · / Search · Enter Details · q Close';
1016
+ }
1017
+ // Ollama doesn't need API key
1018
+ if (currentTabId === 'ollama') {
1019
+ return 'Tab · ↑↓ · / Search · Enter Details · q Close';
1020
+ }
1021
+ // Standard providers: show s Set Key option
1022
+ return 'Tab · ↑↓ · / Search · Enter Details · s Set Key · q Close';
1023
+ },
1024
+ // Show API key status below tab bar for standard providers
1025
+ renderSubHeader: (tabId, styles) => {
1026
+ // Skip for "Others" tab (has its own detail screen)
1027
+ if (tabId === 'others')
1028
+ return [];
1029
+ // Ollama: show connection status
1030
+ if (tabId === 'ollama') {
1031
+ const status = this.ollamaState.status;
1032
+ let indicator;
1033
+ switch (status) {
1034
+ case 'loading':
1035
+ indicator = styles.muted('◐ Detecting local models...');
1036
+ break;
1037
+ case 'connected': {
1038
+ const count = this.ollamaState.availableModels.size;
1039
+ indicator = styles.success(`● Ollama: ${String(count)} model${count !== 1 ? 's' : ''} available`);
1040
+ break;
1041
+ }
1042
+ case 'offline':
1043
+ indicator = styles.error('○ Ollama: Not running (start with: ollama serve)');
1044
+ break;
1045
+ case 'error':
1046
+ indicator = styles.error(`○ Ollama: ${this.ollamaState.error ?? 'Error'}`);
1047
+ break;
1048
+ }
1049
+ return [` ${indicator}`, ''];
1050
+ }
1051
+ const keyStatus = checkApiKeyStatus(tabId);
1052
+ const indicator = keyStatus.hasKey
1053
+ ? styles.success('● API Key: Set')
1054
+ : styles.error('○ API Key: Not set');
1055
+ return [` ${indicator}`, ''];
1056
+ },
1057
+ // Handle 's' key to set API key for current provider
1058
+ handleCustomKey: (key, tabId) => {
1059
+ const char = key.toString();
1060
+ if (char === 's' && tabId !== 'others' && tabId !== 'ollama') {
1061
+ const providerName = PROVIDER_TABS.find(t => t.id === tabId)?.label ?? tabId;
1062
+ return {
1063
+ handled: true,
1064
+ pushScreen: new KeyInputScreen(tabId, providerName, this.getStyles(), () => { this.requestRender(); }),
1065
+ };
1066
+ }
1067
+ return { handled: false };
87
1068
  },
88
1069
  emptyMessage: 'No models available for this provider.',
89
1070
  noResultsMessage: 'No models match the search.',
90
1071
  });
91
- this.currentModel = currentModel;
92
- this.currentProvider = currentProvider;
1072
+ this.activeModel = activeModel;
1073
+ this.activeProvider = activeProvider;
93
1074
  // Set initial tab based on current provider
94
1075
  if (initialTabIndex >= 0) {
95
1076
  this.state.currentTab = initialTabIndex;
96
1077
  // Re-filter items for the selected tab
97
- this.state.filteredItems = MODEL_OPTIONS.filter((item) => item.provider === PROVIDER_TABS[initialTabIndex]?.id);
1078
+ const tabId = PROVIDER_TABS[initialTabIndex]?.id;
1079
+ this.state.filteredItems = listItems.filter((item) => {
1080
+ if (tabId === 'others') {
1081
+ return item.type === 'provider';
1082
+ }
1083
+ // Primary provider tabs show tier items for that provider
1084
+ return item.type === 'tier' && item.provider === tabId;
1085
+ });
98
1086
  }
1087
+ // Start fetching Ollama models in background
1088
+ this.fetchOllamaModels();
99
1089
  }
100
1090
  /**
101
- * Override to handle model selection on Enter.
1091
+ * Fetch available Ollama models and update state.
102
1092
  */
103
- onItemSelected(item) {
104
- if (item.id === this.currentModel) {
105
- // Same model, no change
106
- return undefined;
107
- }
108
- // Save to settings
109
- setSetting('defaultModel', item.id);
110
- setSetting('defaultProvider', item.provider);
111
- const requiresRestart = item.provider !== this.currentProvider;
112
- return {
113
- modelChanged: item.id,
114
- provider: item.provider,
115
- requiresRestart,
116
- };
1093
+ fetchOllamaModels() {
1094
+ listOllamaModels().then((result) => {
1095
+ this.updateOllamaState(result);
1096
+ }).catch(() => {
1097
+ this.ollamaState.status = 'error';
1098
+ this.ollamaState.error = 'Failed to fetch models';
1099
+ this.requestRender();
1100
+ });
1101
+ }
1102
+ /**
1103
+ * Update Ollama state based on fetch result.
1104
+ */
1105
+ updateOllamaState(result) {
1106
+ if (!result.ollamaRunning) {
1107
+ this.ollamaState.status = 'offline';
1108
+ this.ollamaState.availableModels.clear();
1109
+ this.requestRender();
1110
+ return;
1111
+ }
1112
+ if (!result.success) {
1113
+ this.ollamaState.status = 'error';
1114
+ this.ollamaState.error = result.error;
1115
+ this.ollamaState.availableModels.clear();
1116
+ this.requestRender();
1117
+ return;
1118
+ }
1119
+ this.ollamaState.status = 'connected';
1120
+ this.ollamaState.availableModels.clear();
1121
+ // Add all available model names to the set
1122
+ for (const model of result.models) {
1123
+ const modelName = model.name.toLowerCase();
1124
+ const baseName = modelName.split(':')[0];
1125
+ this.ollamaState.availableModels.add(modelName);
1126
+ this.ollamaState.availableModels.add(baseName);
1127
+ }
1128
+ this.requestRender();
1129
+ }
1130
+ /**
1131
+ * Create detail screen for a list item.
1132
+ * For tiers: shows TierDetailScreen to select model for that tier
1133
+ * For providers: shows OthersProviderDetailScreen with provider's tier/models
1134
+ */
1135
+ createDetailScreen(item) {
1136
+ if (item.type === 'provider') {
1137
+ // Show provider detail screen with its tiers
1138
+ return new OthersProviderDetailScreen(item.metadata, this.getStyles(), this.activeModel, this.activeProvider, () => { this.requestRender(); });
1139
+ }
1140
+ // TierItem - show tier detail screen
1141
+ return new TierDetailScreen(item, this.getStyles(), this.activeModel, this.activeProvider, this.ollamaState, () => { this.requestRender(); });
117
1142
  }
118
1143
  }