@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
@@ -2,409 +2,438 @@
2
2
  * Commands Overlay
3
3
  *
4
4
  * Modal overlay for viewing and managing custom command definitions.
5
- * - List tab: Shows existing custom commands
6
- * - Wizard mode: Step-by-step command creation
5
+ * Uses TabbedListOverlay for consistent navigation patterns.
6
+ *
7
+ * Features:
8
+ * - Tabs: All, Project, Personal
9
+ * - Search with / toggle
10
+ * - Detail view for command information
11
+ * - Wizard for creating new commands
7
12
  */
8
13
  import chalk from 'chalk';
9
14
  import * as terminal from './terminal.js';
10
15
  import { getCustomCommandRegistry, } from '../commands/index.js';
11
- import { getStyles } from '../themes/index.js';
16
+ import { TabbedListOverlay, BaseScreen, stay, popScreen, closeOverlay, isEscape, isEnter, isCtrlC, isNavigateUp, isNavigateDown, isBackspace, renderBorder, } from './base/index.js';
17
+ // =============================================================================
18
+ // Constants
12
19
  // =============================================================================
13
- // Rendering - List Mode
20
+ const TABS = [
21
+ { id: 'all', label: 'All' },
22
+ { id: 'project', label: 'Project' },
23
+ { id: 'personal', label: 'Personal' },
24
+ ];
25
+ const PAGE_SIZE = 10;
14
26
  // =============================================================================
15
- function renderListHeader() {
16
- const s = getStyles();
17
- const lines = [];
18
- const cols = terminal.getTerminalWidth();
19
- lines.push(s.muted('─'.repeat(Math.max(1, cols - 1))));
20
- lines.push(' ' + s.primaryBold('Custom Commands'));
21
- lines.push('');
22
- return lines;
27
+ // Helpers
28
+ // =============================================================================
29
+ function isCreateItem(item) {
30
+ return 'type' in item;
31
+ }
32
+ function buildItemsList(registry) {
33
+ const commands = registry.getAll();
34
+ return [...commands, { type: 'create' }];
23
35
  }
24
- function renderCommandsList(state) {
25
- const s = getStyles();
26
- const lines = [];
27
- const commands = state.registry.getAll();
28
- if (commands.length === 0) {
29
- lines.push(s.muted(' No custom commands found'));
36
+ function defaultWizardState() {
37
+ return {
38
+ step: 'location',
39
+ location: 'project',
40
+ name: '',
41
+ description: '',
42
+ prompt: '',
43
+ selectedOption: 0,
44
+ inputBuffer: '',
45
+ error: null,
46
+ };
47
+ }
48
+ // =============================================================================
49
+ // Detail Screen
50
+ // =============================================================================
51
+ class CommandDetailScreen extends BaseScreen {
52
+ command;
53
+ registry;
54
+ styles;
55
+ constructor(command, registry, styles) {
56
+ super();
57
+ this.command = command;
58
+ this.registry = registry;
59
+ this.styles = styles;
60
+ }
61
+ render() {
62
+ const s = this.styles;
63
+ const lines = [];
64
+ const cols = terminal.getTerminalWidth();
65
+ const border = renderBorder(cols, s);
66
+ const cmd = this.command;
67
+ // Header
68
+ lines.push(border);
69
+ lines.push(' ' + s.primaryBold('Command Details'));
30
70
  lines.push('');
31
- lines.push(state.selectedIndex === 0
32
- ? s.primary(' ❯ Create new command')
33
- : s.muted(' Create new command'));
71
+ // Command name
72
+ lines.push(chalk.bold(` /${cmd.name}`));
34
73
  lines.push('');
35
- lines.push(s.muted(' Custom commands are stored in:'));
36
- lines.push(s.muted(` ${state.registry.getProjectDir()}/ (project)`));
37
- lines.push(s.muted(` ${state.registry.getUserDir()}/ (personal)`));
38
- }
39
- else {
40
- lines.push(chalk.bold(' Available commands:'));
74
+ // Location
75
+ lines.push(' ' + s.primary('LOCATION'));
76
+ lines.push(` ${cmd.location === 'project' ? 'Project' : 'Personal'} (${cmd.location})`);
77
+ lines.push('');
78
+ // File path
79
+ lines.push(' ' + s.primary('FILE'));
80
+ const basePath = cmd.location === 'project'
81
+ ? this.registry.getProjectDir()
82
+ : this.registry.getUserDir();
83
+ lines.push(s.muted(` ${basePath}/${cmd.name}.md`));
41
84
  lines.push('');
42
- for (let i = 0; i < commands.length; i++) {
43
- const cmd = commands[i];
44
- const isSelected = i === state.selectedIndex;
45
- const prefix = isSelected ? s.primary('') : ' ';
46
- const name = `/${cmd.name}`;
47
- const nameStyled = isSelected ? s.primary(name.padEnd(25)) : name.padEnd(25);
48
- const desc = s.muted(cmd.description.slice(0, 45) + (cmd.description.length > 45 ? '...' : ''));
49
- const location = s.muted(` (${cmd.location})`);
50
- lines.push(prefix + nameStyled + desc + location);
85
+ // Description
86
+ lines.push(' ' + s.primary('DESCRIPTION'));
87
+ lines.push(...this.wrapText(cmd.description, cols - 6, ' '));
88
+ lines.push('');
89
+ // Prompt
90
+ lines.push(' ' + s.primary('PROMPT TEMPLATE'));
91
+ lines.push(...this.wrapText(cmd.prompt, cols - 6, ' '));
92
+ lines.push('');
93
+ // Footer
94
+ lines.push(border);
95
+ lines.push(` ${s.muted('q/Esc Back')}`);
96
+ lines.push(border);
97
+ return lines;
98
+ }
99
+ handleKey(data) {
100
+ if (isCtrlC(data)) {
101
+ return closeOverlay(undefined);
51
102
  }
52
- lines.push(s.muted(' ──────────────────────────────────'));
53
- const createIdx = commands.length;
54
- const isCreateSelected = state.selectedIndex === createIdx;
55
- lines.push(isCreateSelected
56
- ? s.primary(' ❯ Create new command')
57
- : s.muted(' Create new command'));
103
+ if (isEscape(data) || data.toString() === 'q') {
104
+ return popScreen();
105
+ }
106
+ return stay(false);
58
107
  }
59
- return lines;
60
- }
61
- function renderListFooter() {
62
- const s = getStyles();
63
- const lines = [];
64
- lines.push('');
65
- lines.push(s.muted(' ↑↓ Navigate · Enter Select · Esc Close'));
66
- return lines;
67
- }
68
- function buildListLines(state) {
69
- const allLines = [];
70
- allLines.push(...renderListHeader());
71
- allLines.push(...renderCommandsList(state));
72
- allLines.push(...renderListFooter());
73
- return allLines;
74
- }
75
- // =============================================================================
76
- // Rendering - Wizard Mode
77
- // =============================================================================
78
- function buildWizardLines(state) {
79
- const s = getStyles();
80
- const lines = [];
81
- const cols = terminal.getTerminalWidth();
82
- lines.push(s.muted('─'.repeat(Math.max(1, cols - 1))));
83
- lines.push(' ' + s.primaryBold('Create new command'));
84
- lines.push('');
85
- switch (state.wizardStep) {
86
- case 'location':
87
- lines.push(chalk.bold(' Choose location'));
88
- lines.push('');
89
- lines.push(state.wizardSelectedOption === 0
90
- ? s.primary(' ❯ 1. Project (.compilr-dev/commands/)')
91
- : s.muted(' 1. Project (.compilr-dev/commands/)'));
92
- lines.push(state.wizardSelectedOption === 1
93
- ? s.primary(' ❯ 2. Personal (~/.compilr-dev/commands/)')
94
- : s.muted(' 2. Personal (~/.compilr-dev/commands/)'));
95
- lines.push('');
96
- lines.push(s.muted(' Project commands are shared with your team.'));
97
- lines.push(s.muted(' Personal commands apply to all your projects.'));
98
- break;
99
- case 'name':
100
- lines.push(chalk.bold(' Command name'));
101
- lines.push('');
102
- lines.push(' Enter a unique name:');
103
- lines.push(` > ${state.wizardInputBuffer}█`);
104
- lines.push('');
105
- if (state.wizardError) {
106
- lines.push(s.error(` ${state.wizardError}`));
108
+ wrapText(text, maxWidth, indent) {
109
+ const words = text.split(' ');
110
+ const resultLines = [];
111
+ let currentLine = indent;
112
+ for (const word of words) {
113
+ if (currentLine.length + word.length + 1 > maxWidth) {
114
+ resultLines.push(currentLine);
115
+ currentLine = indent + word;
107
116
  }
108
117
  else {
109
- lines.push(s.muted(' Use lowercase letters, numbers, hyphens (e.g., code-review)'));
110
- lines.push(s.muted(' This becomes the command: /{name}'));
118
+ currentLine += (currentLine.length > indent.length ? ' ' : '') + word;
111
119
  }
112
- break;
113
- case 'description':
114
- lines.push(chalk.bold(' Description'));
115
- lines.push('');
116
- lines.push(' Enter a short description:');
117
- lines.push(` > ${state.wizardInputBuffer}█`);
118
- lines.push('');
119
- lines.push(s.muted(' This is shown in the help menu.'));
120
- break;
121
- case 'prompt':
122
- lines.push(chalk.bold(' Prompt template'));
123
- lines.push('');
124
- lines.push(' Enter the prompt to send:');
125
- lines.push(` > ${state.wizardInputBuffer}█`);
126
- lines.push('');
127
- lines.push(s.muted(' Use $1, $2 for positional args, $ARGUMENTS for all args.'));
128
- lines.push(s.muted(' Example: "Review $1 for bugs and security issues"'));
129
- break;
130
- case 'confirm':
131
- lines.push(chalk.bold(' Confirm'));
132
- lines.push('');
133
- lines.push(` Command: ${s.primary('/' + state.wizardName)}`);
134
- lines.push(` Location: ${s.primary(state.wizardLocation === 'project' ? '.compilr-dev/commands/' : '~/.compilr-dev/commands/')}${state.wizardName}.md`);
135
- lines.push('');
136
- lines.push(chalk.bold(' Description:'));
137
- lines.push(s.muted(` ${state.wizardDescription.slice(0, 60)}${state.wizardDescription.length > 60 ? '...' : ''}`));
138
- lines.push('');
139
- lines.push(chalk.bold(' Prompt template:'));
140
- lines.push(s.muted(` ${state.wizardPrompt.slice(0, 60)}${state.wizardPrompt.length > 60 ? '...' : ''}`));
141
- lines.push('');
142
- lines.push(state.wizardSelectedOption === 0
143
- ? s.primary(' ❯ Save')
144
- : s.muted(' Save'));
145
- lines.push(state.wizardSelectedOption === 1
146
- ? s.primary(' ❯ Cancel')
147
- : s.muted(' Cancel'));
148
- break;
120
+ }
121
+ if (currentLine.length > indent.length) {
122
+ resultLines.push(currentLine);
123
+ }
124
+ return resultLines;
149
125
  }
150
- lines.push('');
151
- lines.push(s.muted(' Esc to go back'));
152
- return lines;
153
126
  }
154
127
  // =============================================================================
155
- // Unified Rendering
128
+ // Wizard Screen
156
129
  // =============================================================================
157
- function buildLines(state) {
158
- if (state.mode === 'wizard') {
159
- return buildWizardLines(state);
130
+ class WizardScreen extends BaseScreen {
131
+ registry;
132
+ styles;
133
+ state;
134
+ constructor(registry, styles) {
135
+ super();
136
+ this.registry = registry;
137
+ this.styles = styles;
138
+ this.state = defaultWizardState();
160
139
  }
161
- return buildListLines(state);
162
- }
163
- function render(state, prevLineCount) {
164
- const lines = buildLines(state);
165
- // Clear previous content
166
- terminal.clearLinesAbove(prevLineCount);
167
- // Write new content
168
- terminal.write(lines.join('\n'));
169
- return lines.length;
170
- }
171
- // =============================================================================
172
- // Main Export
173
- // =============================================================================
174
- /**
175
- * Show the commands overlay
176
- */
177
- export async function showCommandsOverlay() {
178
- const registry = getCustomCommandRegistry();
179
- registry.load();
180
- const state = {
181
- mode: 'list',
182
- selectedIndex: 0,
183
- wizardStep: 'location',
184
- wizardLocation: 'project',
185
- wizardName: '',
186
- wizardDescription: '',
187
- wizardPrompt: '',
188
- wizardSelectedOption: 0,
189
- wizardInputBuffer: '',
190
- wizardError: null,
191
- registry,
192
- };
193
- let lineCount = 0;
194
- terminal.writeLine('');
195
- terminal.hideCursor();
196
- const wasRawMode = process.stdin.isRaw;
197
- terminal.enableRawMode();
198
- lineCount = render(state, 0);
199
- const getMaxIndex = () => {
200
- return state.registry.getAll().length; // includes "Create new"
201
- };
202
- // Reset wizard state for a new creation
203
- const resetWizard = () => {
204
- state.wizardStep = 'location';
205
- state.wizardLocation = 'project';
206
- state.wizardName = '';
207
- state.wizardDescription = '';
208
- state.wizardPrompt = '';
209
- state.wizardSelectedOption = 0;
210
- state.wizardInputBuffer = '';
211
- state.wizardError = null;
212
- };
213
- // Wizard: advance to next step
214
- const wizardNextStep = () => {
215
- switch (state.wizardStep) {
140
+ render() {
141
+ const s = this.styles;
142
+ const lines = [];
143
+ const cols = terminal.getTerminalWidth();
144
+ const border = renderBorder(cols, s);
145
+ lines.push(border);
146
+ lines.push(' ' + s.primaryBold('Create New Command'));
147
+ lines.push('');
148
+ switch (this.state.step) {
216
149
  case 'location':
217
- state.wizardLocation = state.wizardSelectedOption === 0 ? 'project' : 'personal';
218
- state.wizardStep = 'name';
219
- state.wizardInputBuffer = '';
150
+ lines.push(chalk.bold(' Choose location'));
151
+ lines.push('');
152
+ lines.push(this.state.selectedOption === 0
153
+ ? s.primary(' ❯ 1. Project (.compilr-dev/commands/)')
154
+ : s.muted(' 1. Project (.compilr-dev/commands/)'));
155
+ lines.push(this.state.selectedOption === 1
156
+ ? s.primary(' ❯ 2. Personal (~/.compilr-dev/commands/)')
157
+ : s.muted(' 2. Personal (~/.compilr-dev/commands/)'));
158
+ lines.push('');
159
+ lines.push(s.muted(' Project commands are shared with your team.'));
160
+ lines.push(s.muted(' Personal commands apply to all your projects.'));
220
161
  break;
221
162
  case 'name':
222
- state.wizardName = state.wizardInputBuffer.trim();
223
- state.wizardStep = 'description';
224
- state.wizardInputBuffer = '';
163
+ lines.push(chalk.bold(' Command name'));
164
+ lines.push('');
165
+ lines.push(' Enter a unique name:');
166
+ lines.push(` > ${this.state.inputBuffer}█`);
167
+ lines.push('');
168
+ if (this.state.error) {
169
+ lines.push(s.error(` ${this.state.error}`));
170
+ }
171
+ else {
172
+ lines.push(s.muted(' Use lowercase letters, numbers, hyphens (e.g., code-review)'));
173
+ lines.push(s.muted(' This becomes the command: /{name}'));
174
+ }
225
175
  break;
226
176
  case 'description':
227
- state.wizardDescription = state.wizardInputBuffer.trim();
228
- state.wizardStep = 'prompt';
229
- state.wizardInputBuffer = '';
177
+ lines.push(chalk.bold(' Description'));
178
+ lines.push('');
179
+ lines.push(' Enter a short description:');
180
+ lines.push(` > ${this.state.inputBuffer}█`);
181
+ lines.push('');
182
+ lines.push(s.muted(' This is shown in the help menu.'));
230
183
  break;
231
184
  case 'prompt':
232
- state.wizardPrompt = state.wizardInputBuffer.trim();
233
- state.wizardStep = 'confirm';
234
- state.wizardSelectedOption = 0;
185
+ lines.push(chalk.bold(' Prompt template'));
186
+ lines.push('');
187
+ lines.push(' Enter the prompt to send:');
188
+ lines.push(` > ${this.state.inputBuffer}█`);
189
+ lines.push('');
190
+ lines.push(s.muted(' Use $1, $2 for positional args, $ARGUMENTS for all args.'));
191
+ lines.push(s.muted(' Example: "Review $1 for bugs and security issues"'));
235
192
  break;
236
193
  case 'confirm':
237
- if (state.wizardSelectedOption === 0) {
238
- // Save
239
- try {
240
- registry.save({
241
- name: state.wizardName,
242
- description: state.wizardDescription,
243
- prompt: state.wizardPrompt,
244
- }, state.wizardLocation);
245
- // Success - go back to list
246
- registry.load();
247
- state.mode = 'list';
248
- state.selectedIndex = 0;
249
- return true; // Created successfully
250
- }
251
- catch (error) {
252
- state.wizardError = error.message;
253
- }
254
- }
255
- else {
256
- // Cancel - go back to list
257
- state.mode = 'list';
194
+ lines.push(chalk.bold(' Confirm'));
195
+ lines.push('');
196
+ lines.push(` Command: ${s.primary('/' + this.state.name)}`);
197
+ lines.push(` Location: ${s.primary(this.state.location === 'project' ? '.compilr-dev/commands/' : '~/.compilr-dev/commands/')}${this.state.name}.md`);
198
+ lines.push('');
199
+ lines.push(chalk.bold(' Description:'));
200
+ lines.push(s.muted(` ${this.state.description.slice(0, 60)}${this.state.description.length > 60 ? '...' : ''}`));
201
+ lines.push('');
202
+ lines.push(chalk.bold(' Prompt template:'));
203
+ lines.push(s.muted(` ${this.state.prompt.slice(0, 60)}${this.state.prompt.length > 60 ? '...' : ''}`));
204
+ lines.push('');
205
+ lines.push(this.state.selectedOption === 0
206
+ ? s.primary(' Save')
207
+ : s.muted(' Save'));
208
+ lines.push(this.state.selectedOption === 1
209
+ ? s.primary(' Cancel')
210
+ : s.muted(' Cancel'));
211
+ if (this.state.error) {
212
+ lines.push('');
213
+ lines.push(s.error(` ${this.state.error}`));
258
214
  }
259
215
  break;
260
216
  }
261
- state.wizardError = null;
262
- return false;
263
- };
264
- // Wizard: go back to previous step (or exit wizard)
265
- const wizardPrevStep = () => {
266
- switch (state.wizardStep) {
217
+ lines.push('');
218
+ lines.push(border);
219
+ lines.push(` ${s.muted('Esc to go back')}`);
220
+ lines.push(border);
221
+ return lines;
222
+ }
223
+ handleKey(data) {
224
+ // Ctrl+C always closes
225
+ if (isCtrlC(data)) {
226
+ return closeOverlay(undefined);
227
+ }
228
+ // Escape goes back one step (or exits wizard)
229
+ if (isEscape(data)) {
230
+ return this.handleBack();
231
+ }
232
+ // Input steps: name, description, prompt
233
+ if (['name', 'description', 'prompt'].includes(this.state.step)) {
234
+ return this.handleInputStep(data);
235
+ }
236
+ // Selection steps: location, confirm
237
+ if (['location', 'confirm'].includes(this.state.step)) {
238
+ return this.handleSelectionStep(data);
239
+ }
240
+ return stay();
241
+ }
242
+ handleBack() {
243
+ switch (this.state.step) {
267
244
  case 'location':
268
- // Exit wizard, go back to list
269
- state.mode = 'list';
270
- break;
245
+ return popScreen();
271
246
  case 'name':
272
- state.wizardStep = 'location';
273
- state.wizardSelectedOption = state.wizardLocation === 'project' ? 0 : 1;
247
+ this.state.step = 'location';
248
+ this.state.selectedOption = this.state.location === 'project' ? 0 : 1;
274
249
  break;
275
250
  case 'description':
276
- state.wizardStep = 'name';
277
- state.wizardInputBuffer = state.wizardName;
251
+ this.state.step = 'name';
252
+ this.state.inputBuffer = this.state.name;
278
253
  break;
279
254
  case 'prompt':
280
- state.wizardStep = 'description';
281
- state.wizardInputBuffer = state.wizardDescription;
255
+ this.state.step = 'description';
256
+ this.state.inputBuffer = this.state.description;
282
257
  break;
283
258
  case 'confirm':
284
- state.wizardStep = 'prompt';
285
- state.wizardInputBuffer = state.wizardPrompt;
259
+ this.state.step = 'prompt';
260
+ this.state.inputBuffer = this.state.prompt;
286
261
  break;
287
262
  }
288
- state.wizardError = null;
289
- };
290
- return new Promise((resolve) => {
291
- const cleanup = () => {
292
- terminal.clearLinesAbove(lineCount);
293
- terminal.writeLine('');
294
- terminal.showCursor();
295
- if (!wasRawMode) {
296
- terminal.disableRawMode();
297
- }
298
- process.stdin.removeListener('data', onData);
299
- };
300
- const onData = (data) => {
301
- const isEscape = data.length === 1 && data[0] === 0x1b;
302
- const isUpArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x41;
303
- const isDownArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x42;
304
- const isCtrlC = data.length === 1 && data[0] === 0x03;
305
- const isEnter = data.length === 1 && (data[0] === 0x0d || data[0] === 0x0a);
306
- const isBackspace = data.length === 1 && (data[0] === 0x7f || data[0] === 0x08);
307
- // ===== LIST MODE =====
308
- if (state.mode === 'list') {
309
- if (isEscape || isCtrlC) {
310
- cleanup();
311
- resolve();
312
- return;
313
- }
314
- if (isUpArrow && state.selectedIndex > 0) {
315
- state.selectedIndex--;
316
- lineCount = render(state, lineCount);
317
- return;
318
- }
319
- if (isDownArrow && state.selectedIndex < getMaxIndex()) {
320
- state.selectedIndex++;
321
- lineCount = render(state, lineCount);
322
- return;
323
- }
324
- if (isEnter) {
325
- const commands = state.registry.getAll();
326
- if (state.selectedIndex === commands.length) {
327
- // "Create new command" selected - enter wizard mode
328
- resetWizard();
329
- state.mode = 'wizard';
330
- lineCount = render(state, lineCount);
263
+ this.state.error = null;
264
+ return stay();
265
+ }
266
+ handleInputStep(data) {
267
+ const char = data.length === 1 && data[0] >= 0x20 && data[0] < 0x7f
268
+ ? String.fromCharCode(data[0])
269
+ : null;
270
+ if (isEnter(data)) {
271
+ if (this.state.inputBuffer.trim()) {
272
+ // Validate name
273
+ if (this.state.step === 'name') {
274
+ const name = this.state.inputBuffer.trim();
275
+ if (!/^[a-z][a-z0-9-]{1,29}$/.test(name)) {
276
+ this.state.error = 'Invalid name. Use lowercase letters, numbers, hyphens (2-30 chars).';
277
+ return stay();
278
+ }
279
+ if (this.registry.has(name)) {
280
+ this.state.error = `Command "${name}" already exists.`;
281
+ return stay();
331
282
  }
332
- return;
333
283
  }
334
- return;
284
+ this.advanceStep();
335
285
  }
336
- // ===== WIZARD MODE =====
337
- // At this point, mode must be 'wizard' since we handled 'list' above
338
- {
339
- if (isCtrlC) {
340
- cleanup();
341
- resolve();
342
- return;
343
- }
344
- if (isEscape) {
345
- wizardPrevStep();
346
- lineCount = render(state, lineCount);
347
- return;
348
- }
349
- // Input steps: name, description, prompt
350
- if (['name', 'description', 'prompt'].includes(state.wizardStep)) {
351
- if (isEnter) {
352
- if (state.wizardInputBuffer.trim()) {
353
- // Validate name
354
- if (state.wizardStep === 'name') {
355
- const name = state.wizardInputBuffer.trim();
356
- if (!/^[a-z][a-z0-9-]{1,29}$/.test(name)) {
357
- state.wizardError = 'Invalid name. Use lowercase letters, numbers, hyphens (2-30 chars).';
358
- lineCount = render(state, lineCount);
359
- return;
360
- }
361
- if (registry.has(name)) {
362
- state.wizardError = `Command "${name}" already exists.`;
363
- lineCount = render(state, lineCount);
364
- return;
365
- }
366
- }
367
- wizardNextStep();
368
- lineCount = render(state, lineCount);
369
- }
370
- return;
371
- }
372
- if (isBackspace) {
373
- state.wizardInputBuffer = state.wizardInputBuffer.slice(0, -1);
374
- state.wizardError = null;
375
- lineCount = render(state, lineCount);
376
- return;
286
+ return stay();
287
+ }
288
+ if (isBackspace(data)) {
289
+ this.state.inputBuffer = this.state.inputBuffer.slice(0, -1);
290
+ this.state.error = null;
291
+ return stay();
292
+ }
293
+ // Regular character input
294
+ if (char) {
295
+ this.state.inputBuffer += char;
296
+ this.state.error = null;
297
+ }
298
+ return stay();
299
+ }
300
+ handleSelectionStep(data) {
301
+ const maxOptions = 2;
302
+ if (isNavigateUp(data) && this.state.selectedOption > 0) {
303
+ this.state.selectedOption--;
304
+ return stay();
305
+ }
306
+ if (isNavigateDown(data) && this.state.selectedOption < maxOptions - 1) {
307
+ this.state.selectedOption++;
308
+ return stay();
309
+ }
310
+ if (isEnter(data)) {
311
+ if (this.state.step === 'confirm') {
312
+ if (this.state.selectedOption === 0) {
313
+ // Save
314
+ try {
315
+ this.registry.save({
316
+ name: this.state.name,
317
+ description: this.state.description,
318
+ prompt: this.state.prompt,
319
+ }, this.state.location);
320
+ // Reload registry and close overlay
321
+ this.registry.load();
322
+ // Close entire overlay - user can reopen to see new command
323
+ return closeOverlay(undefined);
377
324
  }
378
- // Regular character input
379
- const char = data.toString('utf-8');
380
- if (char.length === 1 && char.charCodeAt(0) >= 32) {
381
- state.wizardInputBuffer += char;
382
- state.wizardError = null;
383
- lineCount = render(state, lineCount);
325
+ catch (error) {
326
+ this.state.error = error.message;
327
+ return stay();
384
328
  }
385
- return;
386
329
  }
387
- // Selection steps: location, confirm
388
- if (['location', 'confirm'].includes(state.wizardStep)) {
389
- const maxOptions = 2;
390
- if (isUpArrow && state.wizardSelectedOption > 0) {
391
- state.wizardSelectedOption--;
392
- lineCount = render(state, lineCount);
393
- return;
394
- }
395
- if (isDownArrow && state.wizardSelectedOption < maxOptions - 1) {
396
- state.wizardSelectedOption++;
397
- lineCount = render(state, lineCount);
398
- return;
399
- }
400
- if (isEnter) {
401
- wizardNextStep();
402
- lineCount = render(state, lineCount);
403
- return;
404
- }
330
+ else {
331
+ // Cancel - go back to list
332
+ return popScreen();
405
333
  }
406
334
  }
407
- };
408
- process.stdin.on('data', onData);
409
- });
335
+ else {
336
+ this.advanceStep();
337
+ }
338
+ }
339
+ return stay();
340
+ }
341
+ advanceStep() {
342
+ switch (this.state.step) {
343
+ case 'location':
344
+ this.state.location = this.state.selectedOption === 0 ? 'project' : 'personal';
345
+ this.state.step = 'name';
346
+ this.state.inputBuffer = '';
347
+ break;
348
+ case 'name':
349
+ this.state.name = this.state.inputBuffer.trim();
350
+ this.state.step = 'description';
351
+ this.state.inputBuffer = '';
352
+ break;
353
+ case 'description':
354
+ this.state.description = this.state.inputBuffer.trim();
355
+ this.state.step = 'prompt';
356
+ this.state.inputBuffer = '';
357
+ break;
358
+ case 'prompt':
359
+ this.state.prompt = this.state.inputBuffer.trim();
360
+ this.state.step = 'confirm';
361
+ this.state.selectedOption = 0;
362
+ break;
363
+ }
364
+ this.state.error = null;
365
+ }
366
+ }
367
+ // =============================================================================
368
+ // Commands Overlay
369
+ // =============================================================================
370
+ class CommandsOverlay extends TabbedListOverlay {
371
+ registry;
372
+ constructor() {
373
+ const registry = getCustomCommandRegistry();
374
+ registry.load();
375
+ const items = buildItemsList(registry);
376
+ super({
377
+ title: 'Custom Commands',
378
+ tabs: TABS,
379
+ items,
380
+ pageSize: PAGE_SIZE,
381
+ filterByTab: (item, tabId) => {
382
+ // "Create new command" always shows
383
+ if (isCreateItem(item))
384
+ return true;
385
+ // Filter by location
386
+ if (tabId === 'all')
387
+ return true;
388
+ return item.location === tabId;
389
+ },
390
+ getSearchText: (item) => {
391
+ if (isCreateItem(item))
392
+ return 'create new';
393
+ return `${item.name} ${item.description}`;
394
+ },
395
+ renderItem: (item, isSelected, styles) => {
396
+ const cols = terminal.getTerminalWidth();
397
+ if (isCreateItem(item)) {
398
+ return isSelected
399
+ ? styles.primary(' ❯ Create new command')
400
+ : styles.muted(' Create new command');
401
+ }
402
+ const cmd = item;
403
+ const prefix = isSelected ? styles.primary(' ❯ ') : ' ';
404
+ const name = `/${cmd.name}`;
405
+ const nameStyled = isSelected ? styles.primary(name.padEnd(25)) : name.padEnd(25);
406
+ const maxDesc = Math.max(20, cols - 45);
407
+ const desc = cmd.description.slice(0, maxDesc) + (cmd.description.length > maxDesc ? '...' : '');
408
+ const descStyled = styles.muted(desc);
409
+ const location = styles.muted(` (${cmd.location})`);
410
+ return prefix + nameStyled + descStyled + location;
411
+ },
412
+ footerHints: (searchMode) => {
413
+ if (searchMode) {
414
+ return 'Type to filter · ↑↓/jk Navigate · Enter Select · Esc Exit search';
415
+ }
416
+ return 'Tab Filter · ↑↓/jk · / Search · Enter Select · q/Esc Close';
417
+ },
418
+ emptyMessage: 'No custom commands found.',
419
+ noResultsMessage: 'No commands match the search.',
420
+ detailScreensFullScreen: false,
421
+ });
422
+ this.registry = registry;
423
+ }
424
+ createDetailScreen(item) {
425
+ if (isCreateItem(item)) {
426
+ return new WizardScreen(this.registry, this.styles);
427
+ }
428
+ return new CommandDetailScreen(item, this.registry, this.styles);
429
+ }
430
+ }
431
+ // =============================================================================
432
+ // Export
433
+ // =============================================================================
434
+ /**
435
+ * Show the commands overlay
436
+ */
437
+ export async function showCommandsOverlay() {
438
+ return new CommandsOverlay().show();
410
439
  }