@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
@@ -1,37 +1,34 @@
1
1
  /**
2
2
  * Config Overlay
3
3
  *
4
- * Claude Code-style settings overlay with 3 tabs:
4
+ * Claude Code-style settings overlay with 4 tabs:
5
5
  * - Status: Read-only info (version, model, provider, cwd, session)
6
6
  * - Config: Interactive settings (toggle/cycle with Enter/Space)
7
- * - Usage: Usage statistics with progress bars (mocked)
7
+ * - Paths: Path configuration and safety settings
8
+ * - Usage: Usage statistics with progress bars
8
9
  *
9
- * Includes theme selector sub-screen with 418 themes.
10
+ * Theme, Model, and Mascot selection are delegated to standalone overlays.
10
11
  */
11
12
  import chalk from 'chalk';
12
13
  import * as fs from 'fs';
13
14
  import * as path from 'path';
14
15
  import * as terminal from './terminal.js';
15
- import { getCuratedThemes, getCurrentTheme, setCurrentTheme, searchAndFilterThemes, getThemeCount, clearStyleCache, getStyles, } from '../themes/index.js';
16
- import { getSettings, setSetting, permissionModeToDisplay, displayToPermissionMode, notificationModeToDisplay, displayToNotificationMode, } from '../settings/index.js';
17
- import { hasApiKey, settingsProviderToCredentialKey } from '../utils/credentials.js';
16
+ import { isEscape, isTab, isShiftTab, isEnter, isSpace, isCtrlC, isQuit, isNavigateUp, isNavigateDown, isBackspace, getNumberKey, isPrintable, extractPrintable, } from './base/key-utils.js';
17
+ import { getCurrentTheme, getStyles, } from '../themes/index.js';
18
+ import { getSettings, setSetting, permissionModeToDisplay, displayToPermissionMode, notificationModeToDisplay, displayToNotificationMode, projectStartupModeToDisplay, displayToProjectStartupMode, } from '../settings/index.js';
19
+ import { getResolvedPathConfig, setDeleteProtection, setProjectMatchRequired, setWorkspacePath, setProjectsPath, setDataPath, } from '../settings/paths.js';
20
+ import { pauseForOverlay, resumeAfterOverlay } from './overlay-controller.js';
21
+ import { MASCOT_LABELS, } from './mascot/index.js';
18
22
  // =============================================================================
19
23
  // Constants
20
24
  // =============================================================================
21
- const VERSION = '2.0.57';
22
- const THEME_PAGE_SIZE = 12;
25
+ const VERSION = '0.4.0';
23
26
  const TABS = [
24
27
  { id: 'status', label: 'Status' },
25
28
  { id: 'config', label: 'Config' },
29
+ { id: 'paths', label: 'Paths' },
26
30
  { id: 'usage', label: 'Usage' },
27
31
  ];
28
- // Provider tabs for model selector
29
- const MODEL_PROVIDER_TABS = [
30
- { id: 'claude', label: 'Claude' },
31
- { id: 'openai', label: 'OpenAI' },
32
- { id: 'gemini', label: 'Gemini' },
33
- { id: 'ollama', label: 'Ollama' },
34
- ];
35
32
  const MODEL_OPTIONS = [
36
33
  // Claude
37
34
  { id: 'claude-sonnet-4-20250514', name: 'Sonnet 4', description: 'Best for everyday tasks', provider: 'claude' },
@@ -49,9 +46,6 @@ const MODEL_OPTIONS = [
49
46
  { id: 'mistral', name: 'Mistral', description: 'Local model', provider: 'ollama' },
50
47
  { id: 'codellama', name: 'Code Llama', description: 'Code-focused local model', provider: 'ollama' },
51
48
  ];
52
- function getModelsForProvider(providerId) {
53
- return MODEL_OPTIONS.filter(m => m.provider === providerId);
54
- }
55
49
  // =============================================================================
56
50
  // Config Items (loaded from settings)
57
51
  // =============================================================================
@@ -99,6 +93,21 @@ function getConfigItems(currentModel) {
99
93
  options: ['normal', 'vim', 'emacs'],
100
94
  },
101
95
  { id: 'autoInstall', label: 'Auto-install IDE extension', type: 'boolean', value: settings.autoInstall },
96
+ {
97
+ id: 'startupMode',
98
+ label: 'Startup behavior',
99
+ type: 'cycle',
100
+ value: settings.startupMode === 'menu' ? 'Show menu' : 'Direct to REPL',
101
+ options: ['Show menu', 'Direct to REPL'],
102
+ },
103
+ {
104
+ id: 'projectStartup',
105
+ label: 'Auto-open project',
106
+ type: 'cycle',
107
+ value: projectStartupModeToDisplay(settings.projectStartup),
108
+ options: ['Last opened', 'Off'],
109
+ },
110
+ { id: 'mascot', label: 'Mascot', type: 'submenu', value: MASCOT_LABELS[settings.mascot] },
102
111
  ];
103
112
  }
104
113
  // Unused helper function - removed to fix lint error
@@ -131,7 +140,7 @@ function renderHeader(state) {
131
140
  tabLine += s.muted(` ${tab.label} `) + ' ';
132
141
  }
133
142
  }
134
- tabLine += s.muted('(tab to cycle)');
143
+ tabLine += s.muted('(Tab · 1-4)');
135
144
  lines.push(tabLine);
136
145
  lines.push('');
137
146
  return lines;
@@ -233,216 +242,93 @@ function renderProgressBar(value, width) {
233
242
  return color('█'.repeat(filled)) + s.muted('░'.repeat(empty));
234
243
  }
235
244
  // =============================================================================
236
- // Rendering - Footer
245
+ // Rendering - Paths Tab
237
246
  // =============================================================================
238
- function renderFooter(state) {
239
- const s = getStyles();
240
- const lines = [];
241
- lines.push('');
242
- if (state.currentTab === 1) {
243
- // Config tab
244
- lines.push(s.muted(' Enter/Space to change · Esc to exit'));
245
- }
246
- else {
247
- lines.push(s.muted(' Esc to exit'));
248
- }
249
- return lines;
250
- }
251
- // =============================================================================
252
- // Rendering - Theme Selector (Curated)
253
- // =============================================================================
254
- function buildThemeCuratedLines(state) {
247
+ function renderPathsTab(state) {
255
248
  const s = getStyles();
256
249
  const lines = [];
250
+ const pathConfig = getResolvedPathConfig();
257
251
  const cols = terminal.getTerminalWidth();
258
- const currentTheme = getCurrentTheme();
259
- const counts = getThemeCount();
260
- lines.push(s.muted('─'.repeat(Math.max(1, cols - 1))));
252
+ // Layout constants
253
+ const labelWidth = 24;
254
+ const valueWidth = Math.max(30, cols - labelWidth - 8);
255
+ lines.push(chalk.bold(' Path Configuration'));
261
256
  lines.push('');
262
- lines.push(chalk.bold(' Theme'));
263
- lines.push('');
264
- lines.push(' Choose a theme:');
265
- lines.push('');
266
- // Show curated themes
267
- for (let i = 0; i < state.curatedThemes.length; i++) {
268
- const theme = state.curatedThemes[i];
269
- const isSelected = i === state.themeSelectedIndex;
270
- const isCurrent = theme.id === currentTheme.id;
257
+ // Helper to render a path item
258
+ const renderPathItem = (index, label, value) => {
259
+ const isSelected = state.pathSelectedItem === index;
271
260
  const prefix = isSelected ? s.primary(' ❯ ') : ' ';
272
- const num = `${(i + 1).toString().padStart(2)}. `;
273
- const icon = theme.type === 'dark' ? '🌙' : '☀️';
274
- const checkmark = isCurrent ? s.success(' ✓') : '';
275
- const label = isSelected ? s.primary(theme.name) : theme.name;
276
- lines.push(prefix + num + label.padEnd(30) + icon + checkmark);
277
- }
278
- // Separator
279
- lines.push(s.muted(' ────────────────────────────────────'));
280
- // Browse all option
281
- const browseIndex = state.curatedThemes.length;
282
- const isBrowseSelected = state.themeSelectedIndex === browseIndex;
283
- const browsePrefix = isBrowseSelected ? s.primary(' ❯ ') : ' ';
284
- const browseLabel = isBrowseSelected
285
- ? s.primary(`Browse all ${String(counts.total)} themes...`)
286
- : `Browse all ${String(counts.total)} themes...`;
287
- lines.push(browsePrefix + browseLabel + ' →');
288
- lines.push('');
289
- // Preview with theme colors
290
- const previewTheme = state.themeSelectedIndex < state.curatedThemes.length
291
- ? state.curatedThemes[state.themeSelectedIndex]
292
- : currentTheme;
293
- lines.push(...renderThemePreview(previewTheme));
294
- lines.push('');
295
- lines.push(s.muted(' ↑↓ Navigate · 1-9,0 Quick select · Enter Select · Esc Back'));
296
- return lines;
297
- }
298
- // =============================================================================
299
- // Rendering - Theme Selector (Browse All)
300
- // =============================================================================
301
- function buildThemeBrowseLines(state) {
302
- const s = getStyles();
303
- const lines = [];
304
- const cols = terminal.getTerminalWidth();
305
- const currentTheme = getCurrentTheme();
306
- const counts = getThemeCount();
307
- lines.push(s.muted('─'.repeat(Math.max(1, cols - 1))));
261
+ if (state.pathEditMode && isSelected) {
262
+ // Edit mode - show input field
263
+ const labelStyled = s.primary(label.padEnd(labelWidth));
264
+ const inputDisplay = state.pathEditInput + '█';
265
+ lines.push(`${prefix}${labelStyled}${inputDisplay}`);
266
+ }
267
+ else {
268
+ // Normal mode - show value
269
+ const labelStyled = isSelected ? s.primary(label.padEnd(labelWidth)) : label.padEnd(labelWidth);
270
+ const valueStyled = isSelected ? s.primary(value) : s.muted(value);
271
+ const truncatedValue = value.length > valueWidth ? value.slice(0, valueWidth - 3) + '...' : value;
272
+ lines.push(`${prefix}${labelStyled}${valueStyled === s.primary(value) ? s.primary(truncatedValue) : s.muted(truncatedValue)}`);
273
+ }
274
+ };
275
+ // Path items (0-2)
276
+ renderPathItem(0, 'Workspace path', pathConfig.workspacePath);
277
+ renderPathItem(1, 'Projects path', pathConfig.projectsPath);
278
+ renderPathItem(2, 'Data path', pathConfig.dataPath);
308
279
  lines.push('');
309
- lines.push(chalk.bold(` All Themes (${String(counts.total)})`));
280
+ lines.push(chalk.bold(' Safety Settings'));
310
281
  lines.push('');
311
- // Search box
312
- const searchBox = ` Search: ${state.browseSearchQuery}█`;
313
- lines.push(searchBox);
314
- // Filter tabs
315
- const filterAll = state.browseFilterType === 'all' ? s.selected(' All ') : s.muted(' All ');
316
- const filterDark = state.browseFilterType === 'dark' ? s.selected(' Dark ') : s.muted(' Dark ');
317
- const filterLight = state.browseFilterType === 'light' ? s.selected(' Light ') : s.muted(' Light ');
318
- lines.push(` Filter: ${filterAll} ${filterDark} ${filterLight} ${s.muted('(Tab to switch)')}`);
282
+ // Helper to render a toggle item with consistent alignment
283
+ const renderToggleItem = (index, label, enabled, hint) => {
284
+ const isSelected = state.pathSelectedItem === index;
285
+ const prefix = isSelected ? s.primary(' ❯ ') : ' ';
286
+ const labelStyled = isSelected ? s.primary(label.padEnd(labelWidth)) : label.padEnd(labelWidth);
287
+ const valueStyled = enabled ? s.success('enabled'.padStart(10)) : s.warning('disabled'.padStart(10));
288
+ lines.push(`${prefix}${labelStyled}${valueStyled}`);
289
+ lines.push(s.muted(` ${hint}`));
290
+ };
291
+ // Toggle items (3-4)
292
+ renderToggleItem(3, 'Delete protection', pathConfig.deleteProtection, 'Only allow file deletion within projects path');
293
+ renderToggleItem(4, 'CWD mismatch warning', pathConfig.requireProjectMatch, 'Warn when current directory differs from active project');
319
294
  lines.push('');
320
- // Results
321
- const totalResults = state.browseResults.length;
322
- const totalPages = Math.ceil(totalResults / THEME_PAGE_SIZE);
323
- const startIndex = state.browseCurrentPage * THEME_PAGE_SIZE;
324
- const endIndex = Math.min(startIndex + THEME_PAGE_SIZE, totalResults);
325
- const pageResults = state.browseResults.slice(startIndex, endIndex);
326
- if (totalResults === 0) {
327
- lines.push(s.muted(' No themes found'));
328
- }
329
- else {
330
- lines.push(s.muted(` Showing ${String(startIndex + 1)}-${String(endIndex)} of ${String(totalResults)}:`));
331
- lines.push('');
332
- for (let i = 0; i < pageResults.length; i++) {
333
- const theme = pageResults[i];
334
- const isSelected = i === state.browseSelectedIndex;
335
- const isCurrent = theme.id === currentTheme.id;
336
- const prefix = isSelected ? s.primary(' ❯ ') : ' ';
337
- const icon = theme.type === 'dark' ? '🌙' : '☀️';
338
- const checkmark = isCurrent ? s.success(' ✓') : '';
339
- const label = isSelected ? s.primary(theme.name) : theme.name;
340
- lines.push(prefix + label.padEnd(35) + icon + checkmark);
295
+ // Allowed paths (if any)
296
+ if (pathConfig.allowedPaths.length > 0) {
297
+ lines.push(chalk.bold(' Additional Allowed Paths'));
298
+ for (const allowed of pathConfig.allowedPaths) {
299
+ lines.push(` ${s.muted('•')} ${allowed}`);
341
300
  }
301
+ lines.push('');
342
302
  }
343
- lines.push('');
344
- // Pagination
345
- if (totalPages > 1) {
346
- const pageInfo = `Page ${String(state.browseCurrentPage + 1)} of ${String(totalPages)}`;
347
- const prevHint = state.browseCurrentPage > 0 ? '← ' : ' ';
348
- const nextHint = state.browseCurrentPage < totalPages - 1 ? ' →' : '';
349
- lines.push(s.muted(` ${prevHint}${pageInfo}${nextHint}`));
350
- }
351
- // Separator
352
- lines.push(s.muted(' ────────────────────────────────────'));
353
- // Back option
354
- lines.push(s.muted(' ← Back to curated themes'));
355
- lines.push('');
356
- // Preview
357
- if (pageResults.length > 0 && state.browseSelectedIndex < pageResults.length) {
358
- lines.push(...renderThemePreview(pageResults[state.browseSelectedIndex]));
359
- }
360
- lines.push('');
361
- lines.push(s.muted(' Type to search · ↑↓ Navigate · ←→ Pages · Tab Filter · Enter Select · Esc Back'));
362
- return lines;
363
- }
364
- // =============================================================================
365
- // Rendering - Theme Preview
366
- // =============================================================================
367
- function renderThemePreview(theme) {
368
- const s = getStyles();
369
- const lines = [];
370
- const colors = theme.colors;
371
- lines.push(s.muted(' Preview:'));
372
- // Use chalk for hex colors in preview
373
- const primary = chalk.hex(colors.primary);
374
- const secondary = chalk.hex(colors.secondary || colors.primary);
375
- const muted = chalk.hex(colors.muted);
376
- const fg = chalk.hex(colors.foreground);
377
- lines.push(' ' + primary.bold('function') + ' ' + secondary('greet') + fg('() {'));
378
- lines.push(' ' + muted('// Say hello'));
379
- lines.push(' ' + fg('console.') + secondary('log') + fg('("Hello, ') + primary('World') + fg('!");'));
380
- lines.push(' ' + fg('}'));
381
303
  return lines;
382
304
  }
383
305
  // =============================================================================
384
- // Rendering - Model Selector
306
+ // Rendering - Footer
385
307
  // =============================================================================
386
- function buildModelSelectorLines(state) {
308
+ function renderFooter(state) {
387
309
  const s = getStyles();
388
310
  const lines = [];
389
- const cols = terminal.getTerminalWidth();
390
- // Header with separator
391
- lines.push(s.muted(''.repeat(Math.max(1, cols - 1))));
392
- // Tab row (similar to main config tabs)
393
- let tabLine = ' Model: ';
394
- for (let i = 0; i < MODEL_PROVIDER_TABS.length; i++) {
395
- const tab = MODEL_PROVIDER_TABS[i];
396
- const isCurrentProvider = tab.id === state.currentProvider;
397
- const indicator = isCurrentProvider ? ' ✓' : '';
398
- if (i === state.modelProviderTab) {
399
- tabLine += s.selected(` ${tab.label}${indicator} `) + ' ';
311
+ lines.push('');
312
+ const tabId = TABS[state.currentTab].id;
313
+ if (tabId === 'config') {
314
+ // Config tab
315
+ lines.push(s.muted(' ↑↓/jk Navigate · Enter/Space Change · q/Esc Close'));
316
+ }
317
+ else if (tabId === 'paths') {
318
+ // Paths tab
319
+ if (state.pathEditMode) {
320
+ lines.push(s.muted(' Type path · Enter Save · Esc Cancel'));
400
321
  }
401
322
  else {
402
- tabLine += s.muted(` ${tab.label}${indicator} `) + ' ';
323
+ lines.push(s.muted(' ↑↓/jk Navigate · Enter Edit/Toggle · q/Esc Close'));
403
324
  }
404
325
  }
405
- tabLine += s.muted('(tab to switch)');
406
- lines.push(tabLine);
407
- lines.push('');
408
- // Current provider info
409
- const selectedProviderTab = MODEL_PROVIDER_TABS[state.modelProviderTab];
410
- const isHotSwappable = selectedProviderTab.id === state.currentProvider;
411
- if (isHotSwappable) {
412
- lines.push(s.success(' Current provider - changes apply immediately'));
413
- }
414
326
  else {
415
- lines.push(s.warning(' Different provider - restart required to apply'));
416
- }
417
- // Check if API key is configured for selected provider
418
- if (selectedProviderTab.id !== 'ollama') {
419
- const credKey = settingsProviderToCredentialKey(selectedProviderTab.id);
420
- if (!hasApiKey(credKey)) {
421
- lines.push(s.error(' ⚠ No API key configured - use /keys to set it'));
422
- }
423
- }
424
- lines.push('');
425
- // Models for selected provider
426
- const models = getModelsForProvider(selectedProviderTab.id);
427
- for (let i = 0; i < models.length; i++) {
428
- const model = models[i];
429
- const isSelected = i === state.modelSelectedIndex;
430
- const isCurrentModel = model.id === state.statusInfo.model;
431
- const prefix = isSelected ? s.primary(' ❯ ') : ' ';
432
- const checkmark = isCurrentModel ? s.success(' ✓') : '';
433
- const label = isSelected ? s.primary(model.name) : model.name;
434
- const desc = s.muted(` · ${model.description}`);
435
- lines.push(prefix + label.padEnd(20) + desc + checkmark);
327
+ lines.push(s.muted(' q/Esc Close'));
436
328
  }
437
- lines.push('');
438
- // Footer
439
- lines.push(s.muted(' Tab Provider · ↑↓ Model · Enter Select · Esc Back'));
440
329
  return lines;
441
330
  }
442
- // =============================================================================
443
- // Unified Rendering
444
- // =============================================================================
445
- // Target height for consistent screen sizes (model selector is tallest at ~25 lines)
331
+ // Target height for consistent screen sizes
446
332
  const TARGET_HEIGHT = 25;
447
333
  function padToHeight(lines, targetHeight) {
448
334
  while (lines.length < targetHeight) {
@@ -451,15 +337,6 @@ function padToHeight(lines, targetHeight) {
451
337
  return lines;
452
338
  }
453
339
  function buildLines(state) {
454
- if (state.mode === 'theme-curated') {
455
- return padToHeight(buildThemeCuratedLines(state), TARGET_HEIGHT);
456
- }
457
- if (state.mode === 'theme-browse') {
458
- return padToHeight(buildThemeBrowseLines(state), TARGET_HEIGHT);
459
- }
460
- if (state.mode === 'model-selector') {
461
- return padToHeight(buildModelSelectorLines(state), TARGET_HEIGHT);
462
- }
463
340
  const allLines = [];
464
341
  allLines.push(...renderHeader(state));
465
342
  switch (TABS[state.currentTab].id) {
@@ -469,6 +346,9 @@ function buildLines(state) {
469
346
  case 'config':
470
347
  allLines.push(...renderConfigTab(state));
471
348
  break;
349
+ case 'paths':
350
+ allLines.push(...renderPathsTab(state));
351
+ break;
472
352
  case 'usage':
473
353
  allLines.push(...renderUsageTab(state));
474
354
  break;
@@ -537,82 +417,41 @@ function toggleOrCycleItem(state) {
537
417
  case 'editorMode':
538
418
  setSetting('editorMode', item.value);
539
419
  break;
420
+ case 'startupMode':
421
+ setSetting('startupMode', item.value === 'Show menu' ? 'menu' : 'repl');
422
+ break;
423
+ case 'projectStartup':
424
+ setSetting('projectStartup', displayToProjectStartupMode(item.value));
425
+ break;
540
426
  // 'model' is not persisted (would need provider integration)
541
427
  }
542
428
  }
543
429
  else if (item.type === 'submenu' && item.id === 'theme') {
544
- // Enter theme selector (curated mode)
545
- state.mode = 'theme-curated';
546
- state.themeSelectedIndex = 0;
547
- // Find current theme in curated list
548
- const currentTheme = getCurrentTheme();
549
- const curatedIndex = state.curatedThemes.findIndex((t) => t.id === currentTheme.id);
550
- if (curatedIndex >= 0) {
551
- state.themeSelectedIndex = curatedIndex;
552
- }
430
+ // Request standalone theme overlay
431
+ state.requestSubOverlay = 'theme';
553
432
  }
554
433
  else if (item.type === 'submenu' && item.id === 'model') {
555
- // Enter model selector
556
- state.mode = 'model-selector';
557
- state.modelSelectedIndex = 0;
558
- state.modelProviderTab = 0;
559
- // Find current model and set provider tab accordingly
560
- const currentModel = MODEL_OPTIONS.find((m) => m.id === state.statusInfo.model);
561
- if (currentModel) {
562
- const providerTabIndex = MODEL_PROVIDER_TABS.findIndex((p) => p.id === currentModel.provider);
563
- if (providerTabIndex >= 0) {
564
- state.modelProviderTab = providerTabIndex;
565
- }
566
- // Find model index within that provider's models
567
- const providerModels = getModelsForProvider(currentModel.provider);
568
- const modelIndex = providerModels.findIndex((m) => m.id === currentModel.id);
569
- if (modelIndex >= 0) {
570
- state.modelSelectedIndex = modelIndex;
571
- }
572
- }
434
+ // Request standalone model overlay
435
+ state.requestSubOverlay = 'model';
573
436
  }
574
- }
575
- // =============================================================================
576
- // Theme Selection Actions
577
- // =============================================================================
578
- function selectTheme(state, theme) {
579
- setCurrentTheme(theme.id);
580
- clearStyleCache();
581
- // Update the config item value
582
- const themeItem = state.configItems.find((item) => item.id === 'theme');
583
- if (themeItem) {
584
- themeItem.value = theme.name;
437
+ else if (item.type === 'submenu' && item.id === 'mascot') {
438
+ // Request standalone mascot overlay
439
+ state.requestSubOverlay = 'mascot';
585
440
  }
586
441
  }
587
- function updateBrowseResults(state) {
588
- state.browseResults = searchAndFilterThemes(state.browseSearchQuery, state.browseFilterType);
589
- state.browseSelectedIndex = 0;
590
- state.browseCurrentPage = 0;
591
- }
592
442
  /**
593
443
  * Show the config overlay
594
444
  */
595
445
  export async function showConfigOverlay(options = {}) {
596
- const curatedThemes = getCuratedThemes();
597
446
  const result = {};
598
447
  const state = {
599
- mode: options.initialMode || 'tabs',
600
448
  currentTab: 1, // Start on Config tab (like Claude Code)
601
449
  selectedItem: 0,
602
450
  scrollOffset: 0,
603
- // Theme curated state
604
- themeSelectedIndex: 0,
605
- curatedThemes,
606
- // Theme browse state
607
- browseSearchQuery: '',
608
- browseFilterType: 'all',
609
- browseCurrentPage: 0,
610
- browseSelectedIndex: 0,
611
- browseResults: [],
612
- // Model selector state
613
- modelSelectedIndex: 0,
614
- modelProviderTab: 0,
615
- currentProvider: options.provider || 'unknown',
451
+ // Path settings state
452
+ pathSelectedItem: 0,
453
+ pathEditMode: false,
454
+ pathEditInput: '',
616
455
  configItems: getConfigItems(options.model),
617
456
  statusInfo: {
618
457
  version: options.version || VERSION,
@@ -632,300 +471,191 @@ export async function showConfigOverlay(options = {}) {
632
471
  contextMax: options.contextMax ?? 200000,
633
472
  messageCount: options.messageCount ?? 0,
634
473
  },
635
- onModelChange: options.onModelChange,
636
474
  };
637
- // If starting in model-selector mode, initialize the provider tab and model index
638
- if (options.initialMode === 'model-selector') {
639
- // Find current model and set provider tab accordingly
640
- const currentModel = MODEL_OPTIONS.find((m) => m.id === state.statusInfo.model);
641
- if (currentModel) {
642
- const providerTabIndex = MODEL_PROVIDER_TABS.findIndex((p) => p.id === currentModel.provider);
643
- if (providerTabIndex >= 0) {
644
- state.modelProviderTab = providerTabIndex;
645
- }
646
- // Find model index within that provider's models
647
- const providerModels = getModelsForProvider(currentModel.provider);
648
- const modelIndex = providerModels.findIndex((m) => m.id === currentModel.id);
649
- if (modelIndex >= 0) {
650
- state.modelSelectedIndex = modelIndex;
651
- }
652
- }
653
- }
654
475
  let lineCount = 0;
655
- terminal.writeLine('');
476
+ let maxLineCount = 0;
477
+ // Pause footer and setup terminal
478
+ pauseForOverlay();
656
479
  terminal.hideCursor();
657
480
  const wasRawMode = process.stdin.isRaw;
658
481
  terminal.enableRawMode();
659
482
  lineCount = render(state, 0);
483
+ maxLineCount = lineCount;
660
484
  return new Promise((resolve) => {
661
485
  const cleanup = () => {
662
- terminal.clearLinesAbove(lineCount);
663
- terminal.writeLine('');
486
+ // Remove event listener first
487
+ process.stdin.removeListener('data', onData);
488
+ // Clear all lines (use maxLineCount to ensure complete cleanup)
489
+ if (maxLineCount > 0) {
490
+ terminal.clearLinesAbove(maxLineCount);
491
+ }
664
492
  terminal.showCursor();
665
493
  if (!wasRawMode) {
666
494
  terminal.disableRawMode();
667
495
  }
668
- process.stdin.removeListener('data', onData);
496
+ // Resume footer
497
+ resumeAfterOverlay();
669
498
  };
670
499
  const onData = (data) => {
671
- const isEscape = data.length === 1 && data[0] === 0x1b;
672
- const isTab = data.length === 1 && data[0] === 0x09;
673
- const isUpArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x41;
674
- const isDownArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x42;
675
- const isLeftArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x44;
676
- const isRightArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x43;
677
- const isCtrlC = data.length === 1 && data[0] === 0x03;
678
- const isEnter = data.length === 1 && (data[0] === 0x0d || data[0] === 0x0a);
679
- const isSpace = data.length === 1 && data[0] === 0x20;
680
- const isBackspace = data.length === 1 && (data[0] === 0x7f || data[0] === 0x08);
681
- const char = data.toString();
682
- const isNumberKey = /^[0-9]$/.test(char);
683
- const isPrintable = data.length === 1 && data[0] >= 32 && data[0] < 127;
684
- // ===== TABS MODE =====
685
- if (state.mode === 'tabs') {
686
- if (isEscape || isCtrlC) {
687
- cleanup();
688
- resolve(result);
689
- return;
690
- }
691
- if (isTab) {
692
- state.currentTab = (state.currentTab + 1) % TABS.length;
693
- state.selectedItem = 0;
500
+ const char = extractPrintable(data);
501
+ // Handle path edit mode first (when editing a path in Paths tab)
502
+ if (state.pathEditMode) {
503
+ if (isEscape(data)) {
504
+ // Cancel edit
505
+ state.pathEditMode = false;
506
+ state.pathEditInput = '';
694
507
  lineCount = render(state, lineCount);
508
+ maxLineCount = Math.max(maxLineCount, lineCount);
695
509
  return;
696
510
  }
697
- // Navigation only in Config tab
698
- if (TABS[state.currentTab].id === 'config') {
699
- if (isUpArrow && state.selectedItem > 0) {
700
- state.selectedItem--;
701
- lineCount = render(state, lineCount);
702
- return;
703
- }
704
- if (isDownArrow && state.selectedItem < state.configItems.length - 1) {
705
- state.selectedItem++;
706
- lineCount = render(state, lineCount);
707
- return;
708
- }
709
- if (isEnter || isSpace) {
710
- toggleOrCycleItem(state);
711
- lineCount = render(state, lineCount);
712
- return;
511
+ if (isEnter(data)) {
512
+ // Save the path
513
+ const newPath = state.pathEditInput.trim();
514
+ if (newPath) {
515
+ switch (state.pathSelectedItem) {
516
+ case 0:
517
+ setWorkspacePath(newPath);
518
+ break;
519
+ case 1:
520
+ setProjectsPath(newPath);
521
+ break;
522
+ case 2:
523
+ setDataPath(newPath);
524
+ break;
525
+ }
713
526
  }
714
- }
715
- return;
716
- }
717
- // ===== THEME CURATED MODE =====
718
- if (state.mode === 'theme-curated') {
719
- const maxIndex = state.curatedThemes.length; // includes "Browse all" option
720
- if (isCtrlC) {
721
- cleanup();
722
- resolve(result);
723
- return;
724
- }
725
- if (isEscape) {
726
- state.mode = 'tabs';
527
+ state.pathEditMode = false;
528
+ state.pathEditInput = '';
727
529
  lineCount = render(state, lineCount);
530
+ maxLineCount = Math.max(maxLineCount, lineCount);
728
531
  return;
729
532
  }
730
- if (isUpArrow && state.themeSelectedIndex > 0) {
731
- state.themeSelectedIndex--;
533
+ if (isBackspace(data)) {
534
+ state.pathEditInput = state.pathEditInput.slice(0, -1);
732
535
  lineCount = render(state, lineCount);
536
+ maxLineCount = Math.max(maxLineCount, lineCount);
733
537
  return;
734
538
  }
735
- if (isDownArrow && state.themeSelectedIndex < maxIndex) {
736
- state.themeSelectedIndex++;
539
+ if (isPrintable(data)) {
540
+ state.pathEditInput += char;
737
541
  lineCount = render(state, lineCount);
542
+ maxLineCount = Math.max(maxLineCount, lineCount);
738
543
  return;
739
544
  }
740
- if (isEnter) {
741
- if (state.themeSelectedIndex === state.curatedThemes.length) {
742
- // "Browse all" selected
743
- state.mode = 'theme-browse';
744
- state.browseSearchQuery = '';
745
- state.browseFilterType = 'all';
746
- updateBrowseResults(state);
747
- lineCount = render(state, lineCount);
748
- }
749
- else {
750
- // Theme selected
751
- const theme = state.curatedThemes[state.themeSelectedIndex];
752
- selectTheme(state, theme);
753
- state.mode = 'tabs';
754
- lineCount = render(state, lineCount);
755
- }
756
- return;
757
- }
758
- // Quick select with number keys (1-9 = themes 1-9, 0 = theme 10)
759
- if (isNumberKey) {
760
- const num = char === '0' ? 10 : parseInt(char, 10);
761
- if (num >= 1 && num <= state.curatedThemes.length) {
762
- const theme = state.curatedThemes[num - 1];
763
- selectTheme(state, theme);
764
- state.mode = 'tabs';
765
- lineCount = render(state, lineCount);
766
- }
767
- return;
768
- }
545
+ return; // Ignore other keys in edit mode
546
+ }
547
+ // q or Esc closes (not in path edit mode)
548
+ if (isEscape(data) || isCtrlC(data) || isQuit(data)) {
549
+ cleanup();
550
+ resolve(result);
769
551
  return;
770
552
  }
771
- // ===== THEME BROWSE MODE =====
772
- if (state.mode === 'theme-browse') {
773
- const totalPages = Math.ceil(state.browseResults.length / THEME_PAGE_SIZE);
774
- const pageResults = state.browseResults.slice(state.browseCurrentPage * THEME_PAGE_SIZE, (state.browseCurrentPage + 1) * THEME_PAGE_SIZE);
775
- if (isCtrlC) {
776
- cleanup();
777
- resolve(result);
778
- return;
779
- }
780
- if (isEscape) {
781
- // Back to curated
782
- state.mode = 'theme-curated';
783
- lineCount = render(state, lineCount);
784
- return;
785
- }
786
- // Tab cycles filter
787
- if (isTab) {
788
- const filters = ['all', 'dark', 'light'];
789
- const currentIdx = filters.indexOf(state.browseFilterType);
790
- state.browseFilterType = filters[(currentIdx + 1) % filters.length];
791
- updateBrowseResults(state);
792
- lineCount = render(state, lineCount);
793
- return;
794
- }
795
- // Navigation
796
- if (isUpArrow && state.browseSelectedIndex > 0) {
797
- state.browseSelectedIndex--;
798
- lineCount = render(state, lineCount);
799
- return;
800
- }
801
- if (isDownArrow && state.browseSelectedIndex < pageResults.length - 1) {
802
- state.browseSelectedIndex++;
803
- lineCount = render(state, lineCount);
804
- return;
805
- }
806
- // Page navigation
807
- if (isLeftArrow && state.browseCurrentPage > 0) {
808
- state.browseCurrentPage--;
809
- state.browseSelectedIndex = 0;
810
- lineCount = render(state, lineCount);
811
- return;
812
- }
813
- if (isRightArrow && state.browseCurrentPage < totalPages - 1) {
814
- state.browseCurrentPage++;
815
- state.browseSelectedIndex = 0;
816
- lineCount = render(state, lineCount);
817
- return;
818
- }
819
- // Select theme
820
- if (isEnter && pageResults.length > 0) {
821
- const theme = pageResults[state.browseSelectedIndex];
822
- selectTheme(state, theme);
823
- state.mode = 'tabs';
824
- lineCount = render(state, lineCount);
825
- return;
826
- }
827
- // Backspace - delete search char
828
- if (isBackspace && state.browseSearchQuery.length > 0) {
829
- state.browseSearchQuery = state.browseSearchQuery.slice(0, -1);
830
- updateBrowseResults(state);
553
+ // Tab switching: Tab for next, Shift+Tab for previous
554
+ if (isTab(data)) {
555
+ state.currentTab = (state.currentTab + 1) % TABS.length;
556
+ state.selectedItem = 0;
557
+ state.pathSelectedItem = 0;
558
+ lineCount = render(state, lineCount);
559
+ maxLineCount = Math.max(maxLineCount, lineCount);
560
+ return;
561
+ }
562
+ if (isShiftTab(data)) {
563
+ state.currentTab = (state.currentTab - 1 + TABS.length) % TABS.length;
564
+ state.selectedItem = 0;
565
+ state.pathSelectedItem = 0;
566
+ lineCount = render(state, lineCount);
567
+ maxLineCount = Math.max(maxLineCount, lineCount);
568
+ return;
569
+ }
570
+ // Number keys 1-4 to directly select tabs
571
+ const tabNumKey = getNumberKey(data);
572
+ if (tabNumKey !== null && tabNumKey >= 1 && tabNumKey <= TABS.length) {
573
+ state.currentTab = tabNumKey - 1;
574
+ state.selectedItem = 0;
575
+ state.pathSelectedItem = 0;
576
+ lineCount = render(state, lineCount);
577
+ maxLineCount = Math.max(maxLineCount, lineCount);
578
+ return;
579
+ }
580
+ // Navigation only in Config tab (↑↓ or j/k)
581
+ if (TABS[state.currentTab].id === 'config') {
582
+ if (isNavigateUp(data) && state.selectedItem > 0) {
583
+ state.selectedItem--;
831
584
  lineCount = render(state, lineCount);
585
+ maxLineCount = Math.max(maxLineCount, lineCount);
832
586
  return;
833
587
  }
834
- // Type to search (printable chars except space which might conflict)
835
- if (isPrintable && !isSpace) {
836
- state.browseSearchQuery += char;
837
- updateBrowseResults(state);
588
+ if (isNavigateDown(data) && state.selectedItem < state.configItems.length - 1) {
589
+ state.selectedItem++;
838
590
  lineCount = render(state, lineCount);
591
+ maxLineCount = Math.max(maxLineCount, lineCount);
839
592
  return;
840
593
  }
841
- return;
842
- }
843
- // ===== MODEL SELECTOR MODE =====
844
- // Mode is always 'model-selector' at this point since we're in the else block
845
- {
846
- const currentProviderTab = MODEL_PROVIDER_TABS[state.modelProviderTab];
847
- const providerModels = getModelsForProvider(currentProviderTab.id);
848
- const maxModelIndex = providerModels.length - 1;
849
- if (isCtrlC) {
850
- cleanup();
851
- resolve(result);
852
- return;
853
- }
854
- if (isEscape) {
855
- // If we started in model-selector mode (via /model command), close entirely
856
- if (options.initialMode === 'model-selector') {
594
+ if (isEnter(data) || isSpace(data)) {
595
+ toggleOrCycleItem(state);
596
+ // Check if a sub-overlay was requested
597
+ if (state.requestSubOverlay) {
598
+ result.requestSubOverlay = state.requestSubOverlay;
857
599
  cleanup();
858
600
  resolve(result);
859
601
  return;
860
602
  }
861
- state.mode = 'tabs';
862
- lineCount = render(state, lineCount);
863
- return;
864
- }
865
- // Tab to switch provider tabs
866
- if (isTab) {
867
- state.modelProviderTab = (state.modelProviderTab + 1) % MODEL_PROVIDER_TABS.length;
868
- state.modelSelectedIndex = 0; // Reset model selection when switching tabs
869
603
  lineCount = render(state, lineCount);
604
+ maxLineCount = Math.max(maxLineCount, lineCount);
870
605
  return;
871
606
  }
872
- // Left/Right arrows also switch provider tabs
873
- if (isLeftArrow) {
874
- state.modelProviderTab = (state.modelProviderTab - 1 + MODEL_PROVIDER_TABS.length) % MODEL_PROVIDER_TABS.length;
875
- state.modelSelectedIndex = 0;
876
- lineCount = render(state, lineCount);
877
- return;
878
- }
879
- if (isRightArrow) {
880
- state.modelProviderTab = (state.modelProviderTab + 1) % MODEL_PROVIDER_TABS.length;
881
- state.modelSelectedIndex = 0;
882
- lineCount = render(state, lineCount);
883
- return;
884
- }
885
- if (isUpArrow && state.modelSelectedIndex > 0) {
886
- state.modelSelectedIndex--;
607
+ }
608
+ // Navigation in Paths tab (5 items: 0-2 paths, 3-4 toggles)
609
+ if (TABS[state.currentTab].id === 'paths') {
610
+ const pathItemCount = 5; // 3 paths + 2 toggles
611
+ if (isNavigateUp(data) && state.pathSelectedItem > 0) {
612
+ state.pathSelectedItem--;
887
613
  lineCount = render(state, lineCount);
614
+ maxLineCount = Math.max(maxLineCount, lineCount);
888
615
  return;
889
616
  }
890
- if (isDownArrow && state.modelSelectedIndex < maxModelIndex) {
891
- state.modelSelectedIndex++;
617
+ if (isNavigateDown(data) && state.pathSelectedItem < pathItemCount - 1) {
618
+ state.pathSelectedItem++;
892
619
  lineCount = render(state, lineCount);
620
+ maxLineCount = Math.max(maxLineCount, lineCount);
893
621
  return;
894
622
  }
895
- if (isEnter) {
896
- const selectedModel = providerModels[state.modelSelectedIndex];
897
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
898
- if (selectedModel) {
899
- const isCurrentProvider = selectedModel.provider === state.currentProvider;
900
- // Update the config item display
901
- const modelItem = state.configItems.find((item) => item.id === 'model');
902
- if (modelItem) {
903
- modelItem.value = selectedModel.name;
623
+ if (isEnter(data) || isSpace(data)) {
624
+ const currentConfig = getResolvedPathConfig();
625
+ if (state.pathSelectedItem <= 2) {
626
+ // Path items (0-2) - enter edit mode
627
+ state.pathEditMode = true;
628
+ // Pre-populate with current value
629
+ switch (state.pathSelectedItem) {
630
+ case 0:
631
+ state.pathEditInput = currentConfig.workspacePath;
632
+ break;
633
+ case 1:
634
+ state.pathEditInput = currentConfig.projectsPath;
635
+ break;
636
+ case 2:
637
+ state.pathEditInput = currentConfig.dataPath;
638
+ break;
904
639
  }
905
- // Update status info
906
- state.statusInfo.model = selectedModel.id;
907
- // Always persist model selection to settings
908
- setSetting('defaultProvider', selectedModel.provider);
909
- setSetting('defaultModel', selectedModel.id);
910
- if (isCurrentProvider) {
911
- result.modelChanged = selectedModel.id;
912
- }
913
- // Different provider: settings are saved, will take effect on restart
914
- // If we started in model-selector mode (via /model command), close entirely
915
- if (options.initialMode === 'model-selector') {
916
- cleanup();
917
- resolve(result);
918
- return;
919
- }
920
- // Going back to config tabs - don't call onModelChange callback here
921
- // as the overlay is still open. The model change will be reflected
922
- // in the config display, and the actual switch happens when overlay closes.
923
- state.mode = 'tabs';
924
640
  lineCount = render(state, lineCount);
641
+ maxLineCount = Math.max(maxLineCount, lineCount);
642
+ return;
643
+ }
644
+ else if (state.pathSelectedItem === 3) {
645
+ // Toggle delete protection
646
+ setDeleteProtection(!currentConfig.deleteProtection);
647
+ lineCount = render(state, lineCount);
648
+ maxLineCount = Math.max(maxLineCount, lineCount);
649
+ return;
650
+ }
651
+ else if (state.pathSelectedItem === 4) {
652
+ // Toggle CWD match warning
653
+ setProjectMatchRequired(!currentConfig.requireProjectMatch);
654
+ lineCount = render(state, lineCount);
655
+ maxLineCount = Math.max(maxLineCount, lineCount);
656
+ return;
925
657
  }
926
- return;
927
658
  }
928
- return;
929
659
  }
930
660
  };
931
661
  process.stdin.on('data', onData);