@compilr-dev/cli 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/README.md +30 -12
  2. package/dist/agent.d.ts +74 -1
  3. package/dist/agent.js +259 -76
  4. package/dist/anchors/index.d.ts +9 -0
  5. package/dist/anchors/index.js +9 -0
  6. package/dist/anchors/project-anchors.d.ts +79 -0
  7. package/dist/anchors/project-anchors.js +202 -0
  8. package/dist/commands/handler-types.d.ts +68 -0
  9. package/dist/commands/handler-types.js +8 -0
  10. package/dist/commands/handlers/agent-commands.d.ts +13 -0
  11. package/dist/commands/handlers/agent-commands.js +305 -0
  12. package/dist/commands/handlers/design-commands.d.ts +15 -0
  13. package/dist/commands/handlers/design-commands.js +334 -0
  14. package/dist/commands/handlers/index.d.ts +20 -0
  15. package/dist/commands/handlers/index.js +43 -0
  16. package/dist/commands/handlers/overlay-commands.d.ts +21 -0
  17. package/dist/commands/handlers/overlay-commands.js +287 -0
  18. package/dist/commands/handlers/project-commands.d.ts +11 -0
  19. package/dist/commands/handlers/project-commands.js +167 -0
  20. package/dist/commands/handlers/simple-commands.d.ts +19 -0
  21. package/dist/commands/handlers/simple-commands.js +144 -0
  22. package/dist/commands/index.d.ts +2 -1
  23. package/dist/commands/registry.d.ts +50 -0
  24. package/dist/commands/registry.js +75 -0
  25. package/dist/commands-v2/handlers/context.d.ts +13 -0
  26. package/dist/commands-v2/handlers/context.js +348 -0
  27. package/dist/commands-v2/handlers/core.d.ts +13 -0
  28. package/dist/commands-v2/handlers/core.js +165 -0
  29. package/dist/commands-v2/handlers/debug.d.ts +11 -0
  30. package/dist/commands-v2/handlers/debug.js +159 -0
  31. package/dist/commands-v2/handlers/index.d.ts +12 -0
  32. package/dist/commands-v2/handlers/index.js +24 -0
  33. package/dist/commands-v2/handlers/project.d.ts +22 -0
  34. package/dist/commands-v2/handlers/project.js +814 -0
  35. package/dist/commands-v2/handlers/settings.d.ts +15 -0
  36. package/dist/commands-v2/handlers/settings.js +235 -0
  37. package/dist/commands-v2/index.d.ts +13 -0
  38. package/dist/commands-v2/index.js +15 -0
  39. package/dist/commands-v2/registry.d.ts +37 -0
  40. package/dist/commands-v2/registry.js +80 -0
  41. package/dist/commands-v2/types.d.ts +75 -0
  42. package/dist/commands-v2/types.js +7 -0
  43. package/dist/commands.js +110 -7
  44. package/dist/index.js +288 -29
  45. package/dist/input-handlers/index.d.ts +7 -0
  46. package/dist/input-handlers/index.js +7 -0
  47. package/dist/input-handlers/memory-handler.d.ts +26 -0
  48. package/dist/input-handlers/memory-handler.js +68 -0
  49. package/dist/repl-helpers.d.ts +63 -0
  50. package/dist/repl-helpers.js +318 -0
  51. package/dist/repl-v2.d.ts +155 -0
  52. package/dist/repl-v2.js +774 -0
  53. package/dist/repl.d.ts +32 -4
  54. package/dist/repl.js +250 -977
  55. package/dist/settings/index.d.ts +23 -0
  56. package/dist/settings/index.js +48 -0
  57. package/dist/settings/paths.d.ts +110 -0
  58. package/dist/settings/paths.js +264 -0
  59. package/dist/templates/compilr-md.js +7 -4
  60. package/dist/templates/index.js +3 -4
  61. package/dist/themes/colors.js +3 -1
  62. package/dist/themes/registry.d.ts +5 -36
  63. package/dist/themes/registry.js +11 -95
  64. package/dist/themes/types.d.ts +3 -38
  65. package/dist/themes/types.js +2 -2
  66. package/dist/tools/anchor-tools.d.ts +31 -0
  67. package/dist/tools/anchor-tools.js +255 -0
  68. package/dist/tools/backlog-wrappers.d.ts +54 -0
  69. package/dist/tools/backlog-wrappers.js +338 -0
  70. package/dist/tools/backlog.js +1 -1
  71. package/dist/tools/db-tools.d.ts +65 -0
  72. package/dist/tools/db-tools.js +19 -0
  73. package/dist/tools/document-db.d.ts +43 -0
  74. package/dist/tools/document-db.js +220 -0
  75. package/dist/tools/project-db.d.ts +102 -0
  76. package/dist/tools/project-db.js +370 -0
  77. package/dist/tools/workitem-db.d.ts +103 -0
  78. package/dist/tools/workitem-db.js +549 -0
  79. package/dist/tools.js +13 -3
  80. package/dist/ui/agents-overlay-v2.d.ts +43 -0
  81. package/dist/ui/agents-overlay-v2.js +809 -0
  82. package/dist/ui/agents-overlay.d.ts +5 -5
  83. package/dist/ui/agents-overlay.js +782 -420
  84. package/dist/ui/anchors-overlay.d.ts +12 -0
  85. package/dist/ui/anchors-overlay.js +775 -0
  86. package/dist/ui/arch-type-overlay.d.ts +1 -6
  87. package/dist/ui/arch-type-overlay.js +175 -203
  88. package/dist/ui/ask-user-overlay-v2.d.ts +26 -0
  89. package/dist/ui/ask-user-overlay-v2.js +555 -0
  90. package/dist/ui/ask-user-overlay.d.ts +2 -2
  91. package/dist/ui/ask-user-overlay.js +443 -535
  92. package/dist/ui/ask-user-simple-overlay-v2.d.ts +25 -0
  93. package/dist/ui/ask-user-simple-overlay-v2.js +215 -0
  94. package/dist/ui/ask-user-simple-overlay.d.ts +2 -2
  95. package/dist/ui/ask-user-simple-overlay.js +182 -209
  96. package/dist/ui/backlog-overlay.d.ts +16 -1
  97. package/dist/ui/backlog-overlay.js +525 -659
  98. package/dist/ui/base/index.d.ts +26 -0
  99. package/dist/ui/base/index.js +33 -0
  100. package/dist/ui/base/inline-overlay-utils.d.ts +217 -0
  101. package/dist/ui/base/inline-overlay-utils.js +320 -0
  102. package/dist/ui/base/inline-overlay.d.ts +159 -0
  103. package/dist/ui/base/inline-overlay.js +257 -0
  104. package/dist/ui/base/key-utils.d.ts +15 -0
  105. package/dist/ui/base/key-utils.js +30 -0
  106. package/dist/ui/base/overlay-base-v2.d.ts +193 -0
  107. package/dist/ui/base/overlay-base-v2.js +246 -0
  108. package/dist/ui/base/overlay-base.d.ts +156 -0
  109. package/dist/ui/base/overlay-base.js +238 -0
  110. package/dist/ui/base/overlay-lifecycle.d.ts +65 -0
  111. package/dist/ui/base/overlay-lifecycle.js +159 -0
  112. package/dist/ui/base/overlay-types.d.ts +185 -0
  113. package/dist/ui/base/overlay-types.js +7 -0
  114. package/dist/ui/base/render-utils.d.ts +8 -0
  115. package/dist/ui/base/render-utils.js +11 -0
  116. package/dist/ui/base/screen-stack.d.ts +148 -0
  117. package/dist/ui/base/screen-stack.js +184 -0
  118. package/dist/ui/base/tabbed-list-overlay-v2.d.ts +103 -0
  119. package/dist/ui/base/tabbed-list-overlay-v2.js +317 -0
  120. package/dist/ui/base/tabbed-list-overlay.d.ts +153 -0
  121. package/dist/ui/base/tabbed-list-overlay.js +369 -0
  122. package/dist/ui/commands-overlay-v2.d.ts +33 -0
  123. package/dist/ui/commands-overlay-v2.js +441 -0
  124. package/dist/ui/commands-overlay.d.ts +7 -2
  125. package/dist/ui/commands-overlay.js +384 -355
  126. package/dist/ui/config-overlay.d.ts +5 -4
  127. package/dist/ui/config-overlay.js +243 -513
  128. package/dist/ui/conversation.d.ts +75 -4
  129. package/dist/ui/conversation.js +374 -161
  130. package/dist/ui/docs-overlay.d.ts +17 -0
  131. package/dist/ui/docs-overlay.js +303 -0
  132. package/dist/ui/ephemeral.d.ts +1 -1
  133. package/dist/ui/ephemeral.js +1 -1
  134. package/dist/ui/features/index.d.ts +34 -0
  135. package/dist/ui/features/index.js +34 -0
  136. package/dist/ui/features/input-feature.d.ts +85 -0
  137. package/dist/ui/features/input-feature.js +238 -0
  138. package/dist/ui/features/list-feature.d.ts +155 -0
  139. package/dist/ui/features/list-feature.js +244 -0
  140. package/dist/ui/features/pagination-feature.d.ts +154 -0
  141. package/dist/ui/features/pagination-feature.js +238 -0
  142. package/dist/ui/features/search-feature.d.ts +148 -0
  143. package/dist/ui/features/search-feature.js +185 -0
  144. package/dist/ui/features/tab-feature.d.ts +194 -0
  145. package/dist/ui/features/tab-feature.js +307 -0
  146. package/dist/ui/footer-v2.d.ts +222 -0
  147. package/dist/ui/footer-v2.js +1349 -0
  148. package/dist/ui/footer.d.ts +107 -0
  149. package/dist/ui/footer.js +359 -67
  150. package/dist/ui/guardrail-overlay.d.ts +29 -0
  151. package/dist/ui/guardrail-overlay.js +145 -0
  152. package/dist/ui/help-overlay-v2.d.ts +34 -0
  153. package/dist/ui/help-overlay-v2.js +309 -0
  154. package/dist/ui/help-overlay.d.ts +16 -0
  155. package/dist/ui/help-overlay.js +316 -0
  156. package/dist/ui/index.d.ts +1 -1
  157. package/dist/ui/index.js +1 -3
  158. package/dist/ui/init-overlay-v2.d.ts +34 -0
  159. package/dist/ui/init-overlay-v2.js +600 -0
  160. package/dist/ui/init-overlay.d.ts +12 -2
  161. package/dist/ui/init-overlay.js +349 -270
  162. package/dist/ui/input-prompt-v2.d.ts +1 -0
  163. package/dist/ui/input-prompt-v2.js +14 -6
  164. package/dist/ui/input-prompt.d.ts +116 -33
  165. package/dist/ui/input-prompt.js +536 -337
  166. package/dist/ui/iteration-limit-overlay-v2.d.ts +21 -0
  167. package/dist/ui/iteration-limit-overlay-v2.js +114 -0
  168. package/dist/ui/iteration-limit-overlay.d.ts +2 -2
  169. package/dist/ui/iteration-limit-overlay.js +92 -128
  170. package/dist/ui/keys-overlay-v2.d.ts +41 -0
  171. package/dist/ui/keys-overlay-v2.js +248 -0
  172. package/dist/ui/keys-overlay.d.ts +1 -0
  173. package/dist/ui/keys-overlay.js +203 -141
  174. package/dist/ui/line-utils.d.ts +88 -0
  175. package/dist/ui/line-utils.js +150 -0
  176. package/dist/ui/live-region.d.ts +161 -0
  177. package/dist/ui/live-region.js +387 -0
  178. package/dist/ui/mascot/expressions.d.ts +32 -0
  179. package/dist/ui/mascot/expressions.js +213 -0
  180. package/dist/ui/mascot/index.d.ts +8 -0
  181. package/dist/ui/mascot/index.js +8 -0
  182. package/dist/ui/mascot/renderer.d.ts +19 -0
  183. package/dist/ui/mascot/renderer.js +97 -0
  184. package/dist/ui/mascot-overlay-v2.d.ts +41 -0
  185. package/dist/ui/mascot-overlay-v2.js +138 -0
  186. package/dist/ui/mascot-overlay.d.ts +21 -0
  187. package/dist/ui/mascot-overlay.js +146 -0
  188. package/dist/ui/model-overlay-v2.d.ts +49 -0
  189. package/dist/ui/model-overlay-v2.js +118 -0
  190. package/dist/ui/model-overlay.d.ts +27 -0
  191. package/dist/ui/model-overlay.js +221 -0
  192. package/dist/ui/model-warning-overlay.js +3 -5
  193. package/dist/ui/new-overlay.d.ts +34 -0
  194. package/dist/ui/new-overlay.js +604 -0
  195. package/dist/ui/overlay/impl/agents-overlay-v2.d.ts +45 -0
  196. package/dist/ui/overlay/impl/agents-overlay-v2.js +825 -0
  197. package/dist/ui/overlay/impl/anchors-overlay-v2.d.ts +47 -0
  198. package/dist/ui/overlay/impl/anchors-overlay-v2.js +783 -0
  199. package/dist/ui/overlay/impl/arch-type-overlay-v2.d.ts +37 -0
  200. package/dist/ui/overlay/impl/arch-type-overlay-v2.js +240 -0
  201. package/dist/ui/overlay/impl/ask-user-overlay-v2.d.ts +72 -0
  202. package/dist/ui/overlay/impl/ask-user-overlay-v2.js +584 -0
  203. package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.d.ts +46 -0
  204. package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.js +204 -0
  205. package/dist/ui/overlay/impl/backlog-overlay-v2.d.ts +49 -0
  206. package/dist/ui/overlay/impl/backlog-overlay-v2.js +642 -0
  207. package/dist/ui/overlay/impl/commands-overlay-v2.d.ts +33 -0
  208. package/dist/ui/overlay/impl/commands-overlay-v2.js +441 -0
  209. package/dist/ui/overlay/impl/config-overlay-v2.d.ts +100 -0
  210. package/dist/ui/overlay/impl/config-overlay-v2.js +654 -0
  211. package/dist/ui/overlay/impl/dashboard-overlay-v2.d.ts +55 -0
  212. package/dist/ui/overlay/impl/dashboard-overlay-v2.js +359 -0
  213. package/dist/ui/overlay/impl/docs-overlay-v2.d.ts +45 -0
  214. package/dist/ui/overlay/impl/docs-overlay-v2.js +114 -0
  215. package/dist/ui/overlay/impl/document-detail-overlay-v2.d.ts +77 -0
  216. package/dist/ui/overlay/impl/document-detail-overlay-v2.js +1071 -0
  217. package/dist/ui/overlay/impl/guardrail-overlay-v2.d.ts +43 -0
  218. package/dist/ui/overlay/impl/guardrail-overlay-v2.js +114 -0
  219. package/dist/ui/overlay/impl/help-overlay-v2.d.ts +34 -0
  220. package/dist/ui/overlay/impl/help-overlay-v2.js +309 -0
  221. package/dist/ui/overlay/impl/init-overlay-v2.d.ts +77 -0
  222. package/dist/ui/overlay/impl/init-overlay-v2.js +593 -0
  223. package/dist/ui/overlay/impl/init-setup-overlay-v2.d.ts +25 -0
  224. package/dist/ui/overlay/impl/init-setup-overlay-v2.js +97 -0
  225. package/dist/ui/overlay/impl/iteration-limit-overlay-v2.d.ts +35 -0
  226. package/dist/ui/overlay/impl/iteration-limit-overlay-v2.js +105 -0
  227. package/dist/ui/overlay/impl/keys-overlay-v2.d.ts +41 -0
  228. package/dist/ui/overlay/impl/keys-overlay-v2.js +248 -0
  229. package/dist/ui/overlay/impl/mascot-overlay-v2.d.ts +41 -0
  230. package/dist/ui/overlay/impl/mascot-overlay-v2.js +138 -0
  231. package/dist/ui/overlay/impl/model-overlay-v2.d.ts +49 -0
  232. package/dist/ui/overlay/impl/model-overlay-v2.js +118 -0
  233. package/dist/ui/overlay/impl/model-warning-overlay-v2.d.ts +46 -0
  234. package/dist/ui/overlay/impl/model-warning-overlay-v2.js +132 -0
  235. package/dist/ui/overlay/impl/new-overlay-v2.d.ts +77 -0
  236. package/dist/ui/overlay/impl/new-overlay-v2.js +593 -0
  237. package/dist/ui/overlay/impl/permission-overlay-v2.d.ts +36 -0
  238. package/dist/ui/overlay/impl/permission-overlay-v2.js +380 -0
  239. package/dist/ui/overlay/impl/projects-overlay-v2.d.ts +36 -0
  240. package/dist/ui/overlay/impl/projects-overlay-v2.js +499 -0
  241. package/dist/ui/overlay/impl/theme-overlay-v2.d.ts +42 -0
  242. package/dist/ui/overlay/impl/theme-overlay-v2.js +135 -0
  243. package/dist/ui/overlay/impl/tools-overlay-v2.d.ts +47 -0
  244. package/dist/ui/overlay/impl/tools-overlay-v2.js +218 -0
  245. package/dist/ui/overlay/impl/tutorial-overlay-v2.d.ts +31 -0
  246. package/dist/ui/overlay/impl/tutorial-overlay-v2.js +1035 -0
  247. package/dist/ui/overlay/impl/workflow-overlay-v2.d.ts +80 -0
  248. package/dist/ui/overlay/impl/workflow-overlay-v2.js +637 -0
  249. package/dist/ui/overlay/index.d.ts +33 -0
  250. package/dist/ui/overlay/index.js +35 -0
  251. package/dist/ui/overlay/key-utils.d.ts +6 -0
  252. package/dist/ui/overlay/key-utils.js +6 -0
  253. package/dist/ui/overlay/overlay-types.d.ts +128 -0
  254. package/dist/ui/overlay/overlay-types.js +22 -0
  255. package/dist/ui/overlay/types.d.ts +135 -0
  256. package/dist/ui/overlay/types.js +22 -0
  257. package/dist/ui/overlays/help-overlay-v2.d.ts +28 -0
  258. package/dist/ui/overlays/help-overlay-v2.js +198 -0
  259. package/dist/ui/overlays/index.d.ts +11 -0
  260. package/dist/ui/overlays/index.js +11 -0
  261. package/dist/ui/overlays.d.ts +0 -4
  262. package/dist/ui/overlays.js +0 -444
  263. package/dist/ui/permission-overlay-v2.d.ts +36 -0
  264. package/dist/ui/permission-overlay-v2.js +380 -0
  265. package/dist/ui/permission-overlay.d.ts +1 -1
  266. package/dist/ui/permission-overlay.js +186 -298
  267. package/dist/ui/projects-overlay.d.ts +19 -0
  268. package/dist/ui/projects-overlay.js +484 -0
  269. package/dist/ui/providers/types.d.ts +178 -0
  270. package/dist/ui/providers/types.js +9 -0
  271. package/dist/ui/render-modes.d.ts +36 -0
  272. package/dist/ui/render-modes.js +44 -0
  273. package/dist/ui/startup-menu.d.ts +36 -0
  274. package/dist/ui/startup-menu.js +236 -0
  275. package/dist/ui/subagent-renderer.d.ts +117 -0
  276. package/dist/ui/subagent-renderer.js +334 -0
  277. package/dist/ui/terminal-codes.d.ts +94 -0
  278. package/dist/ui/terminal-codes.js +124 -0
  279. package/dist/ui/terminal-renderer.d.ts +221 -0
  280. package/dist/ui/terminal-renderer.js +751 -0
  281. package/dist/ui/terminal-ui.d.ts +463 -0
  282. package/dist/ui/terminal-ui.js +2296 -0
  283. package/dist/ui/terminal.d.ts +20 -0
  284. package/dist/ui/terminal.js +72 -0
  285. package/dist/ui/theme-overlay-v2.d.ts +42 -0
  286. package/dist/ui/theme-overlay-v2.js +135 -0
  287. package/dist/ui/theme-overlay.d.ts +24 -0
  288. package/dist/ui/theme-overlay.js +127 -0
  289. package/dist/ui/todo-zone.js +53 -25
  290. package/dist/ui/tool-formatters.d.ts +16 -0
  291. package/dist/ui/tool-formatters.js +516 -0
  292. package/dist/ui/tools-overlay-v2.d.ts +47 -0
  293. package/dist/ui/tools-overlay-v2.js +218 -0
  294. package/dist/ui/tools-overlay.d.ts +10 -2
  295. package/dist/ui/tools-overlay.js +172 -220
  296. package/dist/ui/tutorial-overlay-v2.d.ts +31 -0
  297. package/dist/ui/tutorial-overlay-v2.js +1035 -0
  298. package/dist/ui/tutorial-overlay.d.ts +1 -0
  299. package/dist/ui/tutorial-overlay.js +400 -302
  300. package/dist/ui/workflow-overlay.d.ts +22 -0
  301. package/dist/ui/workflow-overlay.js +636 -0
  302. package/dist/utils/debug-log.d.ts +28 -0
  303. package/dist/utils/debug-log.js +57 -0
  304. package/dist/utils/model-tiers.js +1 -1
  305. package/dist/utils/path-safety.d.ts +56 -0
  306. package/dist/utils/path-safety.js +239 -0
  307. package/dist/workflow/guided-mode-injector.d.ts +42 -0
  308. package/dist/workflow/guided-mode-injector.js +191 -0
  309. package/dist/workflow/index.d.ts +8 -0
  310. package/dist/workflow/index.js +8 -0
  311. package/dist/workflow/step-criteria.d.ts +62 -0
  312. package/dist/workflow/step-criteria.js +150 -0
  313. package/dist/workflow/step-tracker.d.ts +92 -0
  314. package/dist/workflow/step-tracker.js +141 -0
  315. package/package.json +12 -5
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Modal overlay for managing API keys.
5
5
  * Shows status of all providers and allows setting/deleting keys.
6
+ * Refactored to use BaseOverlay + ScreenStack + InputFeature.
6
7
  */
7
8
  export interface KeysOverlayResult {
8
9
  /** Whether any keys were changed */
@@ -3,31 +3,37 @@
3
3
  *
4
4
  * Modal overlay for managing API keys.
5
5
  * Shows status of all providers and allows setting/deleting keys.
6
+ * Refactored to use BaseOverlay + ScreenStack + InputFeature.
6
7
  */
7
8
  import * as terminal from './terminal.js';
8
- import { getStyles } from '../themes/index.js';
9
+ import { BaseOverlay, BaseScreen, ScreenStack, stay, pushScreen, popScreen, closeOverlay, isEscape, isEnter, isCtrlC, isClose, isNavigateUp, isNavigateDown, isD, } from './base/index.js';
10
+ import { InputFeature } from './features/index.js';
9
11
  import { getAllProviderStatuses, setApiKey, deleteApiKey, } from '../utils/credentials.js';
10
- // =============================================================================
11
- // Rendering
12
- // =============================================================================
13
- function render(state, previousLineCount = 0) {
14
- const s = getStyles();
15
- const lines = [];
16
- const cols = terminal.getTerminalWidth();
17
- const border = s.muted('─'.repeat(Math.max(1, cols - 1)));
18
- // Clear previous render
19
- if (previousLineCount > 0) {
20
- terminal.clearLinesAbove(previousLineCount);
12
+ import { debugLog } from '../utils/debug-log.js';
13
+ /**
14
+ * List screen - shows all providers with their key status
15
+ */
16
+ class ListScreen extends BaseScreen {
17
+ state;
18
+ styles;
19
+ constructor(state, styles) {
20
+ super();
21
+ this.state = state;
22
+ this.styles = styles;
21
23
  }
22
- // Header
23
- lines.push(border);
24
- lines.push(' ' + s.primaryBold('API Keys'));
25
- lines.push('');
26
- if (state.mode === 'list') {
27
- // List mode - show all providers
28
- for (let i = 0; i < state.providers.length; i++) {
29
- const p = state.providers[i];
30
- const isCursor = state.selectedIndex === i;
24
+ render() {
25
+ const s = this.styles;
26
+ const cols = terminal.getTerminalWidth();
27
+ const border = s.muted(''.repeat(Math.max(1, cols - 1)));
28
+ const lines = [];
29
+ // Header
30
+ lines.push(border);
31
+ lines.push(' ' + s.primaryBold('API Keys'));
32
+ lines.push('');
33
+ // Provider list
34
+ for (let i = 0; i < this.state.providers.length; i++) {
35
+ const p = this.state.providers[i];
36
+ const isCursor = this.state.selectedIndex === i;
31
37
  const prefix = isCursor ? ' ❯ ' : ' ';
32
38
  // Status indicator
33
39
  let status;
@@ -46,136 +52,192 @@ function render(state, previousLineCount = 0) {
46
52
  const line = `${prefix}${name.padEnd(22)} ${status}`;
47
53
  lines.push(line);
48
54
  }
55
+ // Footer
49
56
  lines.push('');
50
- lines.push(s.muted(' ↑↓ Navigate · Enter Edit · d Delete · Esc Close'));
57
+ lines.push(s.muted(' ↑↓/jk Navigate · Enter Edit · d Delete · q/Esc Close'));
58
+ lines.push(border);
59
+ return lines;
51
60
  }
52
- else {
53
- // Input mode - show key entry
54
- const provider = state.providers.find(p => p.provider === state.editingProvider);
55
- if (provider) {
56
- lines.push(` Set API key for ${s.primaryBold(provider.name)}:`);
57
- lines.push('');
58
- lines.push(` > ${state.inputBuffer}${s.primary('▋')}`);
59
- lines.push('');
60
- lines.push(s.muted(` Get key: ${provider.keyUrl}`));
61
+ handleKey(data) {
62
+ // Close keys
63
+ if (isCtrlC(data) || isClose(data)) {
64
+ return closeOverlay({ changed: this.state.changed });
65
+ }
66
+ // Navigation up
67
+ if (isNavigateUp(data)) {
68
+ if (this.state.selectedIndex > 0) {
69
+ this.state.selectedIndex--;
70
+ return stay();
71
+ }
72
+ return stay(false);
73
+ }
74
+ // Navigation down
75
+ if (isNavigateDown(data)) {
76
+ if (this.state.selectedIndex < this.state.providers.length - 1) {
77
+ this.state.selectedIndex++;
78
+ return stay();
79
+ }
80
+ return stay(false);
81
+ }
82
+ // Enter - edit selected provider
83
+ if (isEnter(data)) {
84
+ const selected = this.state.providers[this.state.selectedIndex];
85
+ if (selected.provider !== 'ollama') {
86
+ return pushScreen(new InputScreen(this.state, this.styles, selected.provider));
87
+ }
88
+ return stay(false);
89
+ }
90
+ // Delete key
91
+ if (isD(data)) {
92
+ const selected = this.state.providers[this.state.selectedIndex];
93
+ if (selected.provider !== 'ollama' && selected.hasKey && !selected.fromEnv) {
94
+ deleteApiKey(selected.provider);
95
+ this.state.providers = getAllProviderStatuses();
96
+ this.state.changed = true;
97
+ return stay();
98
+ }
99
+ return stay(false);
100
+ }
101
+ return stay(false);
102
+ }
103
+ }
104
+ /**
105
+ * Input screen - text input for entering API key using InputFeature
106
+ */
107
+ class InputScreen extends BaseScreen {
108
+ state;
109
+ styles;
110
+ provider;
111
+ input;
112
+ constructor(state, styles, provider) {
113
+ super();
114
+ this.state = state;
115
+ this.styles = styles;
116
+ this.provider = provider;
117
+ this.input = new InputFeature({ placeholder: 'Paste or type your API key...' });
118
+ }
119
+ onEnter() {
120
+ terminal.showCursor();
121
+ }
122
+ onExit() {
123
+ terminal.hideCursor();
124
+ }
125
+ render() {
126
+ const s = this.styles;
127
+ const cols = terminal.getTerminalWidth();
128
+ const border = s.muted('─'.repeat(Math.max(1, cols - 1)));
129
+ const lines = [];
130
+ const providerInfo = this.state.providers.find(p => p.provider === this.provider);
131
+ // Header
132
+ lines.push(border);
133
+ lines.push(' ' + s.primaryBold('API Keys'));
134
+ lines.push('');
135
+ // Input prompt
136
+ lines.push(` Set API key for ${s.primary(providerInfo?.name ?? this.provider)}:`);
137
+ lines.push('');
138
+ lines.push(' ' + this.input.render(s));
139
+ lines.push('');
140
+ // Help text
141
+ if (providerInfo?.keyUrl) {
142
+ lines.push(s.muted(` Get key: ${providerInfo.keyUrl}`));
61
143
  lines.push('');
62
- lines.push(s.muted(' Enter Save · Esc Cancel'));
144
+ }
145
+ // Footer
146
+ lines.push(s.muted(' Enter Save · Esc Cancel · ←→ Move cursor'));
147
+ lines.push(border);
148
+ return lines;
149
+ }
150
+ handleKey(data) {
151
+ // Ctrl+C closes overlay
152
+ if (isCtrlC(data)) {
153
+ return closeOverlay({ changed: this.state.changed });
154
+ }
155
+ // Escape goes back to list
156
+ if (isEscape(data)) {
157
+ return popScreen();
158
+ }
159
+ // Enter saves the key
160
+ if (isEnter(data)) {
161
+ const value = this.input.trimmedValue;
162
+ if (value) {
163
+ setApiKey(this.provider, value);
164
+ this.state.providers = getAllProviderStatuses();
165
+ this.state.changed = true;
166
+ }
167
+ return popScreen();
168
+ }
169
+ // Let InputFeature handle the key
170
+ const result = this.input.handleKey(data);
171
+ if (result.handled) {
172
+ return stay(result.render);
173
+ }
174
+ return stay(false);
175
+ }
176
+ }
177
+ // =============================================================================
178
+ // Overlay Class
179
+ // =============================================================================
180
+ class KeysOverlay extends BaseOverlay {
181
+ screenStack;
182
+ constructor() {
183
+ debugLog('KeysOverlay:constructor', 'start');
184
+ debugLog('KeysOverlay:constructor', 'calling getAllProviderStatuses');
185
+ const providers = getAllProviderStatuses();
186
+ debugLog('KeysOverlay:constructor', 'getAllProviderStatuses done');
187
+ super({
188
+ lineCount: 0,
189
+ maxLineCount: 0,
190
+ selectedIndex: 0,
191
+ changed: false,
192
+ providers,
193
+ });
194
+ debugLog('KeysOverlay:constructor', 'super() done');
195
+ this.screenStack = new ScreenStack();
196
+ this.screenStack.push(new ListScreen(this.state, this.styles));
197
+ debugLog('KeysOverlay:constructor', 'end');
198
+ }
199
+ render() {
200
+ const screen = this.screenStack.current();
201
+ return screen?.render() ?? [];
202
+ }
203
+ handleKey(data) {
204
+ const screen = this.screenStack.current();
205
+ if (!screen) {
206
+ this.close({ changed: this.state.changed });
207
+ return;
208
+ }
209
+ const result = screen.handleKey(data);
210
+ switch (result.action) {
211
+ case 'stay':
212
+ if (result.render) {
213
+ this.update();
214
+ }
215
+ break;
216
+ case 'push':
217
+ this.screenStack.push(result.screen);
218
+ this.update();
219
+ break;
220
+ case 'pop':
221
+ this.screenStack.pop();
222
+ this.update();
223
+ break;
224
+ case 'close':
225
+ this.close(result.result);
226
+ break;
63
227
  }
64
228
  }
65
- // Bottom border
66
- lines.push(border);
67
- // Render all lines
68
- terminal.write(lines.join('\n'));
69
- return lines.length;
70
229
  }
71
230
  // =============================================================================
72
- // Main Export
231
+ // Export Function (Backward Compatible)
73
232
  // =============================================================================
74
233
  /**
75
234
  * Show the keys management overlay
76
235
  */
77
236
  export async function showKeysOverlay() {
78
- const state = {
79
- mode: 'list',
80
- selectedIndex: 0,
81
- editingProvider: null,
82
- inputBuffer: '',
83
- cancelled: false,
84
- providers: getAllProviderStatuses(),
85
- };
86
- let lineCount = 0;
87
- let changed = false;
88
- // Initial render
89
- lineCount = render(state, lineCount);
90
- return new Promise((resolve) => {
91
- const stdin = process.stdin;
92
- stdin.setRawMode(true);
93
- stdin.resume();
94
- const cleanup = () => {
95
- stdin.removeListener('data', handleKey);
96
- stdin.setRawMode(false);
97
- stdin.pause();
98
- // Clear the overlay
99
- terminal.clearLinesAbove(lineCount);
100
- };
101
- const handleKey = (data) => {
102
- const key = data.toString();
103
- if (state.mode === 'list') {
104
- // List mode key handling
105
- switch (key) {
106
- case '\x1B': // Escape
107
- case 'q':
108
- cleanup();
109
- resolve({ changed });
110
- return;
111
- case '\x1B[A': // Up arrow
112
- if (state.selectedIndex > 0) {
113
- state.selectedIndex--;
114
- }
115
- break;
116
- case '\x1B[B': // Down arrow
117
- if (state.selectedIndex < state.providers.length - 1) {
118
- state.selectedIndex++;
119
- }
120
- break;
121
- case '\r': // Enter - edit selected
122
- case '\n': {
123
- const selected = state.providers[state.selectedIndex];
124
- if (selected.provider !== 'ollama') {
125
- state.mode = 'input';
126
- state.editingProvider = selected.provider;
127
- state.inputBuffer = '';
128
- }
129
- break;
130
- }
131
- case 'd':
132
- case 'D': {
133
- // Delete key for selected provider
134
- const selected = state.providers[state.selectedIndex];
135
- if (selected.provider !== 'ollama' && selected.hasKey && !selected.fromEnv) {
136
- deleteApiKey(selected.provider);
137
- state.providers = getAllProviderStatuses();
138
- changed = true;
139
- }
140
- break;
141
- }
142
- }
143
- }
144
- else {
145
- // Input mode key handling
146
- if (key === '\x1B') {
147
- // Escape - cancel input
148
- state.mode = 'list';
149
- state.editingProvider = null;
150
- state.inputBuffer = '';
151
- }
152
- else if (key === '\r' || key === '\n') {
153
- // Enter - save key
154
- if (state.inputBuffer.trim() && state.editingProvider) {
155
- setApiKey(state.editingProvider, state.inputBuffer.trim());
156
- state.providers = getAllProviderStatuses();
157
- changed = true;
158
- }
159
- state.mode = 'list';
160
- state.editingProvider = null;
161
- state.inputBuffer = '';
162
- }
163
- else if (key === '\x7F' || key === '\b') {
164
- // Backspace
165
- state.inputBuffer = state.inputBuffer.slice(0, -1);
166
- }
167
- else {
168
- // Handle paste (multiple characters) or single printable character
169
- // Filter to only printable ASCII characters
170
- const printable = key.split('').filter(c => c >= ' ' && c <= '~').join('');
171
- if (printable.length > 0) {
172
- state.inputBuffer += printable;
173
- }
174
- }
175
- }
176
- // Re-render
177
- lineCount = render(state, lineCount);
178
- };
179
- stdin.on('data', handleKey);
180
- });
237
+ debugLog('showKeysOverlay', 'creating overlay');
238
+ const overlay = new KeysOverlay();
239
+ debugLog('showKeysOverlay', 'calling show()');
240
+ const result = await overlay.show();
241
+ debugLog('showKeysOverlay', 'show() returned');
242
+ return result;
181
243
  }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Unified Line Utilities
3
+ *
4
+ * Single source of truth for ANSI stripping, visible length calculation,
5
+ * and physical line counting. Prevents inconsistencies between components.
6
+ *
7
+ * Key insight: Different ANSI patterns were causing height miscalculations:
8
+ * - Footer used: /\x1b\[[0-9;]*m/g (only matches 'm' terminator)
9
+ * - InputPrompt used: /\x1B\[[0-9;]*[a-zA-Z]/g (matches all terminators)
10
+ *
11
+ * The correct pattern matches all SGR (Select Graphic Rendition) sequences
12
+ * including colors, styles, cursor movements, etc.
13
+ */
14
+ /**
15
+ * Unified ANSI escape sequence pattern.
16
+ * Matches all SGR sequences: CSI (Control Sequence Introducer) followed by
17
+ * parameters and a terminating letter (m for colors, but also J, K, H, etc.)
18
+ */
19
+ export declare const ANSI_PATTERN: RegExp;
20
+ /**
21
+ * Strip all ANSI escape codes from a string.
22
+ * Returns the visible text only.
23
+ */
24
+ export declare function stripAnsi(str: string): string;
25
+ /**
26
+ * Get the visible length of a string (excluding ANSI codes).
27
+ * This is what the terminal actually displays.
28
+ */
29
+ export declare function getVisibleLength(str: string): number;
30
+ /**
31
+ * Calculate how many physical terminal lines a single logical line occupies.
32
+ * Accounts for wrapping when content exceeds terminal width.
33
+ *
34
+ * @param line - The line content (may contain ANSI codes)
35
+ * @param termWidth - Terminal width in columns
36
+ * @returns Number of physical lines (always >= 1)
37
+ *
38
+ * Example:
39
+ * termWidth = 80
40
+ * line = "hello" (5 chars) → 1 line
41
+ * line = "x".repeat(100) (100 chars) → 2 lines (wraps at 80)
42
+ * line = "" (0 chars) → 1 line (empty still takes a line)
43
+ */
44
+ export declare function getPhysicalLineCount(line: string, termWidth: number): number;
45
+ /**
46
+ * Calculate how many physical terminal lines a line takes when starting
47
+ * at a specific column offset (e.g., after a prompt prefix).
48
+ *
49
+ * @param text - The text content (may contain ANSI codes)
50
+ * @param startCol - Starting column (0-indexed, e.g., prompt length)
51
+ * @param termWidth - Terminal width in columns
52
+ * @returns Number of physical lines
53
+ *
54
+ * Example:
55
+ * termWidth = 80, startCol = 10 (after "compilr> ")
56
+ * text = "hello" (5 chars) → 1 line (10 + 5 = 15 < 80)
57
+ * text = "x".repeat(75) → 2 lines (10 + 75 = 85, wraps)
58
+ */
59
+ export declare function getPhysicalLineCountWithOffset(text: string, startCol: number, termWidth: number): number;
60
+ /**
61
+ * Calculate total physical lines for an array of logical lines.
62
+ *
63
+ * @param lines - Array of line strings (may contain ANSI codes)
64
+ * @param termWidth - Terminal width in columns
65
+ * @returns Total number of physical lines
66
+ */
67
+ export declare function getTotalPhysicalLines(lines: string[], termWidth: number): number;
68
+ /**
69
+ * Get detailed height information for each line.
70
+ * Useful for debugging and understanding exactly how lines wrap.
71
+ *
72
+ * @param lines - Array of line strings
73
+ * @param termWidth - Terminal width
74
+ * @returns Array of { visibleLength, physicalLines } for each line
75
+ */
76
+ export declare function getLineHeightDetails(lines: string[], termWidth: number): Array<{
77
+ visibleLength: number;
78
+ physicalLines: number;
79
+ }>;
80
+ /**
81
+ * Truncate a string to a maximum visible length, preserving ANSI codes.
82
+ * Adds ellipsis if truncated.
83
+ *
84
+ * @param str - String to truncate (may contain ANSI codes)
85
+ * @param maxLen - Maximum visible length
86
+ * @returns Truncated string with ellipsis if needed
87
+ */
88
+ export declare function truncateWithAnsi(str: string, maxLen: number): string;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Unified Line Utilities
3
+ *
4
+ * Single source of truth for ANSI stripping, visible length calculation,
5
+ * and physical line counting. Prevents inconsistencies between components.
6
+ *
7
+ * Key insight: Different ANSI patterns were causing height miscalculations:
8
+ * - Footer used: /\x1b\[[0-9;]*m/g (only matches 'm' terminator)
9
+ * - InputPrompt used: /\x1B\[[0-9;]*[a-zA-Z]/g (matches all terminators)
10
+ *
11
+ * The correct pattern matches all SGR (Select Graphic Rendition) sequences
12
+ * including colors, styles, cursor movements, etc.
13
+ */
14
+ // =============================================================================
15
+ // ANSI Handling
16
+ // =============================================================================
17
+ /**
18
+ * Unified ANSI escape sequence pattern.
19
+ * Matches all SGR sequences: CSI (Control Sequence Introducer) followed by
20
+ * parameters and a terminating letter (m for colors, but also J, K, H, etc.)
21
+ */
22
+ // eslint-disable-next-line no-control-regex
23
+ export const ANSI_PATTERN = /\x1b\[[0-9;]*[A-Za-z]/g;
24
+ /**
25
+ * Strip all ANSI escape codes from a string.
26
+ * Returns the visible text only.
27
+ */
28
+ export function stripAnsi(str) {
29
+ return str.replace(ANSI_PATTERN, '');
30
+ }
31
+ /**
32
+ * Get the visible length of a string (excluding ANSI codes).
33
+ * This is what the terminal actually displays.
34
+ */
35
+ export function getVisibleLength(str) {
36
+ return stripAnsi(str).length;
37
+ }
38
+ // =============================================================================
39
+ // Physical Line Calculation
40
+ // =============================================================================
41
+ /**
42
+ * Calculate how many physical terminal lines a single logical line occupies.
43
+ * Accounts for wrapping when content exceeds terminal width.
44
+ *
45
+ * @param line - The line content (may contain ANSI codes)
46
+ * @param termWidth - Terminal width in columns
47
+ * @returns Number of physical lines (always >= 1)
48
+ *
49
+ * Example:
50
+ * termWidth = 80
51
+ * line = "hello" (5 chars) → 1 line
52
+ * line = "x".repeat(100) (100 chars) → 2 lines (wraps at 80)
53
+ * line = "" (0 chars) → 1 line (empty still takes a line)
54
+ */
55
+ export function getPhysicalLineCount(line, termWidth) {
56
+ const visibleLength = getVisibleLength(line);
57
+ if (visibleLength === 0)
58
+ return 1; // Empty line still takes 1 row
59
+ return Math.ceil(visibleLength / termWidth);
60
+ }
61
+ /**
62
+ * Calculate how many physical terminal lines a line takes when starting
63
+ * at a specific column offset (e.g., after a prompt prefix).
64
+ *
65
+ * @param text - The text content (may contain ANSI codes)
66
+ * @param startCol - Starting column (0-indexed, e.g., prompt length)
67
+ * @param termWidth - Terminal width in columns
68
+ * @returns Number of physical lines
69
+ *
70
+ * Example:
71
+ * termWidth = 80, startCol = 10 (after "compilr> ")
72
+ * text = "hello" (5 chars) → 1 line (10 + 5 = 15 < 80)
73
+ * text = "x".repeat(75) → 2 lines (10 + 75 = 85, wraps)
74
+ */
75
+ export function getPhysicalLineCountWithOffset(text, startCol, termWidth) {
76
+ const visibleLength = getVisibleLength(text);
77
+ if (visibleLength === 0)
78
+ return 1;
79
+ const totalLen = startCol + visibleLength;
80
+ return Math.ceil(totalLen / termWidth) || 1;
81
+ }
82
+ /**
83
+ * Calculate total physical lines for an array of logical lines.
84
+ *
85
+ * @param lines - Array of line strings (may contain ANSI codes)
86
+ * @param termWidth - Terminal width in columns
87
+ * @returns Total number of physical lines
88
+ */
89
+ export function getTotalPhysicalLines(lines, termWidth) {
90
+ let total = 0;
91
+ for (const line of lines) {
92
+ total += getPhysicalLineCount(line, termWidth);
93
+ }
94
+ return total;
95
+ }
96
+ /**
97
+ * Get detailed height information for each line.
98
+ * Useful for debugging and understanding exactly how lines wrap.
99
+ *
100
+ * @param lines - Array of line strings
101
+ * @param termWidth - Terminal width
102
+ * @returns Array of { visibleLength, physicalLines } for each line
103
+ */
104
+ export function getLineHeightDetails(lines, termWidth) {
105
+ return lines.map(line => {
106
+ const visibleLength = getVisibleLength(line);
107
+ const physicalLines = visibleLength === 0 ? 1 : Math.ceil(visibleLength / termWidth);
108
+ return { visibleLength, physicalLines };
109
+ });
110
+ }
111
+ // =============================================================================
112
+ // Truncation
113
+ // =============================================================================
114
+ /**
115
+ * Truncate a string to a maximum visible length, preserving ANSI codes.
116
+ * Adds ellipsis if truncated.
117
+ *
118
+ * @param str - String to truncate (may contain ANSI codes)
119
+ * @param maxLen - Maximum visible length
120
+ * @returns Truncated string with ellipsis if needed
121
+ */
122
+ export function truncateWithAnsi(str, maxLen) {
123
+ const visible = stripAnsi(str);
124
+ if (visible.length <= maxLen)
125
+ return str;
126
+ // We need to find the position in the original string that corresponds
127
+ // to maxLen-1 visible characters (leaving room for ellipsis)
128
+ let visibleCount = 0;
129
+ let i = 0;
130
+ while (i < str.length && visibleCount < maxLen - 1) {
131
+ // Check if we're at an ANSI escape sequence
132
+ if (str[i] === '\x1b' && str[i + 1] === '[') {
133
+ // Skip the entire ANSI sequence
134
+ // eslint-disable-next-line no-control-regex
135
+ const match = str.slice(i).match(/^\x1b\[[0-9;]*[A-Za-z]/);
136
+ if (match) {
137
+ i += match[0].length;
138
+ continue;
139
+ }
140
+ }
141
+ visibleCount++;
142
+ i++;
143
+ }
144
+ // Include any trailing ANSI reset codes
145
+ const remainder = str.slice(i);
146
+ // eslint-disable-next-line no-control-regex
147
+ const resetMatch = remainder.match(/^(\x1b\[[0-9;]*m)*/);
148
+ const resets = resetMatch ? resetMatch[0] : '';
149
+ return str.slice(0, i) + '…' + resets;
150
+ }