@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,500 +2,862 @@
2
2
  * Agents Overlay
3
3
  *
4
4
  * Modal overlay for viewing and managing agent configurations.
5
- * - Built-in tab: Lists 9 predefined agent types
6
- * - Custom tab: Lists user-defined agents + create new option
5
+ * Uses TabbedListOverlay for consistent list/tab/search behavior.
6
+ *
7
+ * Features:
8
+ * - Built-in tab: Lists predefined agent types with detail view
9
+ * - Custom tab: Lists user-defined agents with detail/edit + create new
7
10
  * - Wizard mode: Step-by-step agent creation
8
11
  */
9
12
  import * as terminal from './terminal.js';
10
- import { getStyles } from '../themes/index.js';
13
+ import { TabbedListOverlay, BaseScreen, stay, popScreen, closeOverlay, isEscape, isCtrlC, isEnter, isNavigateUp, isNavigateDown, isBackspace, isClose, extractPrintable, renderBorder, } from './base/index.js';
11
14
  import { getAgentRegistry } from '../agents/registry.js';
12
15
  const TABS = [
13
- { id: 'builtin', label: 'built-in' },
14
- { id: 'custom', label: 'custom' },
16
+ { id: 'builtin', label: 'Built-in' },
17
+ { id: 'custom', label: 'Custom' },
15
18
  ];
16
19
  // =============================================================================
17
- // Rendering - List Mode
20
+ // Helper Functions
18
21
  // =============================================================================
19
- function renderListHeader(state) {
20
- const s = getStyles();
22
+ function wrapText(text, maxWidth) {
23
+ if (!text || text.length <= maxWidth)
24
+ return [text || ''];
25
+ const words = text.split(' ');
21
26
  const lines = [];
22
- const cols = terminal.getTerminalWidth();
23
- lines.push(s.muted('─'.repeat(Math.max(1, cols - 1))));
24
- let tabLine = ' ' + s.primaryBold('Agents') + ' ';
25
- for (let i = 0; i < TABS.length; i++) {
26
- const tab = TABS[i];
27
- if (i === state.currentTab) {
28
- tabLine += s.selected(` ${tab.label} `) + ' ';
27
+ let currentLine = '';
28
+ for (const word of words) {
29
+ if (currentLine.length === 0) {
30
+ currentLine = word;
31
+ }
32
+ else if (currentLine.length + 1 + word.length <= maxWidth) {
33
+ currentLine += ' ' + word;
29
34
  }
30
35
  else {
31
- tabLine += s.muted(` ${tab.label} `) + ' ';
36
+ lines.push(currentLine);
37
+ currentLine = word;
32
38
  }
33
39
  }
34
- tabLine += s.muted('(Tab to switch)');
35
- lines.push(tabLine);
36
- lines.push('');
37
- return lines;
38
- }
39
- function renderBuiltinTab(state) {
40
- const s = getStyles();
41
- const lines = [];
42
- const agents = state.registry.getBuiltinAgents();
43
- lines.push(s.secondary(' Built-in agents (via Task tool):'));
44
- lines.push('');
45
- for (let i = 0; i < agents.length; i++) {
46
- const agent = agents[i];
47
- const isSelected = i === state.selectedIndex;
48
- const prefix = isSelected ? s.primary(' ❯ ') : ' ';
49
- const name = `${agent.name} · ${agent.model}`;
50
- const nameStyled = isSelected ? s.primary(name.padEnd(30)) : s.secondary(name.padEnd(30));
51
- const desc = s.muted(agent.description);
52
- lines.push(prefix + nameStyled + desc);
40
+ if (currentLine.length > 0) {
41
+ lines.push(currentLine);
53
42
  }
54
- lines.push('');
55
- lines.push(s.muted(' These agents are available to the LLM via the Task tool.'));
56
- return lines;
43
+ return lines.length > 0 ? lines : [''];
57
44
  }
58
- function renderCustomTab(state) {
59
- const s = getStyles();
60
- const lines = [];
61
- const agents = state.registry.getCustomAgents();
62
- if (agents.length === 0) {
63
- lines.push(s.muted(' No custom agents found'));
45
+ // =============================================================================
46
+ // Detail Screen (View Agent Info)
47
+ // =============================================================================
48
+ /**
49
+ * Screen for viewing agent details.
50
+ * Shows name, model, description, and for custom agents: system prompt.
51
+ * Press 'e' to edit custom agents.
52
+ */
53
+ class DetailScreen extends BaseScreen {
54
+ item;
55
+ styles;
56
+ editState;
57
+ onEdit;
58
+ constructor(item, styles, editState, onEdit) {
59
+ super();
60
+ this.item = item;
61
+ this.styles = styles;
62
+ this.editState = editState;
63
+ this.onEdit = onEdit;
64
+ }
65
+ render() {
66
+ const s = this.styles;
67
+ const cols = terminal.getTerminalWidth();
68
+ const border = renderBorder(cols, s);
69
+ const lines = [];
70
+ // Header
71
+ lines.push(border);
72
+ lines.push(` ${s.primaryBold('Agent Details')}`);
64
73
  lines.push('');
65
- lines.push(state.selectedIndex === 0
66
- ? s.primary(' ❯ Create new agent')
67
- : s.muted(' Create new agent'));
74
+ // Name and model
75
+ lines.push(` ${s.muted('Name:')} ${s.primary(this.item.name)}`);
76
+ lines.push(` ${s.muted('Model:')} ${s.secondary(this.item.model)}`);
77
+ lines.push(` ${s.muted('Type:')} ${this.item.isBuiltin ? s.muted('Built-in') : s.secondary('Custom')}`);
78
+ // Location for custom agents
79
+ if (!this.item.isBuiltin && this.item.location) {
80
+ const locationLabel = this.item.location === 'project' ? 'Project' : 'Personal';
81
+ lines.push(` ${s.muted('Location:')} ${s.secondary(locationLabel)}`);
82
+ }
68
83
  lines.push('');
69
- lines.push(s.muted(' Custom agents are stored in:'));
70
- lines.push(s.muted(` ${state.registry.getProjectDir()}/ (project)`));
71
- lines.push(s.muted(` ${state.registry.getUserDir()}/ (personal)`));
72
- }
73
- else {
74
- lines.push(s.secondary(' Custom agents:'));
84
+ // Description
85
+ lines.push(` ${s.muted('Description:')}`);
86
+ const descLines = wrapText(this.item.description, cols - 6);
87
+ for (const line of descLines) {
88
+ lines.push(` ${s.secondary(line)}`);
89
+ }
90
+ // System prompt for custom agents
91
+ if (!this.item.isBuiltin && this.item.systemPrompt) {
92
+ lines.push('');
93
+ lines.push(` ${s.muted('System Prompt:')}`);
94
+ // Show first few lines of system prompt
95
+ const promptLines = this.item.systemPrompt.split('\n').slice(0, 5);
96
+ for (const line of promptLines) {
97
+ const truncated = line.length > cols - 6 ? line.slice(0, cols - 9) + '...' : line;
98
+ lines.push(` ${s.secondary(truncated)}`);
99
+ }
100
+ if (this.item.systemPrompt.split('\n').length > 5) {
101
+ lines.push(` ${s.muted('...')}`);
102
+ }
103
+ }
104
+ // File path for custom agents
105
+ if (!this.item.isBuiltin && this.item.filePath) {
106
+ lines.push('');
107
+ lines.push(` ${s.muted('File:')} ${s.secondary(this.item.filePath)}`);
108
+ }
109
+ // Usage hint for built-in agents
110
+ if (this.item.isBuiltin) {
111
+ lines.push('');
112
+ lines.push(` ${s.muted('Usage:')}`);
113
+ lines.push(` ${s.secondary('This agent is invoked via the Task tool.')}`);
114
+ lines.push(` ${s.secondary(`Example: Task(subagent_type="${this.item.name}", prompt="...")`)}`);
115
+ }
75
116
  lines.push('');
76
- for (let i = 0; i < agents.length; i++) {
77
- const agent = agents[i];
78
- const isSelected = i === state.selectedIndex;
79
- const prefix = isSelected ? s.primary(' ') : ' ';
80
- const name = `${agent.name} · ${agent.model}`;
81
- const nameStyled = isSelected ? s.primary(name.padEnd(30)) : s.secondary(name.padEnd(30));
82
- const location = s.muted(`(${agent.location})`);
83
- lines.push(prefix + nameStyled + location);
84
- }
85
- lines.push(s.muted(' ──────────────────────────────────'));
86
- const createIdx = agents.length;
87
- const isCreateSelected = state.selectedIndex === createIdx;
88
- lines.push(isCreateSelected
89
- ? s.primary(' ❯ Create new agent')
90
- : s.muted(' Create new agent'));
117
+ lines.push(border);
118
+ // Show edit hint for custom agents
119
+ if (!this.item.isBuiltin) {
120
+ lines.push(` ${s.muted('e Edit · q/Esc Back')}`);
121
+ }
122
+ else {
123
+ lines.push(` ${s.muted('q/Esc Back')}`);
124
+ }
125
+ lines.push(border);
126
+ return lines;
127
+ }
128
+ handleKey(data) {
129
+ // Ctrl+C closes everything
130
+ if (isCtrlC(data)) {
131
+ return closeOverlay(undefined);
132
+ }
133
+ // Escape or q - back to list
134
+ if (isEscape(data) || isClose(data)) {
135
+ return popScreen();
136
+ }
137
+ // 'e' opens edit screen for custom agents
138
+ const char = extractPrintable(data);
139
+ if (char === 'e' && !this.item.isBuiltin) {
140
+ const editScreen = new EditScreen(this.item, this.editState, this.styles);
141
+ this.onEdit(editScreen);
142
+ return stay();
143
+ }
144
+ return stay(false);
91
145
  }
92
- return lines;
93
- }
94
- function renderListFooter() {
95
- const s = getStyles();
96
- const lines = [];
97
- lines.push('');
98
- lines.push(s.muted(' ↑↓ Navigate · Tab Switch · Enter Select · Esc Close'));
99
- return lines;
100
146
  }
101
- function buildListLines(state) {
102
- const allLines = [];
103
- allLines.push(...renderListHeader(state));
104
- if (TABS[state.currentTab].id === 'builtin') {
105
- allLines.push(...renderBuiltinTab(state));
147
+ /**
148
+ * Screen for editing an existing custom agent.
149
+ * Allows editing: model, description, system prompt.
150
+ * Also allows deleting the agent.
151
+ */
152
+ class EditScreen extends BaseScreen {
153
+ originalItem;
154
+ editState;
155
+ styles;
156
+ step = 'field-select';
157
+ selectedOption = 0;
158
+ inputBuffer = '';
159
+ error = null;
160
+ // Working copy of agent data (location is only 'project' | 'personal' since we don't edit built-in)
161
+ editedAgent;
162
+ constructor(originalItem, editState, styles) {
163
+ super();
164
+ this.originalItem = originalItem;
165
+ this.editState = editState;
166
+ this.styles = styles;
167
+ // Initialize with current values
168
+ // Custom agents can only be 'project' or 'personal', never 'builtin'
169
+ const loc = originalItem.location;
170
+ this.editedAgent = {
171
+ name: originalItem.name,
172
+ prompt: originalItem.systemPrompt ?? '',
173
+ description: originalItem.description,
174
+ model: originalItem.model,
175
+ location: (loc === 'project' || loc === 'personal') ? loc : 'project',
176
+ };
106
177
  }
107
- else {
108
- allLines.push(...renderCustomTab(state));
178
+ render() {
179
+ const s = this.styles;
180
+ const cols = terminal.getTerminalWidth();
181
+ const border = renderBorder(cols, s);
182
+ const lines = [];
183
+ lines.push(border);
184
+ lines.push(` ${s.primaryBold('Edit Agent:')} ${s.primary(this.originalItem.name)}`);
185
+ lines.push('');
186
+ switch (this.step) {
187
+ case 'field-select': {
188
+ lines.push(` ${s.secondary('Select field to edit:')}`);
189
+ lines.push('');
190
+ const fields = [
191
+ { id: 'model', label: 'Model', value: this.editedAgent.model },
192
+ { id: 'description', label: 'Description', value: this.truncate(this.editedAgent.description, 40) },
193
+ { id: 'prompt', label: 'System Prompt', value: this.truncate(this.editedAgent.prompt, 40) },
194
+ { id: 'save', label: 'Save Changes', value: '' },
195
+ { id: 'delete', label: 'Delete Agent', value: '' },
196
+ { id: 'cancel', label: 'Cancel', value: '' },
197
+ ];
198
+ for (let i = 0; i < fields.length; i++) {
199
+ const f = fields[i];
200
+ const isSelected = this.selectedOption === i;
201
+ const prefix = isSelected ? s.primary('❯ ') : ' ';
202
+ if (f.id === 'save') {
203
+ const label = s.primary(f.label);
204
+ lines.push(` ${prefix}${isSelected ? label : s.muted(f.label)}`);
205
+ }
206
+ else if (f.id === 'delete') {
207
+ const label = s.error(f.label);
208
+ lines.push(` ${prefix}${isSelected ? label : s.muted(f.label)}`);
209
+ }
210
+ else if (f.id === 'cancel') {
211
+ lines.push(` ${prefix}${isSelected ? s.secondary(f.label) : s.muted(f.label)}`);
212
+ }
213
+ else {
214
+ const label = `${f.label.padEnd(16)}${f.value}`;
215
+ lines.push(` ${prefix}${isSelected ? s.primary(label) : s.muted(label)}`);
216
+ }
217
+ }
218
+ break;
219
+ }
220
+ case 'edit-model': {
221
+ lines.push(` ${s.secondary('Select model')}`);
222
+ lines.push('');
223
+ const models = [
224
+ { id: 'sonnet', label: 'Sonnet', desc: 'Balanced (recommended)' },
225
+ { id: 'opus', label: 'Opus', desc: 'Most capable' },
226
+ { id: 'haiku', label: 'Haiku', desc: 'Fast and efficient' },
227
+ { id: 'inherit', label: 'Inherit', desc: "Use parent's model" },
228
+ ];
229
+ for (let i = 0; i < models.length; i++) {
230
+ const m = models[i];
231
+ const isSelected = this.selectedOption === i;
232
+ const prefix = isSelected ? s.primary('❯ ') : ' ';
233
+ const current = this.editedAgent.model === m.id ? s.muted(' (current)') : '';
234
+ const label = `${String(i + 1)}. ${m.label.padEnd(10)} - ${m.desc}${current}`;
235
+ lines.push(` ${prefix}${isSelected ? s.primary(label) : s.muted(label)}`);
236
+ }
237
+ break;
238
+ }
239
+ case 'edit-description':
240
+ lines.push(` ${s.secondary('Edit description')}`);
241
+ lines.push('');
242
+ lines.push(` When should the LLM use this agent?`);
243
+ lines.push(` > ${this.inputBuffer}█`);
244
+ lines.push('');
245
+ if (this.error) {
246
+ lines.push(` ${s.error(this.error)}`);
247
+ }
248
+ else {
249
+ lines.push(` ${s.muted('Press Enter to save, Esc to cancel')}`);
250
+ }
251
+ break;
252
+ case 'edit-prompt':
253
+ lines.push(` ${s.secondary('Edit system prompt')}`);
254
+ lines.push('');
255
+ lines.push(` Enter the agent's instructions:`);
256
+ lines.push(` > ${this.inputBuffer}█`);
257
+ lines.push('');
258
+ if (this.error) {
259
+ lines.push(` ${s.error(this.error)}`);
260
+ }
261
+ else {
262
+ lines.push(` ${s.muted('Press Enter to save, Esc to cancel')}`);
263
+ lines.push(` ${s.muted('(For multiline prompts, edit the .md file directly)')}`);
264
+ }
265
+ break;
266
+ case 'confirm-delete':
267
+ lines.push(` ${s.error('Delete this agent?')}`);
268
+ lines.push('');
269
+ lines.push(` ${s.muted('Name:')} ${s.primary(this.originalItem.name)}`);
270
+ lines.push(` ${s.muted('File:')} ${s.secondary(this.originalItem.filePath ?? 'unknown')}`);
271
+ lines.push('');
272
+ lines.push(this.selectedOption === 0
273
+ ? ` ${s.error('❯ Yes, delete')}`
274
+ : ` ${s.muted(' Yes, delete')}`);
275
+ lines.push(this.selectedOption === 1
276
+ ? ` ${s.primary('❯ No, cancel')}`
277
+ : ` ${s.muted(' No, cancel')}`);
278
+ break;
279
+ }
280
+ lines.push('');
281
+ lines.push(border);
282
+ lines.push(` ${s.muted('Esc Back')}`);
283
+ lines.push(border);
284
+ return lines;
109
285
  }
110
- allLines.push(...renderListFooter());
111
- return allLines;
112
- }
113
- // =============================================================================
114
- // Rendering - Wizard Mode
115
- // =============================================================================
116
- function buildWizardLines(state) {
117
- const s = getStyles();
118
- const lines = [];
119
- const cols = terminal.getTerminalWidth();
120
- lines.push(s.muted('─'.repeat(Math.max(1, cols - 1))));
121
- lines.push(' ' + s.primaryBold('Create new agent'));
122
- lines.push('');
123
- switch (state.wizardStep) {
124
- case 'location':
125
- lines.push(s.secondary(' Choose location'));
126
- lines.push('');
127
- lines.push(state.wizardSelectedOption === 0
128
- ? s.primary(' ❯ 1. Project (.compilr-dev/agents/)')
129
- : s.muted(' 1. Project (.compilr-dev/agents/)'));
130
- lines.push(state.wizardSelectedOption === 1
131
- ? s.primary(' ❯ 2. Personal (~/.compilr-dev/agents/)')
132
- : s.muted(' 2. Personal (~/.compilr-dev/agents/)'));
133
- lines.push('');
134
- lines.push(s.muted(' Project agents are shared with your team.'));
135
- lines.push(s.muted(' Personal agents apply to all your projects.'));
136
- break;
137
- case 'name':
138
- lines.push(s.secondary(' Agent name (identifier)'));
139
- lines.push('');
140
- lines.push(' Enter a unique identifier:');
141
- lines.push(` > ${state.wizardInputBuffer}█`);
142
- lines.push('');
143
- if (state.wizardError) {
144
- lines.push(s.error(` ${state.wizardError}`));
286
+ handleKey(data) {
287
+ const char = extractPrintable(data);
288
+ // Ctrl+C closes everything
289
+ if (isCtrlC(data)) {
290
+ return closeOverlay(undefined);
291
+ }
292
+ // Escape goes back
293
+ if (isEscape(data)) {
294
+ return this.handleBack();
295
+ }
296
+ // Text input steps
297
+ if (this.step === 'edit-description' || this.step === 'edit-prompt') {
298
+ if (isEnter(data)) {
299
+ const text = this.inputBuffer.trim();
300
+ if (!text) {
301
+ this.error = 'Cannot be empty';
302
+ return stay();
303
+ }
304
+ if (this.step === 'edit-description') {
305
+ this.editedAgent.description = text;
306
+ }
307
+ else {
308
+ this.editedAgent.prompt = text;
309
+ }
310
+ this.step = 'field-select';
311
+ this.inputBuffer = '';
312
+ this.error = null;
313
+ return stay();
145
314
  }
146
- else {
147
- lines.push(s.muted(' Use lowercase letters, numbers, hyphens (e.g., code-reviewer)'));
315
+ if (isBackspace(data)) {
316
+ this.inputBuffer = this.inputBuffer.slice(0, -1);
317
+ this.error = null;
318
+ return stay();
148
319
  }
149
- break;
150
- case 'prompt':
151
- lines.push(s.secondary(' System prompt'));
152
- lines.push('');
153
- lines.push(' Enter the agent\'s instructions:');
154
- lines.push(` > ${state.wizardInputBuffer}█`);
155
- lines.push('');
156
- lines.push(s.muted(' This defines how the agent behaves.'));
157
- lines.push(s.muted(' Press Enter to continue.'));
158
- break;
159
- case 'description':
160
- lines.push(s.secondary(' Description'));
161
- lines.push('');
162
- lines.push(' When should the LLM use this agent?');
163
- lines.push(` > ${state.wizardInputBuffer}█`);
164
- lines.push('');
165
- lines.push(s.muted(' This helps the LLM know when to invoke this agent.'));
166
- break;
167
- case 'model': {
168
- lines.push(s.secondary(' Select model'));
169
- lines.push('');
170
- const models = [
171
- { id: 'sonnet', label: 'Sonnet', desc: 'Balanced (recommended)' },
172
- { id: 'opus', label: 'Opus', desc: 'Most capable' },
173
- { id: 'haiku', label: 'Haiku', desc: 'Fast and efficient' },
174
- { id: 'inherit', label: 'Inherit', desc: 'Use parent\'s model' },
175
- ];
176
- for (let i = 0; i < models.length; i++) {
177
- const m = models[i];
178
- const isSelected = state.wizardSelectedOption === i;
179
- const prefix = isSelected ? s.primary(' ❯ ') : ' ';
180
- const label = isSelected
181
- ? s.primary(`${String(i + 1)}. ${m.label.padEnd(10)} - ${m.desc}`)
182
- : s.muted(`${String(i + 1)}. ${m.label.padEnd(10)} - ${m.desc}`);
183
- lines.push(prefix + label);
320
+ if (char) {
321
+ this.inputBuffer += char;
322
+ this.error = null;
323
+ return stay();
184
324
  }
185
- break;
325
+ return stay(false);
186
326
  }
187
- case 'confirm':
188
- lines.push(s.secondary(' Confirm'));
189
- lines.push('');
190
- lines.push(` Name: ${s.primary(state.wizardName)}`);
191
- lines.push(` Location: ${s.primary(state.wizardLocation === 'project' ? '.compilr-dev/agents/' : '~/.compilr-dev/agents/')}${state.wizardName}.md`);
192
- lines.push(` Model: ${s.primary(state.wizardModel)}`);
193
- lines.push('');
194
- lines.push(s.secondary(' Description:'));
195
- lines.push(s.muted(` ${state.wizardDescription.slice(0, 60)}${state.wizardDescription.length > 60 ? '...' : ''}`));
196
- lines.push('');
197
- lines.push(s.secondary(' System prompt:'));
198
- lines.push(s.muted(` ${state.wizardPrompt.slice(0, 60)}${state.wizardPrompt.length > 60 ? '...' : ''}`));
199
- lines.push('');
200
- lines.push(state.wizardSelectedOption === 0
201
- ? s.primary(' ❯ Save')
202
- : s.muted(' Save'));
203
- lines.push(state.wizardSelectedOption === 1
204
- ? s.primary(' ❯ Cancel')
205
- : s.muted(' Cancel'));
206
- break;
327
+ // Selection steps
328
+ const maxOptions = this.step === 'field-select' ? 6 : this.step === 'edit-model' ? 4 : 2;
329
+ if (isNavigateUp(data) && this.selectedOption > 0) {
330
+ this.selectedOption--;
331
+ return stay();
332
+ }
333
+ if (isNavigateDown(data) && this.selectedOption < maxOptions - 1) {
334
+ this.selectedOption++;
335
+ return stay();
336
+ }
337
+ if (isEnter(data)) {
338
+ return this.handleSelect();
339
+ }
340
+ return stay(false);
207
341
  }
208
- lines.push('');
209
- lines.push(s.muted(' Esc to go back'));
210
- return lines;
211
- }
212
- // =============================================================================
213
- // Unified Rendering
214
- // =============================================================================
215
- function buildLines(state) {
216
- if (state.mode === 'wizard') {
217
- return buildWizardLines(state);
342
+ handleBack() {
343
+ switch (this.step) {
344
+ case 'field-select':
345
+ return popScreen();
346
+ case 'edit-model':
347
+ case 'edit-description':
348
+ case 'edit-prompt':
349
+ case 'confirm-delete':
350
+ this.step = 'field-select';
351
+ this.selectedOption = 0;
352
+ this.inputBuffer = '';
353
+ this.error = null;
354
+ break;
355
+ }
356
+ return stay();
357
+ }
358
+ handleSelect() {
359
+ switch (this.step) {
360
+ case 'field-select':
361
+ switch (this.selectedOption) {
362
+ case 0: { // Model
363
+ this.step = 'edit-model';
364
+ const models = ['sonnet', 'opus', 'haiku', 'inherit'];
365
+ this.selectedOption = models.indexOf(this.editedAgent.model);
366
+ if (this.selectedOption < 0)
367
+ this.selectedOption = 0;
368
+ break;
369
+ }
370
+ case 1: // Description
371
+ this.step = 'edit-description';
372
+ this.inputBuffer = this.editedAgent.description;
373
+ break;
374
+ case 2: // System Prompt
375
+ this.step = 'edit-prompt';
376
+ this.inputBuffer = this.editedAgent.prompt;
377
+ break;
378
+ case 3: // Save
379
+ return this.saveAgent();
380
+ case 4: // Delete
381
+ this.step = 'confirm-delete';
382
+ this.selectedOption = 1; // Default to "No"
383
+ break;
384
+ case 5: // Cancel
385
+ return popScreen();
386
+ }
387
+ break;
388
+ case 'edit-model': {
389
+ const models = ['sonnet', 'opus', 'haiku', 'inherit'];
390
+ this.editedAgent.model = models[this.selectedOption];
391
+ this.step = 'field-select';
392
+ this.selectedOption = 0;
393
+ break;
394
+ }
395
+ case 'confirm-delete':
396
+ if (this.selectedOption === 0) {
397
+ // Delete
398
+ return this.deleteAgent();
399
+ }
400
+ else {
401
+ // Cancel
402
+ this.step = 'field-select';
403
+ this.selectedOption = 0;
404
+ }
405
+ break;
406
+ }
407
+ return stay();
408
+ }
409
+ saveAgent() {
410
+ try {
411
+ this.editState.registry.saveAgent({
412
+ name: this.editedAgent.name,
413
+ description: this.editedAgent.description,
414
+ model: this.editedAgent.model,
415
+ systemPrompt: this.editedAgent.prompt,
416
+ }, this.editedAgent.location);
417
+ this.editState.registry.load();
418
+ // Pop twice: EditScreen -> DetailScreen -> List
419
+ return popScreen();
420
+ }
421
+ catch (error) {
422
+ this.error = error.message;
423
+ return stay();
424
+ }
425
+ }
426
+ deleteAgent() {
427
+ try {
428
+ this.editState.registry.deleteAgent(this.originalItem.name);
429
+ this.editState.registry.load();
430
+ // Pop twice: EditScreen -> DetailScreen -> List
431
+ return popScreen();
432
+ }
433
+ catch (error) {
434
+ this.error = error.message;
435
+ return stay();
436
+ }
437
+ }
438
+ truncate(text, max) {
439
+ if (!text)
440
+ return '';
441
+ if (text.length <= max)
442
+ return text;
443
+ return text.slice(0, max - 3) + '...';
218
444
  }
219
- return buildListLines(state);
220
- }
221
- function render(state, prevLineCount) {
222
- const lines = buildLines(state);
223
- // Clear previous content
224
- terminal.clearLinesAbove(prevLineCount);
225
- // Write new content
226
- terminal.write(lines.join('\n'));
227
- return lines.length;
228
445
  }
229
446
  // =============================================================================
230
- // Main Export
447
+ // Wizard Screen (Multi-Step Agent Creation)
231
448
  // =============================================================================
232
449
  /**
233
- * Show the agents overlay
450
+ * Screen for creating a new agent via multi-step wizard.
451
+ * Steps: location, name, prompt, description, model, confirm
234
452
  */
235
- export async function showAgentsOverlay() {
236
- const registry = getAgentRegistry();
237
- registry.load();
238
- const state = {
239
- mode: 'list',
240
- currentTab: 0,
241
- selectedIndex: 0,
242
- wizardStep: 'location',
243
- wizardLocation: 'project',
244
- wizardName: '',
245
- wizardPrompt: '',
246
- wizardDescription: '',
247
- wizardModel: 'sonnet',
248
- wizardSelectedOption: 0,
249
- wizardInputBuffer: '',
250
- wizardError: null,
251
- registry,
453
+ class WizardScreen extends BaseScreen {
454
+ editState;
455
+ styles;
456
+ step = 'location';
457
+ selectedOption = 0;
458
+ inputBuffer = '';
459
+ error = null;
460
+ newAgent = {
461
+ location: 'project',
462
+ name: '',
463
+ prompt: '',
464
+ description: '',
465
+ model: 'sonnet',
252
466
  };
253
- let lineCount = 0;
254
- terminal.writeLine('');
255
- terminal.hideCursor();
256
- const wasRawMode = process.stdin.isRaw;
257
- terminal.enableRawMode();
258
- lineCount = render(state, 0);
259
- const getMaxIndex = () => {
260
- if (TABS[state.currentTab].id === 'builtin') {
261
- return state.registry.getBuiltinAgents().length - 1;
262
- }
263
- else {
264
- return state.registry.getCustomAgents().length; // includes "Create new"
265
- }
266
- };
267
- // Reset wizard state for a new creation
268
- const resetWizard = () => {
269
- state.wizardStep = 'location';
270
- state.wizardLocation = 'project';
271
- state.wizardName = '';
272
- state.wizardPrompt = '';
273
- state.wizardDescription = '';
274
- state.wizardModel = 'sonnet';
275
- state.wizardSelectedOption = 0;
276
- state.wizardInputBuffer = '';
277
- state.wizardError = null;
278
- };
279
- // Wizard: advance to next step
280
- const wizardNextStep = () => {
281
- switch (state.wizardStep) {
467
+ constructor(editState, styles) {
468
+ super();
469
+ this.editState = editState;
470
+ this.styles = styles;
471
+ }
472
+ render() {
473
+ const s = this.styles;
474
+ const cols = terminal.getTerminalWidth();
475
+ const border = renderBorder(cols, s);
476
+ const lines = [];
477
+ lines.push(border);
478
+ lines.push(` ${s.primaryBold('Create New Agent')}`);
479
+ lines.push('');
480
+ switch (this.step) {
282
481
  case 'location':
283
- state.wizardLocation = state.wizardSelectedOption === 0 ? 'project' : 'personal';
284
- state.wizardStep = 'name';
285
- state.wizardInputBuffer = '';
482
+ lines.push(` ${s.secondary('Choose location')}`);
483
+ lines.push('');
484
+ lines.push(this.selectedOption === 0
485
+ ? ` ${s.primary('❯ 1. Project (.compilr-dev/agents/)')}`
486
+ : ` ${s.muted(' 1. Project (.compilr-dev/agents/)')}`);
487
+ lines.push(this.selectedOption === 1
488
+ ? ` ${s.primary('❯ 2. Personal (~/.compilr-dev/agents/)')}`
489
+ : ` ${s.muted(' 2. Personal (~/.compilr-dev/agents/)')}`);
490
+ lines.push('');
491
+ lines.push(` ${s.muted('Project agents are shared with your team.')}`);
492
+ lines.push(` ${s.muted('Personal agents apply to all your projects.')}`);
286
493
  break;
287
494
  case 'name':
288
- state.wizardName = state.wizardInputBuffer.trim();
289
- state.wizardStep = 'prompt';
290
- state.wizardInputBuffer = '';
495
+ lines.push(` ${s.secondary('Agent name (identifier)')}`);
496
+ lines.push('');
497
+ lines.push(` Enter a unique identifier:`);
498
+ lines.push(` > ${this.inputBuffer}█`);
499
+ lines.push('');
500
+ if (this.error) {
501
+ lines.push(` ${s.error(this.error)}`);
502
+ }
503
+ else {
504
+ lines.push(` ${s.muted('Use lowercase letters, numbers, hyphens (e.g., code-reviewer)')}`);
505
+ }
291
506
  break;
292
507
  case 'prompt':
293
- state.wizardPrompt = state.wizardInputBuffer.trim();
294
- state.wizardStep = 'description';
295
- state.wizardInputBuffer = '';
508
+ lines.push(` ${s.secondary('System prompt')}`);
509
+ lines.push('');
510
+ lines.push(` Enter the agent's instructions:`);
511
+ lines.push(` > ${this.inputBuffer}█`);
512
+ lines.push('');
513
+ lines.push(` ${s.muted('This defines how the agent behaves.')}`);
514
+ lines.push(` ${s.muted('Press Enter to continue.')}`);
296
515
  break;
297
516
  case 'description':
298
- state.wizardDescription = state.wizardInputBuffer.trim();
299
- state.wizardStep = 'model';
300
- state.wizardSelectedOption = 0;
517
+ lines.push(` ${s.secondary('Description')}`);
518
+ lines.push('');
519
+ lines.push(` When should the LLM use this agent?`);
520
+ lines.push(` > ${this.inputBuffer}█`);
521
+ lines.push('');
522
+ lines.push(` ${s.muted('This helps the LLM know when to invoke this agent.')}`);
301
523
  break;
302
524
  case 'model': {
303
- const models = ['sonnet', 'opus', 'haiku', 'inherit'];
304
- state.wizardModel = models[state.wizardSelectedOption];
305
- state.wizardStep = 'confirm';
306
- state.wizardSelectedOption = 0;
525
+ lines.push(` ${s.secondary('Select model')}`);
526
+ lines.push('');
527
+ const models = [
528
+ { id: 'sonnet', label: 'Sonnet', desc: 'Balanced (recommended)' },
529
+ { id: 'opus', label: 'Opus', desc: 'Most capable' },
530
+ { id: 'haiku', label: 'Haiku', desc: 'Fast and efficient' },
531
+ { id: 'inherit', label: 'Inherit', desc: "Use parent's model" },
532
+ ];
533
+ for (let i = 0; i < models.length; i++) {
534
+ const m = models[i];
535
+ const isSelected = this.selectedOption === i;
536
+ const prefix = isSelected ? s.primary('❯ ') : ' ';
537
+ const label = `${String(i + 1)}. ${m.label.padEnd(10)} - ${m.desc}`;
538
+ lines.push(` ${prefix}${isSelected ? s.primary(label) : s.muted(label)}`);
539
+ }
307
540
  break;
308
541
  }
309
- case 'confirm':
310
- if (state.wizardSelectedOption === 0) {
311
- // Save
312
- try {
313
- registry.saveAgent({
314
- name: state.wizardName,
315
- description: state.wizardDescription,
316
- model: state.wizardModel,
317
- systemPrompt: state.wizardPrompt,
318
- }, state.wizardLocation);
319
- // Success - go back to list
320
- registry.load();
321
- state.mode = 'list';
322
- state.selectedIndex = 0;
323
- return true; // Created successfully
542
+ case 'confirm': {
543
+ lines.push(` ${s.secondary('Confirm')}`);
544
+ lines.push('');
545
+ lines.push(` Name: ${s.primary(this.newAgent.name)}`);
546
+ lines.push(` Location: ${s.primary(this.newAgent.location === 'project' ? '.compilr-dev/agents/' : '~/.compilr-dev/agents/')}${this.newAgent.name}.md`);
547
+ lines.push(` Model: ${s.primary(this.newAgent.model)}`);
548
+ lines.push('');
549
+ lines.push(` ${s.secondary('Description:')}`);
550
+ const descPreview = this.newAgent.description.length > 60
551
+ ? this.newAgent.description.slice(0, 57) + '...'
552
+ : this.newAgent.description;
553
+ lines.push(` ${s.muted(descPreview)}`);
554
+ lines.push('');
555
+ lines.push(` ${s.secondary('System prompt:')}`);
556
+ const promptPreview = this.newAgent.prompt.length > 60
557
+ ? this.newAgent.prompt.slice(0, 57) + '...'
558
+ : this.newAgent.prompt;
559
+ lines.push(` ${s.muted(promptPreview)}`);
560
+ lines.push('');
561
+ lines.push(this.selectedOption === 0
562
+ ? ` ${s.primary('❯ Save')}`
563
+ : ` ${s.muted(' Save')}`);
564
+ lines.push(this.selectedOption === 1
565
+ ? ` ${s.primary('❯ Cancel')}`
566
+ : ` ${s.muted(' Cancel')}`);
567
+ break;
568
+ }
569
+ }
570
+ lines.push('');
571
+ lines.push(border);
572
+ lines.push(` ${s.muted('Esc Back')}`);
573
+ lines.push(border);
574
+ return lines;
575
+ }
576
+ handleKey(data) {
577
+ const char = extractPrintable(data);
578
+ // Ctrl+C closes everything
579
+ if (isCtrlC(data)) {
580
+ return closeOverlay(undefined);
581
+ }
582
+ // Escape goes back
583
+ if (isEscape(data)) {
584
+ return this.handleBack();
585
+ }
586
+ // Input steps: name, prompt, description
587
+ if (this.step === 'name' || this.step === 'prompt' || this.step === 'description') {
588
+ if (isEnter(data)) {
589
+ const text = this.inputBuffer.trim();
590
+ if (!text) {
591
+ return stay(false);
592
+ }
593
+ // Validate name
594
+ if (this.step === 'name') {
595
+ if (!/^[a-z][a-z0-9-]{1,49}$/.test(text)) {
596
+ this.error = 'Invalid name. Use lowercase letters, numbers, hyphens.';
597
+ return stay();
324
598
  }
325
- catch (error) {
326
- state.wizardError = error.message;
599
+ if (this.editState.registry.hasAgent(text)) {
600
+ this.error = `Agent "${text}" already exists.`;
601
+ return stay();
327
602
  }
603
+ this.newAgent.name = text;
604
+ this.step = 'prompt';
605
+ this.inputBuffer = '';
606
+ this.error = null;
607
+ }
608
+ else if (this.step === 'prompt') {
609
+ this.newAgent.prompt = text;
610
+ this.step = 'description';
611
+ this.inputBuffer = '';
328
612
  }
329
613
  else {
330
- // Cancel - go back to list
331
- state.mode = 'list';
614
+ this.newAgent.description = text;
615
+ this.step = 'model';
616
+ this.selectedOption = 0;
332
617
  }
333
- break;
618
+ return stay();
619
+ }
620
+ if (isBackspace(data)) {
621
+ this.inputBuffer = this.inputBuffer.slice(0, -1);
622
+ this.error = null;
623
+ return stay();
624
+ }
625
+ if (char) {
626
+ this.inputBuffer += char;
627
+ this.error = null;
628
+ return stay();
629
+ }
630
+ return stay(false);
334
631
  }
335
- state.wizardError = null;
336
- return false;
337
- };
338
- // Wizard: go back to previous step (or exit wizard)
339
- const wizardPrevStep = () => {
340
- switch (state.wizardStep) {
632
+ // Selection steps: location, model, confirm
633
+ const maxOptions = this.step === 'model' ? 4 : 2;
634
+ if (isNavigateUp(data) && this.selectedOption > 0) {
635
+ this.selectedOption--;
636
+ return stay();
637
+ }
638
+ if (isNavigateDown(data) && this.selectedOption < maxOptions - 1) {
639
+ this.selectedOption++;
640
+ return stay();
641
+ }
642
+ if (isEnter(data)) {
643
+ return this.handleSelect();
644
+ }
645
+ return stay(false);
646
+ }
647
+ handleBack() {
648
+ switch (this.step) {
341
649
  case 'location':
342
- // Exit wizard, go back to list
343
- state.mode = 'list';
344
- break;
650
+ return popScreen();
345
651
  case 'name':
346
- state.wizardStep = 'location';
347
- state.wizardSelectedOption = state.wizardLocation === 'project' ? 0 : 1;
652
+ this.step = 'location';
653
+ this.selectedOption = this.newAgent.location === 'project' ? 0 : 1;
348
654
  break;
349
655
  case 'prompt':
350
- state.wizardStep = 'name';
351
- state.wizardInputBuffer = state.wizardName;
656
+ this.step = 'name';
657
+ this.inputBuffer = this.newAgent.name;
352
658
  break;
353
659
  case 'description':
354
- state.wizardStep = 'prompt';
355
- state.wizardInputBuffer = state.wizardPrompt;
660
+ this.step = 'prompt';
661
+ this.inputBuffer = this.newAgent.prompt;
356
662
  break;
357
663
  case 'model':
358
- state.wizardStep = 'description';
359
- state.wizardInputBuffer = state.wizardDescription;
664
+ this.step = 'description';
665
+ this.inputBuffer = this.newAgent.description;
360
666
  break;
361
667
  case 'confirm': {
362
- state.wizardStep = 'model';
668
+ this.step = 'model';
363
669
  const models = ['sonnet', 'opus', 'haiku', 'inherit'];
364
- state.wizardSelectedOption = models.indexOf(state.wizardModel);
670
+ this.selectedOption = models.indexOf(this.newAgent.model);
365
671
  break;
366
672
  }
367
673
  }
368
- state.wizardError = null;
369
- };
370
- return new Promise((resolve) => {
371
- const cleanup = () => {
372
- terminal.clearLinesAbove(lineCount);
373
- terminal.writeLine('');
374
- terminal.showCursor();
375
- if (!wasRawMode) {
376
- terminal.disableRawMode();
674
+ this.error = null;
675
+ return stay();
676
+ }
677
+ handleSelect() {
678
+ switch (this.step) {
679
+ case 'location':
680
+ this.newAgent.location = this.selectedOption === 0 ? 'project' : 'personal';
681
+ this.step = 'name';
682
+ this.inputBuffer = '';
683
+ break;
684
+ case 'model': {
685
+ const models = ['sonnet', 'opus', 'haiku', 'inherit'];
686
+ this.newAgent.model = models[this.selectedOption];
687
+ this.step = 'confirm';
688
+ this.selectedOption = 0;
689
+ break;
377
690
  }
378
- process.stdin.removeListener('data', onData);
379
- };
380
- const onData = (data) => {
381
- const isEscape = data.length === 1 && data[0] === 0x1b;
382
- const isTab = data.length === 1 && data[0] === 0x09;
383
- const isUpArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x41;
384
- const isDownArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x42;
385
- const isCtrlC = data.length === 1 && data[0] === 0x03;
386
- const isEnter = data.length === 1 && (data[0] === 0x0d || data[0] === 0x0a);
387
- const isBackspace = data.length === 1 && (data[0] === 0x7f || data[0] === 0x08);
388
- // ===== LIST MODE =====
389
- if (state.mode === 'list') {
390
- if (isEscape || isCtrlC) {
391
- cleanup();
392
- resolve();
393
- return;
691
+ case 'confirm':
692
+ if (this.selectedOption === 0) {
693
+ // Save
694
+ try {
695
+ this.editState.registry.saveAgent({
696
+ name: this.newAgent.name,
697
+ description: this.newAgent.description,
698
+ model: this.newAgent.model,
699
+ systemPrompt: this.newAgent.prompt,
700
+ }, this.newAgent.location);
701
+ this.editState.registry.load();
702
+ return popScreen();
703
+ }
704
+ catch (error) {
705
+ this.error = error.message;
706
+ return stay();
707
+ }
394
708
  }
395
- if (isTab) {
396
- state.currentTab = (state.currentTab + 1) % TABS.length;
397
- state.selectedIndex = 0;
398
- lineCount = render(state, lineCount);
399
- return;
709
+ else {
710
+ // Cancel
711
+ return popScreen();
400
712
  }
401
- if (isUpArrow && state.selectedIndex > 0) {
402
- state.selectedIndex--;
403
- lineCount = render(state, lineCount);
404
- return;
713
+ }
714
+ return stay();
715
+ }
716
+ }
717
+ // =============================================================================
718
+ // Agents Overlay
719
+ // =============================================================================
720
+ class AgentsOverlay extends TabbedListOverlay {
721
+ editState;
722
+ constructor() {
723
+ const registry = getAgentRegistry();
724
+ registry.load();
725
+ // Build items from registry
726
+ const items = AgentsOverlay.buildItems(registry);
727
+ const config = {
728
+ title: 'Agents',
729
+ tabs: TABS,
730
+ items,
731
+ pageSize: 12,
732
+ detailScreensFullScreen: false,
733
+ filterByTab: (item, tabId) => {
734
+ if (tabId === 'builtin')
735
+ return item.isBuiltin;
736
+ return !item.isBuiltin;
737
+ },
738
+ getSearchText: (item) => `${item.name} ${item.description}`,
739
+ renderItem: (item, isSelected, styles) => {
740
+ const prefix = isSelected ? styles.primary('❯ ') : ' ';
741
+ // Special "Create new agent" option
742
+ if (item.isCreateNew) {
743
+ return isSelected
744
+ ? `${prefix}${styles.primary('Create new agent')}`
745
+ : `${prefix}${styles.muted('Create new agent')}`;
405
746
  }
406
- if (isDownArrow && state.selectedIndex < getMaxIndex()) {
407
- state.selectedIndex++;
408
- lineCount = render(state, lineCount);
409
- return;
747
+ const name = `${item.name} · ${item.model}`;
748
+ const nameStyled = isSelected
749
+ ? styles.primary(name.padEnd(30))
750
+ : styles.secondary(name.padEnd(30));
751
+ if (item.isBuiltin) {
752
+ return `${prefix}${nameStyled}${styles.muted(item.description)}`;
410
753
  }
411
- if (isEnter) {
412
- if (TABS[state.currentTab].id === 'custom') {
413
- const customAgents = state.registry.getCustomAgents();
414
- if (state.selectedIndex === customAgents.length) {
415
- // "Create new agent" selected - enter wizard mode
416
- resetWizard();
417
- state.mode = 'wizard';
418
- lineCount = render(state, lineCount);
419
- }
420
- }
421
- return;
754
+ else {
755
+ return `${prefix}${nameStyled}${styles.muted(`(${item.location ?? 'unknown'})`)}`;
756
+ }
757
+ },
758
+ showCount: true,
759
+ emptyMessage: 'No agents found.',
760
+ noResultsMessage: 'No agents match the search.',
761
+ footerHints: (searchMode) => {
762
+ if (searchMode) {
763
+ return 'Type to search · Enter/Esc Exit search · Backspace Delete';
422
764
  }
765
+ return '/ Search · ↑↓/jk Navigate · Tab Switch tabs · Enter Details · q/Esc Close';
766
+ },
767
+ };
768
+ super(config);
769
+ this.editState = { registry };
770
+ }
771
+ static buildItems(registry) {
772
+ const items = [];
773
+ // Add built-in agents
774
+ for (const agent of registry.getBuiltinAgents()) {
775
+ items.push(AgentsOverlay.agentToItem(agent, true));
776
+ }
777
+ // Add custom agents
778
+ for (const agent of registry.getCustomAgents()) {
779
+ items.push(AgentsOverlay.agentToItem(agent, false));
780
+ }
781
+ // Add "Create new agent" option for custom tab
782
+ items.push({
783
+ name: '',
784
+ model: '',
785
+ description: '',
786
+ isBuiltin: false,
787
+ isCreateNew: true,
788
+ });
789
+ return items;
790
+ }
791
+ static agentToItem(agent, isBuiltin) {
792
+ return {
793
+ name: agent.name,
794
+ model: agent.model,
795
+ description: agent.description,
796
+ location: agent.location,
797
+ systemPrompt: agent.systemPrompt,
798
+ filePath: agent.filePath,
799
+ isBuiltin,
800
+ };
801
+ }
802
+ createDetailScreen(item) {
803
+ // "Create new agent" opens the wizard
804
+ if (item.isCreateNew) {
805
+ return new WizardScreen(this.editState, this.styles);
806
+ }
807
+ // All other agents open the detail view
808
+ // The onEdit callback pushes the edit screen when user presses 'e'
809
+ return new DetailScreen(item, this.styles, this.editState, (editScreen) => {
810
+ this.screenStack.push(editScreen);
811
+ this.update();
812
+ });
813
+ }
814
+ handleKey(data) {
815
+ // Only intercept when on the main list screen
816
+ if (this.screenStack.size() === 1) {
817
+ // Ctrl+C always closes
818
+ if (isCtrlC(data)) {
819
+ this.close(undefined);
423
820
  return;
424
821
  }
425
- // ===== WIZARD MODE =====
426
- // Mode is always 'wizard' at this point since we're in the else block
427
- {
428
- if (isCtrlC) {
429
- cleanup();
430
- resolve();
822
+ // Only handle custom keys when not in search mode
823
+ if (!this.state.searchMode) {
824
+ const char = extractPrintable(data);
825
+ // 'q' or Esc closes
826
+ if (char === 'q' || isEscape(data)) {
827
+ this.close(undefined);
431
828
  return;
432
829
  }
433
- if (isEscape) {
434
- wizardPrevStep();
435
- lineCount = render(state, lineCount);
436
- return;
437
- }
438
- // Input steps: name, prompt, description
439
- if (['name', 'prompt', 'description'].includes(state.wizardStep)) {
440
- if (isEnter) {
441
- if (state.wizardInputBuffer.trim()) {
442
- // Validate name
443
- if (state.wizardStep === 'name') {
444
- const name = state.wizardInputBuffer.trim();
445
- if (!/^[a-z][a-z0-9-]{1,49}$/.test(name)) {
446
- state.wizardError = 'Invalid name. Use lowercase letters, numbers, hyphens.';
447
- lineCount = render(state, lineCount);
448
- return;
449
- }
450
- if (registry.hasAgent(name)) {
451
- state.wizardError = `Agent "${name}" already exists.`;
452
- lineCount = render(state, lineCount);
453
- return;
454
- }
455
- }
456
- wizardNextStep();
457
- lineCount = render(state, lineCount);
458
- }
459
- return;
460
- }
461
- if (isBackspace) {
462
- state.wizardInputBuffer = state.wizardInputBuffer.slice(0, -1);
463
- state.wizardError = null;
464
- lineCount = render(state, lineCount);
465
- return;
466
- }
467
- // Regular character input
468
- const char = data.toString('utf-8');
469
- if (char.length === 1 && char.charCodeAt(0) >= 32) {
470
- state.wizardInputBuffer += char;
471
- state.wizardError = null;
472
- lineCount = render(state, lineCount);
473
- }
474
- return;
475
- }
476
- // Selection steps: location, model, confirm
477
- if (['location', 'model', 'confirm'].includes(state.wizardStep)) {
478
- let maxOptions = 2;
479
- if (state.wizardStep === 'model')
480
- maxOptions = 4;
481
- if (isUpArrow && state.wizardSelectedOption > 0) {
482
- state.wizardSelectedOption--;
483
- lineCount = render(state, lineCount);
484
- return;
485
- }
486
- if (isDownArrow && state.wizardSelectedOption < maxOptions - 1) {
487
- state.wizardSelectedOption++;
488
- lineCount = render(state, lineCount);
489
- return;
490
- }
491
- if (isEnter) {
492
- wizardNextStep();
493
- lineCount = render(state, lineCount);
494
- return;
495
- }
496
- }
497
830
  }
498
- };
499
- process.stdin.on('data', onData);
500
- });
831
+ }
832
+ // After wizard completes, rebuild items
833
+ const prevStackSize = this.screenStack.size();
834
+ super.handleKey(data);
835
+ // If we just popped from wizard, refresh items
836
+ if (prevStackSize > 1 && this.screenStack.size() === 1) {
837
+ this.refreshItems();
838
+ }
839
+ }
840
+ refreshItems() {
841
+ this.editState.registry.load();
842
+ const items = AgentsOverlay.buildItems(this.editState.registry);
843
+ this.state.items = items;
844
+ // Re-apply filters
845
+ const currentTabId = this.listConfig.tabs[this.state.currentTab]?.id ?? 'builtin';
846
+ let filtered = items.filter((item) => this.listConfig.filterByTab(item, currentTabId));
847
+ if (this.state.searchQuery) {
848
+ const query = this.state.searchQuery.toLowerCase();
849
+ filtered = filtered.filter((item) => this.listConfig.getSearchText(item).toLowerCase().includes(query));
850
+ }
851
+ this.state.filteredItems = filtered;
852
+ // Clamp selected index
853
+ if (this.state.selectedIndex >= filtered.length) {
854
+ this.state.selectedIndex = Math.max(0, filtered.length - 1);
855
+ }
856
+ }
857
+ }
858
+ // =============================================================================
859
+ // Export Function (Backward Compatible)
860
+ // =============================================================================
861
+ export async function showAgentsOverlay() {
862
+ return new AgentsOverlay().show();
501
863
  }