@compilr-dev/cli 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/README.md +30 -12
  2. package/dist/agent.d.ts +74 -1
  3. package/dist/agent.js +259 -76
  4. package/dist/anchors/index.d.ts +9 -0
  5. package/dist/anchors/index.js +9 -0
  6. package/dist/anchors/project-anchors.d.ts +79 -0
  7. package/dist/anchors/project-anchors.js +202 -0
  8. package/dist/commands/handler-types.d.ts +68 -0
  9. package/dist/commands/handler-types.js +8 -0
  10. package/dist/commands/handlers/agent-commands.d.ts +13 -0
  11. package/dist/commands/handlers/agent-commands.js +305 -0
  12. package/dist/commands/handlers/design-commands.d.ts +15 -0
  13. package/dist/commands/handlers/design-commands.js +334 -0
  14. package/dist/commands/handlers/index.d.ts +20 -0
  15. package/dist/commands/handlers/index.js +43 -0
  16. package/dist/commands/handlers/overlay-commands.d.ts +21 -0
  17. package/dist/commands/handlers/overlay-commands.js +287 -0
  18. package/dist/commands/handlers/project-commands.d.ts +11 -0
  19. package/dist/commands/handlers/project-commands.js +167 -0
  20. package/dist/commands/handlers/simple-commands.d.ts +19 -0
  21. package/dist/commands/handlers/simple-commands.js +144 -0
  22. package/dist/commands/index.d.ts +2 -1
  23. package/dist/commands/registry.d.ts +50 -0
  24. package/dist/commands/registry.js +75 -0
  25. package/dist/commands-v2/handlers/context.d.ts +13 -0
  26. package/dist/commands-v2/handlers/context.js +348 -0
  27. package/dist/commands-v2/handlers/core.d.ts +13 -0
  28. package/dist/commands-v2/handlers/core.js +165 -0
  29. package/dist/commands-v2/handlers/debug.d.ts +11 -0
  30. package/dist/commands-v2/handlers/debug.js +159 -0
  31. package/dist/commands-v2/handlers/index.d.ts +12 -0
  32. package/dist/commands-v2/handlers/index.js +24 -0
  33. package/dist/commands-v2/handlers/project.d.ts +22 -0
  34. package/dist/commands-v2/handlers/project.js +814 -0
  35. package/dist/commands-v2/handlers/settings.d.ts +15 -0
  36. package/dist/commands-v2/handlers/settings.js +235 -0
  37. package/dist/commands-v2/index.d.ts +13 -0
  38. package/dist/commands-v2/index.js +15 -0
  39. package/dist/commands-v2/registry.d.ts +37 -0
  40. package/dist/commands-v2/registry.js +80 -0
  41. package/dist/commands-v2/types.d.ts +75 -0
  42. package/dist/commands-v2/types.js +7 -0
  43. package/dist/commands.js +110 -7
  44. package/dist/index.js +288 -29
  45. package/dist/input-handlers/index.d.ts +7 -0
  46. package/dist/input-handlers/index.js +7 -0
  47. package/dist/input-handlers/memory-handler.d.ts +26 -0
  48. package/dist/input-handlers/memory-handler.js +68 -0
  49. package/dist/repl-helpers.d.ts +63 -0
  50. package/dist/repl-helpers.js +318 -0
  51. package/dist/repl-v2.d.ts +155 -0
  52. package/dist/repl-v2.js +774 -0
  53. package/dist/repl.d.ts +32 -4
  54. package/dist/repl.js +250 -977
  55. package/dist/settings/index.d.ts +23 -0
  56. package/dist/settings/index.js +48 -0
  57. package/dist/settings/paths.d.ts +110 -0
  58. package/dist/settings/paths.js +264 -0
  59. package/dist/templates/compilr-md.js +7 -4
  60. package/dist/templates/index.js +3 -4
  61. package/dist/themes/colors.js +3 -1
  62. package/dist/themes/registry.d.ts +5 -36
  63. package/dist/themes/registry.js +11 -95
  64. package/dist/themes/types.d.ts +3 -38
  65. package/dist/themes/types.js +2 -2
  66. package/dist/tools/anchor-tools.d.ts +31 -0
  67. package/dist/tools/anchor-tools.js +255 -0
  68. package/dist/tools/backlog-wrappers.d.ts +54 -0
  69. package/dist/tools/backlog-wrappers.js +338 -0
  70. package/dist/tools/backlog.js +1 -1
  71. package/dist/tools/db-tools.d.ts +65 -0
  72. package/dist/tools/db-tools.js +19 -0
  73. package/dist/tools/document-db.d.ts +43 -0
  74. package/dist/tools/document-db.js +220 -0
  75. package/dist/tools/project-db.d.ts +102 -0
  76. package/dist/tools/project-db.js +370 -0
  77. package/dist/tools/workitem-db.d.ts +103 -0
  78. package/dist/tools/workitem-db.js +549 -0
  79. package/dist/tools.js +13 -3
  80. package/dist/ui/agents-overlay-v2.d.ts +43 -0
  81. package/dist/ui/agents-overlay-v2.js +809 -0
  82. package/dist/ui/agents-overlay.d.ts +5 -5
  83. package/dist/ui/agents-overlay.js +782 -420
  84. package/dist/ui/anchors-overlay.d.ts +12 -0
  85. package/dist/ui/anchors-overlay.js +775 -0
  86. package/dist/ui/arch-type-overlay.d.ts +1 -6
  87. package/dist/ui/arch-type-overlay.js +175 -203
  88. package/dist/ui/ask-user-overlay-v2.d.ts +26 -0
  89. package/dist/ui/ask-user-overlay-v2.js +555 -0
  90. package/dist/ui/ask-user-overlay.d.ts +2 -2
  91. package/dist/ui/ask-user-overlay.js +443 -535
  92. package/dist/ui/ask-user-simple-overlay-v2.d.ts +25 -0
  93. package/dist/ui/ask-user-simple-overlay-v2.js +215 -0
  94. package/dist/ui/ask-user-simple-overlay.d.ts +2 -2
  95. package/dist/ui/ask-user-simple-overlay.js +182 -209
  96. package/dist/ui/backlog-overlay.d.ts +16 -1
  97. package/dist/ui/backlog-overlay.js +525 -659
  98. package/dist/ui/base/index.d.ts +26 -0
  99. package/dist/ui/base/index.js +33 -0
  100. package/dist/ui/base/inline-overlay-utils.d.ts +217 -0
  101. package/dist/ui/base/inline-overlay-utils.js +320 -0
  102. package/dist/ui/base/inline-overlay.d.ts +159 -0
  103. package/dist/ui/base/inline-overlay.js +257 -0
  104. package/dist/ui/base/key-utils.d.ts +15 -0
  105. package/dist/ui/base/key-utils.js +30 -0
  106. package/dist/ui/base/overlay-base-v2.d.ts +193 -0
  107. package/dist/ui/base/overlay-base-v2.js +246 -0
  108. package/dist/ui/base/overlay-base.d.ts +156 -0
  109. package/dist/ui/base/overlay-base.js +238 -0
  110. package/dist/ui/base/overlay-lifecycle.d.ts +65 -0
  111. package/dist/ui/base/overlay-lifecycle.js +159 -0
  112. package/dist/ui/base/overlay-types.d.ts +185 -0
  113. package/dist/ui/base/overlay-types.js +7 -0
  114. package/dist/ui/base/render-utils.d.ts +8 -0
  115. package/dist/ui/base/render-utils.js +11 -0
  116. package/dist/ui/base/screen-stack.d.ts +148 -0
  117. package/dist/ui/base/screen-stack.js +184 -0
  118. package/dist/ui/base/tabbed-list-overlay-v2.d.ts +103 -0
  119. package/dist/ui/base/tabbed-list-overlay-v2.js +317 -0
  120. package/dist/ui/base/tabbed-list-overlay.d.ts +153 -0
  121. package/dist/ui/base/tabbed-list-overlay.js +369 -0
  122. package/dist/ui/commands-overlay-v2.d.ts +33 -0
  123. package/dist/ui/commands-overlay-v2.js +441 -0
  124. package/dist/ui/commands-overlay.d.ts +7 -2
  125. package/dist/ui/commands-overlay.js +384 -355
  126. package/dist/ui/config-overlay.d.ts +5 -4
  127. package/dist/ui/config-overlay.js +243 -513
  128. package/dist/ui/conversation.d.ts +75 -4
  129. package/dist/ui/conversation.js +374 -161
  130. package/dist/ui/docs-overlay.d.ts +17 -0
  131. package/dist/ui/docs-overlay.js +303 -0
  132. package/dist/ui/ephemeral.d.ts +1 -1
  133. package/dist/ui/ephemeral.js +1 -1
  134. package/dist/ui/features/index.d.ts +34 -0
  135. package/dist/ui/features/index.js +34 -0
  136. package/dist/ui/features/input-feature.d.ts +85 -0
  137. package/dist/ui/features/input-feature.js +238 -0
  138. package/dist/ui/features/list-feature.d.ts +155 -0
  139. package/dist/ui/features/list-feature.js +244 -0
  140. package/dist/ui/features/pagination-feature.d.ts +154 -0
  141. package/dist/ui/features/pagination-feature.js +238 -0
  142. package/dist/ui/features/search-feature.d.ts +148 -0
  143. package/dist/ui/features/search-feature.js +185 -0
  144. package/dist/ui/features/tab-feature.d.ts +194 -0
  145. package/dist/ui/features/tab-feature.js +307 -0
  146. package/dist/ui/footer-v2.d.ts +222 -0
  147. package/dist/ui/footer-v2.js +1349 -0
  148. package/dist/ui/footer.d.ts +107 -0
  149. package/dist/ui/footer.js +359 -67
  150. package/dist/ui/guardrail-overlay.d.ts +29 -0
  151. package/dist/ui/guardrail-overlay.js +145 -0
  152. package/dist/ui/help-overlay-v2.d.ts +34 -0
  153. package/dist/ui/help-overlay-v2.js +309 -0
  154. package/dist/ui/help-overlay.d.ts +16 -0
  155. package/dist/ui/help-overlay.js +316 -0
  156. package/dist/ui/index.d.ts +1 -1
  157. package/dist/ui/index.js +1 -3
  158. package/dist/ui/init-overlay-v2.d.ts +34 -0
  159. package/dist/ui/init-overlay-v2.js +600 -0
  160. package/dist/ui/init-overlay.d.ts +12 -2
  161. package/dist/ui/init-overlay.js +349 -270
  162. package/dist/ui/input-prompt-v2.d.ts +1 -0
  163. package/dist/ui/input-prompt-v2.js +14 -6
  164. package/dist/ui/input-prompt.d.ts +116 -33
  165. package/dist/ui/input-prompt.js +536 -337
  166. package/dist/ui/iteration-limit-overlay-v2.d.ts +21 -0
  167. package/dist/ui/iteration-limit-overlay-v2.js +114 -0
  168. package/dist/ui/iteration-limit-overlay.d.ts +2 -2
  169. package/dist/ui/iteration-limit-overlay.js +92 -128
  170. package/dist/ui/keys-overlay-v2.d.ts +41 -0
  171. package/dist/ui/keys-overlay-v2.js +248 -0
  172. package/dist/ui/keys-overlay.d.ts +1 -0
  173. package/dist/ui/keys-overlay.js +203 -141
  174. package/dist/ui/line-utils.d.ts +88 -0
  175. package/dist/ui/line-utils.js +150 -0
  176. package/dist/ui/live-region.d.ts +161 -0
  177. package/dist/ui/live-region.js +387 -0
  178. package/dist/ui/mascot/expressions.d.ts +32 -0
  179. package/dist/ui/mascot/expressions.js +213 -0
  180. package/dist/ui/mascot/index.d.ts +8 -0
  181. package/dist/ui/mascot/index.js +8 -0
  182. package/dist/ui/mascot/renderer.d.ts +19 -0
  183. package/dist/ui/mascot/renderer.js +97 -0
  184. package/dist/ui/mascot-overlay-v2.d.ts +41 -0
  185. package/dist/ui/mascot-overlay-v2.js +138 -0
  186. package/dist/ui/mascot-overlay.d.ts +21 -0
  187. package/dist/ui/mascot-overlay.js +146 -0
  188. package/dist/ui/model-overlay-v2.d.ts +49 -0
  189. package/dist/ui/model-overlay-v2.js +118 -0
  190. package/dist/ui/model-overlay.d.ts +27 -0
  191. package/dist/ui/model-overlay.js +221 -0
  192. package/dist/ui/model-warning-overlay.js +3 -5
  193. package/dist/ui/new-overlay.d.ts +34 -0
  194. package/dist/ui/new-overlay.js +604 -0
  195. package/dist/ui/overlay/impl/agents-overlay-v2.d.ts +45 -0
  196. package/dist/ui/overlay/impl/agents-overlay-v2.js +825 -0
  197. package/dist/ui/overlay/impl/anchors-overlay-v2.d.ts +47 -0
  198. package/dist/ui/overlay/impl/anchors-overlay-v2.js +783 -0
  199. package/dist/ui/overlay/impl/arch-type-overlay-v2.d.ts +37 -0
  200. package/dist/ui/overlay/impl/arch-type-overlay-v2.js +240 -0
  201. package/dist/ui/overlay/impl/ask-user-overlay-v2.d.ts +72 -0
  202. package/dist/ui/overlay/impl/ask-user-overlay-v2.js +584 -0
  203. package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.d.ts +46 -0
  204. package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.js +204 -0
  205. package/dist/ui/overlay/impl/backlog-overlay-v2.d.ts +49 -0
  206. package/dist/ui/overlay/impl/backlog-overlay-v2.js +642 -0
  207. package/dist/ui/overlay/impl/commands-overlay-v2.d.ts +33 -0
  208. package/dist/ui/overlay/impl/commands-overlay-v2.js +441 -0
  209. package/dist/ui/overlay/impl/config-overlay-v2.d.ts +100 -0
  210. package/dist/ui/overlay/impl/config-overlay-v2.js +654 -0
  211. package/dist/ui/overlay/impl/dashboard-overlay-v2.d.ts +55 -0
  212. package/dist/ui/overlay/impl/dashboard-overlay-v2.js +359 -0
  213. package/dist/ui/overlay/impl/docs-overlay-v2.d.ts +45 -0
  214. package/dist/ui/overlay/impl/docs-overlay-v2.js +114 -0
  215. package/dist/ui/overlay/impl/document-detail-overlay-v2.d.ts +77 -0
  216. package/dist/ui/overlay/impl/document-detail-overlay-v2.js +1071 -0
  217. package/dist/ui/overlay/impl/guardrail-overlay-v2.d.ts +43 -0
  218. package/dist/ui/overlay/impl/guardrail-overlay-v2.js +114 -0
  219. package/dist/ui/overlay/impl/help-overlay-v2.d.ts +34 -0
  220. package/dist/ui/overlay/impl/help-overlay-v2.js +309 -0
  221. package/dist/ui/overlay/impl/init-overlay-v2.d.ts +77 -0
  222. package/dist/ui/overlay/impl/init-overlay-v2.js +593 -0
  223. package/dist/ui/overlay/impl/init-setup-overlay-v2.d.ts +25 -0
  224. package/dist/ui/overlay/impl/init-setup-overlay-v2.js +97 -0
  225. package/dist/ui/overlay/impl/iteration-limit-overlay-v2.d.ts +35 -0
  226. package/dist/ui/overlay/impl/iteration-limit-overlay-v2.js +105 -0
  227. package/dist/ui/overlay/impl/keys-overlay-v2.d.ts +41 -0
  228. package/dist/ui/overlay/impl/keys-overlay-v2.js +248 -0
  229. package/dist/ui/overlay/impl/mascot-overlay-v2.d.ts +41 -0
  230. package/dist/ui/overlay/impl/mascot-overlay-v2.js +138 -0
  231. package/dist/ui/overlay/impl/model-overlay-v2.d.ts +49 -0
  232. package/dist/ui/overlay/impl/model-overlay-v2.js +118 -0
  233. package/dist/ui/overlay/impl/model-warning-overlay-v2.d.ts +46 -0
  234. package/dist/ui/overlay/impl/model-warning-overlay-v2.js +132 -0
  235. package/dist/ui/overlay/impl/new-overlay-v2.d.ts +77 -0
  236. package/dist/ui/overlay/impl/new-overlay-v2.js +593 -0
  237. package/dist/ui/overlay/impl/permission-overlay-v2.d.ts +36 -0
  238. package/dist/ui/overlay/impl/permission-overlay-v2.js +380 -0
  239. package/dist/ui/overlay/impl/projects-overlay-v2.d.ts +36 -0
  240. package/dist/ui/overlay/impl/projects-overlay-v2.js +499 -0
  241. package/dist/ui/overlay/impl/theme-overlay-v2.d.ts +42 -0
  242. package/dist/ui/overlay/impl/theme-overlay-v2.js +135 -0
  243. package/dist/ui/overlay/impl/tools-overlay-v2.d.ts +47 -0
  244. package/dist/ui/overlay/impl/tools-overlay-v2.js +218 -0
  245. package/dist/ui/overlay/impl/tutorial-overlay-v2.d.ts +31 -0
  246. package/dist/ui/overlay/impl/tutorial-overlay-v2.js +1035 -0
  247. package/dist/ui/overlay/impl/workflow-overlay-v2.d.ts +80 -0
  248. package/dist/ui/overlay/impl/workflow-overlay-v2.js +637 -0
  249. package/dist/ui/overlay/index.d.ts +33 -0
  250. package/dist/ui/overlay/index.js +35 -0
  251. package/dist/ui/overlay/key-utils.d.ts +6 -0
  252. package/dist/ui/overlay/key-utils.js +6 -0
  253. package/dist/ui/overlay/overlay-types.d.ts +128 -0
  254. package/dist/ui/overlay/overlay-types.js +22 -0
  255. package/dist/ui/overlay/types.d.ts +135 -0
  256. package/dist/ui/overlay/types.js +22 -0
  257. package/dist/ui/overlays/help-overlay-v2.d.ts +28 -0
  258. package/dist/ui/overlays/help-overlay-v2.js +198 -0
  259. package/dist/ui/overlays/index.d.ts +11 -0
  260. package/dist/ui/overlays/index.js +11 -0
  261. package/dist/ui/overlays.d.ts +0 -4
  262. package/dist/ui/overlays.js +0 -444
  263. package/dist/ui/permission-overlay-v2.d.ts +36 -0
  264. package/dist/ui/permission-overlay-v2.js +380 -0
  265. package/dist/ui/permission-overlay.d.ts +1 -1
  266. package/dist/ui/permission-overlay.js +186 -298
  267. package/dist/ui/projects-overlay.d.ts +19 -0
  268. package/dist/ui/projects-overlay.js +484 -0
  269. package/dist/ui/providers/types.d.ts +178 -0
  270. package/dist/ui/providers/types.js +9 -0
  271. package/dist/ui/render-modes.d.ts +36 -0
  272. package/dist/ui/render-modes.js +44 -0
  273. package/dist/ui/startup-menu.d.ts +36 -0
  274. package/dist/ui/startup-menu.js +236 -0
  275. package/dist/ui/subagent-renderer.d.ts +117 -0
  276. package/dist/ui/subagent-renderer.js +334 -0
  277. package/dist/ui/terminal-codes.d.ts +94 -0
  278. package/dist/ui/terminal-codes.js +124 -0
  279. package/dist/ui/terminal-renderer.d.ts +221 -0
  280. package/dist/ui/terminal-renderer.js +751 -0
  281. package/dist/ui/terminal-ui.d.ts +463 -0
  282. package/dist/ui/terminal-ui.js +2296 -0
  283. package/dist/ui/terminal.d.ts +20 -0
  284. package/dist/ui/terminal.js +72 -0
  285. package/dist/ui/theme-overlay-v2.d.ts +42 -0
  286. package/dist/ui/theme-overlay-v2.js +135 -0
  287. package/dist/ui/theme-overlay.d.ts +24 -0
  288. package/dist/ui/theme-overlay.js +127 -0
  289. package/dist/ui/todo-zone.js +53 -25
  290. package/dist/ui/tool-formatters.d.ts +16 -0
  291. package/dist/ui/tool-formatters.js +516 -0
  292. package/dist/ui/tools-overlay-v2.d.ts +47 -0
  293. package/dist/ui/tools-overlay-v2.js +218 -0
  294. package/dist/ui/tools-overlay.d.ts +10 -2
  295. package/dist/ui/tools-overlay.js +172 -220
  296. package/dist/ui/tutorial-overlay-v2.d.ts +31 -0
  297. package/dist/ui/tutorial-overlay-v2.js +1035 -0
  298. package/dist/ui/tutorial-overlay.d.ts +1 -0
  299. package/dist/ui/tutorial-overlay.js +400 -302
  300. package/dist/ui/workflow-overlay.d.ts +22 -0
  301. package/dist/ui/workflow-overlay.js +636 -0
  302. package/dist/utils/debug-log.d.ts +28 -0
  303. package/dist/utils/debug-log.js +57 -0
  304. package/dist/utils/model-tiers.js +1 -1
  305. package/dist/utils/path-safety.d.ts +56 -0
  306. package/dist/utils/path-safety.js +239 -0
  307. package/dist/workflow/guided-mode-injector.d.ts +42 -0
  308. package/dist/workflow/guided-mode-injector.js +191 -0
  309. package/dist/workflow/index.d.ts +8 -0
  310. package/dist/workflow/index.js +8 -0
  311. package/dist/workflow/step-criteria.d.ts +62 -0
  312. package/dist/workflow/step-criteria.js +150 -0
  313. package/dist/workflow/step-tracker.d.ts +92 -0
  314. package/dist/workflow/step-tracker.js +141 -0
  315. package/package.json +12 -5
@@ -0,0 +1,484 @@
1
+ /**
2
+ * Projects Overlay
3
+ *
4
+ * Interactive overlay for viewing and managing projects.
5
+ * Uses TabbedListOverlay for consistent tabbed list behavior.
6
+ *
7
+ * Features:
8
+ * - Tabbed filtering by status (All, Active, Paused, Completed, Archived)
9
+ * - Paginated list with search
10
+ * - Project detail preview
11
+ * - Archive/Restore workflow
12
+ * - Delete workflow with path validation
13
+ * - Keyboard navigation (vim-style + arrows)
14
+ */
15
+ import * as fs from 'fs';
16
+ import * as terminal from './terminal.js';
17
+ import { TabbedListOverlay, BaseScreen, stay, popScreen, closeOverlay, isEnter, isCtrlC, isEscape, isBackspace, isClose, renderBorder, } from './base/index.js';
18
+ import { projectRepository, workItemRepository } from '../db/repositories/index.js';
19
+ import { clearActiveProject, getActiveProject } from '../tools/project-db.js';
20
+ import { validateDeletePath } from '../utils/path-safety.js';
21
+ import { getAllowedDeletePaths } from '../settings/paths.js';
22
+ import { clearProjectAnchors } from '../anchors/index.js';
23
+ // =============================================================================
24
+ // Constants
25
+ // =============================================================================
26
+ const PAGE_SIZE = 10;
27
+ const TABS = [
28
+ { id: 'all', label: 'All' },
29
+ { id: 'active', label: 'Active' },
30
+ { id: 'paused', label: 'Paused' },
31
+ { id: 'completed', label: 'Complete' },
32
+ { id: 'archived', label: 'Archived' },
33
+ ];
34
+ const STATUS_ICONS = {
35
+ active: '●',
36
+ paused: '◐',
37
+ completed: '✓',
38
+ archived: '◌',
39
+ };
40
+ const STATUS_LABELS = {
41
+ active: 'Active',
42
+ paused: 'Paused',
43
+ completed: 'Complete',
44
+ archived: 'Archived',
45
+ };
46
+ // =============================================================================
47
+ // Helper Functions
48
+ // =============================================================================
49
+ function truncate(str, maxLen) {
50
+ if (str.length <= maxLen)
51
+ return str;
52
+ return str.slice(0, maxLen - 3) + '...';
53
+ }
54
+ function renderProgressBar(percent, width, s) {
55
+ const filled = Math.round((percent / 100) * width);
56
+ const empty = width - filled;
57
+ return s.primary('█'.repeat(filled)) + s.muted('░'.repeat(empty));
58
+ }
59
+ // =============================================================================
60
+ // Archive Confirm Screen
61
+ // =============================================================================
62
+ class ArchiveConfirmScreen extends BaseScreen {
63
+ confirmState;
64
+ styles;
65
+ onComplete;
66
+ constructor(confirmState, styles, onComplete) {
67
+ super();
68
+ this.confirmState = confirmState;
69
+ this.styles = styles;
70
+ this.onComplete = onComplete;
71
+ }
72
+ render() {
73
+ const s = this.styles;
74
+ const cols = terminal.getTerminalWidth();
75
+ const border = renderBorder(cols, s);
76
+ const project = this.confirmState.targetProject;
77
+ const lines = [];
78
+ if (!project) {
79
+ lines.push(border);
80
+ lines.push(s.error(' No project selected'));
81
+ lines.push(border);
82
+ return lines;
83
+ }
84
+ const isArchived = project.status === 'archived';
85
+ const action = isArchived ? 'RESTORE' : 'ARCHIVE';
86
+ lines.push(border);
87
+ lines.push(` ${s.primaryBold(action + ' PROJECT')}`);
88
+ lines.push('');
89
+ if (isArchived) {
90
+ lines.push(` ${s.foreground('Restore this project?')}`);
91
+ lines.push('');
92
+ lines.push(s.muted(' Project: ') + project.displayName);
93
+ lines.push('');
94
+ lines.push(s.foreground(' The project will be marked as "active" and visible again.'));
95
+ }
96
+ else {
97
+ lines.push(` ${s.warning('Archive this project?')}`);
98
+ lines.push('');
99
+ lines.push(s.muted(' Project: ') + project.displayName);
100
+ lines.push('');
101
+ lines.push(s.foreground(' The project will be hidden from the active view.'));
102
+ lines.push(s.foreground(' You can restore it later from the Archived tab.'));
103
+ }
104
+ lines.push('');
105
+ lines.push(border);
106
+ lines.push(s.muted(' Enter Confirm · Esc Cancel'));
107
+ lines.push(border);
108
+ return lines;
109
+ }
110
+ handleKey(data) {
111
+ if (isCtrlC(data)) {
112
+ return closeOverlay({ action: 'cancel' });
113
+ }
114
+ if (isEscape(data)) {
115
+ this.confirmState.targetProject = null;
116
+ return popScreen();
117
+ }
118
+ if (isEnter(data)) {
119
+ const project = this.confirmState.targetProject;
120
+ if (!project)
121
+ return popScreen();
122
+ const isArchived = project.status === 'archived';
123
+ const newStatus = isArchived ? 'active' : 'archived';
124
+ projectRepository.update(project.id, { status: newStatus });
125
+ // Clear as active if archiving the active project
126
+ if (!isArchived && getActiveProject()?.id === project.id) {
127
+ clearActiveProject();
128
+ }
129
+ this.onComplete();
130
+ this.confirmState.targetProject = null;
131
+ return popScreen();
132
+ }
133
+ return stay(false);
134
+ }
135
+ }
136
+ // =============================================================================
137
+ // Delete Confirm Screen
138
+ // =============================================================================
139
+ class DeleteConfirmScreen extends BaseScreen {
140
+ confirmState;
141
+ styles;
142
+ onComplete;
143
+ constructor(confirmState, styles, onComplete) {
144
+ super();
145
+ this.confirmState = confirmState;
146
+ this.styles = styles;
147
+ this.onComplete = onComplete;
148
+ }
149
+ render() {
150
+ const s = this.styles;
151
+ const cols = terminal.getTerminalWidth();
152
+ const border = renderBorder(cols, s);
153
+ const project = this.confirmState.targetProject;
154
+ const validation = this.confirmState.deleteValidation;
155
+ const lines = [];
156
+ if (!project) {
157
+ lines.push(border);
158
+ lines.push(s.error(' No project selected'));
159
+ lines.push(border);
160
+ return lines;
161
+ }
162
+ lines.push(border);
163
+ lines.push(` ${s.error('DELETE PROJECT')}`);
164
+ lines.push('');
165
+ // If validation failed, show error state
166
+ if (validation && !validation.valid) {
167
+ lines.push(s.error(' CANNOT DELETE: Path validation failed'));
168
+ lines.push('');
169
+ lines.push(s.muted(' Project: ') + project.displayName);
170
+ lines.push(s.muted(' Path: ') + project.path);
171
+ lines.push('');
172
+ lines.push(s.error(` Error: ${validation.reason || 'Unknown error'}`));
173
+ lines.push('');
174
+ lines.push(s.foreground(' Allowed directories:'));
175
+ for (const allowed of getAllowedDeletePaths()) {
176
+ lines.push(s.muted(` • ${allowed}`));
177
+ }
178
+ lines.push('');
179
+ lines.push(s.foreground(' Options:'));
180
+ lines.push(s.muted(' 1. Move the project to an allowed directory'));
181
+ lines.push(s.muted(' 2. Add path to allowedPaths in /config'));
182
+ lines.push(s.muted(' 3. Delete files manually and remove from database only'));
183
+ lines.push('');
184
+ lines.push(border);
185
+ lines.push(s.muted(' Esc Back'));
186
+ lines.push(border);
187
+ return lines;
188
+ }
189
+ lines.push(s.warning(' WARNING: This action cannot be undone!'));
190
+ lines.push('');
191
+ lines.push(s.muted(' Project: ') + project.displayName);
192
+ lines.push(s.muted(' Name: ') + project.name);
193
+ lines.push(s.muted(' Path: ') + project.path);
194
+ lines.push('');
195
+ // Show validation status
196
+ if (validation && validation.valid) {
197
+ if (fs.existsSync(project.path + '/.compilr')) {
198
+ lines.push(s.success(' ✓ Path is within allowed directory'));
199
+ lines.push(s.success(' ✓ Project has .compilr marker'));
200
+ }
201
+ else if (fs.existsSync(project.path + '/.git')) {
202
+ lines.push(s.success(' ✓ Path is within allowed directory'));
203
+ lines.push(s.success(' ✓ Project has .git repository'));
204
+ }
205
+ else if (fs.existsSync(project.path + '/package.json')) {
206
+ lines.push(s.success(' ✓ Path is within allowed directory'));
207
+ lines.push(s.success(' ✓ Project has package.json'));
208
+ }
209
+ else {
210
+ lines.push(s.success(' ✓ Path is within allowed directory'));
211
+ }
212
+ if (validation.warnings && validation.warnings.length > 0) {
213
+ lines.push('');
214
+ for (const warning of validation.warnings) {
215
+ lines.push(s.warning(` ⚠ ${warning}`));
216
+ }
217
+ }
218
+ lines.push('');
219
+ }
220
+ lines.push(s.foreground(' This will permanently delete:'));
221
+ lines.push(s.muted(' • Database record (project, work items, documents)'));
222
+ lines.push(s.muted(' • Project files on disk'));
223
+ if (project.status !== 'archived') {
224
+ lines.push('');
225
+ lines.push(s.warning(' Note: Project will be archived first, then deleted.'));
226
+ }
227
+ lines.push('');
228
+ lines.push(s.muted(' ' + '─'.repeat(cols - 4)));
229
+ lines.push('');
230
+ lines.push(s.foreground(' Type the project name to confirm deletion:'));
231
+ lines.push('');
232
+ lines.push(` > ${this.confirmState.deleteConfirmInput}█`);
233
+ if (this.confirmState.deleteError) {
234
+ lines.push('');
235
+ lines.push(s.error(` ${this.confirmState.deleteError}`));
236
+ }
237
+ lines.push('');
238
+ lines.push(border);
239
+ lines.push(s.muted(' Enter Confirm · Esc Cancel'));
240
+ lines.push(border);
241
+ return lines;
242
+ }
243
+ handleKey(data) {
244
+ const char = data.length === 1 && data[0] >= 0x20 && data[0] < 0x7f
245
+ ? String.fromCharCode(data[0])
246
+ : null;
247
+ if (isCtrlC(data)) {
248
+ return closeOverlay({ action: 'cancel' });
249
+ }
250
+ if (isEscape(data)) {
251
+ this.confirmState.targetProject = null;
252
+ this.confirmState.deleteConfirmInput = '';
253
+ this.confirmState.deleteError = null;
254
+ this.confirmState.deleteValidation = null;
255
+ return popScreen();
256
+ }
257
+ if (isBackspace(data)) {
258
+ this.confirmState.deleteConfirmInput = this.confirmState.deleteConfirmInput.slice(0, -1);
259
+ this.confirmState.deleteError = null;
260
+ return stay();
261
+ }
262
+ if (char) {
263
+ this.confirmState.deleteConfirmInput += char;
264
+ this.confirmState.deleteError = null;
265
+ return stay();
266
+ }
267
+ if (isEnter(data)) {
268
+ const project = this.confirmState.targetProject;
269
+ if (!project)
270
+ return popScreen();
271
+ // If validation failed, don't allow Enter to proceed
272
+ if (this.confirmState.deleteValidation && !this.confirmState.deleteValidation.valid) {
273
+ return stay(false);
274
+ }
275
+ if (this.confirmState.deleteConfirmInput !== project.name) {
276
+ this.confirmState.deleteError = `Type "${project.name}" exactly to confirm`;
277
+ return stay();
278
+ }
279
+ // Archive first if not already archived
280
+ if (project.status !== 'archived') {
281
+ projectRepository.update(project.id, { status: 'archived' });
282
+ }
283
+ // Delete from database
284
+ projectRepository.delete(project.id);
285
+ // Clear project-specific anchors
286
+ clearProjectAnchors(String(project.id));
287
+ // Clear as active project if needed
288
+ if (getActiveProject()?.id === project.id) {
289
+ clearActiveProject();
290
+ }
291
+ // Delete filesystem folder (only if validation passed and path exists)
292
+ const projectPath = project.path;
293
+ const validation = this.confirmState.deleteValidation;
294
+ if (projectPath && validation?.valid && fs.existsSync(projectPath)) {
295
+ try {
296
+ fs.rmSync(projectPath, { recursive: true, force: true });
297
+ }
298
+ catch {
299
+ // Ignore delete errors - DB is already cleaned up
300
+ }
301
+ }
302
+ this.onComplete();
303
+ this.confirmState.targetProject = null;
304
+ this.confirmState.deleteConfirmInput = '';
305
+ this.confirmState.deleteError = null;
306
+ this.confirmState.deleteValidation = null;
307
+ return popScreen();
308
+ }
309
+ return stay(false);
310
+ }
311
+ }
312
+ // =============================================================================
313
+ // Projects Overlay
314
+ // =============================================================================
315
+ class ProjectsOverlay extends TabbedListOverlay {
316
+ /** State for confirmation screens */
317
+ confirmState = {
318
+ targetProject: null,
319
+ deleteConfirmInput: '',
320
+ deleteError: null,
321
+ deleteValidation: null,
322
+ };
323
+ constructor() {
324
+ // Load projects from database
325
+ const { projects } = projectRepository.list({ status: 'all' });
326
+ const config = {
327
+ title: 'Projects',
328
+ tabs: TABS,
329
+ items: projects,
330
+ pageSize: PAGE_SIZE,
331
+ filterByTab: (project, tabId) => {
332
+ if (tabId === 'all') {
333
+ // 'all' shows everything except archived
334
+ return project.status !== 'archived';
335
+ }
336
+ return project.status === tabId;
337
+ },
338
+ getSearchText: (project) => `${project.displayName} ${project.name} ${project.path} ${project.type}`,
339
+ renderItem: (project, isSelected, s) => {
340
+ const cols = terminal.getTerminalWidth();
341
+ const nameW = Math.min(24, Math.floor(cols * 0.25));
342
+ const typeW = 10;
343
+ const statusW = 12;
344
+ const pathW = Math.max(20, cols - nameW - typeW - statusW - 12);
345
+ const cursor = isSelected ? s.primary('❯ ') : ' ';
346
+ const statusIcon = STATUS_ICONS[project.status];
347
+ const statusLabel = `${statusIcon} ${STATUS_LABELS[project.status]}`;
348
+ const row = [
349
+ truncate(project.displayName, nameW - 1).padEnd(nameW),
350
+ project.type.padEnd(typeW),
351
+ statusLabel.padEnd(statusW),
352
+ truncate(project.path, pathW - 1),
353
+ ].join('');
354
+ if (isSelected) {
355
+ return `${cursor}${s.primary(row)}`;
356
+ }
357
+ return `${cursor}${row}`;
358
+ },
359
+ showCount: true,
360
+ emptyMessage: 'No projects found. Use /new to create one.',
361
+ noResultsMessage: 'No projects match the search.',
362
+ footerHints: (searchMode) => {
363
+ if (searchMode) {
364
+ return 'Type to filter · ↑↓/jk Navigate · Enter Open · Esc Exit search';
365
+ }
366
+ return '↑↓/jk Navigate · ←→/hl Tabs · / Search · Enter Open · x Archive · d Delete · q/Esc Close';
367
+ },
368
+ renderSelectedPreview: (project, s) => {
369
+ const lines = [];
370
+ lines.push(s.muted(' ' + '─'.repeat(60)));
371
+ // Last activity
372
+ const lastActivity = project.lastActivityAt
373
+ ? new Date(project.lastActivityAt).toLocaleDateString('en-US', {
374
+ year: 'numeric',
375
+ month: 'short',
376
+ day: 'numeric',
377
+ hour: '2-digit',
378
+ minute: '2-digit',
379
+ })
380
+ : 'Never';
381
+ lines.push(` ${s.muted('Last Activity:')} ${lastActivity}`);
382
+ // Work items progress
383
+ const { items: workItems, total } = workItemRepository.query({ project_id: project.id });
384
+ const completed = workItems.filter(w => w.status === 'completed').length;
385
+ if (total > 0) {
386
+ const percent = Math.round((completed / total) * 100);
387
+ const progressBar = renderProgressBar(percent, 20, s);
388
+ lines.push(` ${s.muted('Work Items:')} ${progressBar} ${String(completed)}/${String(total)} (${String(percent)}%)`);
389
+ }
390
+ else {
391
+ lines.push(` ${s.muted('Work Items:')} ${s.muted('None')}`);
392
+ }
393
+ return lines;
394
+ },
395
+ };
396
+ super(config);
397
+ }
398
+ /**
399
+ * No detail screen - we handle Enter to close with result instead
400
+ */
401
+ createDetailScreen(_project) {
402
+ return null;
403
+ }
404
+ /**
405
+ * Refresh projects from database after archive/delete
406
+ */
407
+ refreshProjects() {
408
+ const { projects } = projectRepository.list({ status: 'all' });
409
+ this.state.items = projects;
410
+ // Re-apply tab filter
411
+ const tabId = TABS[this.state.currentTab]?.id ?? 'all';
412
+ let filtered = projects.filter((p) => {
413
+ if (tabId === 'all')
414
+ return p.status !== 'archived';
415
+ return p.status === tabId;
416
+ });
417
+ // Re-apply search filter
418
+ if (this.state.searchQuery) {
419
+ const query = this.state.searchQuery.toLowerCase();
420
+ filtered = filtered.filter((p) => p.displayName.toLowerCase().includes(query) ||
421
+ p.name.toLowerCase().includes(query) ||
422
+ p.path.toLowerCase().includes(query));
423
+ }
424
+ this.state.filteredItems = filtered;
425
+ this.state.selectedIndex = Math.min(this.state.selectedIndex, Math.max(0, filtered.length - 1));
426
+ }
427
+ /**
428
+ * Override to intercept custom keys before TabbedListOverlay handles them
429
+ */
430
+ handleKey(data) {
431
+ const char = data.length === 1 && data[0] >= 0x20 && data[0] < 0x7f
432
+ ? String.fromCharCode(data[0])
433
+ : null;
434
+ // Only intercept when on the main list screen (screen stack size = 1)
435
+ if (this.screenStack.size() === 1) {
436
+ // Ctrl+C always closes with cancel result
437
+ if (isCtrlC(data)) {
438
+ this.close({ action: 'cancel' });
439
+ return;
440
+ }
441
+ // Don't intercept if in search mode
442
+ if (!this.state.searchMode) {
443
+ // q/Esc closes with cancel result
444
+ if (isClose(data)) {
445
+ this.close({ action: 'cancel' });
446
+ return;
447
+ }
448
+ // Enter opens workflow (instead of detail view)
449
+ if (isEnter(data) && this.state.filteredItems.length > 0) {
450
+ const selected = this.state.filteredItems[this.state.selectedIndex];
451
+ this.close({ action: 'open-workflow', projectId: selected.id });
452
+ return;
453
+ }
454
+ // 'x' archive/restore
455
+ if ((char === 'x' || char === 'X') && this.state.filteredItems.length > 0) {
456
+ const project = this.state.filteredItems[this.state.selectedIndex];
457
+ this.confirmState.targetProject = project;
458
+ this.screenStack.push(new ArchiveConfirmScreen(this.confirmState, this.styles, () => { this.refreshProjects(); }));
459
+ this.update();
460
+ return;
461
+ }
462
+ // 'd' delete
463
+ if ((char === 'd' || char === 'D') && this.state.filteredItems.length > 0) {
464
+ const project = this.state.filteredItems[this.state.selectedIndex];
465
+ this.confirmState.targetProject = project;
466
+ this.confirmState.deleteConfirmInput = '';
467
+ this.confirmState.deleteError = null;
468
+ this.confirmState.deleteValidation = validateDeletePath(project.path);
469
+ this.screenStack.push(new DeleteConfirmScreen(this.confirmState, this.styles, () => { this.refreshProjects(); }));
470
+ this.update();
471
+ return;
472
+ }
473
+ }
474
+ }
475
+ // Let TabbedListOverlay handle everything else
476
+ super.handleKey(data);
477
+ }
478
+ }
479
+ // =============================================================================
480
+ // Export Function (Backward Compatible)
481
+ // =============================================================================
482
+ export async function showProjectsOverlay() {
483
+ return new ProjectsOverlay().show();
484
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Content Provider Interfaces
3
+ *
4
+ * Components implement these interfaces to provide content for rendering.
5
+ * The TerminalRenderer collects lines from providers and writes to terminal.
6
+ *
7
+ * Key principle: Providers return string[], never write to terminal directly.
8
+ */
9
+ /**
10
+ * Base interface for all content providers.
11
+ * Returns lines to render. Empty array = nothing to show.
12
+ */
13
+ export interface ContentProvider {
14
+ /** Returns lines to render. Empty array = nothing to show */
15
+ getLines(): string[];
16
+ /** Optional: get cursor position within this component's content */
17
+ getCursorPosition?(): CursorPosition | null;
18
+ /** Optional: does this provider need keyboard input? */
19
+ hasKeyboardFocus?(): boolean;
20
+ }
21
+ /**
22
+ * Cursor position within rendered content
23
+ */
24
+ export interface CursorPosition {
25
+ /** Row within the provider's content (0-indexed) */
26
+ row: number;
27
+ /** Column within the row (0-indexed) */
28
+ col: number;
29
+ }
30
+ /**
31
+ * Menu options available in startup menu
32
+ */
33
+ export type MenuOption = 'init' | 'projects' | 'continue' | 'chat' | 'config' | 'help' | 'exit';
34
+ /**
35
+ * Actions from menu keyboard handling
36
+ */
37
+ export type MenuAction = 'up' | 'down' | 'select' | 'exit' | null;
38
+ /**
39
+ * Provider for the startup menu (MENU mode)
40
+ */
41
+ export interface MenuProvider extends ContentProvider {
42
+ /** Returns full menu screen content */
43
+ getLines(): string[];
44
+ /** Handle keyboard input, return action taken */
45
+ handleKey(key: Buffer): MenuAction;
46
+ /** Get currently selected menu option */
47
+ getSelectedOption(): MenuOption;
48
+ /** Get highlighted index (for rendering) */
49
+ getHighlightedIndex(): number;
50
+ }
51
+ /**
52
+ * Provider for subagent status display
53
+ */
54
+ export interface SubagentProvider extends ContentProvider {
55
+ /** Returns subagent status lines (empty if no active subagents) */
56
+ getLines(): string[];
57
+ /** Check if any subagent is currently active */
58
+ hasActiveSubagent(): boolean;
59
+ }
60
+ /**
61
+ * Provider for spinner/progress display
62
+ */
63
+ export interface SpinnerProvider extends ContentProvider {
64
+ /** Returns spinner line when agent is running (empty when idle) */
65
+ getLines(): string[];
66
+ /** Check if agent is currently running */
67
+ isAgentRunning(): boolean;
68
+ }
69
+ /**
70
+ * Provider for todo list display
71
+ */
72
+ export interface TodoProvider extends ContentProvider {
73
+ /** Returns todo list (empty when agent running or no todos) */
74
+ getLines(): string[];
75
+ /** Get count of todos */
76
+ getTodoCount(): number;
77
+ }
78
+ /**
79
+ * Provider for queued messages display
80
+ */
81
+ export interface QueueProvider extends ContentProvider {
82
+ /** Returns queued messages (empty if none) */
83
+ getLines(): string[];
84
+ /** Get count of queued messages */
85
+ getQueueCount(): number;
86
+ }
87
+ /**
88
+ * Actions from input keyboard handling
89
+ */
90
+ export type InputAction = {
91
+ type: 'submit';
92
+ value: string;
93
+ } | {
94
+ type: 'command';
95
+ value: string;
96
+ } | {
97
+ type: 'cancel';
98
+ } | {
99
+ type: 'change';
100
+ } | {
101
+ type: 'escape';
102
+ } | null;
103
+ /**
104
+ * Provider for input prompt
105
+ */
106
+ export interface InputProvider extends ContentProvider {
107
+ /** Returns separator + input prompt lines (may be multi-line) */
108
+ getLines(): string[];
109
+ /** Get cursor position within input content */
110
+ getCursorPosition(): CursorPosition;
111
+ /** Handle keyboard input, return action taken */
112
+ handleKey(key: Buffer): InputAction;
113
+ /** Get current input value */
114
+ getValue(): string;
115
+ /** Set input value programmatically */
116
+ setValue(value: string): void;
117
+ /** Clear input */
118
+ clear(): void;
119
+ /** Is input in queue mode (typing queued while agent runs) */
120
+ isQueueMode(): boolean;
121
+ }
122
+ /**
123
+ * Provider for mode indicator line
124
+ */
125
+ export interface ModeProvider extends ContentProvider {
126
+ /** Returns mode indicator line */
127
+ getLines(): string[];
128
+ /** Get current mode name */
129
+ getModeName(): string;
130
+ }
131
+ /**
132
+ * Actions from overlay keyboard handling
133
+ */
134
+ export type OverlayAction = {
135
+ type: 'close';
136
+ } | {
137
+ type: 'close';
138
+ result: unknown;
139
+ } | {
140
+ type: 'navigate';
141
+ direction: 'up' | 'down' | 'left' | 'right';
142
+ } | {
143
+ type: 'select';
144
+ } | {
145
+ type: 'scroll';
146
+ direction: 'up' | 'down';
147
+ } | {
148
+ type: 'tab';
149
+ direction: 'next' | 'prev';
150
+ } | null;
151
+ /**
152
+ * Provider for full-screen overlays
153
+ */
154
+ export interface OverlayProvider extends ContentProvider {
155
+ /** Returns full overlay content */
156
+ getLines(): string[];
157
+ /** Check if overlay is active */
158
+ isActive(): boolean;
159
+ /** Handle keyboard input, return action taken */
160
+ handleKey(key: Buffer): OverlayAction;
161
+ /** Get cursor position if overlay needs visible cursor */
162
+ getCursorPosition(): CursorPosition | null;
163
+ /** Get overlay title (for header) */
164
+ getTitle(): string;
165
+ }
166
+ /**
167
+ * All providers needed by TerminalRenderer
168
+ */
169
+ export interface ProviderRegistry {
170
+ menu?: MenuProvider;
171
+ subagent?: SubagentProvider;
172
+ spinner?: SpinnerProvider;
173
+ todo?: TodoProvider;
174
+ queue?: QueueProvider;
175
+ input?: InputProvider;
176
+ mode?: ModeProvider;
177
+ overlay?: OverlayProvider;
178
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Content Provider Interfaces
3
+ *
4
+ * Components implement these interfaces to provide content for rendering.
5
+ * The TerminalRenderer collects lines from providers and writes to terminal.
6
+ *
7
+ * Key principle: Providers return string[], never write to terminal directly.
8
+ */
9
+ export {};