@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,218 @@
1
+ /**
2
+ * Tools Overlay V2
3
+ *
4
+ * Displays available agent tools in a categorized, searchable list.
5
+ * Uses TabbedListOverlayV2 for the new Overlay interface.
6
+ *
7
+ * Features:
8
+ * - Tabs: All, Files, Code, Git, Project, Agent
9
+ * - Search with / toggle
10
+ * - Detail view for full tool information
11
+ * - Works with TerminalUI's overlay management
12
+ */
13
+ import * as terminal from './terminal.js';
14
+ import { TabbedListOverlayV2, BaseScreen, stay, popScreen, closeOverlay, isEscape, isCtrlC, isClose, renderBorder, wrapText, } from './base/index.js';
15
+ // =============================================================================
16
+ // Tab Definitions & Tool Categories
17
+ // =============================================================================
18
+ const TOOL_TABS = [
19
+ { id: 'all', label: 'All' },
20
+ { id: 'files', label: 'Files' },
21
+ { id: 'code', label: 'Code' },
22
+ { id: 'git', label: 'Git' },
23
+ { id: 'project', label: 'Project' },
24
+ { id: 'agent', label: 'Agent' },
25
+ ];
26
+ /**
27
+ * Tool category mappings.
28
+ * Uses exact names or prefix patterns (ending with *).
29
+ */
30
+ const TOOL_CATEGORIES = {
31
+ all: null, // No filter - show all tools
32
+ files: ['read_file', 'write_file', 'edit', 'glob', 'readFile', 'writeFile'],
33
+ code: ['grep', 'bash', 'bashOutput', 'run_tests', 'run_lint', 'detect_project', 'find_project_root', 'detectProject', 'findProjectRoot'],
34
+ git: ['git_*', 'gitStatus', 'gitDiff', 'gitLog', 'gitCommit', 'gitBranch'],
35
+ project: ['project_*', 'workitem_*', 'document_*', 'anchor_*'],
36
+ agent: ['task', 'todo_write', 'todo_read', 'todoWrite', 'todoRead', 'suggest', 'ask_user', 'ask_user_simple', 'askUser', 'askUserSimple', 'webFetch'],
37
+ };
38
+ /**
39
+ * Check if a tool matches a category filter.
40
+ */
41
+ function toolMatchesCategory(toolName, categoryId) {
42
+ const patterns = TOOL_CATEGORIES[categoryId];
43
+ if (patterns === null)
44
+ return true; // 'all' category
45
+ for (const pattern of patterns) {
46
+ if (pattern.endsWith('*')) {
47
+ // Prefix match
48
+ const prefix = pattern.slice(0, -1);
49
+ if (toolName.startsWith(prefix))
50
+ return true;
51
+ }
52
+ else {
53
+ // Exact match
54
+ if (toolName === pattern)
55
+ return true;
56
+ }
57
+ }
58
+ return false;
59
+ }
60
+ // =============================================================================
61
+ // Constants
62
+ // =============================================================================
63
+ const PAGE_SIZE = 12;
64
+ const NAME_WIDTH = 26;
65
+ const MAX_DESCRIPTION_LENGTH = 300;
66
+ const MAX_PARAMETERS_SHOWN = 5;
67
+ /**
68
+ * Normalize text by replacing newlines with spaces (for single-line display)
69
+ */
70
+ function normalizeText(text) {
71
+ return text.replace(/\n+/g, ' ').replace(/\s+/g, ' ').trim();
72
+ }
73
+ // =============================================================================
74
+ // Detail Screen
75
+ // =============================================================================
76
+ /**
77
+ * Detail screen - shows full information for selected tool
78
+ */
79
+ class ToolDetailScreen extends BaseScreen {
80
+ tool;
81
+ styles;
82
+ constructor(tool, styles) {
83
+ super();
84
+ this.tool = tool;
85
+ this.styles = styles;
86
+ }
87
+ render() {
88
+ const s = this.styles;
89
+ const cols = terminal.getTerminalWidth();
90
+ const border = renderBorder(cols, s);
91
+ const lines = [];
92
+ // Header
93
+ lines.push(border);
94
+ lines.push(` ${s.primaryBold('Tool Details')}`);
95
+ lines.push('');
96
+ // Tool name
97
+ lines.push(` ${s.muted('Name:')} ${s.primaryBold(this.tool.name)}`);
98
+ lines.push('');
99
+ // Permission info
100
+ if (this.tool.permission) {
101
+ const levelColor = this.tool.permission.level === 'once' ? s.warning : s.muted;
102
+ lines.push(` ${s.muted('Permission:')} ${levelColor(this.tool.permission.level)} ${s.muted(`(${this.tool.permission.description})`)}`);
103
+ }
104
+ else {
105
+ lines.push(` ${s.muted('Permission:')} ${s.success('always')} ${s.muted('(no approval required)')}`);
106
+ }
107
+ lines.push('');
108
+ // Description (normalize newlines for proper wrapping)
109
+ lines.push(` ${s.muted('Description:')}`);
110
+ let description = normalizeText(this.tool.description ?? 'No description available.');
111
+ if (description.length > MAX_DESCRIPTION_LENGTH) {
112
+ description = description.slice(0, MAX_DESCRIPTION_LENGTH).trim() + '...';
113
+ }
114
+ const wrappedDesc = wrapText(description, cols - 6);
115
+ for (const line of wrappedDesc) {
116
+ lines.push(` ${s.secondary(line)}`);
117
+ }
118
+ lines.push('');
119
+ // Parameters
120
+ if (this.tool.parameters && this.tool.parameters.length > 0) {
121
+ // Sort: required first, then optional
122
+ const sorted = [...this.tool.parameters].sort((a, b) => {
123
+ if (a.required && !b.required)
124
+ return -1;
125
+ if (!a.required && b.required)
126
+ return 1;
127
+ return 0;
128
+ });
129
+ const shown = sorted.slice(0, MAX_PARAMETERS_SHOWN);
130
+ const hidden = sorted.length - shown.length;
131
+ lines.push(` ${s.muted('Parameters:')}`);
132
+ for (const param of shown) {
133
+ const requiredBadge = param.required ? s.warning('*') : ' ';
134
+ const typeStr = s.muted(`(${param.type})`);
135
+ lines.push(` ${requiredBadge} ${s.secondary(param.name)} ${typeStr}`);
136
+ }
137
+ if (hidden > 0) {
138
+ lines.push(` ${s.muted(` + ${String(hidden)} more optional parameters`)}`);
139
+ }
140
+ lines.push('');
141
+ lines.push(` ${s.muted('* = required')}`);
142
+ }
143
+ else {
144
+ lines.push(` ${s.muted('Parameters:')} ${s.muted('None')}`);
145
+ }
146
+ // Footer
147
+ lines.push(border);
148
+ lines.push(` ${s.muted('q/Esc Back')}`);
149
+ lines.push(border);
150
+ return lines;
151
+ }
152
+ handleKey(data) {
153
+ // Ctrl+C closes overlay completely
154
+ if (isCtrlC(data)) {
155
+ return closeOverlay({ dismissed: true });
156
+ }
157
+ // Esc or q goes back to list
158
+ if (isEscape(data) || isClose(data)) {
159
+ return popScreen();
160
+ }
161
+ return stay(false);
162
+ }
163
+ /**
164
+ * Minimum height for stable rendering.
165
+ */
166
+ getMinHeight() {
167
+ return 20;
168
+ }
169
+ }
170
+ // =============================================================================
171
+ // Tools Overlay V2 Class
172
+ // =============================================================================
173
+ /**
174
+ * Tools overlay using the new Overlay interface.
175
+ * Displays available tools in a searchable, tabbed list.
176
+ */
177
+ export class ToolsOverlayV2 extends TabbedListOverlayV2 {
178
+ type = 'inline';
179
+ id = 'tools-overlay-v2';
180
+ constructor(options) {
181
+ // Sort tools alphabetically
182
+ const sortedTools = [...options.tools].sort((a, b) => a.name.localeCompare(b.name));
183
+ super({
184
+ title: 'Available Tools',
185
+ tabs: TOOL_TABS,
186
+ items: sortedTools,
187
+ pageSize: PAGE_SIZE,
188
+ filterByTab: (tool, tabId) => toolMatchesCategory(tool.name, tabId),
189
+ getSearchText: (tool) => `${tool.name} ${tool.description ?? ''}`,
190
+ renderItem: (tool, isSelected, styles) => {
191
+ const cols = terminal.getTerminalWidth();
192
+ const descWidth = Math.max(20, cols - NAME_WIDTH - 10);
193
+ const cursor = isSelected ? styles.primary('> ') : ' ';
194
+ const name = isSelected
195
+ ? styles.primary(tool.name.padEnd(NAME_WIDTH))
196
+ : styles.muted(tool.name.padEnd(NAME_WIDTH));
197
+ // Normalize and truncate description
198
+ const normalizedDesc = normalizeText(tool.description ?? '');
199
+ const desc = normalizedDesc.length > descWidth
200
+ ? normalizedDesc.slice(0, descWidth - 1) + '...'
201
+ : normalizedDesc;
202
+ const descColor = isSelected ? styles.secondary(desc) : styles.muted(desc);
203
+ return ` ${cursor}${name} ${descColor}`;
204
+ },
205
+ footerHints: (searchMode) => {
206
+ if (searchMode) {
207
+ return 'Type to filter · ↑↓/jk Navigate · Enter Details · Esc Exit search';
208
+ }
209
+ return 'Tab Filter · ↑↓/jk · ←→/hl Pages · / Search · Enter Details · q/Esc Close';
210
+ },
211
+ emptyMessage: 'No tools available.',
212
+ noResultsMessage: 'No tools match the current filter.',
213
+ });
214
+ }
215
+ createDetailScreen(tool) {
216
+ return new ToolDetailScreen(tool, this.getStyles());
217
+ }
218
+ }
@@ -1,8 +1,13 @@
1
1
  /**
2
2
  * Tools Overlay
3
3
  *
4
- * Displays available agent tools in a scrollable list with descriptions.
5
- * Supports detail view for full tool information.
4
+ * Displays available agent tools in a categorized, searchable list.
5
+ * Uses TabbedListOverlay for consistent UX patterns.
6
+ *
7
+ * Features:
8
+ * - Tabs: All, Files, Code, Git, Project, Agent
9
+ * - Search with / toggle
10
+ * - Detail view for full tool information
6
11
  */
7
12
  export interface ToolParameter {
8
13
  name: string;
@@ -23,4 +28,7 @@ export interface ToolInfo {
23
28
  export interface ToolsOverlayResult {
24
29
  dismissed: boolean;
25
30
  }
31
+ /**
32
+ * Show the tools overlay
33
+ */
26
34
  export declare function showToolsOverlay(tools: ToolInfo[]): Promise<ToolsOverlayResult>;
@@ -1,161 +1,121 @@
1
1
  /**
2
2
  * Tools Overlay
3
3
  *
4
- * Displays available agent tools in a scrollable list with descriptions.
5
- * Supports detail view for full tool information.
4
+ * Displays available agent tools in a categorized, searchable list.
5
+ * Uses TabbedListOverlay for consistent UX patterns.
6
+ *
7
+ * Features:
8
+ * - Tabs: All, Files, Code, Git, Project, Agent
9
+ * - Search with / toggle
10
+ * - Detail view for full tool information
6
11
  */
7
12
  import * as terminal from './terminal.js';
8
- import { getStyles } from '../themes/index.js';
13
+ import { TabbedListOverlay, BaseScreen, stay, popScreen, closeOverlay, isCtrlC, isClose, isEscape, renderBorder, } from './base/index.js';
14
+ // =============================================================================
15
+ // Tab Definitions & Tool Categories
16
+ // =============================================================================
17
+ const TOOL_TABS = [
18
+ { id: 'all', label: 'All' },
19
+ { id: 'files', label: 'Files' },
20
+ { id: 'code', label: 'Code' },
21
+ { id: 'git', label: 'Git' },
22
+ { id: 'project', label: 'Project' },
23
+ { id: 'agent', label: 'Agent' },
24
+ ];
25
+ /**
26
+ * Tool category mappings.
27
+ * Uses exact names or prefix patterns (ending with *).
28
+ */
29
+ const TOOL_CATEGORIES = {
30
+ all: null, // No filter - show all tools
31
+ files: ['read_file', 'write_file', 'edit', 'glob', 'readFile', 'writeFile'],
32
+ code: ['grep', 'bash', 'bashOutput', 'run_tests', 'run_lint', 'detect_project', 'find_project_root', 'detectProject', 'findProjectRoot'],
33
+ git: ['git_*', 'gitStatus', 'gitDiff', 'gitLog', 'gitCommit', 'gitBranch'],
34
+ project: ['project_*', 'workitem_*', 'document_*', 'anchor_*'],
35
+ agent: ['task', 'todo_write', 'todo_read', 'todoWrite', 'todoRead', 'suggest', 'ask_user', 'ask_user_simple', 'askUser', 'askUserSimple', 'webFetch'],
36
+ };
37
+ /**
38
+ * Check if a tool matches a category filter.
39
+ */
40
+ function toolMatchesCategory(toolName, categoryId) {
41
+ const patterns = TOOL_CATEGORIES[categoryId];
42
+ if (patterns === null)
43
+ return true; // 'all' category
44
+ for (const pattern of patterns) {
45
+ if (pattern.endsWith('*')) {
46
+ // Prefix match
47
+ const prefix = pattern.slice(0, -1);
48
+ if (toolName.startsWith(prefix))
49
+ return true;
50
+ }
51
+ else {
52
+ // Exact match
53
+ if (toolName === pattern)
54
+ return true;
55
+ }
56
+ }
57
+ return false;
58
+ }
9
59
  // =============================================================================
10
60
  // Constants
11
61
  // =============================================================================
12
- /** Number of tools visible at once */
13
- const VISIBLE_ROWS = 12;
14
- /** Width for tool name column */
15
- const NAME_WIDTH = 18;
16
- /** Max description length in detail view */
62
+ const PAGE_SIZE = 12;
63
+ const NAME_WIDTH = 26;
17
64
  const MAX_DESCRIPTION_LENGTH = 300;
18
- /** Max parameters to show in detail view */
19
65
  const MAX_PARAMETERS_SHOWN = 5;
20
- // =============================================================================
21
- // Main Function
22
- // =============================================================================
23
- export async function showToolsOverlay(tools) {
24
- const s = getStyles();
25
- const stdin = process.stdin;
26
- // Sort tools alphabetically
27
- const sortedTools = [...tools].sort((a, b) => a.name.localeCompare(b.name));
28
- // State
29
- let selectedIndex = 0;
30
- let scrollOffset = 0;
31
- let lineCount = 0;
32
- let mode = 'list';
33
- // Get terminal width for layout
34
- const termWidth = terminal.getTerminalWidth();
35
- const descWidth = Math.max(20, termWidth - NAME_WIDTH - 10); // 10 for padding/cursor
36
- // Helper to truncate text
37
- const truncate = (text, maxLen) => {
38
- if (text.length <= maxLen)
39
- return text;
40
- return text.slice(0, maxLen - 1) + '…';
41
- };
42
- // Helper to get border
43
- const getBorder = () => {
44
- return s.muted('─'.repeat(Math.max(1, termWidth - 1)));
45
- };
46
- // Helper to wrap text to multiple lines
47
- const wrapText = (text, maxWidth) => {
48
- const words = text.split(' ');
49
- const lines = [];
50
- let currentLine = '';
51
- for (const word of words) {
52
- if (currentLine.length + word.length + 1 <= maxWidth) {
53
- currentLine += (currentLine ? ' ' : '') + word;
54
- }
55
- else {
56
- if (currentLine)
57
- lines.push(currentLine);
58
- currentLine = word;
59
- }
60
- }
61
- if (currentLine)
62
- lines.push(currentLine);
63
- return lines;
64
- };
65
- // Render list view
66
- const renderList = () => {
67
- // Clear previous render
68
- if (lineCount > 0) {
69
- terminal.clearLinesAbove(lineCount);
70
- }
71
- const lines = [];
72
- // Header
73
- lines.push(getBorder());
74
- const title = `Available Tools`;
75
- const countBadge = s.muted(`[${String(sortedTools.length)} tools]`);
76
- lines.push(` ${s.primaryBold(title)} ${countBadge}`);
77
- lines.push('');
78
- // Column headers
79
- const nameHeader = s.muted('Tool'.padEnd(NAME_WIDTH));
80
- const descHeader = s.muted('Description');
81
- lines.push(` ${nameHeader} ${descHeader}`);
82
- lines.push(` ${s.muted('─'.repeat(NAME_WIDTH))} ${s.muted('─'.repeat(Math.min(descWidth, 40)))}`);
83
- // Visible tools
84
- const visibleTools = sortedTools.slice(scrollOffset, scrollOffset + VISIBLE_ROWS);
85
- for (let i = 0; i < visibleTools.length; i++) {
86
- const tool = visibleTools[i];
87
- const absoluteIndex = scrollOffset + i;
88
- const isSelected = absoluteIndex === selectedIndex;
89
- const cursor = isSelected ? s.primary('❯ ') : ' ';
90
- const name = isSelected
91
- ? s.primaryBold(tool.name.padEnd(NAME_WIDTH))
92
- : s.secondary(tool.name.padEnd(NAME_WIDTH));
93
- const desc = truncate(tool.description ?? '', descWidth);
94
- const descColor = isSelected ? s.secondary(desc) : s.muted(desc);
95
- lines.push(` ${cursor}${name} ${descColor}`);
96
- }
97
- // Padding if fewer tools than VISIBLE_ROWS
98
- const emptyRows = VISIBLE_ROWS - visibleTools.length;
99
- for (let i = 0; i < emptyRows; i++) {
100
- lines.push('');
101
- }
102
- // Scroll indicator
103
- lines.push('');
104
- if (sortedTools.length > VISIBLE_ROWS) {
105
- const scrollInfo = `${String(scrollOffset + 1)}-${String(Math.min(scrollOffset + VISIBLE_ROWS, sortedTools.length))} of ${String(sortedTools.length)}`;
106
- const canUp = scrollOffset > 0;
107
- const canDown = scrollOffset + VISIBLE_ROWS < sortedTools.length;
108
- const arrows = (canUp ? '↑' : ' ') + (canDown ? '↓' : ' ');
109
- lines.push(` ${s.muted(scrollInfo)} ${s.muted(arrows)}`);
110
- }
111
- // Footer
112
- lines.push(getBorder());
113
- lines.push(` ${s.muted('↑↓ Navigate · Enter Details · Esc Close')}`);
114
- lines.push(getBorder());
115
- // Print all lines
116
- terminal.write(lines.join('\n'));
117
- return lines.length;
118
- };
119
- // Render detail view
120
- const renderDetail = () => {
121
- // Clear previous render
122
- if (lineCount > 0) {
123
- terminal.clearLinesAbove(lineCount);
124
- }
125
- const tool = sortedTools[selectedIndex];
66
+ /**
67
+ * Normalize text by replacing newlines with spaces (for single-line display)
68
+ */
69
+ function normalizeText(text) {
70
+ return text.replace(/\n+/g, ' ').replace(/\s+/g, ' ').trim();
71
+ }
72
+ /**
73
+ * Detail screen - shows full information for selected tool
74
+ */
75
+ class ToolDetailScreen extends BaseScreen {
76
+ tool;
77
+ styles;
78
+ constructor(tool, styles) {
79
+ super();
80
+ this.tool = tool;
81
+ this.styles = styles;
82
+ }
83
+ render() {
84
+ const s = this.styles;
85
+ const cols = terminal.getTerminalWidth();
86
+ const border = renderBorder(cols, s);
126
87
  const lines = [];
127
88
  // Header
128
- lines.push(getBorder());
89
+ lines.push(border);
129
90
  lines.push(` ${s.primaryBold('Tool Details')}`);
130
91
  lines.push('');
131
92
  // Tool name
132
- lines.push(` ${s.muted('Name:')} ${s.primaryBold(tool.name)}`);
93
+ lines.push(` ${s.muted('Name:')} ${s.primaryBold(this.tool.name)}`);
133
94
  lines.push('');
134
- // Permission info (if any)
135
- if (tool.permission) {
136
- const levelColor = tool.permission.level === 'once' ? s.warning : s.muted;
137
- lines.push(` ${s.muted('Permission:')} ${levelColor(tool.permission.level)} ${s.muted(`(${tool.permission.description})`)}`);
138
- lines.push('');
95
+ // Permission info
96
+ if (this.tool.permission) {
97
+ const levelColor = this.tool.permission.level === 'once' ? s.warning : s.muted;
98
+ lines.push(` ${s.muted('Permission:')} ${levelColor(this.tool.permission.level)} ${s.muted(`(${this.tool.permission.description})`)}`);
139
99
  }
140
100
  else {
141
101
  lines.push(` ${s.muted('Permission:')} ${s.success('always')} ${s.muted('(no approval required)')}`);
142
- lines.push('');
143
102
  }
144
- // Description (truncated if too long)
103
+ lines.push('');
104
+ // Description (normalize newlines for proper wrapping)
145
105
  lines.push(` ${s.muted('Description:')}`);
146
- let description = tool.description ?? 'No description available.';
106
+ let description = normalizeText(this.tool.description ?? 'No description available.');
147
107
  if (description.length > MAX_DESCRIPTION_LENGTH) {
148
108
  description = description.slice(0, MAX_DESCRIPTION_LENGTH).trim() + '...';
149
109
  }
150
- const wrappedDesc = wrapText(description, termWidth - 6);
110
+ const wrappedDesc = this.wrapText(description, cols - 6);
151
111
  for (const line of wrappedDesc) {
152
112
  lines.push(` ${s.secondary(line)}`);
153
113
  }
154
114
  lines.push('');
155
- // Parameters (limited to avoid verbose displays)
156
- if (tool.parameters && tool.parameters.length > 0) {
115
+ // Parameters
116
+ if (this.tool.parameters && this.tool.parameters.length > 0) {
157
117
  // Sort: required first, then optional
158
- const sorted = [...tool.parameters].sort((a, b) => {
118
+ const sorted = [...this.tool.parameters].sort((a, b) => {
159
119
  if (a.required && !b.required)
160
120
  return -1;
161
121
  if (!a.required && b.required)
@@ -180,99 +140,91 @@ export async function showToolsOverlay(tools) {
180
140
  lines.push(` ${s.muted('Parameters:')} ${s.muted('None')}`);
181
141
  }
182
142
  // Footer
183
- lines.push(getBorder());
184
- lines.push(` ${s.muted('Esc Back to list')}`);
185
- lines.push(getBorder());
186
- // Print all lines
187
- terminal.write(lines.join('\n'));
188
- return lines.length;
189
- };
190
- // Render based on mode
191
- const render = () => {
192
- if (mode === 'detail') {
193
- return renderDetail();
194
- }
195
- return renderList();
196
- };
197
- // Ensure selected item is visible
198
- const ensureVisible = () => {
199
- if (selectedIndex < scrollOffset) {
200
- scrollOffset = selectedIndex;
143
+ lines.push(border);
144
+ lines.push(` ${s.muted('q/Esc Back')}`);
145
+ lines.push(border);
146
+ return lines;
147
+ }
148
+ handleKey(data) {
149
+ // Ctrl+C closes overlay completely
150
+ if (isCtrlC(data)) {
151
+ return closeOverlay({ dismissed: true });
201
152
  }
202
- else if (selectedIndex >= scrollOffset + VISIBLE_ROWS) {
203
- scrollOffset = selectedIndex - VISIBLE_ROWS + 1;
153
+ // Esc or q goes back to list
154
+ if (isEscape(data) || isClose(data)) {
155
+ return popScreen();
204
156
  }
205
- };
206
- // Initial render
207
- lineCount = render();
208
- // Set up raw mode for keyboard input
209
- const wasRaw = stdin.isRaw;
210
- stdin.setRawMode(true);
211
- stdin.resume();
212
- return new Promise((resolve) => {
213
- const cleanup = () => {
214
- stdin.removeListener('data', handleKey);
215
- stdin.setRawMode(wasRaw);
216
- // Clear the overlay
217
- terminal.clearLinesAbove(lineCount);
218
- };
219
- const handleKey = (data) => {
220
- const key = data.toString();
221
- // Handle key presses
222
- const isEscape = key === '\x1B' && data.length === 1;
223
- const isEnter = key === '\r' || key === '\n';
224
- const isUpArrow = key === '\x1B[A';
225
- const isDownArrow = key === '\x1B[B';
226
- const isPageUp = key === '\x1B[5~';
227
- const isPageDown = key === '\x1B[6~';
228
- const isCtrlC = key === '\x03';
229
- const isQ = key === 'q' || key === 'Q';
230
- if (isCtrlC) {
231
- cleanup();
232
- resolve({ dismissed: true });
233
- return;
234
- }
235
- if (mode === 'detail') {
236
- // Detail view: Esc goes back to list
237
- if (isEscape || isQ) {
238
- mode = 'list';
239
- lineCount = render();
240
- }
241
- return;
242
- }
243
- // List view
244
- if (isEscape || isQ) {
245
- cleanup();
246
- resolve({ dismissed: true });
247
- return;
248
- }
249
- if (isEnter) {
250
- mode = 'detail';
251
- lineCount = render();
252
- return;
253
- }
254
- // Navigation
255
- if (isUpArrow && selectedIndex > 0) {
256
- selectedIndex--;
257
- ensureVisible();
258
- lineCount = render();
259
- }
260
- else if (isDownArrow && selectedIndex < sortedTools.length - 1) {
261
- selectedIndex++;
262
- ensureVisible();
263
- lineCount = render();
264
- }
265
- else if (isPageUp) {
266
- selectedIndex = Math.max(0, selectedIndex - VISIBLE_ROWS);
267
- ensureVisible();
268
- lineCount = render();
157
+ return stay(false);
158
+ }
159
+ wrapText(text, maxWidth) {
160
+ const words = text.split(' ');
161
+ const resultLines = [];
162
+ let currentLine = '';
163
+ for (const word of words) {
164
+ if (currentLine.length + word.length + 1 <= maxWidth) {
165
+ currentLine += (currentLine ? ' ' : '') + word;
269
166
  }
270
- else if (isPageDown) {
271
- selectedIndex = Math.min(sortedTools.length - 1, selectedIndex + VISIBLE_ROWS);
272
- ensureVisible();
273
- lineCount = render();
167
+ else {
168
+ if (currentLine)
169
+ resultLines.push(currentLine);
170
+ currentLine = word;
274
171
  }
275
- };
276
- stdin.on('data', handleKey);
277
- });
172
+ }
173
+ if (currentLine)
174
+ resultLines.push(currentLine);
175
+ return resultLines;
176
+ }
177
+ }
178
+ // =============================================================================
179
+ // Tools Overlay Class
180
+ // =============================================================================
181
+ class ToolsOverlay extends TabbedListOverlay {
182
+ constructor(tools) {
183
+ // Sort tools alphabetically
184
+ const sortedTools = [...tools].sort((a, b) => a.name.localeCompare(b.name));
185
+ super({
186
+ title: 'Available Tools',
187
+ tabs: TOOL_TABS,
188
+ items: sortedTools,
189
+ pageSize: PAGE_SIZE,
190
+ filterByTab: (tool, tabId) => toolMatchesCategory(tool.name, tabId),
191
+ getSearchText: (tool) => `${tool.name} ${tool.description ?? ''}`,
192
+ renderItem: (tool, isSelected, styles) => {
193
+ const cols = terminal.getTerminalWidth();
194
+ const descWidth = Math.max(20, cols - NAME_WIDTH - 10);
195
+ const cursor = isSelected ? styles.primary('❯ ') : ' ';
196
+ const name = isSelected
197
+ ? styles.primaryBold(tool.name.padEnd(NAME_WIDTH))
198
+ : styles.secondary(tool.name.padEnd(NAME_WIDTH));
199
+ // Normalize and truncate description
200
+ const normalizedDesc = normalizeText(tool.description ?? '');
201
+ const desc = normalizedDesc.length > descWidth
202
+ ? normalizedDesc.slice(0, descWidth - 1) + '…'
203
+ : normalizedDesc;
204
+ const descColor = isSelected ? styles.secondary(desc) : styles.muted(desc);
205
+ return ` ${cursor}${name} ${descColor}`;
206
+ },
207
+ footerHints: (searchMode) => {
208
+ if (searchMode) {
209
+ return 'Type to filter · ↑↓/jk Navigate · Enter Details · Esc Exit search';
210
+ }
211
+ return 'Tab Filter · ↑↓/jk · ←→/hl Pages · / Search · Enter Details · q/Esc Close';
212
+ },
213
+ emptyMessage: 'No tools available.',
214
+ noResultsMessage: 'No tools match the current filter.',
215
+ detailScreensFullScreen: false,
216
+ });
217
+ }
218
+ createDetailScreen(tool) {
219
+ return new ToolDetailScreen(tool, this.styles);
220
+ }
221
+ }
222
+ // =============================================================================
223
+ // Export Function
224
+ // =============================================================================
225
+ /**
226
+ * Show the tools overlay
227
+ */
228
+ export async function showToolsOverlay(tools) {
229
+ return new ToolsOverlay(tools).show();
278
230
  }