apex-code 0.0.1-alpha.7 → 0.0.1-alpha.9

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 (393) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/README.md +5 -0
  3. package/dist/cli/args.d.ts +4 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +44 -9
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/concurrent-session.d.ts +3 -0
  8. package/dist/cli/concurrent-session.d.ts.map +1 -0
  9. package/dist/cli/concurrent-session.js +13 -0
  10. package/dist/cli/concurrent-session.js.map +1 -0
  11. package/dist/cli/file-processor.d.ts.map +1 -1
  12. package/dist/cli/file-processor.js +2 -1
  13. package/dist/cli/file-processor.js.map +1 -1
  14. package/dist/cli.d.ts.map +1 -1
  15. package/dist/cli.js +1 -2
  16. package/dist/cli.js.map +1 -1
  17. package/dist/config.d.ts +3 -2
  18. package/dist/config.d.ts.map +1 -1
  19. package/dist/config.js +21 -13
  20. package/dist/config.js.map +1 -1
  21. package/dist/core/agent-session-services.d.ts.map +1 -1
  22. package/dist/core/agent-session-services.js +20 -0
  23. package/dist/core/agent-session-services.js.map +1 -1
  24. package/dist/core/agent-session.d.ts +41 -9
  25. package/dist/core/agent-session.d.ts.map +1 -1
  26. package/dist/core/agent-session.js +172 -101
  27. package/dist/core/agent-session.js.map +1 -1
  28. package/dist/core/auth-storage.d.ts +4 -1
  29. package/dist/core/auth-storage.d.ts.map +1 -1
  30. package/dist/core/auth-storage.js +12 -9
  31. package/dist/core/auth-storage.js.map +1 -1
  32. package/dist/core/bash-executor.d.ts.map +1 -1
  33. package/dist/core/bash-executor.js +10 -9
  34. package/dist/core/bash-executor.js.map +1 -1
  35. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  36. package/dist/core/compaction/branch-summarization.js +3 -0
  37. package/dist/core/compaction/branch-summarization.js.map +1 -1
  38. package/dist/core/compaction/compaction.d.ts +4 -3
  39. package/dist/core/compaction/compaction.d.ts.map +1 -1
  40. package/dist/core/compaction/compaction.js +41 -32
  41. package/dist/core/compaction/compaction.js.map +1 -1
  42. package/dist/core/defaults.d.ts +1 -0
  43. package/dist/core/defaults.d.ts.map +1 -1
  44. package/dist/core/defaults.js +9 -0
  45. package/dist/core/defaults.js.map +1 -1
  46. package/dist/core/durable-state/sqlite.d.ts.map +1 -1
  47. package/dist/core/durable-state/sqlite.js +26 -2
  48. package/dist/core/durable-state/sqlite.js.map +1 -1
  49. package/dist/core/experimental.d.ts +4 -0
  50. package/dist/core/experimental.d.ts.map +1 -1
  51. package/dist/core/experimental.js +4 -0
  52. package/dist/core/experimental.js.map +1 -1
  53. package/dist/core/extensions/index.d.ts +2 -2
  54. package/dist/core/extensions/index.d.ts.map +1 -1
  55. package/dist/core/extensions/index.js +1 -1
  56. package/dist/core/extensions/index.js.map +1 -1
  57. package/dist/core/extensions/loader.d.ts.map +1 -1
  58. package/dist/core/extensions/loader.js +115 -48
  59. package/dist/core/extensions/loader.js.map +1 -1
  60. package/dist/core/extensions/types.d.ts +42 -9
  61. package/dist/core/extensions/types.d.ts.map +1 -1
  62. package/dist/core/extensions/types.js +3 -0
  63. package/dist/core/extensions/types.js.map +1 -1
  64. package/dist/core/footer-data-provider.d.ts +1 -1
  65. package/dist/core/footer-data-provider.d.ts.map +1 -1
  66. package/dist/core/footer-data-provider.js +1 -1
  67. package/dist/core/footer-data-provider.js.map +1 -1
  68. package/dist/core/http-dispatcher.d.ts +1 -2
  69. package/dist/core/http-dispatcher.d.ts.map +1 -1
  70. package/dist/core/http-dispatcher.js +2 -17
  71. package/dist/core/http-dispatcher.js.map +1 -1
  72. package/dist/core/http-idle-timeout.d.ts +12 -0
  73. package/dist/core/http-idle-timeout.d.ts.map +1 -0
  74. package/dist/core/http-idle-timeout.js +27 -0
  75. package/dist/core/http-idle-timeout.js.map +1 -0
  76. package/dist/core/keybindings.d.ts +38 -13
  77. package/dist/core/keybindings.d.ts.map +1 -1
  78. package/dist/core/keybindings.js +26 -5
  79. package/dist/core/keybindings.js.map +1 -1
  80. package/dist/core/lsp/client.d.ts +2 -0
  81. package/dist/core/lsp/client.d.ts.map +1 -1
  82. package/dist/core/lsp/client.js +27 -2
  83. package/dist/core/lsp/client.js.map +1 -1
  84. package/dist/core/model-config.d.ts +10 -0
  85. package/dist/core/model-config.d.ts.map +1 -1
  86. package/dist/core/model-config.js +4 -1
  87. package/dist/core/model-config.js.map +1 -1
  88. package/dist/core/model-resolver.d.ts +1 -0
  89. package/dist/core/model-resolver.d.ts.map +1 -1
  90. package/dist/core/model-resolver.js +14 -8
  91. package/dist/core/model-resolver.js.map +1 -1
  92. package/dist/core/models-store.d.ts.map +1 -1
  93. package/dist/core/models-store.js +2 -1
  94. package/dist/core/models-store.js.map +1 -1
  95. package/dist/core/package-manager.d.ts.map +1 -1
  96. package/dist/core/package-manager.js +22 -14
  97. package/dist/core/package-manager.js.map +1 -1
  98. package/dist/core/pi-manifest.d.ts.map +1 -1
  99. package/dist/core/pi-manifest.js +2 -1
  100. package/dist/core/pi-manifest.js.map +1 -1
  101. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  102. package/dist/core/remote-catalog-provider.js +2 -1
  103. package/dist/core/remote-catalog-provider.js.map +1 -1
  104. package/dist/core/resource-loader.d.ts.map +1 -1
  105. package/dist/core/resource-loader.js +3 -2
  106. package/dist/core/resource-loader.js.map +1 -1
  107. package/dist/core/sandbox/child-entry.d.ts.map +1 -1
  108. package/dist/core/sandbox/child-entry.js +8 -1
  109. package/dist/core/sandbox/child-entry.js.map +1 -1
  110. package/dist/core/sandbox/cli-launch.d.ts +3 -0
  111. package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
  112. package/dist/core/sandbox/cli-launch.js +25 -4
  113. package/dist/core/sandbox/cli-launch.js.map +1 -1
  114. package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
  115. package/dist/core/sandbox/cli-supervisor.js +53 -13
  116. package/dist/core/sandbox/cli-supervisor.js.map +1 -1
  117. package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
  118. package/dist/core/sandbox/linux-backend.js +15 -0
  119. package/dist/core/sandbox/linux-backend.js.map +1 -1
  120. package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
  121. package/dist/core/sandbox/macos-backend.js +24 -1
  122. package/dist/core/sandbox/macos-backend.js.map +1 -1
  123. package/dist/core/sandbox/rpc/credential-client.d.ts +20 -0
  124. package/dist/core/sandbox/rpc/credential-client.d.ts.map +1 -0
  125. package/dist/core/sandbox/rpc/credential-client.js +170 -0
  126. package/dist/core/sandbox/rpc/credential-client.js.map +1 -0
  127. package/dist/core/sandbox/rpc/credential-proxy.d.ts +44 -0
  128. package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -0
  129. package/dist/core/sandbox/rpc/credential-proxy.js +381 -0
  130. package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -0
  131. package/dist/core/sandbox/supervisor.d.ts +11 -0
  132. package/dist/core/sandbox/supervisor.d.ts.map +1 -1
  133. package/dist/core/sandbox/supervisor.js.map +1 -1
  134. package/dist/core/sandbox/terminal-size.d.ts +40 -0
  135. package/dist/core/sandbox/terminal-size.d.ts.map +1 -0
  136. package/dist/core/sandbox/terminal-size.js +105 -0
  137. package/dist/core/sandbox/terminal-size.js.map +1 -0
  138. package/dist/core/sdk.d.ts +8 -5
  139. package/dist/core/sdk.d.ts.map +1 -1
  140. package/dist/core/sdk.js +26 -7
  141. package/dist/core/sdk.js.map +1 -1
  142. package/dist/core/session-export.d.ts +4 -0
  143. package/dist/core/session-export.d.ts.map +1 -0
  144. package/dist/core/session-export.js +32 -0
  145. package/dist/core/session-export.js.map +1 -0
  146. package/dist/core/session-lease.d.ts +31 -0
  147. package/dist/core/session-lease.d.ts.map +1 -0
  148. package/dist/core/session-lease.js +138 -0
  149. package/dist/core/session-lease.js.map +1 -0
  150. package/dist/core/session-manager.d.ts +5 -0
  151. package/dist/core/session-manager.d.ts.map +1 -1
  152. package/dist/core/session-manager.js +5 -4
  153. package/dist/core/session-manager.js.map +1 -1
  154. package/dist/core/settings-diagnostics.d.ts +9 -0
  155. package/dist/core/settings-diagnostics.d.ts.map +1 -0
  156. package/dist/core/settings-diagnostics.js +21 -0
  157. package/dist/core/settings-diagnostics.js.map +1 -0
  158. package/dist/core/settings-manager.d.ts +35 -0
  159. package/dist/core/settings-manager.d.ts.map +1 -1
  160. package/dist/core/settings-manager.js +93 -12
  161. package/dist/core/settings-manager.js.map +1 -1
  162. package/dist/core/skills.d.ts.map +1 -1
  163. package/dist/core/skills.js +51 -33
  164. package/dist/core/skills.js.map +1 -1
  165. package/dist/core/slash-commands.d.ts.map +1 -1
  166. package/dist/core/slash-commands.js +4 -1
  167. package/dist/core/slash-commands.js.map +1 -1
  168. package/dist/core/system-prompt.d.ts +2 -2
  169. package/dist/core/system-prompt.d.ts.map +1 -1
  170. package/dist/core/system-prompt.js +53 -62
  171. package/dist/core/system-prompt.js.map +1 -1
  172. package/dist/core/tools/bash.d.ts +14 -1
  173. package/dist/core/tools/bash.d.ts.map +1 -1
  174. package/dist/core/tools/bash.js +38 -20
  175. package/dist/core/tools/bash.js.map +1 -1
  176. package/dist/core/tools/delegate.d.ts.map +1 -1
  177. package/dist/core/tools/delegate.js +24 -0
  178. package/dist/core/tools/delegate.js.map +1 -1
  179. package/dist/core/tools/edit-diff.d.ts +0 -5
  180. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  181. package/dist/core/tools/edit-diff.js +2 -5
  182. package/dist/core/tools/edit-diff.js.map +1 -1
  183. package/dist/core/tools/edit.d.ts.map +1 -1
  184. package/dist/core/tools/edit.js +22 -4
  185. package/dist/core/tools/edit.js.map +1 -1
  186. package/dist/core/tools/find.d.ts.map +1 -1
  187. package/dist/core/tools/find.js +1 -1
  188. package/dist/core/tools/find.js.map +1 -1
  189. package/dist/core/tools/grep.d.ts +2 -0
  190. package/dist/core/tools/grep.d.ts.map +1 -1
  191. package/dist/core/tools/grep.js +35 -10
  192. package/dist/core/tools/grep.js.map +1 -1
  193. package/dist/core/tools/index.d.ts +4 -1
  194. package/dist/core/tools/index.d.ts.map +1 -1
  195. package/dist/core/tools/index.js +9 -0
  196. package/dist/core/tools/index.js.map +1 -1
  197. package/dist/core/tools/ls.d.ts +2 -0
  198. package/dist/core/tools/ls.d.ts.map +1 -1
  199. package/dist/core/tools/ls.js +5 -1
  200. package/dist/core/tools/ls.js.map +1 -1
  201. package/dist/core/tools/powershell.d.ts +16 -0
  202. package/dist/core/tools/powershell.d.ts.map +1 -0
  203. package/dist/core/tools/powershell.js +39 -0
  204. package/dist/core/tools/powershell.js.map +1 -0
  205. package/dist/core/tools/read.d.ts.map +1 -1
  206. package/dist/core/tools/read.js +2 -0
  207. package/dist/core/tools/read.js.map +1 -1
  208. package/dist/core/tools/web-search-exa.d.ts +33 -0
  209. package/dist/core/tools/web-search-exa.d.ts.map +1 -0
  210. package/dist/core/tools/web-search-exa.js +128 -0
  211. package/dist/core/tools/web-search-exa.js.map +1 -0
  212. package/dist/core/tools/write.d.ts.map +1 -1
  213. package/dist/core/tools/write.js +2 -0
  214. package/dist/core/tools/write.js.map +1 -1
  215. package/dist/core/trust-manager.d.ts.map +1 -1
  216. package/dist/core/trust-manager.js +2 -1
  217. package/dist/core/trust-manager.js.map +1 -1
  218. package/dist/core/web-search-provider.d.ts +64 -0
  219. package/dist/core/web-search-provider.d.ts.map +1 -0
  220. package/dist/core/web-search-provider.js +149 -0
  221. package/dist/core/web-search-provider.js.map +1 -0
  222. package/dist/extensions/llama/index.d.ts.map +1 -1
  223. package/dist/extensions/llama/index.js +2 -0
  224. package/dist/extensions/llama/index.js.map +1 -1
  225. package/dist/extensions/llama/provider.d.ts.map +1 -1
  226. package/dist/extensions/llama/provider.js +6 -2
  227. package/dist/extensions/llama/provider.js.map +1 -1
  228. package/dist/index.d.ts +6 -6
  229. package/dist/index.d.ts.map +1 -1
  230. package/dist/index.js +4 -4
  231. package/dist/index.js.map +1 -1
  232. package/dist/main.d.ts +5 -0
  233. package/dist/main.d.ts.map +1 -1
  234. package/dist/main.js +38 -16
  235. package/dist/main.js.map +1 -1
  236. package/dist/migrations.d.ts.map +1 -1
  237. package/dist/migrations.js +4 -3
  238. package/dist/migrations.js.map +1 -1
  239. package/dist/modes/interactive/components/custom-editor.d.ts +23 -0
  240. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  241. package/dist/modes/interactive/components/custom-editor.js +105 -2
  242. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  243. package/dist/modes/interactive/components/dynamic-border.d.ts +7 -1
  244. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  245. package/dist/modes/interactive/components/dynamic-border.js +11 -2
  246. package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  247. package/dist/modes/interactive/components/extension-selector.d.ts +4 -0
  248. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  249. package/dist/modes/interactive/components/extension-selector.js +24 -6
  250. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  251. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
  252. package/dist/modes/interactive/components/first-time-setup.js +6 -3
  253. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  254. package/dist/modes/interactive/components/first-use-hints.d.ts +8 -0
  255. package/dist/modes/interactive/components/first-use-hints.d.ts.map +1 -0
  256. package/dist/modes/interactive/components/first-use-hints.js +22 -0
  257. package/dist/modes/interactive/components/first-use-hints.js.map +1 -0
  258. package/dist/modes/interactive/components/footer.d.ts +12 -0
  259. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  260. package/dist/modes/interactive/components/footer.js +128 -9
  261. package/dist/modes/interactive/components/footer.js.map +1 -1
  262. package/dist/modes/interactive/components/index.d.ts +1 -0
  263. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  264. package/dist/modes/interactive/components/index.js +1 -0
  265. package/dist/modes/interactive/components/index.js.map +1 -1
  266. package/dist/modes/interactive/components/model-selector.d.ts +12 -3
  267. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  268. package/dist/modes/interactive/components/model-selector.js +71 -14
  269. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  270. package/dist/modes/interactive/components/settings-selector.d.ts +10 -3
  271. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  272. package/dist/modes/interactive/components/settings-selector.js +128 -56
  273. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  274. package/dist/modes/interactive/components/settings-submenu.d.ts +72 -0
  275. package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -0
  276. package/dist/modes/interactive/components/settings-submenu.js +165 -0
  277. package/dist/modes/interactive/components/settings-submenu.js.map +1 -0
  278. package/dist/modes/interactive/components/splash-header.d.ts +29 -4
  279. package/dist/modes/interactive/components/splash-header.d.ts.map +1 -1
  280. package/dist/modes/interactive/components/splash-header.js +58 -23
  281. package/dist/modes/interactive/components/splash-header.js.map +1 -1
  282. package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
  283. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  284. package/dist/modes/interactive/components/thinking-selector.js +74 -17
  285. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  286. package/dist/modes/interactive/components/tool-execution.d.ts +14 -0
  287. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  288. package/dist/modes/interactive/components/tool-execution.js +113 -35
  289. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  290. package/dist/modes/interactive/components/tool-panel.d.ts +30 -0
  291. package/dist/modes/interactive/components/tool-panel.d.ts.map +1 -0
  292. package/dist/modes/interactive/components/tool-panel.js +130 -0
  293. package/dist/modes/interactive/components/tool-panel.js.map +1 -0
  294. package/dist/modes/interactive/external-editor.d.ts.map +1 -1
  295. package/dist/modes/interactive/external-editor.js +2 -1
  296. package/dist/modes/interactive/external-editor.js.map +1 -1
  297. package/dist/modes/interactive/interactive-mode.d.ts +55 -9
  298. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  299. package/dist/modes/interactive/interactive-mode.js +446 -87
  300. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  301. package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
  302. package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
  303. package/dist/modes/interactive/model-catalog-refresh.js +34 -0
  304. package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
  305. package/dist/modes/interactive/theme/apex.json +62 -63
  306. package/dist/modes/interactive/theme/dark.json +2 -0
  307. package/dist/modes/interactive/theme/light.json +2 -0
  308. package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
  309. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  310. package/dist/modes/interactive/theme/theme-controller.js +25 -11
  311. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  312. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  313. package/dist/modes/interactive/theme/theme.d.ts +14 -3
  314. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  315. package/dist/modes/interactive/theme/theme.js +60 -10
  316. package/dist/modes/interactive/theme/theme.js.map +1 -1
  317. package/dist/modes/json-event.d.ts +18 -12
  318. package/dist/modes/json-event.d.ts.map +1 -1
  319. package/dist/modes/json-event.js +22 -5
  320. package/dist/modes/json-event.js.map +1 -1
  321. package/dist/package-manager-cli.d.ts +1 -0
  322. package/dist/package-manager-cli.d.ts.map +1 -1
  323. package/dist/package-manager-cli.js +197 -5
  324. package/dist/package-manager-cli.js.map +1 -1
  325. package/dist/server/create-harness.d.ts.map +1 -1
  326. package/dist/server/create-harness.js +2 -0
  327. package/dist/server/create-harness.js.map +1 -1
  328. package/dist/themes/apex-logo.d.ts +36 -16
  329. package/dist/themes/apex-logo.d.ts.map +1 -1
  330. package/dist/themes/apex-logo.js +32 -28
  331. package/dist/themes/apex-logo.js.map +1 -1
  332. package/dist/utils/frontmatter.d.ts.map +1 -1
  333. package/dist/utils/frontmatter.js +2 -1
  334. package/dist/utils/frontmatter.js.map +1 -1
  335. package/dist/utils/management-http.d.ts +5 -3
  336. package/dist/utils/management-http.d.ts.map +1 -1
  337. package/dist/utils/management-http.js +14 -10
  338. package/dist/utils/management-http.js.map +1 -1
  339. package/dist/utils/shell.d.ts +3 -0
  340. package/dist/utils/shell.d.ts.map +1 -1
  341. package/dist/utils/shell.js +17 -5
  342. package/dist/utils/shell.js.map +1 -1
  343. package/dist/utils/syntax-highlight.d.ts +1 -0
  344. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  345. package/dist/utils/syntax-highlight.js +60 -1
  346. package/dist/utils/syntax-highlight.js.map +1 -1
  347. package/dist/utils/text.d.ts +8 -0
  348. package/dist/utils/text.d.ts.map +1 -0
  349. package/dist/utils/text.js +9 -0
  350. package/dist/utils/text.js.map +1 -0
  351. package/dist/utils/tools-manager.d.ts +10 -1
  352. package/dist/utils/tools-manager.d.ts.map +1 -1
  353. package/dist/utils/tools-manager.js +15 -20
  354. package/dist/utils/tools-manager.js.map +1 -1
  355. package/docs/compaction.md +18 -3
  356. package/docs/custom-provider.md +6 -3
  357. package/docs/environment-variables.md +1 -0
  358. package/docs/extensions.md +21 -7
  359. package/docs/json.md +12 -5
  360. package/docs/keybindings.md +15 -9
  361. package/docs/llama-cpp.md +2 -0
  362. package/docs/models.md +8 -2
  363. package/docs/packages.md +2 -2
  364. package/docs/rpc.md +24 -7
  365. package/docs/sdk.md +16 -2
  366. package/docs/settings.md +73 -2
  367. package/docs/skills.md +4 -3
  368. package/docs/terminal-setup.md +34 -9
  369. package/docs/themes.md +24 -3
  370. package/docs/tui.md +2 -2
  371. package/docs/usage.md +9 -4
  372. package/docs/windows.md +24 -2
  373. package/examples/extensions/border-status-editor.ts +1 -1
  374. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  375. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  376. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  377. package/examples/extensions/git-checkpoint.ts +2 -2
  378. package/examples/extensions/gondolin/package-lock.json +2 -2
  379. package/examples/extensions/gondolin/package.json +1 -1
  380. package/examples/extensions/notify.ts +3 -1
  381. package/examples/extensions/sandbox/package-lock.json +2 -2
  382. package/examples/extensions/sandbox/package.json +1 -1
  383. package/examples/extensions/subagent/README.md +3 -1
  384. package/examples/extensions/subagent/agents.ts +40 -9
  385. package/examples/extensions/subagent/index.ts +27 -4
  386. package/examples/extensions/titlebar-spinner.ts +1 -1
  387. package/examples/extensions/with-deps/package-lock.json +2 -2
  388. package/examples/extensions/with-deps/package.json +1 -1
  389. package/examples/rpc-extension-ui.ts +1 -1
  390. package/examples/sdk/06-extensions.ts +1 -1
  391. package/examples/sdk/README.md +1 -1
  392. package/npm-shrinkwrap.json +280 -445
  393. package/package.json +9 -11
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-submenu.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/settings-submenu.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,SAAS,EAIT,KAAK,UAAU,EAEf,KAAK,uBAAuB,EAG5B,MAAM,wBAAwB,CAAC;AAQhC,MAAM,WAAW,oBAAoB;IACpC,6CAA6C;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uDAAuD;IACvD,MAAM,CAAC,EAAE,uBAAuB,CAAC;CACjC;AAED;;;GAGG;AACH,qBAAa,aAAc,SAAQ,SAAS;IAC3C,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,UAAU,CAAe;IACjC,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,mBAAmB,CAAC,CAA0B;IAEtD,YACC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,UAAU,EAAE,EACrB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACjC,QAAQ,EAAE,MAAM,IAAI,EACpB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EAC3C,cAAc,CAAC,EAAE,oBAAoB,EA2CrC;IAED,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,WAAW;IAUnB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAiB9B;CACD;AAMD,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IAClC,2FAA2F;IAC3F,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,CAAC;IAC9D,oEAAoE;IACpE,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,CAAC;IACpE,qFAAqF;IACrF,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC;IAC3D,6DAA6D;IAC7D,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC;IACpE,2DAA2D;IAC3D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qEAAqE;IACrE,MAAM,CAAC,EAAE,uBAAuB,CAAC;CACjC;AAED,UAAU,qBAAqB;IAC9B,4GAA4G;IAC5G,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,8EAA8E;IAC9E,IAAI,CAAC,EAAE,OAAO,CAAC;CACf;AAED;;;;;;GAMG;AACH,qBAAa,cAAe,SAAQ,SAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuB;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA4C;IACvE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;IACtC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAwB;IAC7C,OAAO,CAAC,eAAe,CAAY;IACnC,OAAO,CAAC,OAAO,CAAyB;IAExC,YACC,KAAK,EAAE,kBAAkB,EAAE,EAC3B,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,EACrD,QAAQ,EAAE,MAAM,IAAI,EACpB,IAAI,GAAE,qBAA0B,EAShC;IAED,OAAO,CAAC,SAAS;IA8CjB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAE9B;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE9B;IAED,UAAU,IAAI,IAAI,CAEjB;CACD","sourcesContent":["import {\n\ttype Component,\n\tContainer,\n\tfuzzyFilter,\n\tgetKeybindings,\n\tInput,\n\ttype SelectItem,\n\tSelectList,\n\ttype SelectListLayoutOptions,\n\tSpacer,\n\tText,\n} from \"@earendil-works/pi-tui\";\nimport { getSelectListTheme, theme } from \"../theme/theme.ts\";\n\nconst SUBMENU_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nexport interface SelectSubmenuOptions {\n\t/** Enable type-to-search fuzzy filtering. */\n\tsearchable?: boolean;\n\t/** Override the select list layout (column widths). */\n\tlayout?: SelectListLayoutOptions;\n}\n\n/**\n * Single-step submenu that shows a titled select list.\n * With `searchable: true`, typing filters the list using fuzzy matching.\n */\nexport class SelectSubmenu extends Container {\n\tprivate selectList: SelectList;\n\tprivate listChildIndex: number;\n\tprivate allOptions: SelectItem[];\n\tprivate listLayout: SelectListLayoutOptions;\n\tprivate searchInput: Input | undefined;\n\tprivate onSelectCb: (value: string) => void;\n\tprivate onCancelCb: () => void;\n\tprivate onSelectionChangeCb?: (value: string) => void;\n\n\tconstructor(\n\t\ttitle: string,\n\t\tdescription: string,\n\t\toptions: SelectItem[],\n\t\tcurrentValue: string,\n\t\tonSelect: (value: string) => void,\n\t\tonCancel: () => void,\n\t\tonSelectionChange?: (value: string) => void,\n\t\tsubmenuOptions?: SelectSubmenuOptions,\n\t) {\n\t\tsuper();\n\n\t\tthis.allOptions = options;\n\t\tthis.listLayout = submenuOptions?.layout ?? SUBMENU_SELECT_LIST_LAYOUT;\n\t\tthis.onSelectCb = onSelect;\n\t\tthis.onCancelCb = onCancel;\n\t\tthis.onSelectionChangeCb = onSelectionChange;\n\n\t\t// Title\n\t\tthis.addChild(new Text(theme.bold(theme.fg(\"accent\", title)), 0, 0));\n\n\t\t// Description\n\t\tif (description) {\n\t\t\tthis.addChild(new Spacer(1));\n\t\t\tthis.addChild(new Text(theme.fg(\"muted\", description), 0, 0));\n\t\t}\n\n\t\t// Search input\n\t\tif (submenuOptions?.searchable) {\n\t\t\tthis.addChild(new Spacer(1));\n\t\t\tthis.searchInput = new Input();\n\t\t\tthis.searchInput.onSubmit = () => {\n\t\t\t\tthis.selectList.handleInput(\"\\r\");\n\t\t\t};\n\t\t\tthis.addChild(this.searchInput);\n\t\t}\n\n\t\t// Spacer\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Select list\n\t\tthis.selectList = this.buildSelectList(options, currentValue);\n\t\tthis.listChildIndex = this.children.length;\n\t\tthis.addChild(this.selectList);\n\n\t\t// Hint\n\t\tthis.addChild(new Spacer(1));\n\t\tconst hint = submenuOptions?.searchable\n\t\t\t? \" Type to filter \\u00b7 Enter to select \\u00b7 Esc to go back\"\n\t\t\t: \" Enter to select \\u00b7 Esc to go back\";\n\t\tthis.addChild(new Text(theme.fg(\"dim\", hint), 0, 0));\n\t}\n\n\tprivate buildSelectList(options: SelectItem[], preselect: string): SelectList {\n\t\tconst list = new SelectList(options, Math.min(options.length, 10), getSelectListTheme(), this.listLayout);\n\n\t\tconst idx = options.findIndex((o) => o.value === preselect);\n\t\tif (idx !== -1) list.setSelectedIndex(idx);\n\n\t\tlist.onSelect = (item) => this.onSelectCb(item.value);\n\t\tlist.onCancel = this.onCancelCb;\n\t\tif (this.onSelectionChangeCb) {\n\t\t\tconst cb = this.onSelectionChangeCb;\n\t\t\tlist.onSelectionChange = (item) => cb(item.value);\n\t\t}\n\n\t\treturn list;\n\t}\n\n\tprivate applyFilter(query: string): void {\n\t\tconst filtered = query\n\t\t\t? fuzzyFilter(this.allOptions, query, (item) => `${item.label} ${item.description ?? \"\"}`)\n\t\t\t: this.allOptions;\n\n\t\tconst newList = this.buildSelectList(filtered, \"\");\n\t\tthis.children[this.listChildIndex] = newList;\n\t\tthis.selectList = newList;\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (this.searchInput) {\n\t\t\tconst kb = getKeybindings();\n\t\t\tconst isNav =\n\t\t\t\tkb.matches(data, \"tui.select.up\") ||\n\t\t\t\tkb.matches(data, \"tui.select.down\") ||\n\t\t\t\tkb.matches(data, \"tui.select.confirm\") ||\n\t\t\t\tkb.matches(data, \"tui.select.cancel\");\n\t\t\tif (isNav) {\n\t\t\t\tthis.selectList.handleInput(data);\n\t\t\t} else {\n\t\t\t\tthis.searchInput.handleInput(data);\n\t\t\t\tthis.applyFilter(this.searchInput.getValue());\n\t\t\t}\n\t\t} else {\n\t\t\tthis.selectList.handleInput(data);\n\t\t}\n\t}\n}\n\n// ============================================================================\n// SteppedSubmenu — reusable multi-step selector\n// ============================================================================\n\n/** One step in a {@link SteppedSubmenu}. */\nexport interface SteppedSubmenuStep {\n\t/** Unique key \\u2014 the selected value is stored in the result context under this key. */\n\tkey: string;\n\t/** Title shown at the top of the step. Receives prior selections. */\n\ttitle: string | ((context: Record<string, string>) => string);\n\t/** Description shown below the title. Receives prior selections. */\n\tdescription: string | ((context: Record<string, string>) => string);\n\t/** Build the option list for this step. Called fresh each time the step is shown. */\n\toptions: (context: Record<string, string>) => SelectItem[];\n\t/** Optionally pre-select a value when entering this step. */\n\tpreselect?: (context: Record<string, string>) => string | undefined;\n\t/** Enable type-to-search fuzzy filtering for this step. */\n\tsearchable?: boolean;\n\t/** Override the select list layout (column widths) for this step. */\n\tlayout?: SelectListLayoutOptions;\n}\n\ninterface SteppedSubmenuOptions {\n\t/** Start at this step index (0-based), skipping earlier steps. Requires initialContext for skipped keys. */\n\tstartAtStep?: number;\n\t/** Pre-fill selections for skipped steps. */\n\tinitialContext?: Record<string, string>;\n\t/** After completing the last step, loop back to step 0 instead of closing. */\n\tloop?: boolean;\n}\n\n/**\n * Generic N-step submenu built on top of {@link SelectSubmenu}.\n *\n * Each step's options can depend on prior selections via the shared context.\n * Esc goes back one step; Esc at step 0 cancels.\n * With `loop: true`, completing the final step invokes `onComplete` then returns to step 0.\n */\nexport class SteppedSubmenu extends Container {\n\tprivate readonly steps: SteppedSubmenuStep[];\n\tprivate readonly onComplete: (context: Record<string, string>) => void;\n\tprivate readonly onCancel: () => void;\n\tprivate readonly opts: SteppedSubmenuOptions;\n\tprivate activeComponent: Component;\n\tprivate context: Record<string, string>;\n\n\tconstructor(\n\t\tsteps: SteppedSubmenuStep[],\n\t\tonComplete: (context: Record<string, string>) => void,\n\t\tonCancel: () => void,\n\t\topts: SteppedSubmenuOptions = {},\n\t) {\n\t\tsuper();\n\t\tthis.steps = steps;\n\t\tthis.onComplete = onComplete;\n\t\tthis.onCancel = onCancel;\n\t\tthis.opts = opts;\n\t\tthis.context = { ...(opts.initialContext ?? {}) };\n\t\tthis.activeComponent = this.buildStep(opts.startAtStep ?? 0);\n\t}\n\n\tprivate buildStep(stepIndex: number): Component {\n\t\tconst step = this.steps[stepIndex];\n\t\tconst total = this.steps.length;\n\t\tconst stepLabel = total > 1 ? `Step ${stepIndex + 1}/${total} \\u00b7 ` : \"\";\n\n\t\tconst title = typeof step.title === \"function\" ? step.title(this.context) : step.title;\n\t\tconst desc = typeof step.description === \"function\" ? step.description(this.context) : step.description;\n\t\tconst items = step.options(this.context);\n\t\tconst preselect = step.preselect?.(this.context) ?? \"\";\n\n\t\treturn new SelectSubmenu(\n\t\t\ttitle,\n\t\t\t`${stepLabel}${desc}`,\n\t\t\titems,\n\t\t\tpreselect,\n\t\t\t(value) => {\n\t\t\t\tthis.context[step.key] = value;\n\n\t\t\t\tif (stepIndex < total - 1) {\n\t\t\t\t\t// Advance to next step\n\t\t\t\t\tthis.activeComponent = this.buildStep(stepIndex + 1);\n\t\t\t\t} else {\n\t\t\t\t\t// Final step \\u2014 deliver result\n\t\t\t\t\tthis.onComplete({ ...this.context });\n\n\t\t\t\t\tif (this.opts.loop) {\n\t\t\t\t\t\tthis.context = {};\n\t\t\t\t\t\tthis.activeComponent = this.buildStep(0);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.onCancel();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tif (stepIndex > 0) {\n\t\t\t\t\tdelete this.context[step.key];\n\t\t\t\t\tthis.activeComponent = this.buildStep(stepIndex - 1);\n\t\t\t\t} else {\n\t\t\t\t\tthis.onCancel();\n\t\t\t\t}\n\t\t\t},\n\t\t\tundefined,\n\t\t\tstep.searchable || step.layout ? { searchable: step.searchable, layout: step.layout } : undefined,\n\t\t);\n\t}\n\n\trender(width: number): string[] {\n\t\treturn this.activeComponent.render(width);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.activeComponent.handleInput?.(data);\n\t}\n\n\tinvalidate(): void {\n\t\tthis.activeComponent.invalidate?.();\n\t}\n}\n"]}
@@ -0,0 +1,165 @@
1
+ import { Container, fuzzyFilter, getKeybindings, Input, SelectList, Spacer, Text, } from "@earendil-works/pi-tui";
2
+ import { getSelectListTheme, theme } from "../theme/theme.js";
3
+ const SUBMENU_SELECT_LIST_LAYOUT = {
4
+ minPrimaryColumnWidth: 12,
5
+ maxPrimaryColumnWidth: 32,
6
+ };
7
+ /**
8
+ * Single-step submenu that shows a titled select list.
9
+ * With `searchable: true`, typing filters the list using fuzzy matching.
10
+ */
11
+ export class SelectSubmenu extends Container {
12
+ selectList;
13
+ listChildIndex;
14
+ allOptions;
15
+ listLayout;
16
+ searchInput;
17
+ onSelectCb;
18
+ onCancelCb;
19
+ onSelectionChangeCb;
20
+ constructor(title, description, options, currentValue, onSelect, onCancel, onSelectionChange, submenuOptions) {
21
+ super();
22
+ this.allOptions = options;
23
+ this.listLayout = submenuOptions?.layout ?? SUBMENU_SELECT_LIST_LAYOUT;
24
+ this.onSelectCb = onSelect;
25
+ this.onCancelCb = onCancel;
26
+ this.onSelectionChangeCb = onSelectionChange;
27
+ // Title
28
+ this.addChild(new Text(theme.bold(theme.fg("accent", title)), 0, 0));
29
+ // Description
30
+ if (description) {
31
+ this.addChild(new Spacer(1));
32
+ this.addChild(new Text(theme.fg("muted", description), 0, 0));
33
+ }
34
+ // Search input
35
+ if (submenuOptions?.searchable) {
36
+ this.addChild(new Spacer(1));
37
+ this.searchInput = new Input();
38
+ this.searchInput.onSubmit = () => {
39
+ this.selectList.handleInput("\r");
40
+ };
41
+ this.addChild(this.searchInput);
42
+ }
43
+ // Spacer
44
+ this.addChild(new Spacer(1));
45
+ // Select list
46
+ this.selectList = this.buildSelectList(options, currentValue);
47
+ this.listChildIndex = this.children.length;
48
+ this.addChild(this.selectList);
49
+ // Hint
50
+ this.addChild(new Spacer(1));
51
+ const hint = submenuOptions?.searchable
52
+ ? " Type to filter \u00b7 Enter to select \u00b7 Esc to go back"
53
+ : " Enter to select \u00b7 Esc to go back";
54
+ this.addChild(new Text(theme.fg("dim", hint), 0, 0));
55
+ }
56
+ buildSelectList(options, preselect) {
57
+ const list = new SelectList(options, Math.min(options.length, 10), getSelectListTheme(), this.listLayout);
58
+ const idx = options.findIndex((o) => o.value === preselect);
59
+ if (idx !== -1)
60
+ list.setSelectedIndex(idx);
61
+ list.onSelect = (item) => this.onSelectCb(item.value);
62
+ list.onCancel = this.onCancelCb;
63
+ if (this.onSelectionChangeCb) {
64
+ const cb = this.onSelectionChangeCb;
65
+ list.onSelectionChange = (item) => cb(item.value);
66
+ }
67
+ return list;
68
+ }
69
+ applyFilter(query) {
70
+ const filtered = query
71
+ ? fuzzyFilter(this.allOptions, query, (item) => `${item.label} ${item.description ?? ""}`)
72
+ : this.allOptions;
73
+ const newList = this.buildSelectList(filtered, "");
74
+ this.children[this.listChildIndex] = newList;
75
+ this.selectList = newList;
76
+ }
77
+ handleInput(data) {
78
+ if (this.searchInput) {
79
+ const kb = getKeybindings();
80
+ const isNav = kb.matches(data, "tui.select.up") ||
81
+ kb.matches(data, "tui.select.down") ||
82
+ kb.matches(data, "tui.select.confirm") ||
83
+ kb.matches(data, "tui.select.cancel");
84
+ if (isNav) {
85
+ this.selectList.handleInput(data);
86
+ }
87
+ else {
88
+ this.searchInput.handleInput(data);
89
+ this.applyFilter(this.searchInput.getValue());
90
+ }
91
+ }
92
+ else {
93
+ this.selectList.handleInput(data);
94
+ }
95
+ }
96
+ }
97
+ /**
98
+ * Generic N-step submenu built on top of {@link SelectSubmenu}.
99
+ *
100
+ * Each step's options can depend on prior selections via the shared context.
101
+ * Esc goes back one step; Esc at step 0 cancels.
102
+ * With `loop: true`, completing the final step invokes `onComplete` then returns to step 0.
103
+ */
104
+ export class SteppedSubmenu extends Container {
105
+ steps;
106
+ onComplete;
107
+ onCancel;
108
+ opts;
109
+ activeComponent;
110
+ context;
111
+ constructor(steps, onComplete, onCancel, opts = {}) {
112
+ super();
113
+ this.steps = steps;
114
+ this.onComplete = onComplete;
115
+ this.onCancel = onCancel;
116
+ this.opts = opts;
117
+ this.context = { ...(opts.initialContext ?? {}) };
118
+ this.activeComponent = this.buildStep(opts.startAtStep ?? 0);
119
+ }
120
+ buildStep(stepIndex) {
121
+ const step = this.steps[stepIndex];
122
+ const total = this.steps.length;
123
+ const stepLabel = total > 1 ? `Step ${stepIndex + 1}/${total} \u00b7 ` : "";
124
+ const title = typeof step.title === "function" ? step.title(this.context) : step.title;
125
+ const desc = typeof step.description === "function" ? step.description(this.context) : step.description;
126
+ const items = step.options(this.context);
127
+ const preselect = step.preselect?.(this.context) ?? "";
128
+ return new SelectSubmenu(title, `${stepLabel}${desc}`, items, preselect, (value) => {
129
+ this.context[step.key] = value;
130
+ if (stepIndex < total - 1) {
131
+ // Advance to next step
132
+ this.activeComponent = this.buildStep(stepIndex + 1);
133
+ }
134
+ else {
135
+ // Final step \u2014 deliver result
136
+ this.onComplete({ ...this.context });
137
+ if (this.opts.loop) {
138
+ this.context = {};
139
+ this.activeComponent = this.buildStep(0);
140
+ }
141
+ else {
142
+ this.onCancel();
143
+ }
144
+ }
145
+ }, () => {
146
+ if (stepIndex > 0) {
147
+ delete this.context[step.key];
148
+ this.activeComponent = this.buildStep(stepIndex - 1);
149
+ }
150
+ else {
151
+ this.onCancel();
152
+ }
153
+ }, undefined, step.searchable || step.layout ? { searchable: step.searchable, layout: step.layout } : undefined);
154
+ }
155
+ render(width) {
156
+ return this.activeComponent.render(width);
157
+ }
158
+ handleInput(data) {
159
+ this.activeComponent.handleInput?.(data);
160
+ }
161
+ invalidate() {
162
+ this.activeComponent.invalidate?.();
163
+ }
164
+ }
165
+ //# sourceMappingURL=settings-submenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-submenu.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/settings-submenu.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Component,\n\tContainer,\n\tfuzzyFilter,\n\tgetKeybindings,\n\tInput,\n\ttype SelectItem,\n\tSelectList,\n\ttype SelectListLayoutOptions,\n\tSpacer,\n\tText,\n} from \"@earendil-works/pi-tui\";\nimport { getSelectListTheme, theme } from \"../theme/theme.ts\";\n\nconst SUBMENU_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nexport interface SelectSubmenuOptions {\n\t/** Enable type-to-search fuzzy filtering. */\n\tsearchable?: boolean;\n\t/** Override the select list layout (column widths). */\n\tlayout?: SelectListLayoutOptions;\n}\n\n/**\n * Single-step submenu that shows a titled select list.\n * With `searchable: true`, typing filters the list using fuzzy matching.\n */\nexport class SelectSubmenu extends Container {\n\tprivate selectList: SelectList;\n\tprivate listChildIndex: number;\n\tprivate allOptions: SelectItem[];\n\tprivate listLayout: SelectListLayoutOptions;\n\tprivate searchInput: Input | undefined;\n\tprivate onSelectCb: (value: string) => void;\n\tprivate onCancelCb: () => void;\n\tprivate onSelectionChangeCb?: (value: string) => void;\n\n\tconstructor(\n\t\ttitle: string,\n\t\tdescription: string,\n\t\toptions: SelectItem[],\n\t\tcurrentValue: string,\n\t\tonSelect: (value: string) => void,\n\t\tonCancel: () => void,\n\t\tonSelectionChange?: (value: string) => void,\n\t\tsubmenuOptions?: SelectSubmenuOptions,\n\t) {\n\t\tsuper();\n\n\t\tthis.allOptions = options;\n\t\tthis.listLayout = submenuOptions?.layout ?? SUBMENU_SELECT_LIST_LAYOUT;\n\t\tthis.onSelectCb = onSelect;\n\t\tthis.onCancelCb = onCancel;\n\t\tthis.onSelectionChangeCb = onSelectionChange;\n\n\t\t// Title\n\t\tthis.addChild(new Text(theme.bold(theme.fg(\"accent\", title)), 0, 0));\n\n\t\t// Description\n\t\tif (description) {\n\t\t\tthis.addChild(new Spacer(1));\n\t\t\tthis.addChild(new Text(theme.fg(\"muted\", description), 0, 0));\n\t\t}\n\n\t\t// Search input\n\t\tif (submenuOptions?.searchable) {\n\t\t\tthis.addChild(new Spacer(1));\n\t\t\tthis.searchInput = new Input();\n\t\t\tthis.searchInput.onSubmit = () => {\n\t\t\t\tthis.selectList.handleInput(\"\\r\");\n\t\t\t};\n\t\t\tthis.addChild(this.searchInput);\n\t\t}\n\n\t\t// Spacer\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Select list\n\t\tthis.selectList = this.buildSelectList(options, currentValue);\n\t\tthis.listChildIndex = this.children.length;\n\t\tthis.addChild(this.selectList);\n\n\t\t// Hint\n\t\tthis.addChild(new Spacer(1));\n\t\tconst hint = submenuOptions?.searchable\n\t\t\t? \" Type to filter \\u00b7 Enter to select \\u00b7 Esc to go back\"\n\t\t\t: \" Enter to select \\u00b7 Esc to go back\";\n\t\tthis.addChild(new Text(theme.fg(\"dim\", hint), 0, 0));\n\t}\n\n\tprivate buildSelectList(options: SelectItem[], preselect: string): SelectList {\n\t\tconst list = new SelectList(options, Math.min(options.length, 10), getSelectListTheme(), this.listLayout);\n\n\t\tconst idx = options.findIndex((o) => o.value === preselect);\n\t\tif (idx !== -1) list.setSelectedIndex(idx);\n\n\t\tlist.onSelect = (item) => this.onSelectCb(item.value);\n\t\tlist.onCancel = this.onCancelCb;\n\t\tif (this.onSelectionChangeCb) {\n\t\t\tconst cb = this.onSelectionChangeCb;\n\t\t\tlist.onSelectionChange = (item) => cb(item.value);\n\t\t}\n\n\t\treturn list;\n\t}\n\n\tprivate applyFilter(query: string): void {\n\t\tconst filtered = query\n\t\t\t? fuzzyFilter(this.allOptions, query, (item) => `${item.label} ${item.description ?? \"\"}`)\n\t\t\t: this.allOptions;\n\n\t\tconst newList = this.buildSelectList(filtered, \"\");\n\t\tthis.children[this.listChildIndex] = newList;\n\t\tthis.selectList = newList;\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (this.searchInput) {\n\t\t\tconst kb = getKeybindings();\n\t\t\tconst isNav =\n\t\t\t\tkb.matches(data, \"tui.select.up\") ||\n\t\t\t\tkb.matches(data, \"tui.select.down\") ||\n\t\t\t\tkb.matches(data, \"tui.select.confirm\") ||\n\t\t\t\tkb.matches(data, \"tui.select.cancel\");\n\t\t\tif (isNav) {\n\t\t\t\tthis.selectList.handleInput(data);\n\t\t\t} else {\n\t\t\t\tthis.searchInput.handleInput(data);\n\t\t\t\tthis.applyFilter(this.searchInput.getValue());\n\t\t\t}\n\t\t} else {\n\t\t\tthis.selectList.handleInput(data);\n\t\t}\n\t}\n}\n\n// ============================================================================\n// SteppedSubmenu — reusable multi-step selector\n// ============================================================================\n\n/** One step in a {@link SteppedSubmenu}. */\nexport interface SteppedSubmenuStep {\n\t/** Unique key \\u2014 the selected value is stored in the result context under this key. */\n\tkey: string;\n\t/** Title shown at the top of the step. Receives prior selections. */\n\ttitle: string | ((context: Record<string, string>) => string);\n\t/** Description shown below the title. Receives prior selections. */\n\tdescription: string | ((context: Record<string, string>) => string);\n\t/** Build the option list for this step. Called fresh each time the step is shown. */\n\toptions: (context: Record<string, string>) => SelectItem[];\n\t/** Optionally pre-select a value when entering this step. */\n\tpreselect?: (context: Record<string, string>) => string | undefined;\n\t/** Enable type-to-search fuzzy filtering for this step. */\n\tsearchable?: boolean;\n\t/** Override the select list layout (column widths) for this step. */\n\tlayout?: SelectListLayoutOptions;\n}\n\ninterface SteppedSubmenuOptions {\n\t/** Start at this step index (0-based), skipping earlier steps. Requires initialContext for skipped keys. */\n\tstartAtStep?: number;\n\t/** Pre-fill selections for skipped steps. */\n\tinitialContext?: Record<string, string>;\n\t/** After completing the last step, loop back to step 0 instead of closing. */\n\tloop?: boolean;\n}\n\n/**\n * Generic N-step submenu built on top of {@link SelectSubmenu}.\n *\n * Each step's options can depend on prior selections via the shared context.\n * Esc goes back one step; Esc at step 0 cancels.\n * With `loop: true`, completing the final step invokes `onComplete` then returns to step 0.\n */\nexport class SteppedSubmenu extends Container {\n\tprivate readonly steps: SteppedSubmenuStep[];\n\tprivate readonly onComplete: (context: Record<string, string>) => void;\n\tprivate readonly onCancel: () => void;\n\tprivate readonly opts: SteppedSubmenuOptions;\n\tprivate activeComponent: Component;\n\tprivate context: Record<string, string>;\n\n\tconstructor(\n\t\tsteps: SteppedSubmenuStep[],\n\t\tonComplete: (context: Record<string, string>) => void,\n\t\tonCancel: () => void,\n\t\topts: SteppedSubmenuOptions = {},\n\t) {\n\t\tsuper();\n\t\tthis.steps = steps;\n\t\tthis.onComplete = onComplete;\n\t\tthis.onCancel = onCancel;\n\t\tthis.opts = opts;\n\t\tthis.context = { ...(opts.initialContext ?? {}) };\n\t\tthis.activeComponent = this.buildStep(opts.startAtStep ?? 0);\n\t}\n\n\tprivate buildStep(stepIndex: number): Component {\n\t\tconst step = this.steps[stepIndex];\n\t\tconst total = this.steps.length;\n\t\tconst stepLabel = total > 1 ? `Step ${stepIndex + 1}/${total} \\u00b7 ` : \"\";\n\n\t\tconst title = typeof step.title === \"function\" ? step.title(this.context) : step.title;\n\t\tconst desc = typeof step.description === \"function\" ? step.description(this.context) : step.description;\n\t\tconst items = step.options(this.context);\n\t\tconst preselect = step.preselect?.(this.context) ?? \"\";\n\n\t\treturn new SelectSubmenu(\n\t\t\ttitle,\n\t\t\t`${stepLabel}${desc}`,\n\t\t\titems,\n\t\t\tpreselect,\n\t\t\t(value) => {\n\t\t\t\tthis.context[step.key] = value;\n\n\t\t\t\tif (stepIndex < total - 1) {\n\t\t\t\t\t// Advance to next step\n\t\t\t\t\tthis.activeComponent = this.buildStep(stepIndex + 1);\n\t\t\t\t} else {\n\t\t\t\t\t// Final step \\u2014 deliver result\n\t\t\t\t\tthis.onComplete({ ...this.context });\n\n\t\t\t\t\tif (this.opts.loop) {\n\t\t\t\t\t\tthis.context = {};\n\t\t\t\t\t\tthis.activeComponent = this.buildStep(0);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.onCancel();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tif (stepIndex > 0) {\n\t\t\t\t\tdelete this.context[step.key];\n\t\t\t\t\tthis.activeComponent = this.buildStep(stepIndex - 1);\n\t\t\t\t} else {\n\t\t\t\t\tthis.onCancel();\n\t\t\t\t}\n\t\t\t},\n\t\t\tundefined,\n\t\t\tstep.searchable || step.layout ? { searchable: step.searchable, layout: step.layout } : undefined,\n\t\t);\n\t}\n\n\trender(width: number): string[] {\n\t\treturn this.activeComponent.render(width);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.activeComponent.handleInput?.(data);\n\t}\n\n\tinvalidate(): void {\n\t\tthis.activeComponent.invalidate?.();\n\t}\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import { type Component } from "@earendil-works/pi-tui";
2
+ import { type BrandMark, type MarkRow, type MarkSymbolPreset } from "../../../themes/apex-logo.ts";
2
3
  /** One `label value` row in the metadata column. */
3
4
  export interface SplashHeaderMetadataLine {
4
5
  label: string;
@@ -7,23 +8,48 @@ export interface SplashHeaderMetadataLine {
7
8
  export interface ApexSplashHeaderOptions {
8
9
  /** Rows appended after `cwd`, for extensions that want to contribute context. */
9
10
  getExtraMetadata?: () => readonly SplashHeaderMetadataLine[];
11
+ /**
12
+ * One line of counted totals, rendered between the rules under the mark.
13
+ * The header does not know how to count anything; the caller supplies the
14
+ * finished line so the resource loader stays on its own side of the seam.
15
+ */
16
+ getInventory?: () => string | undefined;
17
+ /**
18
+ * Affordance appended to the inventory line, e.g. the command that shows the
19
+ * detail. Dropped whole rather than clipped, so it never renders as a stub.
20
+ */
21
+ inventoryHint?: string;
10
22
  /** Hint rendered under the metadata block. Return undefined for none. */
11
23
  getHint?: () => string | undefined;
24
+ /**
25
+ * One row of entry-point sigils under the hint, already coloured by the
26
+ * caller. The header does not know the keybindings, so it does not build it.
27
+ */
28
+ getShortcuts?: () => string | undefined;
12
29
  /** Resolves `terminal.symbolPreset`. Defaults to unicode. */
13
- getSymbolPreset?: () => "unicode" | "ascii";
30
+ getSymbolPreset?: () => MarkSymbolPreset;
14
31
  /** Emit a leading blank line above the mark. */
15
32
  topPadding?: boolean;
16
33
  /** Override the mark. Used by tests and by extensions that rebrand. */
17
34
  logo?: string;
18
35
  }
19
36
  /**
20
- * Startup header: brand mark on the left, runtime metadata on the right.
37
+ * Paint one mark row: accent run then text run.
38
+ *
39
+ * Empty runs are skipped so no row carries a bare, unclosed SGR pair.
40
+ */
41
+ export declare function paintMarkRow(row: MarkRow): string;
42
+ /** The whole mark as painted lines, for surfaces that just want to stamp it. */
43
+ export declare function paintBrandMark(brandMark: BrandMark): string[];
44
+ /**
45
+ * Startup header: brand mark on the left, runtime metadata on the right, then a
46
+ * ruled band carrying the counted inventory.
21
47
  *
22
48
  * Values are read through getters rather than captured at construction, so the
23
49
  * model fills in once the session binds without an explicit refresh.
24
50
  *
25
51
  * The metadata column is dropped when the terminal cannot hold it, and the mark
26
- * falls back to a compact variant rather than being truncated mid-glyph.
52
+ * falls back to a type lockup rather than being truncated mid-glyph.
27
53
  */
28
54
  export declare class ApexSplashHeader implements Component {
29
55
  private readonly version;
@@ -34,7 +60,6 @@ export declare class ApexSplashHeader implements Component {
34
60
  constructor(version: string, getModelId: () => string | undefined, getCwd: () => string, verboseInstructions?: string, options?: ApexSplashHeaderOptions);
35
61
  invalidate(): void;
36
62
  render(width: number): string[];
37
- private selectLogo;
38
63
  private buildMetadata;
39
64
  private padLine;
40
65
  }
@@ -1 +1 @@
1
- {"version":3,"file":"splash-header.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/splash-header.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,wBAAwB,CAAC;AAKvF,qDAAqD;AACrD,MAAM,WAAW,wBAAwB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACvC,iFAAiF;IACjF,gBAAgB,CAAC,EAAE,MAAM,SAAS,wBAAwB,EAAE,CAAC;IAC7D,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACnC,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,SAAS,GAAG,OAAO,CAAC;IAC5C,gDAAgD;IAChD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AA8DD;;;;;;;;GAQG;AACH,qBAAa,gBAAiB,YAAW,SAAS;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2B;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAElD,YACC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,MAAM,GAAG,SAAS,EACpC,MAAM,EAAE,MAAM,MAAM,EACpB,mBAAmB,CAAC,EAAE,MAAM,EAC5B,OAAO,GAAE,uBAA4B,EAOrC;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAwC9B;IAED,OAAO,CAAC,UAAU;IAYlB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,OAAO;CAIf","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@earendil-works/pi-tui\";\nimport { APEX_PEAK_LOGO, APEX_PEAK_LOGO_ASCII, APEX_PEAK_LOGO_COMPACT } from \"../../../themes/apex-logo.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { formatCwdForFooter } from \"./footer.ts\";\n\n/** One `label value` row in the metadata column. */\nexport interface SplashHeaderMetadataLine {\n\tlabel: string;\n\tvalue: string;\n}\n\nexport interface ApexSplashHeaderOptions {\n\t/** Rows appended after `cwd`, for extensions that want to contribute context. */\n\tgetExtraMetadata?: () => readonly SplashHeaderMetadataLine[];\n\t/** Hint rendered under the metadata block. Return undefined for none. */\n\tgetHint?: () => string | undefined;\n\t/** Resolves `terminal.symbolPreset`. Defaults to unicode. */\n\tgetSymbolPreset?: () => \"unicode\" | \"ascii\";\n\t/** Emit a leading blank line above the mark. */\n\ttopPadding?: boolean;\n\t/** Override the mark. Used by tests and by extensions that rebrand. */\n\tlogo?: string;\n}\n\nconst GUTTER = 4;\nconst LABEL_WIDTH = 9;\n/** Smallest value column worth rendering; below this the metadata column is dropped. */\nconst MIN_VALUE_WIDTH = 8;\n\n/**\n * Keep the rightmost path segments that fit, so the leaf directory stays visible.\n *\n * `truncateToWidth` clips the tail, which for a path hides the part that\n * identifies the session. This clips the head instead.\n */\nfunction truncatePathTail(path: string, maxWidth: number): string {\n\tif (maxWidth <= 0) {\n\t\treturn \"\";\n\t}\n\tif (visibleWidth(path) <= maxWidth) {\n\t\treturn path;\n\t}\n\tconst ellipsis = \"…/\";\n\tconst budget = Math.max(0, maxWidth - visibleWidth(ellipsis));\n\tconst segments = path.split(\"/\");\n\tlet tail = \"\";\n\tfor (let index = segments.length - 1; index >= 0; index--) {\n\t\tconst candidate = tail ? `${segments[index]}/${tail}` : segments[index];\n\t\tif (visibleWidth(candidate) > budget) {\n\t\t\tbreak;\n\t\t}\n\t\ttail = candidate;\n\t}\n\t// A single segment longer than the whole budget: clip it from the left.\n\treturn tail ? ellipsis + tail : ellipsis + trailingByWidth(path, budget);\n}\n\n/**\n * The widest suffix of `text` that fits `maxWidth` terminal columns.\n *\n * Walks grapheme clusters from the right rather than using `String.slice`, whose\n * UTF-16 offsets would cut an astral character in half — emitting a lone\n * surrogate — and would miscount every double-width character on the way.\n */\nfunction trailingByWidth(text: string, maxWidth: number): string {\n\tif (maxWidth <= 0) {\n\t\treturn \"\";\n\t}\n\tconst graphemes = [...new Intl.Segmenter(undefined, { granularity: \"grapheme\" }).segment(text)].map(\n\t\t(entry) => entry.segment,\n\t);\n\tlet width = 0;\n\tlet index = graphemes.length;\n\twhile (index > 0) {\n\t\tconst next = width + visibleWidth(graphemes[index - 1]);\n\t\tif (next > maxWidth) {\n\t\t\tbreak;\n\t\t}\n\t\twidth = next;\n\t\tindex -= 1;\n\t}\n\treturn graphemes.slice(index).join(\"\");\n}\n\n/**\n * Startup header: brand mark on the left, runtime metadata on the right.\n *\n * Values are read through getters rather than captured at construction, so the\n * model fills in once the session binds without an explicit refresh.\n *\n * The metadata column is dropped when the terminal cannot hold it, and the mark\n * falls back to a compact variant rather than being truncated mid-glyph.\n */\nexport class ApexSplashHeader implements Component {\n\tprivate readonly version: string;\n\tprivate readonly getModelId: () => string | undefined;\n\tprivate readonly getCwd: () => string;\n\tprivate readonly verboseInstructions: string | undefined;\n\tprivate readonly options: ApexSplashHeaderOptions;\n\n\tconstructor(\n\t\tversion: string,\n\t\tgetModelId: () => string | undefined,\n\t\tgetCwd: () => string,\n\t\tverboseInstructions?: string,\n\t\toptions: ApexSplashHeaderOptions = {},\n\t) {\n\t\tthis.version = version;\n\t\tthis.getModelId = getModelId;\n\t\tthis.getCwd = getCwd;\n\t\tthis.verboseInstructions = verboseInstructions;\n\t\tthis.options = options;\n\t}\n\n\tinvalidate(): void {\n\t\t// Render output is derived from current theme and session state.\n\t}\n\n\trender(width: number): string[] {\n\t\tconst safeWidth = Math.max(1, width);\n\t\tconst paddingX = safeWidth > 1 ? 1 : 0;\n\t\tconst contentWidth = Math.max(1, safeWidth - paddingX * 2);\n\n\t\tconst logoLines = this.selectLogo(contentWidth);\n\t\tconst logoWidth = logoLines.reduce((max, line) => Math.max(max, visibleWidth(line)), 0);\n\n\t\tconst metaWidth = contentWidth - logoWidth - GUTTER;\n\t\tconst showMeta = metaWidth >= LABEL_WIDTH + MIN_VALUE_WIDTH;\n\t\tconst valueWidth = Math.max(1, metaWidth - LABEL_WIDTH);\n\t\tconst metaLines = showMeta ? this.buildMetadata(valueWidth) : [];\n\t\tconst metaStart = Math.max(0, Math.floor((logoLines.length - metaLines.length) / 2));\n\n\t\tconst lines = this.options.topPadding ? [this.padLine(\"\", safeWidth, paddingX)] : [];\n\n\t\tlogoLines.forEach((line, index) => {\n\t\t\tconst meta = index >= metaStart && index < metaStart + metaLines.length ? metaLines[index - metaStart] : \"\";\n\t\t\tconst gap = showMeta ? \" \".repeat(Math.max(0, logoWidth - visibleWidth(line) + GUTTER)) : \"\";\n\t\t\tconst content = truncateToWidth(theme.fg(\"text\", line) + gap + meta, contentWidth, \"\");\n\t\t\tlines.push(this.padLine(content, safeWidth, paddingX));\n\t\t});\n\n\t\t// The hint is product copy rather than a runtime fact, so it sits under the\n\t\t// whole block at full width instead of inside the narrow metadata column,\n\t\t// where it would truncate even on a wide terminal.\n\t\tconst hint = this.options.getHint?.();\n\t\tif (hint) {\n\t\t\tlines.push(this.padLine(\"\", safeWidth, paddingX));\n\t\t\tlines.push(this.padLine(theme.fg(\"dim\", truncateToWidth(hint, contentWidth)), safeWidth, paddingX));\n\t\t}\n\n\t\tif (this.verboseInstructions) {\n\t\t\tlines.push(this.padLine(\"\", safeWidth, paddingX));\n\t\t\tfor (const instruction of this.verboseInstructions.split(\"\\n\")) {\n\t\t\t\tlines.push(this.padLine(truncateToWidth(instruction, contentWidth), safeWidth, paddingX));\n\t\t\t}\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\tprivate selectLogo(contentWidth: number): string[] {\n\t\tif (this.options.logo) {\n\t\t\treturn this.options.logo.split(\"\\n\");\n\t\t}\n\t\tif (this.options.getSymbolPreset?.() === \"ascii\") {\n\t\t\treturn APEX_PEAK_LOGO_ASCII.split(\"\\n\");\n\t\t}\n\t\tconst full = APEX_PEAK_LOGO.split(\"\\n\");\n\t\tconst fullWidth = full.reduce((max, line) => Math.max(max, visibleWidth(line)), 0);\n\t\treturn contentWidth >= fullWidth ? full : APEX_PEAK_LOGO_COMPACT.split(\"\\n\");\n\t}\n\n\tprivate buildMetadata(valueWidth: number): string[] {\n\t\tconst labelled = (label: string, value: string) =>\n\t\t\ttheme.fg(\"dim\", label.padEnd(LABEL_WIDTH)) + theme.fg(\"muted\", truncateToWidth(value, valueWidth));\n\n\t\tconst cwd = formatCwdForFooter(this.getCwd(), process.env.HOME || process.env.USERPROFILE);\n\t\treturn [\n\t\t\tlabelled(\"version\", `v${this.version}`),\n\t\t\tlabelled(\"model\", this.getModelId() ?? \"—\"),\n\t\t\ttheme.fg(\"dim\", \"cwd\".padEnd(LABEL_WIDTH)) + theme.fg(\"muted\", truncatePathTail(cwd, valueWidth)),\n\t\t\t...(this.options.getExtraMetadata?.() ?? []).map((line) => labelled(line.label, line.value)),\n\t\t];\n\t}\n\n\tprivate padLine(content: string, safeWidth: number, paddingX: number): string {\n\t\tconst trailing = Math.max(0, safeWidth - paddingX - visibleWidth(content));\n\t\treturn \" \".repeat(paddingX) + content + \" \".repeat(trailing);\n\t}\n}\n"]}
1
+ {"version":3,"file":"splash-header.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/splash-header.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,wBAAwB,CAAC;AACvF,OAAO,EACN,KAAK,SAAS,EAEd,KAAK,OAAO,EACZ,KAAK,gBAAgB,EAErB,MAAM,8BAA8B,CAAC;AAItC,qDAAqD;AACrD,MAAM,WAAW,wBAAwB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACvC,iFAAiF;IACjF,gBAAgB,CAAC,EAAE,MAAM,SAAS,wBAAwB,EAAE,CAAC;IAC7D;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACxC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACnC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACxC,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,gBAAgB,CAAC;IACzC,gDAAgD;IAChD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAIjD;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,EAAE,CAE7D;AAgED;;;;;;;;;GASG;AACH,qBAAa,gBAAiB,YAAW,SAAS;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2B;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAElD,YACC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,MAAM,GAAG,SAAS,EACpC,MAAM,EAAE,MAAM,MAAM,EACpB,mBAAmB,CAAC,EAAE,MAAM,EAC5B,OAAO,GAAE,uBAA4B,EAOrC;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAwE9B;IAED,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,OAAO;CAIf","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@earendil-works/pi-tui\";\nimport {\n\ttype BrandMark,\n\tcustomBrandMark,\n\ttype MarkRow,\n\ttype MarkSymbolPreset,\n\tselectBrandMark,\n} from \"../../../themes/apex-logo.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { formatCwdForFooter } from \"./footer.ts\";\n\n/** One `label value` row in the metadata column. */\nexport interface SplashHeaderMetadataLine {\n\tlabel: string;\n\tvalue: string;\n}\n\nexport interface ApexSplashHeaderOptions {\n\t/** Rows appended after `cwd`, for extensions that want to contribute context. */\n\tgetExtraMetadata?: () => readonly SplashHeaderMetadataLine[];\n\t/**\n\t * One line of counted totals, rendered between the rules under the mark.\n\t * The header does not know how to count anything; the caller supplies the\n\t * finished line so the resource loader stays on its own side of the seam.\n\t */\n\tgetInventory?: () => string | undefined;\n\t/**\n\t * Affordance appended to the inventory line, e.g. the command that shows the\n\t * detail. Dropped whole rather than clipped, so it never renders as a stub.\n\t */\n\tinventoryHint?: string;\n\t/** Hint rendered under the metadata block. Return undefined for none. */\n\tgetHint?: () => string | undefined;\n\t/**\n\t * One row of entry-point sigils under the hint, already coloured by the\n\t * caller. The header does not know the keybindings, so it does not build it.\n\t */\n\tgetShortcuts?: () => string | undefined;\n\t/** Resolves `terminal.symbolPreset`. Defaults to unicode. */\n\tgetSymbolPreset?: () => MarkSymbolPreset;\n\t/** Emit a leading blank line above the mark. */\n\ttopPadding?: boolean;\n\t/** Override the mark. Used by tests and by extensions that rebrand. */\n\tlogo?: string;\n}\n\n/**\n * Paint one mark row: accent run then text run.\n *\n * Empty runs are skipped so no row carries a bare, unclosed SGR pair.\n */\nexport function paintMarkRow(row: MarkRow): string {\n\tconst accent = row.accent ? theme.fg(\"accent\", row.accent) : \"\";\n\tconst text = row.text ? theme.fg(\"text\", row.text) : \"\";\n\treturn accent + text;\n}\n\n/** The whole mark as painted lines, for surfaces that just want to stamp it. */\nexport function paintBrandMark(brandMark: BrandMark): string[] {\n\treturn brandMark.rows.map(paintMarkRow);\n}\n\nconst GUTTER = 4;\nconst LABEL_WIDTH = 9;\n/** Smallest value column worth rendering; below this the metadata column is dropped. */\nconst MIN_VALUE_WIDTH = 8;\n/** Below this the rules and the inventory line are dropped rather than clipped. */\nconst MIN_RULE_WIDTH = 12;\n\n/**\n * Keep the rightmost path segments that fit, so the leaf directory stays visible.\n *\n * `truncateToWidth` clips the tail, which for a path hides the part that\n * identifies the session. This clips the head instead.\n */\nfunction truncatePathTail(path: string, maxWidth: number): string {\n\tif (maxWidth <= 0) {\n\t\treturn \"\";\n\t}\n\tif (visibleWidth(path) <= maxWidth) {\n\t\treturn path;\n\t}\n\tconst ellipsis = \"…/\";\n\tconst budget = Math.max(0, maxWidth - visibleWidth(ellipsis));\n\tconst segments = path.split(\"/\");\n\tlet tail = \"\";\n\tfor (let index = segments.length - 1; index >= 0; index--) {\n\t\tconst candidate = tail ? `${segments[index]}/${tail}` : segments[index];\n\t\tif (visibleWidth(candidate) > budget) {\n\t\t\tbreak;\n\t\t}\n\t\ttail = candidate;\n\t}\n\t// A single segment longer than the whole budget: clip it from the left.\n\treturn tail ? ellipsis + tail : ellipsis + trailingByWidth(path, budget);\n}\n\n/**\n * The widest suffix of `text` that fits `maxWidth` terminal columns.\n *\n * Walks grapheme clusters from the right rather than using `String.slice`, whose\n * UTF-16 offsets would cut an astral character in half — emitting a lone\n * surrogate — and would miscount every double-width character on the way.\n */\nfunction trailingByWidth(text: string, maxWidth: number): string {\n\tif (maxWidth <= 0) {\n\t\treturn \"\";\n\t}\n\tconst graphemes = [...new Intl.Segmenter(undefined, { granularity: \"grapheme\" }).segment(text)].map(\n\t\t(entry) => entry.segment,\n\t);\n\tlet width = 0;\n\tlet index = graphemes.length;\n\twhile (index > 0) {\n\t\tconst next = width + visibleWidth(graphemes[index - 1]);\n\t\tif (next > maxWidth) {\n\t\t\tbreak;\n\t\t}\n\t\twidth = next;\n\t\tindex -= 1;\n\t}\n\treturn graphemes.slice(index).join(\"\");\n}\n\n/**\n * Startup header: brand mark on the left, runtime metadata on the right, then a\n * ruled band carrying the counted inventory.\n *\n * Values are read through getters rather than captured at construction, so the\n * model fills in once the session binds without an explicit refresh.\n *\n * The metadata column is dropped when the terminal cannot hold it, and the mark\n * falls back to a type lockup rather than being truncated mid-glyph.\n */\nexport class ApexSplashHeader implements Component {\n\tprivate readonly version: string;\n\tprivate readonly getModelId: () => string | undefined;\n\tprivate readonly getCwd: () => string;\n\tprivate readonly verboseInstructions: string | undefined;\n\tprivate readonly options: ApexSplashHeaderOptions;\n\n\tconstructor(\n\t\tversion: string,\n\t\tgetModelId: () => string | undefined,\n\t\tgetCwd: () => string,\n\t\tverboseInstructions?: string,\n\t\toptions: ApexSplashHeaderOptions = {},\n\t) {\n\t\tthis.version = version;\n\t\tthis.getModelId = getModelId;\n\t\tthis.getCwd = getCwd;\n\t\tthis.verboseInstructions = verboseInstructions;\n\t\tthis.options = options;\n\t}\n\n\tinvalidate(): void {\n\t\t// Render output is derived from current theme and session state.\n\t}\n\n\trender(width: number): string[] {\n\t\tconst safeWidth = Math.max(1, width);\n\t\tconst paddingX = safeWidth > 1 ? 1 : 0;\n\t\tconst contentWidth = Math.max(1, safeWidth - paddingX * 2);\n\t\tconst symbolPreset = this.options.getSymbolPreset?.() ?? \"unicode\";\n\n\t\tconst brandMark = this.options.logo\n\t\t\t? customBrandMark(this.options.logo)\n\t\t\t: selectBrandMark(contentWidth, symbolPreset);\n\n\t\tconst metaWidth = contentWidth - brandMark.width - GUTTER;\n\t\tconst showMeta = metaWidth >= LABEL_WIDTH + MIN_VALUE_WIDTH;\n\t\tconst valueWidth = Math.max(1, metaWidth - LABEL_WIDTH);\n\t\tconst metaLines = showMeta ? this.buildMetadata(valueWidth) : [];\n\t\t// Bottom-aligned, so the last metadata row sits on the mark's ember\n\t\t// baseline and the two read as one block rather than two stacks.\n\t\tconst metaStart = Math.max(0, brandMark.rows.length - metaLines.length);\n\n\t\tconst lines = this.options.topPadding ? [this.padLine(\"\", safeWidth, paddingX)] : [];\n\n\t\t// The mark and the metadata column are two stacks of different heights\n\t\t// sharing one block, so the block is as tall as the taller of them. A\n\t\t// one-row mark beside three metadata rows still shows all three.\n\t\tconst blockRows = Math.max(brandMark.rows.length, metaStart + metaLines.length);\n\t\tfor (let index = 0; index < blockRows; index++) {\n\t\t\tconst row = brandMark.rows[index];\n\t\t\tconst painted = row ? paintMarkRow(row) : \"\";\n\t\t\tconst rowWidth = row ? visibleWidth(row.accent + row.text) : 0;\n\t\t\tconst meta = index >= metaStart ? (metaLines[index - metaStart] ?? \"\") : \"\";\n\t\t\tconst gap = showMeta ? \" \".repeat(Math.max(0, brandMark.width - rowWidth + GUTTER)) : \"\";\n\t\t\tconst content = truncateToWidth(painted + (meta ? gap + meta : \"\"), contentWidth, \"\");\n\t\t\tlines.push(this.padLine(content, safeWidth, paddingX));\n\t\t}\n\n\t\t// Counted totals sit in a ruled band of their own. The rules are what\n\t\t// carry the structure on this screen, so nothing here is boxed.\n\t\tconst inventory = this.options.getInventory?.();\n\t\tif (inventory && contentWidth >= MIN_RULE_WIDTH) {\n\t\t\tconst rule = theme.fg(\"borderMuted\", (symbolPreset === \"ascii\" ? \"-\" : \"─\").repeat(contentWidth));\n\t\t\tlines.push(this.padLine(\"\", safeWidth, paddingX));\n\t\t\tlines.push(this.padLine(rule, safeWidth, paddingX));\n\t\t\tconst hint = this.options.inventoryHint;\n\t\t\tconst withHint = hint ? `${inventory} ${theme.fg(\"dim\", hint)}` : inventory;\n\t\t\tconst fitted =\n\t\t\t\tvisibleWidth(withHint) <= contentWidth ? withHint : truncateToWidth(inventory, contentWidth, \"\");\n\t\t\tlines.push(this.padLine(fitted, safeWidth, paddingX));\n\t\t\tlines.push(this.padLine(rule, safeWidth, paddingX));\n\t\t}\n\n\t\t// The hint is product copy rather than a runtime fact, so it sits under the\n\t\t// whole block at full width instead of inside the narrow metadata column,\n\t\t// where it would truncate even on a wide terminal.\n\t\tconst hint = this.options.getHint?.();\n\t\tif (hint) {\n\t\t\tlines.push(this.padLine(\"\", safeWidth, paddingX));\n\t\t\tlines.push(this.padLine(theme.fg(\"dim\", truncateToWidth(hint, contentWidth)), safeWidth, paddingX));\n\t\t}\n\n\t\tconst shortcuts = this.options.getShortcuts?.();\n\t\tif (shortcuts) {\n\t\t\tlines.push(this.padLine(\"\", safeWidth, paddingX));\n\t\t\tlines.push(this.padLine(truncateToWidth(shortcuts, contentWidth, \"\"), safeWidth, paddingX));\n\t\t}\n\n\t\tif (this.verboseInstructions) {\n\t\t\tlines.push(this.padLine(\"\", safeWidth, paddingX));\n\t\t\tfor (const instruction of this.verboseInstructions.split(\"\\n\")) {\n\t\t\t\tlines.push(this.padLine(truncateToWidth(instruction, contentWidth), safeWidth, paddingX));\n\t\t\t}\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\tprivate buildMetadata(valueWidth: number): string[] {\n\t\tconst labelled = (label: string, value: string) =>\n\t\t\ttheme.fg(\"dim\", label.padEnd(LABEL_WIDTH)) + theme.fg(\"muted\", truncateToWidth(value, valueWidth));\n\n\t\tconst cwd = formatCwdForFooter(this.getCwd(), process.env.HOME || process.env.USERPROFILE);\n\t\treturn [\n\t\t\tlabelled(\"version\", `v${this.version}`),\n\t\t\tlabelled(\"model\", this.getModelId() ?? \"—\"),\n\t\t\ttheme.fg(\"dim\", \"cwd\".padEnd(LABEL_WIDTH)) + theme.fg(\"muted\", truncatePathTail(cwd, valueWidth)),\n\t\t\t...(this.options.getExtraMetadata?.() ?? []).map((line) => labelled(line.label, line.value)),\n\t\t];\n\t}\n\n\tprivate padLine(content: string, safeWidth: number, paddingX: number): string {\n\t\tconst trailing = Math.max(0, safeWidth - paddingX - visibleWidth(content));\n\t\treturn \" \".repeat(paddingX) + content + \" \".repeat(trailing);\n\t}\n}\n"]}
@@ -1,11 +1,27 @@
1
1
  import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2
- import { APEX_PEAK_LOGO, APEX_PEAK_LOGO_ASCII, APEX_PEAK_LOGO_COMPACT } from "../../../themes/apex-logo.js";
2
+ import { customBrandMark, selectBrandMark, } from "../../../themes/apex-logo.js";
3
3
  import { theme } from "../theme/theme.js";
4
4
  import { formatCwdForFooter } from "./footer.js";
5
+ /**
6
+ * Paint one mark row: accent run then text run.
7
+ *
8
+ * Empty runs are skipped so no row carries a bare, unclosed SGR pair.
9
+ */
10
+ export function paintMarkRow(row) {
11
+ const accent = row.accent ? theme.fg("accent", row.accent) : "";
12
+ const text = row.text ? theme.fg("text", row.text) : "";
13
+ return accent + text;
14
+ }
15
+ /** The whole mark as painted lines, for surfaces that just want to stamp it. */
16
+ export function paintBrandMark(brandMark) {
17
+ return brandMark.rows.map(paintMarkRow);
18
+ }
5
19
  const GUTTER = 4;
6
20
  const LABEL_WIDTH = 9;
7
21
  /** Smallest value column worth rendering; below this the metadata column is dropped. */
8
22
  const MIN_VALUE_WIDTH = 8;
23
+ /** Below this the rules and the inventory line are dropped rather than clipped. */
24
+ const MIN_RULE_WIDTH = 12;
9
25
  /**
10
26
  * Keep the rightmost path segments that fit, so the leaf directory stays visible.
11
27
  *
@@ -58,13 +74,14 @@ function trailingByWidth(text, maxWidth) {
58
74
  return graphemes.slice(index).join("");
59
75
  }
60
76
  /**
61
- * Startup header: brand mark on the left, runtime metadata on the right.
77
+ * Startup header: brand mark on the left, runtime metadata on the right, then a
78
+ * ruled band carrying the counted inventory.
62
79
  *
63
80
  * Values are read through getters rather than captured at construction, so the
64
81
  * model fills in once the session binds without an explicit refresh.
65
82
  *
66
83
  * The metadata column is dropped when the terminal cannot hold it, and the mark
67
- * falls back to a compact variant rather than being truncated mid-glyph.
84
+ * falls back to a type lockup rather than being truncated mid-glyph.
68
85
  */
69
86
  export class ApexSplashHeader {
70
87
  version;
@@ -86,20 +103,44 @@ export class ApexSplashHeader {
86
103
  const safeWidth = Math.max(1, width);
87
104
  const paddingX = safeWidth > 1 ? 1 : 0;
88
105
  const contentWidth = Math.max(1, safeWidth - paddingX * 2);
89
- const logoLines = this.selectLogo(contentWidth);
90
- const logoWidth = logoLines.reduce((max, line) => Math.max(max, visibleWidth(line)), 0);
91
- const metaWidth = contentWidth - logoWidth - GUTTER;
106
+ const symbolPreset = this.options.getSymbolPreset?.() ?? "unicode";
107
+ const brandMark = this.options.logo
108
+ ? customBrandMark(this.options.logo)
109
+ : selectBrandMark(contentWidth, symbolPreset);
110
+ const metaWidth = contentWidth - brandMark.width - GUTTER;
92
111
  const showMeta = metaWidth >= LABEL_WIDTH + MIN_VALUE_WIDTH;
93
112
  const valueWidth = Math.max(1, metaWidth - LABEL_WIDTH);
94
113
  const metaLines = showMeta ? this.buildMetadata(valueWidth) : [];
95
- const metaStart = Math.max(0, Math.floor((logoLines.length - metaLines.length) / 2));
114
+ // Bottom-aligned, so the last metadata row sits on the mark's ember
115
+ // baseline and the two read as one block rather than two stacks.
116
+ const metaStart = Math.max(0, brandMark.rows.length - metaLines.length);
96
117
  const lines = this.options.topPadding ? [this.padLine("", safeWidth, paddingX)] : [];
97
- logoLines.forEach((line, index) => {
98
- const meta = index >= metaStart && index < metaStart + metaLines.length ? metaLines[index - metaStart] : "";
99
- const gap = showMeta ? " ".repeat(Math.max(0, logoWidth - visibleWidth(line) + GUTTER)) : "";
100
- const content = truncateToWidth(theme.fg("text", line) + gap + meta, contentWidth, "");
118
+ // The mark and the metadata column are two stacks of different heights
119
+ // sharing one block, so the block is as tall as the taller of them. A
120
+ // one-row mark beside three metadata rows still shows all three.
121
+ const blockRows = Math.max(brandMark.rows.length, metaStart + metaLines.length);
122
+ for (let index = 0; index < blockRows; index++) {
123
+ const row = brandMark.rows[index];
124
+ const painted = row ? paintMarkRow(row) : "";
125
+ const rowWidth = row ? visibleWidth(row.accent + row.text) : 0;
126
+ const meta = index >= metaStart ? (metaLines[index - metaStart] ?? "") : "";
127
+ const gap = showMeta ? " ".repeat(Math.max(0, brandMark.width - rowWidth + GUTTER)) : "";
128
+ const content = truncateToWidth(painted + (meta ? gap + meta : ""), contentWidth, "");
101
129
  lines.push(this.padLine(content, safeWidth, paddingX));
102
- });
130
+ }
131
+ // Counted totals sit in a ruled band of their own. The rules are what
132
+ // carry the structure on this screen, so nothing here is boxed.
133
+ const inventory = this.options.getInventory?.();
134
+ if (inventory && contentWidth >= MIN_RULE_WIDTH) {
135
+ const rule = theme.fg("borderMuted", (symbolPreset === "ascii" ? "-" : "─").repeat(contentWidth));
136
+ lines.push(this.padLine("", safeWidth, paddingX));
137
+ lines.push(this.padLine(rule, safeWidth, paddingX));
138
+ const hint = this.options.inventoryHint;
139
+ const withHint = hint ? `${inventory} ${theme.fg("dim", hint)}` : inventory;
140
+ const fitted = visibleWidth(withHint) <= contentWidth ? withHint : truncateToWidth(inventory, contentWidth, "");
141
+ lines.push(this.padLine(fitted, safeWidth, paddingX));
142
+ lines.push(this.padLine(rule, safeWidth, paddingX));
143
+ }
103
144
  // The hint is product copy rather than a runtime fact, so it sits under the
104
145
  // whole block at full width instead of inside the narrow metadata column,
105
146
  // where it would truncate even on a wide terminal.
@@ -108,6 +149,11 @@ export class ApexSplashHeader {
108
149
  lines.push(this.padLine("", safeWidth, paddingX));
109
150
  lines.push(this.padLine(theme.fg("dim", truncateToWidth(hint, contentWidth)), safeWidth, paddingX));
110
151
  }
152
+ const shortcuts = this.options.getShortcuts?.();
153
+ if (shortcuts) {
154
+ lines.push(this.padLine("", safeWidth, paddingX));
155
+ lines.push(this.padLine(truncateToWidth(shortcuts, contentWidth, ""), safeWidth, paddingX));
156
+ }
111
157
  if (this.verboseInstructions) {
112
158
  lines.push(this.padLine("", safeWidth, paddingX));
113
159
  for (const instruction of this.verboseInstructions.split("\n")) {
@@ -116,17 +162,6 @@ export class ApexSplashHeader {
116
162
  }
117
163
  return lines;
118
164
  }
119
- selectLogo(contentWidth) {
120
- if (this.options.logo) {
121
- return this.options.logo.split("\n");
122
- }
123
- if (this.options.getSymbolPreset?.() === "ascii") {
124
- return APEX_PEAK_LOGO_ASCII.split("\n");
125
- }
126
- const full = APEX_PEAK_LOGO.split("\n");
127
- const fullWidth = full.reduce((max, line) => Math.max(max, visibleWidth(line)), 0);
128
- return contentWidth >= fullWidth ? full : APEX_PEAK_LOGO_COMPACT.split("\n");
129
- }
130
165
  buildMetadata(valueWidth) {
131
166
  const labelled = (label, value) => theme.fg("dim", label.padEnd(LABEL_WIDTH)) + theme.fg("muted", truncateToWidth(value, valueWidth));
132
167
  const cwd = formatCwdForFooter(this.getCwd(), process.env.HOME || process.env.USERPROFILE);
@@ -1 +1 @@
1
- {"version":3,"file":"splash-header.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/splash-header.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@earendil-works/pi-tui\";\nimport { APEX_PEAK_LOGO, APEX_PEAK_LOGO_ASCII, APEX_PEAK_LOGO_COMPACT } from \"../../../themes/apex-logo.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { formatCwdForFooter } from \"./footer.ts\";\n\n/** One `label value` row in the metadata column. */\nexport interface SplashHeaderMetadataLine {\n\tlabel: string;\n\tvalue: string;\n}\n\nexport interface ApexSplashHeaderOptions {\n\t/** Rows appended after `cwd`, for extensions that want to contribute context. */\n\tgetExtraMetadata?: () => readonly SplashHeaderMetadataLine[];\n\t/** Hint rendered under the metadata block. Return undefined for none. */\n\tgetHint?: () => string | undefined;\n\t/** Resolves `terminal.symbolPreset`. Defaults to unicode. */\n\tgetSymbolPreset?: () => \"unicode\" | \"ascii\";\n\t/** Emit a leading blank line above the mark. */\n\ttopPadding?: boolean;\n\t/** Override the mark. Used by tests and by extensions that rebrand. */\n\tlogo?: string;\n}\n\nconst GUTTER = 4;\nconst LABEL_WIDTH = 9;\n/** Smallest value column worth rendering; below this the metadata column is dropped. */\nconst MIN_VALUE_WIDTH = 8;\n\n/**\n * Keep the rightmost path segments that fit, so the leaf directory stays visible.\n *\n * `truncateToWidth` clips the tail, which for a path hides the part that\n * identifies the session. This clips the head instead.\n */\nfunction truncatePathTail(path: string, maxWidth: number): string {\n\tif (maxWidth <= 0) {\n\t\treturn \"\";\n\t}\n\tif (visibleWidth(path) <= maxWidth) {\n\t\treturn path;\n\t}\n\tconst ellipsis = \"…/\";\n\tconst budget = Math.max(0, maxWidth - visibleWidth(ellipsis));\n\tconst segments = path.split(\"/\");\n\tlet tail = \"\";\n\tfor (let index = segments.length - 1; index >= 0; index--) {\n\t\tconst candidate = tail ? `${segments[index]}/${tail}` : segments[index];\n\t\tif (visibleWidth(candidate) > budget) {\n\t\t\tbreak;\n\t\t}\n\t\ttail = candidate;\n\t}\n\t// A single segment longer than the whole budget: clip it from the left.\n\treturn tail ? ellipsis + tail : ellipsis + trailingByWidth(path, budget);\n}\n\n/**\n * The widest suffix of `text` that fits `maxWidth` terminal columns.\n *\n * Walks grapheme clusters from the right rather than using `String.slice`, whose\n * UTF-16 offsets would cut an astral character in half — emitting a lone\n * surrogate — and would miscount every double-width character on the way.\n */\nfunction trailingByWidth(text: string, maxWidth: number): string {\n\tif (maxWidth <= 0) {\n\t\treturn \"\";\n\t}\n\tconst graphemes = [...new Intl.Segmenter(undefined, { granularity: \"grapheme\" }).segment(text)].map(\n\t\t(entry) => entry.segment,\n\t);\n\tlet width = 0;\n\tlet index = graphemes.length;\n\twhile (index > 0) {\n\t\tconst next = width + visibleWidth(graphemes[index - 1]);\n\t\tif (next > maxWidth) {\n\t\t\tbreak;\n\t\t}\n\t\twidth = next;\n\t\tindex -= 1;\n\t}\n\treturn graphemes.slice(index).join(\"\");\n}\n\n/**\n * Startup header: brand mark on the left, runtime metadata on the right.\n *\n * Values are read through getters rather than captured at construction, so the\n * model fills in once the session binds without an explicit refresh.\n *\n * The metadata column is dropped when the terminal cannot hold it, and the mark\n * falls back to a compact variant rather than being truncated mid-glyph.\n */\nexport class ApexSplashHeader implements Component {\n\tprivate readonly version: string;\n\tprivate readonly getModelId: () => string | undefined;\n\tprivate readonly getCwd: () => string;\n\tprivate readonly verboseInstructions: string | undefined;\n\tprivate readonly options: ApexSplashHeaderOptions;\n\n\tconstructor(\n\t\tversion: string,\n\t\tgetModelId: () => string | undefined,\n\t\tgetCwd: () => string,\n\t\tverboseInstructions?: string,\n\t\toptions: ApexSplashHeaderOptions = {},\n\t) {\n\t\tthis.version = version;\n\t\tthis.getModelId = getModelId;\n\t\tthis.getCwd = getCwd;\n\t\tthis.verboseInstructions = verboseInstructions;\n\t\tthis.options = options;\n\t}\n\n\tinvalidate(): void {\n\t\t// Render output is derived from current theme and session state.\n\t}\n\n\trender(width: number): string[] {\n\t\tconst safeWidth = Math.max(1, width);\n\t\tconst paddingX = safeWidth > 1 ? 1 : 0;\n\t\tconst contentWidth = Math.max(1, safeWidth - paddingX * 2);\n\n\t\tconst logoLines = this.selectLogo(contentWidth);\n\t\tconst logoWidth = logoLines.reduce((max, line) => Math.max(max, visibleWidth(line)), 0);\n\n\t\tconst metaWidth = contentWidth - logoWidth - GUTTER;\n\t\tconst showMeta = metaWidth >= LABEL_WIDTH + MIN_VALUE_WIDTH;\n\t\tconst valueWidth = Math.max(1, metaWidth - LABEL_WIDTH);\n\t\tconst metaLines = showMeta ? this.buildMetadata(valueWidth) : [];\n\t\tconst metaStart = Math.max(0, Math.floor((logoLines.length - metaLines.length) / 2));\n\n\t\tconst lines = this.options.topPadding ? [this.padLine(\"\", safeWidth, paddingX)] : [];\n\n\t\tlogoLines.forEach((line, index) => {\n\t\t\tconst meta = index >= metaStart && index < metaStart + metaLines.length ? metaLines[index - metaStart] : \"\";\n\t\t\tconst gap = showMeta ? \" \".repeat(Math.max(0, logoWidth - visibleWidth(line) + GUTTER)) : \"\";\n\t\t\tconst content = truncateToWidth(theme.fg(\"text\", line) + gap + meta, contentWidth, \"\");\n\t\t\tlines.push(this.padLine(content, safeWidth, paddingX));\n\t\t});\n\n\t\t// The hint is product copy rather than a runtime fact, so it sits under the\n\t\t// whole block at full width instead of inside the narrow metadata column,\n\t\t// where it would truncate even on a wide terminal.\n\t\tconst hint = this.options.getHint?.();\n\t\tif (hint) {\n\t\t\tlines.push(this.padLine(\"\", safeWidth, paddingX));\n\t\t\tlines.push(this.padLine(theme.fg(\"dim\", truncateToWidth(hint, contentWidth)), safeWidth, paddingX));\n\t\t}\n\n\t\tif (this.verboseInstructions) {\n\t\t\tlines.push(this.padLine(\"\", safeWidth, paddingX));\n\t\t\tfor (const instruction of this.verboseInstructions.split(\"\\n\")) {\n\t\t\t\tlines.push(this.padLine(truncateToWidth(instruction, contentWidth), safeWidth, paddingX));\n\t\t\t}\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\tprivate selectLogo(contentWidth: number): string[] {\n\t\tif (this.options.logo) {\n\t\t\treturn this.options.logo.split(\"\\n\");\n\t\t}\n\t\tif (this.options.getSymbolPreset?.() === \"ascii\") {\n\t\t\treturn APEX_PEAK_LOGO_ASCII.split(\"\\n\");\n\t\t}\n\t\tconst full = APEX_PEAK_LOGO.split(\"\\n\");\n\t\tconst fullWidth = full.reduce((max, line) => Math.max(max, visibleWidth(line)), 0);\n\t\treturn contentWidth >= fullWidth ? full : APEX_PEAK_LOGO_COMPACT.split(\"\\n\");\n\t}\n\n\tprivate buildMetadata(valueWidth: number): string[] {\n\t\tconst labelled = (label: string, value: string) =>\n\t\t\ttheme.fg(\"dim\", label.padEnd(LABEL_WIDTH)) + theme.fg(\"muted\", truncateToWidth(value, valueWidth));\n\n\t\tconst cwd = formatCwdForFooter(this.getCwd(), process.env.HOME || process.env.USERPROFILE);\n\t\treturn [\n\t\t\tlabelled(\"version\", `v${this.version}`),\n\t\t\tlabelled(\"model\", this.getModelId() ?? \"—\"),\n\t\t\ttheme.fg(\"dim\", \"cwd\".padEnd(LABEL_WIDTH)) + theme.fg(\"muted\", truncatePathTail(cwd, valueWidth)),\n\t\t\t...(this.options.getExtraMetadata?.() ?? []).map((line) => labelled(line.label, line.value)),\n\t\t];\n\t}\n\n\tprivate padLine(content: string, safeWidth: number, paddingX: number): string {\n\t\tconst trailing = Math.max(0, safeWidth - paddingX - visibleWidth(content));\n\t\treturn \" \".repeat(paddingX) + content + \" \".repeat(trailing);\n\t}\n}\n"]}
1
+ {"version":3,"file":"splash-header.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/splash-header.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@earendil-works/pi-tui\";\nimport {\n\ttype BrandMark,\n\tcustomBrandMark,\n\ttype MarkRow,\n\ttype MarkSymbolPreset,\n\tselectBrandMark,\n} from \"../../../themes/apex-logo.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { formatCwdForFooter } from \"./footer.ts\";\n\n/** One `label value` row in the metadata column. */\nexport interface SplashHeaderMetadataLine {\n\tlabel: string;\n\tvalue: string;\n}\n\nexport interface ApexSplashHeaderOptions {\n\t/** Rows appended after `cwd`, for extensions that want to contribute context. */\n\tgetExtraMetadata?: () => readonly SplashHeaderMetadataLine[];\n\t/**\n\t * One line of counted totals, rendered between the rules under the mark.\n\t * The header does not know how to count anything; the caller supplies the\n\t * finished line so the resource loader stays on its own side of the seam.\n\t */\n\tgetInventory?: () => string | undefined;\n\t/**\n\t * Affordance appended to the inventory line, e.g. the command that shows the\n\t * detail. Dropped whole rather than clipped, so it never renders as a stub.\n\t */\n\tinventoryHint?: string;\n\t/** Hint rendered under the metadata block. Return undefined for none. */\n\tgetHint?: () => string | undefined;\n\t/**\n\t * One row of entry-point sigils under the hint, already coloured by the\n\t * caller. The header does not know the keybindings, so it does not build it.\n\t */\n\tgetShortcuts?: () => string | undefined;\n\t/** Resolves `terminal.symbolPreset`. Defaults to unicode. */\n\tgetSymbolPreset?: () => MarkSymbolPreset;\n\t/** Emit a leading blank line above the mark. */\n\ttopPadding?: boolean;\n\t/** Override the mark. Used by tests and by extensions that rebrand. */\n\tlogo?: string;\n}\n\n/**\n * Paint one mark row: accent run then text run.\n *\n * Empty runs are skipped so no row carries a bare, unclosed SGR pair.\n */\nexport function paintMarkRow(row: MarkRow): string {\n\tconst accent = row.accent ? theme.fg(\"accent\", row.accent) : \"\";\n\tconst text = row.text ? theme.fg(\"text\", row.text) : \"\";\n\treturn accent + text;\n}\n\n/** The whole mark as painted lines, for surfaces that just want to stamp it. */\nexport function paintBrandMark(brandMark: BrandMark): string[] {\n\treturn brandMark.rows.map(paintMarkRow);\n}\n\nconst GUTTER = 4;\nconst LABEL_WIDTH = 9;\n/** Smallest value column worth rendering; below this the metadata column is dropped. */\nconst MIN_VALUE_WIDTH = 8;\n/** Below this the rules and the inventory line are dropped rather than clipped. */\nconst MIN_RULE_WIDTH = 12;\n\n/**\n * Keep the rightmost path segments that fit, so the leaf directory stays visible.\n *\n * `truncateToWidth` clips the tail, which for a path hides the part that\n * identifies the session. This clips the head instead.\n */\nfunction truncatePathTail(path: string, maxWidth: number): string {\n\tif (maxWidth <= 0) {\n\t\treturn \"\";\n\t}\n\tif (visibleWidth(path) <= maxWidth) {\n\t\treturn path;\n\t}\n\tconst ellipsis = \"…/\";\n\tconst budget = Math.max(0, maxWidth - visibleWidth(ellipsis));\n\tconst segments = path.split(\"/\");\n\tlet tail = \"\";\n\tfor (let index = segments.length - 1; index >= 0; index--) {\n\t\tconst candidate = tail ? `${segments[index]}/${tail}` : segments[index];\n\t\tif (visibleWidth(candidate) > budget) {\n\t\t\tbreak;\n\t\t}\n\t\ttail = candidate;\n\t}\n\t// A single segment longer than the whole budget: clip it from the left.\n\treturn tail ? ellipsis + tail : ellipsis + trailingByWidth(path, budget);\n}\n\n/**\n * The widest suffix of `text` that fits `maxWidth` terminal columns.\n *\n * Walks grapheme clusters from the right rather than using `String.slice`, whose\n * UTF-16 offsets would cut an astral character in half — emitting a lone\n * surrogate — and would miscount every double-width character on the way.\n */\nfunction trailingByWidth(text: string, maxWidth: number): string {\n\tif (maxWidth <= 0) {\n\t\treturn \"\";\n\t}\n\tconst graphemes = [...new Intl.Segmenter(undefined, { granularity: \"grapheme\" }).segment(text)].map(\n\t\t(entry) => entry.segment,\n\t);\n\tlet width = 0;\n\tlet index = graphemes.length;\n\twhile (index > 0) {\n\t\tconst next = width + visibleWidth(graphemes[index - 1]);\n\t\tif (next > maxWidth) {\n\t\t\tbreak;\n\t\t}\n\t\twidth = next;\n\t\tindex -= 1;\n\t}\n\treturn graphemes.slice(index).join(\"\");\n}\n\n/**\n * Startup header: brand mark on the left, runtime metadata on the right, then a\n * ruled band carrying the counted inventory.\n *\n * Values are read through getters rather than captured at construction, so the\n * model fills in once the session binds without an explicit refresh.\n *\n * The metadata column is dropped when the terminal cannot hold it, and the mark\n * falls back to a type lockup rather than being truncated mid-glyph.\n */\nexport class ApexSplashHeader implements Component {\n\tprivate readonly version: string;\n\tprivate readonly getModelId: () => string | undefined;\n\tprivate readonly getCwd: () => string;\n\tprivate readonly verboseInstructions: string | undefined;\n\tprivate readonly options: ApexSplashHeaderOptions;\n\n\tconstructor(\n\t\tversion: string,\n\t\tgetModelId: () => string | undefined,\n\t\tgetCwd: () => string,\n\t\tverboseInstructions?: string,\n\t\toptions: ApexSplashHeaderOptions = {},\n\t) {\n\t\tthis.version = version;\n\t\tthis.getModelId = getModelId;\n\t\tthis.getCwd = getCwd;\n\t\tthis.verboseInstructions = verboseInstructions;\n\t\tthis.options = options;\n\t}\n\n\tinvalidate(): void {\n\t\t// Render output is derived from current theme and session state.\n\t}\n\n\trender(width: number): string[] {\n\t\tconst safeWidth = Math.max(1, width);\n\t\tconst paddingX = safeWidth > 1 ? 1 : 0;\n\t\tconst contentWidth = Math.max(1, safeWidth - paddingX * 2);\n\t\tconst symbolPreset = this.options.getSymbolPreset?.() ?? \"unicode\";\n\n\t\tconst brandMark = this.options.logo\n\t\t\t? customBrandMark(this.options.logo)\n\t\t\t: selectBrandMark(contentWidth, symbolPreset);\n\n\t\tconst metaWidth = contentWidth - brandMark.width - GUTTER;\n\t\tconst showMeta = metaWidth >= LABEL_WIDTH + MIN_VALUE_WIDTH;\n\t\tconst valueWidth = Math.max(1, metaWidth - LABEL_WIDTH);\n\t\tconst metaLines = showMeta ? this.buildMetadata(valueWidth) : [];\n\t\t// Bottom-aligned, so the last metadata row sits on the mark's ember\n\t\t// baseline and the two read as one block rather than two stacks.\n\t\tconst metaStart = Math.max(0, brandMark.rows.length - metaLines.length);\n\n\t\tconst lines = this.options.topPadding ? [this.padLine(\"\", safeWidth, paddingX)] : [];\n\n\t\t// The mark and the metadata column are two stacks of different heights\n\t\t// sharing one block, so the block is as tall as the taller of them. A\n\t\t// one-row mark beside three metadata rows still shows all three.\n\t\tconst blockRows = Math.max(brandMark.rows.length, metaStart + metaLines.length);\n\t\tfor (let index = 0; index < blockRows; index++) {\n\t\t\tconst row = brandMark.rows[index];\n\t\t\tconst painted = row ? paintMarkRow(row) : \"\";\n\t\t\tconst rowWidth = row ? visibleWidth(row.accent + row.text) : 0;\n\t\t\tconst meta = index >= metaStart ? (metaLines[index - metaStart] ?? \"\") : \"\";\n\t\t\tconst gap = showMeta ? \" \".repeat(Math.max(0, brandMark.width - rowWidth + GUTTER)) : \"\";\n\t\t\tconst content = truncateToWidth(painted + (meta ? gap + meta : \"\"), contentWidth, \"\");\n\t\t\tlines.push(this.padLine(content, safeWidth, paddingX));\n\t\t}\n\n\t\t// Counted totals sit in a ruled band of their own. The rules are what\n\t\t// carry the structure on this screen, so nothing here is boxed.\n\t\tconst inventory = this.options.getInventory?.();\n\t\tif (inventory && contentWidth >= MIN_RULE_WIDTH) {\n\t\t\tconst rule = theme.fg(\"borderMuted\", (symbolPreset === \"ascii\" ? \"-\" : \"─\").repeat(contentWidth));\n\t\t\tlines.push(this.padLine(\"\", safeWidth, paddingX));\n\t\t\tlines.push(this.padLine(rule, safeWidth, paddingX));\n\t\t\tconst hint = this.options.inventoryHint;\n\t\t\tconst withHint = hint ? `${inventory} ${theme.fg(\"dim\", hint)}` : inventory;\n\t\t\tconst fitted =\n\t\t\t\tvisibleWidth(withHint) <= contentWidth ? withHint : truncateToWidth(inventory, contentWidth, \"\");\n\t\t\tlines.push(this.padLine(fitted, safeWidth, paddingX));\n\t\t\tlines.push(this.padLine(rule, safeWidth, paddingX));\n\t\t}\n\n\t\t// The hint is product copy rather than a runtime fact, so it sits under the\n\t\t// whole block at full width instead of inside the narrow metadata column,\n\t\t// where it would truncate even on a wide terminal.\n\t\tconst hint = this.options.getHint?.();\n\t\tif (hint) {\n\t\t\tlines.push(this.padLine(\"\", safeWidth, paddingX));\n\t\t\tlines.push(this.padLine(theme.fg(\"dim\", truncateToWidth(hint, contentWidth)), safeWidth, paddingX));\n\t\t}\n\n\t\tconst shortcuts = this.options.getShortcuts?.();\n\t\tif (shortcuts) {\n\t\t\tlines.push(this.padLine(\"\", safeWidth, paddingX));\n\t\t\tlines.push(this.padLine(truncateToWidth(shortcuts, contentWidth, \"\"), safeWidth, paddingX));\n\t\t}\n\n\t\tif (this.verboseInstructions) {\n\t\t\tlines.push(this.padLine(\"\", safeWidth, paddingX));\n\t\t\tfor (const instruction of this.verboseInstructions.split(\"\\n\")) {\n\t\t\t\tlines.push(this.padLine(truncateToWidth(instruction, contentWidth), safeWidth, paddingX));\n\t\t\t}\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\tprivate buildMetadata(valueWidth: number): string[] {\n\t\tconst labelled = (label: string, value: string) =>\n\t\t\ttheme.fg(\"dim\", label.padEnd(LABEL_WIDTH)) + theme.fg(\"muted\", truncateToWidth(value, valueWidth));\n\n\t\tconst cwd = formatCwdForFooter(this.getCwd(), process.env.HOME || process.env.USERPROFILE);\n\t\treturn [\n\t\t\tlabelled(\"version\", `v${this.version}`),\n\t\t\tlabelled(\"model\", this.getModelId() ?? \"—\"),\n\t\t\ttheme.fg(\"dim\", \"cwd\".padEnd(LABEL_WIDTH)) + theme.fg(\"muted\", truncatePathTail(cwd, valueWidth)),\n\t\t\t...(this.options.getExtraMetadata?.() ?? []).map((line) => labelled(line.label, line.value)),\n\t\t];\n\t}\n\n\tprivate padLine(content: string, safeWidth: number, paddingX: number): string {\n\t\tconst trailing = Math.max(0, safeWidth - paddingX - visibleWidth(content));\n\t\treturn \" \".repeat(paddingX) + content + \" \".repeat(trailing);\n\t}\n}\n"]}
@@ -1,11 +1,23 @@
1
- import { Container, SelectList } from "@earendil-works/pi-tui";
1
+ import { Container, type Focusable, SelectList } from "@earendil-works/pi-tui";
2
2
  import type { ThinkingLevel } from "apex-code-agent-core";
3
3
  /**
4
4
  * Component that renders a thinking level selector with borders
5
5
  */
6
- export declare class ThinkingSelectorComponent extends Container {
6
+ export declare class ThinkingSelectorComponent extends Container implements Focusable {
7
+ private searchInput;
7
8
  private selectList;
8
- constructor(currentLevel: ThinkingLevel, availableLevels: ThinkingLevel[], onSelect: (level: ThinkingLevel) => void, onCancel: () => void);
9
+ private selectListChildIndex;
10
+ private allItems;
11
+ private onSelect;
12
+ private onCancel;
13
+ private onSelectAsDefault?;
14
+ private _focused;
15
+ get focused(): boolean;
16
+ set focused(value: boolean);
17
+ constructor(currentLevel: ThinkingLevel, availableLevels: ThinkingLevel[], onSelect: (level: ThinkingLevel) => void, onCancel: () => void, onSelectAsDefault?: (level: ThinkingLevel) => void, defaultThinkingLevel?: ThinkingLevel);
18
+ private buildSelectList;
19
+ private applyFilter;
20
+ handleInput(keyData: string): void;
9
21
  getSelectList(): SelectList;
10
22
  }
11
23
  //# sourceMappingURL=thinking-selector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"thinking-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/thinking-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAmB,UAAU,EAAgC,MAAM,wBAAwB,CAAC;AAC9G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAmB1D;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,SAAS;IACvD,OAAO,CAAC,UAAU,CAAa;IAE/B,YACC,YAAY,EAAE,aAAa,EAC3B,eAAe,EAAE,aAAa,EAAE,EAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EACxC,QAAQ,EAAE,MAAM,IAAI,EAuCpB;IAED,aAAa,IAAI,UAAU,CAE1B;CACD","sourcesContent":["import { Container, type SelectItem, SelectList, type SelectListLayoutOptions } from \"@earendil-works/pi-tui\";\nimport type { ThinkingLevel } from \"apex-code-agent-core\";\nimport { getSelectListTheme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\n\nconst THINKING_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nconst LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Extra-high reasoning (~32k tokens)\",\n\tmax: \"Maximum reasoning\",\n};\n\n/**\n * Component that renders a thinking level selector with borders\n */\nexport class ThinkingSelectorComponent extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\tcurrentLevel: ThinkingLevel,\n\t\tavailableLevels: ThinkingLevel[],\n\t\tonSelect: (level: ThinkingLevel) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst thinkingLevels: SelectItem[] = availableLevels.map((level) => ({\n\t\t\tvalue: level,\n\t\t\tlabel: level,\n\t\t\tdescription: LEVEL_DESCRIPTIONS[level],\n\t\t}));\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Create selector\n\t\tthis.selectList = new SelectList(\n\t\t\tthinkingLevels,\n\t\t\tthinkingLevels.length,\n\t\t\tgetSelectListTheme(),\n\t\t\tTHINKING_SELECT_LIST_LAYOUT,\n\t\t);\n\n\t\t// Preselect current level\n\t\tconst currentIndex = thinkingLevels.findIndex((item) => item.value === currentLevel);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value as ThinkingLevel);\n\t\t};\n\n\t\tthis.selectList.onCancel = () => {\n\t\t\tonCancel();\n\t\t};\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSelectList(): SelectList {\n\t\treturn this.selectList;\n\t}\n}\n"]}
1
+ {"version":3,"file":"thinking-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/thinking-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,SAAS,EACT,KAAK,SAAS,EAMd,UAAU,EAIV,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAoB1D;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,SAAU,YAAW,SAAS;IAC5E,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,iBAAiB,CAAC,CAAiC;IAC3D,OAAO,CAAC,QAAQ,CAAS;IAEzB,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,YACC,YAAY,EAAE,aAAa,EAC3B,eAAe,EAAE,aAAa,EAAE,EAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EACxC,QAAQ,EAAE,MAAM,IAAI,EACpB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EAClD,oBAAoB,CAAC,EAAE,aAAa,EAoCpC;IAED,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,WAAW;IAUnB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAoBjC;IAED,aAAa,IAAI,UAAU,CAE1B;CACD","sourcesContent":["import {\n\tContainer,\n\ttype Focusable,\n\tfuzzyFilter,\n\tgetKeybindings,\n\tInput,\n\tmatchesKey,\n\ttype SelectItem,\n\tSelectList,\n\ttype SelectListLayoutOptions,\n\tSpacer,\n\tText,\n} from \"@earendil-works/pi-tui\";\nimport type { ThinkingLevel } from \"apex-code-agent-core\";\nimport { getSelectListTheme, theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyDisplayText } from \"./keybinding-hints.ts\";\n\nconst THINKING_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nconst LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Extra-high reasoning (~32k tokens)\",\n\tmax: \"Maximum reasoning\",\n};\n\n/**\n * Component that renders a thinking level selector with borders\n */\nexport class ThinkingSelectorComponent extends Container implements Focusable {\n\tprivate searchInput: Input;\n\tprivate selectList: SelectList;\n\tprivate selectListChildIndex: number;\n\tprivate allItems: SelectItem[];\n\tprivate onSelect: (level: ThinkingLevel) => void;\n\tprivate onCancel: () => void;\n\tprivate onSelectAsDefault?: (level: ThinkingLevel) => void;\n\tprivate _focused = false;\n\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\n\tconstructor(\n\t\tcurrentLevel: ThinkingLevel,\n\t\tavailableLevels: ThinkingLevel[],\n\t\tonSelect: (level: ThinkingLevel) => void,\n\t\tonCancel: () => void,\n\t\tonSelectAsDefault?: (level: ThinkingLevel) => void,\n\t\tdefaultThinkingLevel?: ThinkingLevel,\n\t) {\n\t\tsuper();\n\t\tthis.onSelect = onSelect;\n\t\tthis.onCancel = onCancel;\n\t\tthis.onSelectAsDefault = onSelectAsDefault;\n\n\t\tthis.allItems = availableLevels.map((level) => ({\n\t\t\tvalue: level,\n\t\t\tlabel: level,\n\t\t\tdescription:\n\t\t\t\tlevel === defaultThinkingLevel ? `${LEVEL_DESCRIPTIONS[level]} · default` : LEVEL_DESCRIPTIONS[level],\n\t\t}));\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(\"Thinking Level\", 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(`${keyDisplayText(\"app.thinking.cycle\")} cycles thinking levels in-session`, 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.searchInput = new Input();\n\t\tthis.searchInput.onSubmit = () => this.selectList.handleInput(\"\\r\");\n\t\tthis.addChild(this.searchInput);\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create selector\n\t\tthis.selectList = this.buildSelectList(this.allItems, currentLevel);\n\t\tthis.selectListChildIndex = this.children.length;\n\t\tthis.addChild(this.selectList);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"dim\", \" Enter to select · Ctrl+S to set as default · Esc to cancel\"), 0, 0));\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tprivate buildSelectList(items: SelectItem[], preselect?: ThinkingLevel): SelectList {\n\t\tconst list = new SelectList(items, Math.max(1, items.length), getSelectListTheme(), THINKING_SELECT_LIST_LAYOUT);\n\t\tconst currentIndex = items.findIndex((item) => item.value === preselect);\n\t\tif (currentIndex !== -1) {\n\t\t\tlist.setSelectedIndex(currentIndex);\n\t\t}\n\t\tlist.onSelect = (item) => this.onSelect(item.value as ThinkingLevel);\n\t\tlist.onCancel = () => this.onCancel();\n\t\treturn list;\n\t}\n\n\tprivate applyFilter(query: string): void {\n\t\tconst filtered = query\n\t\t\t? fuzzyFilter(this.allItems, query, (item) => `${item.label} ${item.description ?? \"\"}`)\n\t\t\t: this.allItems;\n\t\tconst selectedValue = this.selectList.getSelectedItem()?.value as ThinkingLevel | undefined;\n\t\tconst newList = this.buildSelectList(filtered, selectedValue);\n\t\tthis.children[this.selectListChildIndex] = newList;\n\t\tthis.selectList = newList;\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tif (matchesKey(keyData, \"ctrl+s\") && this.onSelectAsDefault) {\n\t\t\tconst item = this.selectList.getSelectedItem();\n\t\t\tif (item) this.onSelectAsDefault(item.value as ThinkingLevel);\n\t\t\treturn;\n\t\t}\n\n\t\tconst kb = getKeybindings();\n\t\tconst isNav =\n\t\t\tkb.matches(keyData, \"tui.select.up\") ||\n\t\t\tkb.matches(keyData, \"tui.select.down\") ||\n\t\t\tkb.matches(keyData, \"tui.select.confirm\") ||\n\t\t\tkb.matches(keyData, \"tui.select.cancel\");\n\t\tif (isNav) {\n\t\t\tthis.selectList.handleInput(keyData);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.searchInput.handleInput(keyData);\n\t\tthis.applyFilter(this.searchInput.getValue());\n\t}\n\n\tgetSelectList(): SelectList {\n\t\treturn this.selectList;\n\t}\n}\n"]}