@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
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Input Feature
3
+ *
4
+ * Composable text input handling for overlays.
5
+ * Provides cursor movement, text editing, and rendering.
6
+ *
7
+ * Usage:
8
+ * ```typescript
9
+ * class MyScreen extends BaseScreen {
10
+ * private input = new InputFeature({ placeholder: 'Enter text...' });
11
+ *
12
+ * render(): string[] {
13
+ * return [
14
+ * ' ' + this.input.render(styles),
15
+ * ];
16
+ * }
17
+ *
18
+ * handleKey(data: Buffer): ScreenResult {
19
+ * const result = this.input.handleKey(data);
20
+ * if (result.handled) {
21
+ * return stay();
22
+ * }
23
+ * // Handle other keys (Enter, Escape, etc.)
24
+ * }
25
+ * }
26
+ * ```
27
+ */
28
+ import { isLeftArrow, isRightArrow, isBackspace, isDelete, isHome, isEnd, isCtrlA, isCtrlE, isWordLeft, isWordRight, extractPrintable, } from '../base/key-utils.js';
29
+ // =============================================================================
30
+ // InputFeature Class
31
+ // =============================================================================
32
+ export class InputFeature {
33
+ /** Current text value */
34
+ _value;
35
+ /** Cursor position (0 = before first char, value.length = after last char) */
36
+ _cursor;
37
+ /** Placeholder text */
38
+ placeholder;
39
+ /** Maximum length */
40
+ maxLength;
41
+ constructor(options = {}) {
42
+ this._value = options.initialValue ?? '';
43
+ this._cursor = this._value.length;
44
+ this.placeholder = options.placeholder ?? '';
45
+ this.maxLength = options.maxLength ?? 0;
46
+ }
47
+ // ===========================================================================
48
+ // Getters/Setters
49
+ // ===========================================================================
50
+ /** Get current value */
51
+ get value() {
52
+ return this._value;
53
+ }
54
+ /** Set value (resets cursor to end) */
55
+ set value(v) {
56
+ this._value = v;
57
+ this._cursor = v.length;
58
+ }
59
+ /** Get cursor position */
60
+ get cursor() {
61
+ return this._cursor;
62
+ }
63
+ /** Check if empty */
64
+ get isEmpty() {
65
+ return this._value.length === 0;
66
+ }
67
+ /** Get trimmed value */
68
+ get trimmedValue() {
69
+ return this._value.trim();
70
+ }
71
+ // ===========================================================================
72
+ // Key Handling
73
+ // ===========================================================================
74
+ /**
75
+ * Handle a key press.
76
+ * Returns whether the key was handled and whether to re-render.
77
+ */
78
+ handleKey(data) {
79
+ // Left arrow - move cursor left
80
+ if (isLeftArrow(data)) {
81
+ if (this._cursor > 0) {
82
+ this._cursor--;
83
+ return { handled: true, render: true };
84
+ }
85
+ return { handled: true, render: false };
86
+ }
87
+ // Right arrow - move cursor right
88
+ if (isRightArrow(data)) {
89
+ if (this._cursor < this._value.length) {
90
+ this._cursor++;
91
+ return { handled: true, render: true };
92
+ }
93
+ return { handled: true, render: false };
94
+ }
95
+ // Home or Ctrl+A - move to start
96
+ if (isHome(data) || isCtrlA(data)) {
97
+ if (this._cursor > 0) {
98
+ this._cursor = 0;
99
+ return { handled: true, render: true };
100
+ }
101
+ return { handled: true, render: false };
102
+ }
103
+ // End or Ctrl+E - move to end
104
+ if (isEnd(data) || isCtrlE(data)) {
105
+ if (this._cursor < this._value.length) {
106
+ this._cursor = this._value.length;
107
+ return { handled: true, render: true };
108
+ }
109
+ return { handled: true, render: false };
110
+ }
111
+ // Word left (Ctrl+Left, Alt+b)
112
+ if (isWordLeft(data)) {
113
+ const newPos = this.findWordBoundaryLeft();
114
+ if (newPos !== this._cursor) {
115
+ this._cursor = newPos;
116
+ return { handled: true, render: true };
117
+ }
118
+ return { handled: true, render: false };
119
+ }
120
+ // Word right (Ctrl+Right, Alt+f)
121
+ if (isWordRight(data)) {
122
+ const newPos = this.findWordBoundaryRight();
123
+ if (newPos !== this._cursor) {
124
+ this._cursor = newPos;
125
+ return { handled: true, render: true };
126
+ }
127
+ return { handled: true, render: false };
128
+ }
129
+ // Backspace - delete before cursor
130
+ if (isBackspace(data)) {
131
+ if (this._cursor > 0) {
132
+ this._value =
133
+ this._value.slice(0, this._cursor - 1) + this._value.slice(this._cursor);
134
+ this._cursor--;
135
+ return { handled: true, render: true };
136
+ }
137
+ return { handled: true, render: false };
138
+ }
139
+ // Delete - delete at cursor
140
+ if (isDelete(data)) {
141
+ if (this._cursor < this._value.length) {
142
+ this._value =
143
+ this._value.slice(0, this._cursor) + this._value.slice(this._cursor + 1);
144
+ return { handled: true, render: true };
145
+ }
146
+ return { handled: true, render: false };
147
+ }
148
+ // Printable characters - insert at cursor
149
+ const printable = extractPrintable(data);
150
+ if (printable.length > 0) {
151
+ // Check max length
152
+ if (this.maxLength > 0 && this._value.length + printable.length > this.maxLength) {
153
+ const allowed = this.maxLength - this._value.length;
154
+ if (allowed <= 0) {
155
+ return { handled: true, render: false };
156
+ }
157
+ const truncated = printable.slice(0, allowed);
158
+ this._value =
159
+ this._value.slice(0, this._cursor) + truncated + this._value.slice(this._cursor);
160
+ this._cursor += truncated.length;
161
+ return { handled: true, render: true };
162
+ }
163
+ this._value =
164
+ this._value.slice(0, this._cursor) + printable + this._value.slice(this._cursor);
165
+ this._cursor += printable.length;
166
+ return { handled: true, render: true };
167
+ }
168
+ // Not handled
169
+ return { handled: false, render: false };
170
+ }
171
+ // ===========================================================================
172
+ // Rendering
173
+ // ===========================================================================
174
+ /**
175
+ * Render the input field with cursor.
176
+ * Returns a string like: "> text with cursor_"
177
+ *
178
+ * @param styles - Theme styles for coloring
179
+ * @param prefix - Prefix before the input (default: "> ")
180
+ */
181
+ render(styles, prefix = '> ') {
182
+ if (this._value.length === 0 && this.placeholder) {
183
+ // Show placeholder with cursor at start
184
+ return styles.primary(prefix) + styles.primary('▋') + styles.muted(this.placeholder);
185
+ }
186
+ // Split text at cursor position
187
+ const before = this._value.slice(0, this._cursor);
188
+ const after = this._value.slice(this._cursor);
189
+ return styles.primary(prefix) + before + styles.primary('▋') + after;
190
+ }
191
+ /**
192
+ * Render input field without cursor (for non-focused state).
193
+ */
194
+ renderWithoutCursor(styles, prefix = '> ') {
195
+ if (this._value.length === 0 && this.placeholder) {
196
+ return styles.muted(prefix + this.placeholder);
197
+ }
198
+ return prefix + this._value;
199
+ }
200
+ // ===========================================================================
201
+ // Helpers
202
+ // ===========================================================================
203
+ /** Clear the input */
204
+ clear() {
205
+ this._value = '';
206
+ this._cursor = 0;
207
+ }
208
+ /** Find word boundary to the left of cursor */
209
+ findWordBoundaryLeft() {
210
+ if (this._cursor === 0)
211
+ return 0;
212
+ let pos = this._cursor - 1;
213
+ // Skip whitespace
214
+ while (pos > 0 && /\s/.test(this._value[pos])) {
215
+ pos--;
216
+ }
217
+ // Skip word characters
218
+ while (pos > 0 && !/\s/.test(this._value[pos - 1])) {
219
+ pos--;
220
+ }
221
+ return pos;
222
+ }
223
+ /** Find word boundary to the right of cursor */
224
+ findWordBoundaryRight() {
225
+ if (this._cursor >= this._value.length)
226
+ return this._value.length;
227
+ let pos = this._cursor;
228
+ // Skip current word
229
+ while (pos < this._value.length && !/\s/.test(this._value[pos])) {
230
+ pos++;
231
+ }
232
+ // Skip whitespace
233
+ while (pos < this._value.length && /\s/.test(this._value[pos])) {
234
+ pos++;
235
+ }
236
+ return pos;
237
+ }
238
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * List Feature
3
+ *
4
+ * Composable feature for list selection and navigation.
5
+ * Handles up/down navigation, selection, and rendering.
6
+ *
7
+ * Usage:
8
+ * ```typescript
9
+ * interface MyState extends OverlayState, ListState {
10
+ * items: string[];
11
+ * }
12
+ *
13
+ * class MyOverlay extends BaseOverlay<MyState, string | null> {
14
+ * private listFeature = new ListFeature<string>(
15
+ * () => this.state.items,
16
+ * (item) => this.onSelect(item)
17
+ * );
18
+ *
19
+ * handleKey(data: Buffer): void {
20
+ * const result = this.listFeature.handleKey(data, this.state);
21
+ * if (result?.handled) {
22
+ * if (result.action === 'render') this.update();
23
+ * return;
24
+ * }
25
+ * // ... other key handling
26
+ * }
27
+ *
28
+ * render(): string[] {
29
+ * return [
30
+ * ...this.renderHeader('Select Item'),
31
+ * ...this.listFeature.renderItems(this.state, (opts) => {
32
+ * const prefix = opts.isSelected ? ' > ' : ' ';
33
+ * return opts.isSelected
34
+ * ? this.styles.primary(`${prefix}${opts.item}`)
35
+ * : this.styles.muted(`${prefix}${opts.item}`);
36
+ * }),
37
+ * ...this.renderFooter('↑↓/jk Navigate · Enter Select')
38
+ * ];
39
+ * }
40
+ * }
41
+ * ```
42
+ */
43
+ import type { KeyResult, ListState, ListItemRenderer } from '../base/overlay-types.js';
44
+ /**
45
+ * Configuration for ListFeature.
46
+ */
47
+ export interface ListFeatureConfig<T> {
48
+ /**
49
+ * Function to get the current list of items.
50
+ * Called each time keys are handled or items are rendered.
51
+ */
52
+ getItems: () => T[];
53
+ /**
54
+ * Optional callback when an item is selected (Enter pressed).
55
+ * If not provided, Enter key is not handled by this feature.
56
+ */
57
+ onSelect?: (item: T, index: number) => void;
58
+ /**
59
+ * Whether to wrap navigation at boundaries.
60
+ * If true, navigating up from first item goes to last, and vice versa.
61
+ * Default: false
62
+ */
63
+ wrapNavigation?: boolean;
64
+ }
65
+ /**
66
+ * Feature for list selection and navigation.
67
+ *
68
+ * Handles:
69
+ * - Up/Down arrows and j/k for navigation
70
+ * - Enter for selection (if onSelect provided)
71
+ *
72
+ * @template T - Type of items in the list
73
+ */
74
+ export declare class ListFeature<T> {
75
+ private readonly config;
76
+ /**
77
+ * Create a new ListFeature.
78
+ *
79
+ * @param getItems - Function to get current items
80
+ * @param onSelect - Optional callback for selection
81
+ */
82
+ constructor(getItems: () => T[], onSelect?: (item: T, index: number) => void);
83
+ /**
84
+ * Create from config object (alternative constructor).
85
+ */
86
+ static fromConfig<T>(config: ListFeatureConfig<T>): ListFeature<T>;
87
+ /**
88
+ * Handle a key press.
89
+ *
90
+ * @param data - Raw key buffer
91
+ * @param state - State containing selectedIndex
92
+ * @returns KeyResult if handled, null otherwise
93
+ */
94
+ handleKey(data: Buffer, state: ListState): KeyResult | null;
95
+ /**
96
+ * Render list items using a custom renderer.
97
+ *
98
+ * @param state - State containing selectedIndex
99
+ * @param renderer - Function to render each item
100
+ * @returns Array of rendered lines
101
+ */
102
+ renderItems(state: ListState, renderer: ListItemRenderer<T>): string[];
103
+ /**
104
+ * Render a subset of items (for pagination).
105
+ *
106
+ * @param state - State containing selectedIndex
107
+ * @param startIndex - First item index to render
108
+ * @param count - Number of items to render
109
+ * @param renderer - Function to render each item
110
+ * @returns Array of rendered lines
111
+ */
112
+ renderItemsSlice(state: ListState, startIndex: number, count: number, renderer: ListItemRenderer<T>): string[];
113
+ /**
114
+ * Get the currently selected item.
115
+ *
116
+ * @param state - State containing selectedIndex
117
+ * @returns Selected item or undefined if out of bounds
118
+ */
119
+ getSelectedItem(state: ListState): T | undefined;
120
+ /**
121
+ * Get the total number of items.
122
+ */
123
+ getItemCount(): number;
124
+ /**
125
+ * Ensure selectedIndex is within bounds.
126
+ * Call this after items change.
127
+ *
128
+ * @param state - State to adjust
129
+ */
130
+ clampSelection(state: ListState): void;
131
+ }
132
+ /**
133
+ * Create a simple list renderer with standard styling.
134
+ *
135
+ * @param styles - Theme styles object
136
+ * @param getLabel - Function to get display label from item
137
+ * @returns Renderer function
138
+ */
139
+ export declare function createSimpleListRenderer<T>(styles: {
140
+ primary: (s: string) => string;
141
+ muted: (s: string) => string;
142
+ }, getLabel: (item: T) => string): ListItemRenderer<T>;
143
+ /**
144
+ * Create a list renderer with label and description.
145
+ *
146
+ * @param styles - Theme styles object
147
+ * @param getLabel - Function to get display label
148
+ * @param getDescription - Function to get description
149
+ * @param labelWidth - Fixed width for label column (for alignment)
150
+ * @returns Renderer function
151
+ */
152
+ export declare function createLabelDescriptionRenderer<T>(styles: {
153
+ primary: (s: string) => string;
154
+ muted: (s: string) => string;
155
+ }, getLabel: (item: T) => string, getDescription: (item: T) => string, labelWidth?: number): ListItemRenderer<T>;
@@ -0,0 +1,244 @@
1
+ /**
2
+ * List Feature
3
+ *
4
+ * Composable feature for list selection and navigation.
5
+ * Handles up/down navigation, selection, and rendering.
6
+ *
7
+ * Usage:
8
+ * ```typescript
9
+ * interface MyState extends OverlayState, ListState {
10
+ * items: string[];
11
+ * }
12
+ *
13
+ * class MyOverlay extends BaseOverlay<MyState, string | null> {
14
+ * private listFeature = new ListFeature<string>(
15
+ * () => this.state.items,
16
+ * (item) => this.onSelect(item)
17
+ * );
18
+ *
19
+ * handleKey(data: Buffer): void {
20
+ * const result = this.listFeature.handleKey(data, this.state);
21
+ * if (result?.handled) {
22
+ * if (result.action === 'render') this.update();
23
+ * return;
24
+ * }
25
+ * // ... other key handling
26
+ * }
27
+ *
28
+ * render(): string[] {
29
+ * return [
30
+ * ...this.renderHeader('Select Item'),
31
+ * ...this.listFeature.renderItems(this.state, (opts) => {
32
+ * const prefix = opts.isSelected ? ' > ' : ' ';
33
+ * return opts.isSelected
34
+ * ? this.styles.primary(`${prefix}${opts.item}`)
35
+ * : this.styles.muted(`${prefix}${opts.item}`);
36
+ * }),
37
+ * ...this.renderFooter('↑↓/jk Navigate · Enter Select')
38
+ * ];
39
+ * }
40
+ * }
41
+ * ```
42
+ */
43
+ import { isNavigateUp, isNavigateDown, isEnter, } from '../base/key-utils.js';
44
+ // =============================================================================
45
+ // ListFeature Class
46
+ // =============================================================================
47
+ /**
48
+ * Feature for list selection and navigation.
49
+ *
50
+ * Handles:
51
+ * - Up/Down arrows and j/k for navigation
52
+ * - Enter for selection (if onSelect provided)
53
+ *
54
+ * @template T - Type of items in the list
55
+ */
56
+ export class ListFeature {
57
+ config;
58
+ /**
59
+ * Create a new ListFeature.
60
+ *
61
+ * @param getItems - Function to get current items
62
+ * @param onSelect - Optional callback for selection
63
+ */
64
+ constructor(getItems, onSelect) {
65
+ this.config = { getItems, onSelect };
66
+ }
67
+ /**
68
+ * Create from config object (alternative constructor).
69
+ */
70
+ static fromConfig(config) {
71
+ const feature = new ListFeature(config.getItems, config.onSelect);
72
+ if (config.wrapNavigation !== undefined) {
73
+ feature.config.wrapNavigation = config.wrapNavigation;
74
+ }
75
+ return feature;
76
+ }
77
+ // ===========================================================================
78
+ // Key Handling
79
+ // ===========================================================================
80
+ /**
81
+ * Handle a key press.
82
+ *
83
+ * @param data - Raw key buffer
84
+ * @param state - State containing selectedIndex
85
+ * @returns KeyResult if handled, null otherwise
86
+ */
87
+ handleKey(data, state) {
88
+ const items = this.config.getItems();
89
+ const itemCount = items.length;
90
+ if (itemCount === 0) {
91
+ return null; // Nothing to navigate
92
+ }
93
+ // Navigate up
94
+ if (isNavigateUp(data)) {
95
+ if (state.selectedIndex > 0) {
96
+ state.selectedIndex--;
97
+ return { handled: true, action: 'render' };
98
+ }
99
+ else if (this.config.wrapNavigation) {
100
+ state.selectedIndex = itemCount - 1;
101
+ return { handled: true, action: 'render' };
102
+ }
103
+ return { handled: true }; // At top, consume but no change
104
+ }
105
+ // Navigate down
106
+ if (isNavigateDown(data)) {
107
+ if (state.selectedIndex < itemCount - 1) {
108
+ state.selectedIndex++;
109
+ return { handled: true, action: 'render' };
110
+ }
111
+ else if (this.config.wrapNavigation) {
112
+ state.selectedIndex = 0;
113
+ return { handled: true, action: 'render' };
114
+ }
115
+ return { handled: true }; // At bottom, consume but no change
116
+ }
117
+ // Select (Enter)
118
+ if (isEnter(data) && this.config.onSelect) {
119
+ const item = items[state.selectedIndex];
120
+ if (item !== undefined) {
121
+ this.config.onSelect(item, state.selectedIndex);
122
+ return { handled: true };
123
+ }
124
+ }
125
+ return null; // Not handled
126
+ }
127
+ // ===========================================================================
128
+ // Rendering
129
+ // ===========================================================================
130
+ /**
131
+ * Render list items using a custom renderer.
132
+ *
133
+ * @param state - State containing selectedIndex
134
+ * @param renderer - Function to render each item
135
+ * @returns Array of rendered lines
136
+ */
137
+ renderItems(state, renderer) {
138
+ const items = this.config.getItems();
139
+ return items.map((item, index) => renderer({
140
+ item,
141
+ isSelected: index === state.selectedIndex,
142
+ index,
143
+ }));
144
+ }
145
+ /**
146
+ * Render a subset of items (for pagination).
147
+ *
148
+ * @param state - State containing selectedIndex
149
+ * @param startIndex - First item index to render
150
+ * @param count - Number of items to render
151
+ * @param renderer - Function to render each item
152
+ * @returns Array of rendered lines
153
+ */
154
+ renderItemsSlice(state, startIndex, count, renderer) {
155
+ const items = this.config.getItems();
156
+ const endIndex = Math.min(startIndex + count, items.length);
157
+ const lines = [];
158
+ for (let i = startIndex; i < endIndex; i++) {
159
+ const item = items[i];
160
+ lines.push(renderer({
161
+ item,
162
+ isSelected: i === state.selectedIndex,
163
+ index: i,
164
+ }));
165
+ }
166
+ return lines;
167
+ }
168
+ // ===========================================================================
169
+ // Utilities
170
+ // ===========================================================================
171
+ /**
172
+ * Get the currently selected item.
173
+ *
174
+ * @param state - State containing selectedIndex
175
+ * @returns Selected item or undefined if out of bounds
176
+ */
177
+ getSelectedItem(state) {
178
+ const items = this.config.getItems();
179
+ return items[state.selectedIndex];
180
+ }
181
+ /**
182
+ * Get the total number of items.
183
+ */
184
+ getItemCount() {
185
+ return this.config.getItems().length;
186
+ }
187
+ /**
188
+ * Ensure selectedIndex is within bounds.
189
+ * Call this after items change.
190
+ *
191
+ * @param state - State to adjust
192
+ */
193
+ clampSelection(state) {
194
+ const itemCount = this.getItemCount();
195
+ if (itemCount === 0) {
196
+ state.selectedIndex = 0;
197
+ }
198
+ else if (state.selectedIndex >= itemCount) {
199
+ state.selectedIndex = itemCount - 1;
200
+ }
201
+ else if (state.selectedIndex < 0) {
202
+ state.selectedIndex = 0;
203
+ }
204
+ }
205
+ }
206
+ // =============================================================================
207
+ // Helper Functions
208
+ // =============================================================================
209
+ /**
210
+ * Create a simple list renderer with standard styling.
211
+ *
212
+ * @param styles - Theme styles object
213
+ * @param getLabel - Function to get display label from item
214
+ * @returns Renderer function
215
+ */
216
+ export function createSimpleListRenderer(styles, getLabel) {
217
+ return ({ item, isSelected }) => {
218
+ const prefix = isSelected ? ' > ' : ' ';
219
+ const label = getLabel(item);
220
+ return isSelected
221
+ ? styles.primary(`${prefix}${label}`)
222
+ : styles.muted(`${prefix}${label}`);
223
+ };
224
+ }
225
+ /**
226
+ * Create a list renderer with label and description.
227
+ *
228
+ * @param styles - Theme styles object
229
+ * @param getLabel - Function to get display label
230
+ * @param getDescription - Function to get description
231
+ * @param labelWidth - Fixed width for label column (for alignment)
232
+ * @returns Renderer function
233
+ */
234
+ export function createLabelDescriptionRenderer(styles, getLabel, getDescription, labelWidth = 20) {
235
+ return ({ item, isSelected }) => {
236
+ const prefix = isSelected ? ' > ' : ' ';
237
+ const label = getLabel(item).padEnd(labelWidth);
238
+ const desc = getDescription(item);
239
+ if (isSelected) {
240
+ return styles.primary(`${prefix}${label}`) + styles.muted(desc);
241
+ }
242
+ return styles.muted(`${prefix}${label}${desc}`);
243
+ };
244
+ }