@compilr-dev/cli 0.4.0 → 0.5.1

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 (634) hide show
  1. package/README.md +254 -68
  2. package/dist/.tsbuildinfo.app +1 -0
  3. package/dist/.tsbuildinfo.data +1 -0
  4. package/dist/.tsbuildinfo.domain +1 -0
  5. package/dist/.tsbuildinfo.foundation +1 -0
  6. package/dist/agent.d.ts +134 -4
  7. package/dist/agent.js +345 -166
  8. package/dist/anchors/index.d.ts +9 -0
  9. package/dist/anchors/index.js +9 -0
  10. package/dist/anchors/project-anchors.d.ts +79 -0
  11. package/dist/anchors/project-anchors.js +202 -0
  12. package/dist/auth/api-client.d.ts +124 -0
  13. package/dist/auth/api-client.js +261 -0
  14. package/dist/auth/index.d.ts +172 -0
  15. package/dist/auth/index.js +545 -0
  16. package/dist/auth/storage.d.ts +52 -0
  17. package/dist/auth/storage.js +118 -0
  18. package/dist/changelog/index.d.ts +16 -0
  19. package/dist/changelog/index.js +24 -0
  20. package/dist/changelog/releases.d.ts +17 -0
  21. package/dist/changelog/releases.js +63 -0
  22. package/dist/commands/index.d.ts +2 -1
  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.d.ts +13 -0
  28. package/dist/commands-v2/handlers/context.js +533 -0
  29. package/dist/commands-v2/handlers/core.d.ts +14 -0
  30. package/dist/commands-v2/handlers/core.js +290 -0
  31. package/dist/commands-v2/handlers/debug.d.ts +11 -0
  32. package/dist/commands-v2/handlers/debug.js +177 -0
  33. package/dist/commands-v2/handlers/delegations.d.ts +8 -0
  34. package/dist/commands-v2/handlers/delegations.js +29 -0
  35. package/dist/commands-v2/handlers/files.d.ts +8 -0
  36. package/dist/commands-v2/handlers/files.js +162 -0
  37. package/dist/commands-v2/handlers/filter.d.ts +9 -0
  38. package/dist/commands-v2/handlers/filter.js +130 -0
  39. package/dist/commands-v2/handlers/games.d.ts +7 -0
  40. package/dist/commands-v2/handlers/games.js +57 -0
  41. package/dist/commands-v2/handlers/index.d.ts +25 -0
  42. package/dist/commands-v2/handlers/index.js +63 -0
  43. package/dist/commands-v2/handlers/mcp.d.ts +8 -0
  44. package/dist/commands-v2/handlers/mcp.js +39 -0
  45. package/dist/commands-v2/handlers/notifications.d.ts +9 -0
  46. package/dist/commands-v2/handlers/notifications.js +34 -0
  47. package/dist/commands-v2/handlers/project.d.ts +22 -0
  48. package/dist/commands-v2/handlers/project.js +1035 -0
  49. package/dist/commands-v2/handlers/reset.d.ts +11 -0
  50. package/dist/commands-v2/handlers/reset.js +118 -0
  51. package/dist/commands-v2/handlers/session.d.ts +161 -0
  52. package/dist/commands-v2/handlers/session.js +805 -0
  53. package/dist/commands-v2/handlers/settings.d.ts +17 -0
  54. package/dist/commands-v2/handlers/settings.js +417 -0
  55. package/dist/commands-v2/handlers/tasks.d.ts +5 -0
  56. package/dist/commands-v2/handlers/tasks.js +36 -0
  57. package/dist/commands-v2/handlers/team.d.ts +9 -0
  58. package/dist/commands-v2/handlers/team.js +549 -0
  59. package/dist/commands-v2/handlers/terminals.d.ts +9 -0
  60. package/dist/commands-v2/handlers/terminals.js +34 -0
  61. package/dist/commands-v2/index.d.ts +14 -0
  62. package/dist/commands-v2/index.js +18 -0
  63. package/dist/commands-v2/registry.d.ts +52 -0
  64. package/dist/commands-v2/registry.js +114 -0
  65. package/dist/commands-v2/types.d.ts +153 -0
  66. package/dist/commands-v2/types.js +7 -0
  67. package/dist/commands.js +123 -7
  68. package/dist/compilr-diff-companion.vsix +0 -0
  69. package/dist/db/index.js +98 -4
  70. package/dist/db/repositories/document-repository.d.ts +2 -0
  71. package/dist/db/repositories/document-repository.js +5 -1
  72. package/dist/db/repositories/index.d.ts +2 -0
  73. package/dist/db/repositories/index.js +1 -0
  74. package/dist/db/repositories/plan-repository.d.ts +101 -0
  75. package/dist/db/repositories/plan-repository.js +275 -0
  76. package/dist/db/repositories/project-repository.d.ts +6 -0
  77. package/dist/db/repositories/project-repository.js +41 -0
  78. package/dist/db/repositories/work-item-repository.d.ts +15 -0
  79. package/dist/db/repositories/work-item-repository.js +69 -4
  80. package/dist/db/schema.d.ts +40 -3
  81. package/dist/db/schema.js +66 -3
  82. package/dist/episodes/index.d.ts +20 -0
  83. package/dist/episodes/index.js +27 -0
  84. package/dist/episodes/recorder.d.ts +51 -0
  85. package/dist/episodes/recorder.js +195 -0
  86. package/dist/episodes/significant-work.d.ts +21 -0
  87. package/dist/episodes/significant-work.js +56 -0
  88. package/dist/episodes/store.d.ts +38 -0
  89. package/dist/episodes/store.js +199 -0
  90. package/dist/episodes/types.d.ts +35 -0
  91. package/dist/episodes/types.js +6 -0
  92. package/dist/episodes/work-at-risk.d.ts +12 -0
  93. package/dist/episodes/work-at-risk.js +38 -0
  94. package/dist/episodes/work-summary-anchor.d.ts +23 -0
  95. package/dist/episodes/work-summary-anchor.js +73 -0
  96. package/dist/games/coins.d.ts +66 -0
  97. package/dist/games/coins.js +165 -0
  98. package/dist/games/game-base.d.ts +84 -0
  99. package/dist/games/game-base.js +204 -0
  100. package/dist/games/index.d.ts +16 -0
  101. package/dist/games/index.js +49 -0
  102. package/dist/games/scores.d.ts +69 -0
  103. package/dist/games/scores.js +191 -0
  104. package/dist/games/tetris/board.d.ts +59 -0
  105. package/dist/games/tetris/board.js +170 -0
  106. package/dist/games/tetris/index.d.ts +109 -0
  107. package/dist/games/tetris/index.js +610 -0
  108. package/dist/games/tetris/pieces.d.ts +44 -0
  109. package/dist/games/tetris/pieces.js +271 -0
  110. package/dist/games/tetris/renderer.d.ts +26 -0
  111. package/dist/games/tetris/renderer.js +77 -0
  112. package/dist/guide/guide-content.d.ts +23 -0
  113. package/dist/guide/guide-content.js +196 -0
  114. package/dist/guide/index.d.ts +8 -0
  115. package/dist/guide/index.js +7 -0
  116. package/dist/guide/shared-content.d.ts +37 -0
  117. package/dist/guide/shared-content.js +1272 -0
  118. package/dist/guide/tutorial-helpers.d.ts +57 -0
  119. package/dist/guide/tutorial-helpers.js +147 -0
  120. package/dist/handlers/ask-user-handlers.d.ts +32 -0
  121. package/dist/handlers/ask-user-handlers.js +104 -0
  122. package/dist/handlers/delegation-handlers.d.ts +34 -0
  123. package/dist/handlers/delegation-handlers.js +291 -0
  124. package/dist/handlers/permission-handler.d.ts +30 -0
  125. package/dist/handlers/permission-handler.js +205 -0
  126. package/dist/index.d.ts +11 -1
  127. package/dist/index.js +615 -179
  128. package/dist/input-handlers/index.d.ts +7 -0
  129. package/dist/input-handlers/index.js +7 -0
  130. package/dist/input-handlers/memory-handler.d.ts +26 -0
  131. package/dist/input-handlers/memory-handler.js +69 -0
  132. package/dist/models/index.d.ts +10 -0
  133. package/dist/models/index.js +12 -0
  134. package/dist/models/model-registry.d.ts +38 -0
  135. package/dist/models/model-registry.js +69 -0
  136. package/dist/models/model-tiers.d.ts +28 -0
  137. package/dist/models/model-tiers.js +71 -0
  138. package/dist/models/model-validation.d.ts +25 -0
  139. package/dist/models/model-validation.js +291 -0
  140. package/dist/models/ollama-models.d.ts +73 -0
  141. package/dist/models/ollama-models.js +178 -0
  142. package/dist/models/provider-types.d.ts +6 -0
  143. package/dist/models/provider-types.js +1 -0
  144. package/dist/models/providers.d.ts +35 -0
  145. package/dist/models/providers.js +58 -0
  146. package/dist/models/types.d.ts +4 -0
  147. package/dist/models/types.js +4 -0
  148. package/dist/multi-agent/activity.d.ts +21 -0
  149. package/dist/multi-agent/activity.js +34 -0
  150. package/dist/multi-agent/agent-selection.d.ts +55 -0
  151. package/dist/multi-agent/agent-selection.js +90 -0
  152. package/dist/multi-agent/artifacts.d.ts +197 -0
  153. package/dist/multi-agent/artifacts.js +379 -0
  154. package/dist/multi-agent/checkpointer.d.ts +138 -0
  155. package/dist/multi-agent/checkpointer.js +471 -0
  156. package/dist/multi-agent/collision-utils.d.ts +16 -0
  157. package/dist/multi-agent/collision-utils.js +28 -0
  158. package/dist/multi-agent/context-resolver.d.ts +97 -0
  159. package/dist/multi-agent/context-resolver.js +316 -0
  160. package/dist/multi-agent/custom-agents.d.ts +83 -0
  161. package/dist/multi-agent/custom-agents.js +227 -0
  162. package/dist/multi-agent/delegation-tracker.d.ts +157 -0
  163. package/dist/multi-agent/delegation-tracker.js +243 -0
  164. package/dist/multi-agent/file-lock-hook.d.ts +29 -0
  165. package/dist/multi-agent/file-lock-hook.js +97 -0
  166. package/dist/multi-agent/file-locks.d.ts +58 -0
  167. package/dist/multi-agent/file-locks.js +194 -0
  168. package/dist/multi-agent/index.d.ts +24 -0
  169. package/dist/multi-agent/index.js +30 -0
  170. package/dist/multi-agent/mention-parser.d.ts +64 -0
  171. package/dist/multi-agent/mention-parser.js +146 -0
  172. package/dist/multi-agent/notification-manager.d.ts +84 -0
  173. package/dist/multi-agent/notification-manager.js +224 -0
  174. package/dist/multi-agent/pending-requests.d.ts +122 -0
  175. package/dist/multi-agent/pending-requests.js +155 -0
  176. package/dist/multi-agent/session-registry.d.ts +139 -0
  177. package/dist/multi-agent/session-registry.js +514 -0
  178. package/dist/multi-agent/shared-context.d.ts +293 -0
  179. package/dist/multi-agent/shared-context.js +671 -0
  180. package/dist/multi-agent/skill-requirements.d.ts +66 -0
  181. package/dist/multi-agent/skill-requirements.js +178 -0
  182. package/dist/multi-agent/task-assignment.d.ts +69 -0
  183. package/dist/multi-agent/task-assignment.js +123 -0
  184. package/dist/multi-agent/task-suggestion.d.ts +31 -0
  185. package/dist/multi-agent/task-suggestion.js +72 -0
  186. package/dist/multi-agent/team-agent.d.ts +201 -0
  187. package/dist/multi-agent/team-agent.js +488 -0
  188. package/dist/multi-agent/team.d.ts +286 -0
  189. package/dist/multi-agent/team.js +610 -0
  190. package/dist/multi-agent/tool-config.d.ts +110 -0
  191. package/dist/multi-agent/tool-config.js +661 -0
  192. package/dist/multi-agent/types.d.ts +211 -0
  193. package/dist/multi-agent/types.js +617 -0
  194. package/dist/prompts/plan-mode-prompt.d.ts +11 -0
  195. package/dist/prompts/plan-mode-prompt.js +95 -0
  196. package/dist/repl-helpers.d.ts +63 -0
  197. package/dist/repl-helpers.js +319 -0
  198. package/dist/repl-v2.d.ts +554 -0
  199. package/dist/repl-v2.js +3286 -0
  200. package/dist/session/index.d.ts +6 -0
  201. package/dist/session/index.js +6 -0
  202. package/dist/session/project-session-manager.d.ts +158 -0
  203. package/dist/session/project-session-manager.js +650 -0
  204. package/dist/settings/index.d.ts +156 -13
  205. package/dist/settings/index.js +377 -24
  206. package/dist/settings/mcp-config.d.ts +76 -0
  207. package/dist/settings/mcp-config.js +143 -0
  208. package/dist/settings/paths.d.ts +114 -0
  209. package/dist/settings/paths.js +270 -0
  210. package/dist/shared-handlers.d.ts +62 -0
  211. package/dist/shared-handlers.js +48 -0
  212. package/dist/system-prompt/builder.d.ts +5 -0
  213. package/dist/system-prompt/builder.js +4 -0
  214. package/dist/system-prompt/index.d.ts +18 -0
  215. package/dist/system-prompt/index.js +18 -0
  216. package/dist/system-prompt/modules.d.ts +5 -0
  217. package/dist/system-prompt/modules.js +4 -0
  218. package/dist/tabbed-menu.js +2 -1
  219. package/dist/templates/compilr-md-import.d.ts +16 -0
  220. package/dist/templates/compilr-md-import.js +241 -0
  221. package/dist/templates/compilr-md.js +10 -58
  222. package/dist/templates/config-json.d.ts +1 -25
  223. package/dist/templates/index.d.ts +2 -0
  224. package/dist/templates/index.js +35 -75
  225. package/dist/themes/colors.js +3 -1
  226. package/dist/themes/registry.d.ts +5 -36
  227. package/dist/themes/registry.js +11 -95
  228. package/dist/themes/types.d.ts +3 -38
  229. package/dist/themes/types.js +2 -2
  230. package/dist/tool-names.d.ts +108 -0
  231. package/dist/tool-names.js +227 -0
  232. package/dist/tools/anchor-tools.d.ts +31 -0
  233. package/dist/tools/anchor-tools.js +255 -0
  234. package/dist/tools/artifact-tools.d.ts +42 -0
  235. package/dist/tools/artifact-tools.js +328 -0
  236. package/dist/tools/ask-user-simple.d.ts +1 -1
  237. package/dist/tools/ask-user-simple.js +2 -1
  238. package/dist/tools/ask-user.d.ts +1 -1
  239. package/dist/tools/ask-user.js +2 -1
  240. package/dist/tools/backlog-wrappers.d.ts +56 -0
  241. package/dist/tools/backlog-wrappers.js +353 -0
  242. package/dist/tools/backlog.d.ts +2 -2
  243. package/dist/tools/backlog.js +2 -2
  244. package/dist/tools/db-tools.d.ts +12 -0
  245. package/dist/tools/db-tools.js +14 -0
  246. package/dist/tools/delegate-background.d.ts +27 -0
  247. package/dist/tools/delegate-background.js +115 -0
  248. package/dist/tools/delegate.d.ts +22 -0
  249. package/dist/tools/delegate.js +97 -0
  250. package/dist/tools/delegation-status.d.ts +16 -0
  251. package/dist/tools/delegation-status.js +128 -0
  252. package/dist/tools/document-db.d.ts +43 -0
  253. package/dist/tools/document-db.js +220 -0
  254. package/dist/tools/guide-tool.d.ts +12 -0
  255. package/dist/tools/guide-tool.js +59 -0
  256. package/dist/tools/handoff.d.ts +25 -0
  257. package/dist/tools/handoff.js +99 -0
  258. package/dist/tools/meta-tools.d.ts +26 -0
  259. package/dist/tools/meta-tools.js +47 -0
  260. package/dist/tools/plan-tools.d.ts +54 -0
  261. package/dist/tools/plan-tools.js +338 -0
  262. package/dist/tools/platform-adapter.d.ts +29 -0
  263. package/dist/tools/platform-adapter.js +394 -0
  264. package/dist/tools/project-db.d.ts +34 -0
  265. package/dist/tools/project-db.js +39 -0
  266. package/dist/tools/recall-work-tool.d.ts +18 -0
  267. package/dist/tools/recall-work-tool.js +82 -0
  268. package/dist/tools/workitem-db.d.ts +135 -0
  269. package/dist/tools/workitem-db.js +730 -0
  270. package/dist/tools.d.ts +67 -2
  271. package/dist/tools.js +238 -38
  272. package/dist/ui/ask-user-overlay.d.ts +2 -2
  273. package/dist/ui/ask-user-overlay.js +443 -535
  274. package/dist/ui/ask-user-simple-overlay.d.ts +2 -2
  275. package/dist/ui/ask-user-simple-overlay.js +182 -209
  276. package/dist/ui/autocomplete-controller.d.ts +42 -0
  277. package/dist/ui/autocomplete-controller.js +384 -0
  278. package/dist/ui/base/index.d.ts +26 -0
  279. package/dist/ui/base/index.js +33 -0
  280. package/dist/ui/base/inline-overlay-utils.d.ts +217 -0
  281. package/dist/ui/base/inline-overlay-utils.js +320 -0
  282. package/dist/ui/base/inline-overlay.d.ts +159 -0
  283. package/dist/ui/base/inline-overlay.js +257 -0
  284. package/dist/ui/base/key-utils.d.ts +15 -0
  285. package/dist/ui/base/key-utils.js +30 -0
  286. package/dist/ui/base/overlay-base-v2.d.ts +203 -0
  287. package/dist/ui/base/overlay-base-v2.js +260 -0
  288. package/dist/ui/base/overlay-base.d.ts +156 -0
  289. package/dist/ui/base/overlay-base.js +238 -0
  290. package/dist/ui/base/overlay-lifecycle.d.ts +65 -0
  291. package/dist/ui/base/overlay-lifecycle.js +159 -0
  292. package/dist/ui/base/overlay-types.d.ts +185 -0
  293. package/dist/ui/base/overlay-types.js +7 -0
  294. package/dist/ui/base/render-utils.d.ts +27 -0
  295. package/dist/ui/base/render-utils.js +36 -0
  296. package/dist/ui/base/screen-stack.d.ts +148 -0
  297. package/dist/ui/base/screen-stack.js +184 -0
  298. package/dist/ui/base/tabbed-list-overlay-v2.d.ts +118 -0
  299. package/dist/ui/base/tabbed-list-overlay-v2.js +335 -0
  300. package/dist/ui/base/tabbed-list-overlay.d.ts +153 -0
  301. package/dist/ui/base/tabbed-list-overlay.js +369 -0
  302. package/dist/ui/constants/labels.d.ts +14 -0
  303. package/dist/ui/constants/labels.js +51 -0
  304. package/dist/ui/conversation-store.d.ts +55 -0
  305. package/dist/ui/conversation-store.js +107 -0
  306. package/dist/ui/conversation.d.ts +75 -4
  307. package/dist/ui/conversation.js +376 -165
  308. package/dist/ui/diff.d.ts +7 -1
  309. package/dist/ui/diff.js +85 -48
  310. package/dist/ui/ephemeral.d.ts +1 -1
  311. package/dist/ui/ephemeral.js +4 -10
  312. package/dist/ui/features/index.d.ts +34 -0
  313. package/dist/ui/features/index.js +34 -0
  314. package/dist/ui/features/input-feature.d.ts +85 -0
  315. package/dist/ui/features/input-feature.js +238 -0
  316. package/dist/ui/features/list-feature.d.ts +155 -0
  317. package/dist/ui/features/list-feature.js +244 -0
  318. package/dist/ui/features/pagination-feature.d.ts +154 -0
  319. package/dist/ui/features/pagination-feature.js +238 -0
  320. package/dist/ui/features/search-feature.d.ts +148 -0
  321. package/dist/ui/features/search-feature.js +185 -0
  322. package/dist/ui/features/tab-feature.d.ts +194 -0
  323. package/dist/ui/features/tab-feature.js +307 -0
  324. package/dist/ui/file-autocomplete.d.ts +24 -0
  325. package/dist/ui/file-autocomplete.js +56 -0
  326. package/dist/ui/footer-renderer.d.ts +69 -0
  327. package/dist/ui/footer-renderer.js +431 -0
  328. package/dist/ui/footer.d.ts +181 -7
  329. package/dist/ui/footer.js +523 -74
  330. package/dist/ui/guardrail-overlay.d.ts +29 -0
  331. package/dist/ui/guardrail-overlay.js +145 -0
  332. package/dist/ui/index.d.ts +1 -1
  333. package/dist/ui/index.js +1 -3
  334. package/dist/ui/input-controller.d.ts +51 -0
  335. package/dist/ui/input-controller.js +176 -0
  336. package/dist/ui/input-prompt.d.ts +135 -33
  337. package/dist/ui/input-prompt.js +728 -337
  338. package/dist/ui/iteration-limit-overlay.d.ts +2 -2
  339. package/dist/ui/iteration-limit-overlay.js +92 -128
  340. package/dist/ui/keyboard-handler.d.ts +57 -0
  341. package/dist/ui/keyboard-handler.js +557 -0
  342. package/dist/ui/keys-overlay.d.ts +1 -0
  343. package/dist/ui/keys-overlay.js +203 -141
  344. package/dist/ui/line-utils.d.ts +88 -0
  345. package/dist/ui/line-utils.js +150 -0
  346. package/dist/ui/live-region-facade.d.ts +42 -0
  347. package/dist/ui/live-region-facade.js +205 -0
  348. package/dist/ui/live-region.d.ts +157 -0
  349. package/dist/ui/live-region.js +379 -0
  350. package/dist/ui/mascot/expressions.d.ts +32 -0
  351. package/dist/ui/mascot/expressions.js +213 -0
  352. package/dist/ui/mascot/index.d.ts +8 -0
  353. package/dist/ui/mascot/index.js +8 -0
  354. package/dist/ui/mascot/renderer.d.ts +19 -0
  355. package/dist/ui/mascot/renderer.js +132 -0
  356. package/dist/ui/overlay/data/tutorial-content.d.ts +9 -0
  357. package/dist/ui/overlay/data/tutorial-content.js +9 -0
  358. package/dist/ui/overlay/data/tutorial-registry.d.ts +12 -0
  359. package/dist/ui/overlay/data/tutorial-registry.js +116 -0
  360. package/dist/ui/overlay/data/tutorial-types.d.ts +35 -0
  361. package/dist/ui/overlay/data/tutorial-types.js +6 -0
  362. package/dist/ui/overlay/data/tutorials/basics/first-conversation.d.ts +7 -0
  363. package/dist/ui/overlay/data/tutorials/basics/first-conversation.js +220 -0
  364. package/dist/ui/overlay/data/tutorials/basics/first-project.d.ts +7 -0
  365. package/dist/ui/overlay/data/tutorials/basics/first-project.js +284 -0
  366. package/dist/ui/overlay/data/tutorials/basics/navigation.d.ts +8 -0
  367. package/dist/ui/overlay/data/tutorials/basics/navigation.js +22 -0
  368. package/dist/ui/overlay/data/tutorials/basics/welcome.d.ts +7 -0
  369. package/dist/ui/overlay/data/tutorials/basics/welcome.js +174 -0
  370. package/dist/ui/overlay/data/tutorials/config/context-management.d.ts +7 -0
  371. package/dist/ui/overlay/data/tutorials/config/context-management.js +158 -0
  372. package/dist/ui/overlay/data/tutorials/config/mcp-servers.d.ts +8 -0
  373. package/dist/ui/overlay/data/tutorials/config/mcp-servers.js +155 -0
  374. package/dist/ui/overlay/data/tutorials/config/model-selection.d.ts +7 -0
  375. package/dist/ui/overlay/data/tutorials/config/model-selection.js +162 -0
  376. package/dist/ui/overlay/data/tutorials/config/permissions-safety.d.ts +7 -0
  377. package/dist/ui/overlay/data/tutorials/config/permissions-safety.js +163 -0
  378. package/dist/ui/overlay/data/tutorials/config/settings-config.d.ts +7 -0
  379. package/dist/ui/overlay/data/tutorials/config/settings-config.js +166 -0
  380. package/dist/ui/overlay/data/tutorials/planning/arch.d.ts +7 -0
  381. package/dist/ui/overlay/data/tutorials/planning/arch.js +168 -0
  382. package/dist/ui/overlay/data/tutorials/planning/backlog.d.ts +7 -0
  383. package/dist/ui/overlay/data/tutorials/planning/backlog.js +103 -0
  384. package/dist/ui/overlay/data/tutorials/planning/build.d.ts +7 -0
  385. package/dist/ui/overlay/data/tutorials/planning/build.js +173 -0
  386. package/dist/ui/overlay/data/tutorials/planning/design.d.ts +7 -0
  387. package/dist/ui/overlay/data/tutorials/planning/design.js +205 -0
  388. package/dist/ui/overlay/data/tutorials/planning/docs.d.ts +7 -0
  389. package/dist/ui/overlay/data/tutorials/planning/docs.js +143 -0
  390. package/dist/ui/overlay/data/tutorials/planning/prd.d.ts +7 -0
  391. package/dist/ui/overlay/data/tutorials/planning/prd.js +173 -0
  392. package/dist/ui/overlay/data/tutorials/planning/scaffold.d.ts +7 -0
  393. package/dist/ui/overlay/data/tutorials/planning/scaffold.js +164 -0
  394. package/dist/ui/overlay/data/tutorials/planning/sketch.d.ts +7 -0
  395. package/dist/ui/overlay/data/tutorials/planning/sketch.js +58 -0
  396. package/dist/ui/overlay/data/tutorials/projects/anchors.d.ts +7 -0
  397. package/dist/ui/overlay/data/tutorials/projects/anchors.js +248 -0
  398. package/dist/ui/overlay/data/tutorials/projects/import-project.d.ts +7 -0
  399. package/dist/ui/overlay/data/tutorials/projects/import-project.js +172 -0
  400. package/dist/ui/overlay/data/tutorials/projects/managing-projects.d.ts +8 -0
  401. package/dist/ui/overlay/data/tutorials/projects/managing-projects.js +212 -0
  402. package/dist/ui/overlay/data/tutorials/projects/new-project.d.ts +7 -0
  403. package/dist/ui/overlay/data/tutorials/projects/new-project.js +251 -0
  404. package/dist/ui/overlay/data/tutorials/projects/session-management.d.ts +7 -0
  405. package/dist/ui/overlay/data/tutorials/projects/session-management.js +169 -0
  406. package/dist/ui/overlay/data/tutorials/teams/background-execution.d.ts +7 -0
  407. package/dist/ui/overlay/data/tutorials/teams/background-execution.js +171 -0
  408. package/dist/ui/overlay/data/tutorials/teams/multi-terminal.d.ts +8 -0
  409. package/dist/ui/overlay/data/tutorials/teams/multi-terminal.js +147 -0
  410. package/dist/ui/overlay/data/tutorials/teams/task-assignment.d.ts +7 -0
  411. package/dist/ui/overlay/data/tutorials/teams/task-assignment.js +204 -0
  412. package/dist/ui/overlay/data/tutorials/teams/team-overview.d.ts +7 -0
  413. package/dist/ui/overlay/data/tutorials/teams/team-overview.js +165 -0
  414. package/dist/ui/overlay/data/tutorials/teams/working-with-agents.d.ts +7 -0
  415. package/dist/ui/overlay/data/tutorials/teams/working-with-agents.js +172 -0
  416. package/dist/ui/overlay/impl/agents-overlay-v2.d.ts +45 -0
  417. package/dist/ui/overlay/impl/agents-overlay-v2.js +814 -0
  418. package/dist/ui/overlay/impl/anchors-overlay-v2.d.ts +47 -0
  419. package/dist/ui/overlay/impl/anchors-overlay-v2.js +749 -0
  420. package/dist/ui/overlay/impl/arch-type-overlay-v2.d.ts +37 -0
  421. package/dist/ui/overlay/impl/arch-type-overlay-v2.js +240 -0
  422. package/dist/ui/overlay/impl/artifact-detail-overlay-v2.d.ts +43 -0
  423. package/dist/ui/overlay/impl/artifact-detail-overlay-v2.js +232 -0
  424. package/dist/ui/overlay/impl/artifact-overlay-v2.d.ts +40 -0
  425. package/dist/ui/overlay/impl/artifact-overlay-v2.js +115 -0
  426. package/dist/ui/overlay/impl/ask-user-overlay-v2.d.ts +72 -0
  427. package/dist/ui/overlay/impl/ask-user-overlay-v2.js +581 -0
  428. package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.d.ts +46 -0
  429. package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.js +204 -0
  430. package/dist/ui/overlay/impl/background-overlay-v2.d.ts +40 -0
  431. package/dist/ui/overlay/impl/background-overlay-v2.js +147 -0
  432. package/dist/ui/overlay/impl/backlog-overlay-v2.d.ts +52 -0
  433. package/dist/ui/overlay/impl/backlog-overlay-v2.js +681 -0
  434. package/dist/ui/overlay/impl/changelog-overlay-v2.d.ts +44 -0
  435. package/dist/ui/overlay/impl/changelog-overlay-v2.js +165 -0
  436. package/dist/ui/overlay/impl/commands-overlay-v2.d.ts +33 -0
  437. package/dist/ui/overlay/impl/commands-overlay-v2.js +439 -0
  438. package/dist/ui/overlay/impl/config-overlay-v2.d.ts +111 -0
  439. package/dist/ui/overlay/impl/config-overlay-v2.js +718 -0
  440. package/dist/ui/overlay/impl/custom-agent-form-overlay-v2.d.ts +83 -0
  441. package/dist/ui/overlay/impl/custom-agent-form-overlay-v2.js +711 -0
  442. package/dist/ui/overlay/impl/dashboard-overlay-v2.d.ts +57 -0
  443. package/dist/ui/overlay/impl/dashboard-overlay-v2.js +382 -0
  444. package/dist/ui/overlay/impl/delegations-overlay-v2.d.ts +28 -0
  445. package/dist/ui/overlay/impl/delegations-overlay-v2.js +279 -0
  446. package/dist/ui/overlay/impl/docs-overlay-v2.d.ts +45 -0
  447. package/dist/ui/overlay/impl/docs-overlay-v2.js +117 -0
  448. package/dist/ui/overlay/impl/document-detail-overlay-v2.d.ts +84 -0
  449. package/dist/ui/overlay/impl/document-detail-overlay-v2.js +1112 -0
  450. package/dist/ui/overlay/impl/filter-overlay-v2.d.ts +41 -0
  451. package/dist/ui/overlay/impl/filter-overlay-v2.js +110 -0
  452. package/dist/ui/overlay/impl/games-overlay-v2.d.ts +31 -0
  453. package/dist/ui/overlay/impl/games-overlay-v2.js +135 -0
  454. package/dist/ui/overlay/impl/guardrail-overlay-v2.d.ts +43 -0
  455. package/dist/ui/overlay/impl/guardrail-overlay-v2.js +114 -0
  456. package/dist/ui/overlay/impl/help-overlay-v2.d.ts +57 -0
  457. package/dist/ui/overlay/impl/help-overlay-v2.js +287 -0
  458. package/dist/ui/overlay/impl/init-setup-overlay-v2.d.ts +25 -0
  459. package/dist/ui/overlay/impl/init-setup-overlay-v2.js +97 -0
  460. package/dist/ui/overlay/impl/iteration-limit-overlay-v2.d.ts +35 -0
  461. package/dist/ui/overlay/impl/iteration-limit-overlay-v2.js +105 -0
  462. package/dist/ui/overlay/impl/keys-overlay-v2.d.ts +41 -0
  463. package/dist/ui/overlay/impl/keys-overlay-v2.js +248 -0
  464. package/dist/ui/overlay/impl/login-overlay-v2.d.ts +49 -0
  465. package/dist/ui/overlay/impl/login-overlay-v2.js +277 -0
  466. package/dist/ui/overlay/impl/mascot-overlay-v2.d.ts +41 -0
  467. package/dist/ui/overlay/impl/mascot-overlay-v2.js +138 -0
  468. package/dist/ui/overlay/impl/mcp-overlay-v2.d.ts +63 -0
  469. package/dist/ui/overlay/impl/mcp-overlay-v2.js +907 -0
  470. package/dist/ui/overlay/impl/model-overlay-v2.d.ts +93 -0
  471. package/dist/ui/overlay/impl/model-overlay-v2.js +1143 -0
  472. package/dist/ui/overlay/impl/model-warning-overlay-v2.d.ts +46 -0
  473. package/dist/ui/overlay/impl/model-warning-overlay-v2.js +132 -0
  474. package/dist/ui/overlay/impl/new-overlay-v2.d.ts +108 -0
  475. package/dist/ui/overlay/impl/new-overlay-v2.js +1243 -0
  476. package/dist/ui/overlay/impl/notifications-overlay-v2.d.ts +20 -0
  477. package/dist/ui/overlay/impl/notifications-overlay-v2.js +116 -0
  478. package/dist/ui/overlay/impl/onboarding-wizard-overlay-v2.d.ts +76 -0
  479. package/dist/ui/overlay/impl/onboarding-wizard-overlay-v2.js +728 -0
  480. package/dist/ui/overlay/impl/pending-overlay-v2.d.ts +51 -0
  481. package/dist/ui/overlay/impl/pending-overlay-v2.js +445 -0
  482. package/dist/ui/overlay/impl/permission-overlay-v2.d.ts +36 -0
  483. package/dist/ui/overlay/impl/permission-overlay-v2.js +380 -0
  484. package/dist/ui/overlay/impl/permissions-overlay-v2.d.ts +85 -0
  485. package/dist/ui/overlay/impl/permissions-overlay-v2.js +820 -0
  486. package/dist/ui/overlay/impl/plan-approval-overlay-v2.d.ts +35 -0
  487. package/dist/ui/overlay/impl/plan-approval-overlay-v2.js +181 -0
  488. package/dist/ui/overlay/impl/project-edit-overlay-v2.d.ts +36 -0
  489. package/dist/ui/overlay/impl/project-edit-overlay-v2.js +195 -0
  490. package/dist/ui/overlay/impl/projects-overlay-v2.d.ts +37 -0
  491. package/dist/ui/overlay/impl/projects-overlay-v2.js +733 -0
  492. package/dist/ui/overlay/impl/reset-overlay-v2.d.ts +39 -0
  493. package/dist/ui/overlay/impl/reset-overlay-v2.js +107 -0
  494. package/dist/ui/overlay/impl/resume-overlay-v2.d.ts +60 -0
  495. package/dist/ui/overlay/impl/resume-overlay-v2.js +414 -0
  496. package/dist/ui/overlay/impl/session-mode-overlay-v2.d.ts +43 -0
  497. package/dist/ui/overlay/impl/session-mode-overlay-v2.js +124 -0
  498. package/dist/ui/overlay/impl/tasks-overlay-v2.d.ts +28 -0
  499. package/dist/ui/overlay/impl/tasks-overlay-v2.js +283 -0
  500. package/dist/ui/overlay/impl/team-overlay-v2.d.ts +86 -0
  501. package/dist/ui/overlay/impl/team-overlay-v2.js +692 -0
  502. package/dist/ui/overlay/impl/terminals-overlay-v2.d.ts +26 -0
  503. package/dist/ui/overlay/impl/terminals-overlay-v2.js +217 -0
  504. package/dist/ui/overlay/impl/theme-overlay-v2.d.ts +42 -0
  505. package/dist/ui/overlay/impl/theme-overlay-v2.js +135 -0
  506. package/dist/ui/overlay/impl/tools-overlay-v2.d.ts +47 -0
  507. package/dist/ui/overlay/impl/tools-overlay-v2.js +214 -0
  508. package/dist/ui/overlay/impl/tutorial-overlay-v2.d.ts +45 -0
  509. package/dist/ui/overlay/impl/tutorial-overlay-v2.js +212 -0
  510. package/dist/ui/overlay/impl/workflow-overlay-v2.d.ts +80 -0
  511. package/dist/ui/overlay/impl/workflow-overlay-v2.js +641 -0
  512. package/dist/ui/overlay/index.d.ts +52 -0
  513. package/dist/ui/overlay/index.js +54 -0
  514. package/dist/ui/overlay/key-utils.d.ts +6 -0
  515. package/dist/ui/overlay/key-utils.js +6 -0
  516. package/dist/ui/overlay/types.d.ts +140 -0
  517. package/dist/ui/overlay/types.js +22 -0
  518. package/dist/ui/overlay-manager.d.ts +43 -0
  519. package/dist/ui/overlay-manager.js +238 -0
  520. package/dist/ui/overlays.d.ts +0 -4
  521. package/dist/ui/overlays.js +4 -460
  522. package/dist/ui/permission-overlay.d.ts +1 -1
  523. package/dist/ui/permission-overlay.js +189 -300
  524. package/dist/ui/providers/types.d.ts +178 -0
  525. package/dist/ui/providers/types.js +9 -0
  526. package/dist/ui/render-modes.d.ts +36 -0
  527. package/dist/ui/render-modes.js +44 -0
  528. package/dist/ui/startup-menu.d.ts +36 -0
  529. package/dist/ui/startup-menu.js +236 -0
  530. package/dist/ui/status-bar-controller.d.ts +33 -0
  531. package/dist/ui/status-bar-controller.js +99 -0
  532. package/dist/ui/subagent-renderer.d.ts +117 -0
  533. package/dist/ui/subagent-renderer.js +318 -0
  534. package/dist/ui/terminal-autocomplete-utils.d.ts +23 -0
  535. package/dist/ui/terminal-autocomplete-utils.js +83 -0
  536. package/dist/ui/terminal-codes.d.ts +94 -0
  537. package/dist/ui/terminal-codes.js +124 -0
  538. package/dist/ui/terminal-line-builders.d.ts +17 -0
  539. package/dist/ui/terminal-line-builders.js +42 -0
  540. package/dist/ui/terminal-render-item.d.ts +16 -0
  541. package/dist/ui/terminal-render-item.js +267 -0
  542. package/dist/ui/terminal-renderer.d.ts +220 -0
  543. package/dist/ui/terminal-renderer.js +750 -0
  544. package/dist/ui/terminal-types.d.ts +179 -0
  545. package/dist/ui/terminal-types.js +34 -0
  546. package/dist/ui/terminal-ui.d.ts +331 -0
  547. package/dist/ui/terminal-ui.js +819 -0
  548. package/dist/ui/terminal.d.ts +20 -0
  549. package/dist/ui/terminal.js +72 -0
  550. package/dist/ui/todo-zone.d.ts +19 -1
  551. package/dist/ui/todo-zone.js +124 -38
  552. package/dist/ui/tool-formatters.d.ts +16 -0
  553. package/dist/ui/tool-formatters.js +1027 -0
  554. package/dist/ui/turn-metrics.d.ts +56 -0
  555. package/dist/ui/turn-metrics.js +75 -0
  556. package/dist/ui/types.d.ts +28 -0
  557. package/dist/ui/types.js +1 -0
  558. package/dist/ui/vscode-diff-ipc.d.ts +102 -0
  559. package/dist/ui/vscode-diff-ipc.js +385 -0
  560. package/dist/utils/credentials.d.ts +24 -5
  561. package/dist/utils/credentials.js +123 -9
  562. package/dist/utils/debug-log.d.ts +28 -0
  563. package/dist/utils/debug-log.js +57 -0
  564. package/dist/utils/format-tokens.d.ts +13 -0
  565. package/dist/utils/format-tokens.js +18 -0
  566. package/dist/utils/git-config.d.ts +26 -0
  567. package/dist/utils/git-config.js +54 -0
  568. package/dist/utils/message-utils.d.ts +61 -0
  569. package/dist/utils/message-utils.js +72 -0
  570. package/dist/utils/model-tiers.d.ts +8 -1
  571. package/dist/utils/model-tiers.js +39 -17
  572. package/dist/utils/open-browser.d.ts +5 -0
  573. package/dist/utils/open-browser.js +32 -0
  574. package/dist/utils/path-safety.d.ts +56 -0
  575. package/dist/utils/path-safety.js +240 -0
  576. package/dist/utils/project-detection.d.ts +58 -0
  577. package/dist/utils/project-detection.js +424 -0
  578. package/dist/utils/project-memory.js +2 -1
  579. package/dist/utils/project-status.d.ts +2 -2
  580. package/dist/utils/startup-perf.d.ts +18 -0
  581. package/dist/utils/startup-perf.js +60 -0
  582. package/dist/utils/token-tracker.d.ts +62 -0
  583. package/dist/utils/token-tracker.js +150 -0
  584. package/dist/utils/token-types.d.ts +23 -0
  585. package/dist/utils/token-types.js +18 -0
  586. package/dist/utils/types/config-types.d.ts +32 -0
  587. package/dist/utils/types/config-types.js +8 -0
  588. package/dist/utils/update-checker.d.ts +28 -0
  589. package/dist/utils/update-checker.js +106 -0
  590. package/dist/utils/version.d.ts +7 -0
  591. package/dist/utils/version.js +10 -0
  592. package/dist/utils/vscode-detect.d.ts +39 -0
  593. package/dist/utils/vscode-detect.js +137 -0
  594. package/dist/workflow/guided-mode-injector.d.ts +42 -0
  595. package/dist/workflow/guided-mode-injector.js +191 -0
  596. package/dist/workflow/index.d.ts +8 -0
  597. package/dist/workflow/index.js +8 -0
  598. package/dist/workflow/step-criteria.d.ts +62 -0
  599. package/dist/workflow/step-criteria.js +150 -0
  600. package/dist/workflow/step-tracker.d.ts +92 -0
  601. package/dist/workflow/step-tracker.js +141 -0
  602. package/package.json +32 -12
  603. package/dist/index.old.d.ts +0 -7
  604. package/dist/index.old.js +0 -1014
  605. package/dist/repl.d.ts +0 -121
  606. package/dist/repl.js +0 -1878
  607. package/dist/templates/claude-md.d.ts +0 -7
  608. package/dist/templates/claude-md.js +0 -189
  609. package/dist/test-autocomplete.d.ts +0 -7
  610. package/dist/test-autocomplete.js +0 -85
  611. package/dist/test-tabbed-menu.d.ts +0 -7
  612. package/dist/test-tabbed-menu.js +0 -25
  613. package/dist/tool-selector.d.ts +0 -71
  614. package/dist/tool-selector.js +0 -184
  615. package/dist/ui/agents-overlay.d.ts +0 -12
  616. package/dist/ui/agents-overlay.js +0 -501
  617. package/dist/ui/arch-type-overlay.d.ts +0 -20
  618. package/dist/ui/arch-type-overlay.js +0 -229
  619. package/dist/ui/backlog-overlay.d.ts +0 -17
  620. package/dist/ui/backlog-overlay.js +0 -786
  621. package/dist/ui/commands-overlay.d.ts +0 -11
  622. package/dist/ui/commands-overlay.js +0 -410
  623. package/dist/ui/config-overlay.d.ts +0 -34
  624. package/dist/ui/config-overlay.js +0 -977
  625. package/dist/ui/init-overlay.d.ts +0 -24
  626. package/dist/ui/init-overlay.js +0 -525
  627. package/dist/ui/input-prompt-v2.d.ts +0 -179
  628. package/dist/ui/input-prompt-v2.js +0 -991
  629. package/dist/ui/model-warning-overlay.d.ts +0 -30
  630. package/dist/ui/model-warning-overlay.js +0 -171
  631. package/dist/ui/tools-overlay.d.ts +0 -26
  632. package/dist/ui/tools-overlay.js +0 -278
  633. package/dist/ui/tutorial-overlay.d.ts +0 -10
  634. package/dist/ui/tutorial-overlay.js +0 -936
@@ -0,0 +1,1143 @@
1
+ /**
2
+ * Model Overlay V2
3
+ *
4
+ * Model selection overlay using TabbedListOverlayV2 for the new Overlay interface.
5
+ * Works with TerminalUI's overlay management.
6
+ *
7
+ * Features:
8
+ * - Tabs by provider (Claude, OpenAI, Gemini, Ollama)
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)
13
+ * - API key status checking
14
+ * - Persists selection to settings
15
+ */
16
+ import { TabbedListOverlayV2, BaseScreen, pushScreen, popScreen, closeOverlay, stay, renderBorder, } from '../../base/index.js';
17
+ import { getSetting, setSetting } from '../../../settings/index.js';
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';
25
+ // =============================================================================
26
+ // Constants
27
+ // =============================================================================
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'];
34
+ const PROVIDER_TABS = [
35
+ { id: 'claude', label: 'Claude' },
36
+ { id: 'openai', label: 'OpenAI' },
37
+ { id: 'gemini', label: 'Gemini' },
38
+ { id: 'ollama', label: 'Ollama (local)' },
39
+ { id: 'others', label: 'Others' },
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
+ }
921
+ // =============================================================================
922
+ // Model Overlay V2 Class
923
+ // =============================================================================
924
+ /**
925
+ * Model selection overlay using the new Overlay interface.
926
+ * Displays available models in a tabbed list by provider.
927
+ * Others tab shows providers; selecting a provider shows its models.
928
+ */
929
+ export class ModelOverlayV2 extends TabbedListOverlayV2 {
930
+ type = 'inline';
931
+ id = 'model-overlay-v2';
932
+ activeModel;
933
+ activeProvider;
934
+ /** Ollama auto-detection state */
935
+ ollamaState = {
936
+ status: 'loading',
937
+ availableModels: new Set(),
938
+ };
939
+ minHeight = 18;
940
+ constructor(options = {}) {
941
+ const activeModel = options.currentModel ?? 'claude-sonnet-4-5-20250929';
942
+ const activeProvider = options.currentProvider ?? 'claude';
943
+ // Find the initial tab based on current provider
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();
947
+ super({
948
+ title: 'Select Model',
949
+ tabs: PROVIDER_TABS,
950
+ items: listItems,
951
+ pageSize: PAGE_SIZE,
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
+ },
968
+ renderItem: (item, isSelected, styles) => {
969
+ const prefix = isSelected ? styles.primary(' > ') : ' ';
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}`;
1007
+ },
1008
+ footerHints: (searchMode) => {
1009
+ if (searchMode) {
1010
+ return 'Type to filter · ↑↓/jk Navigate · Enter Details · Esc Exit search';
1011
+ }
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 };
1068
+ },
1069
+ emptyMessage: 'No models available for this provider.',
1070
+ noResultsMessage: 'No models match the search.',
1071
+ });
1072
+ this.activeModel = activeModel;
1073
+ this.activeProvider = activeProvider;
1074
+ // Set initial tab based on current provider
1075
+ if (initialTabIndex >= 0) {
1076
+ this.state.currentTab = initialTabIndex;
1077
+ // Re-filter items for the selected tab
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
+ });
1086
+ }
1087
+ // Start fetching Ollama models in background
1088
+ this.fetchOllamaModels();
1089
+ }
1090
+ /**
1091
+ * Fetch available Ollama models and update state.
1092
+ */
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(); });
1142
+ }
1143
+ }