apex-code 0.0.1-alpha.8 → 0.0.4

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 (634) hide show
  1. package/CHANGELOG.md +231 -0
  2. package/README.md +9 -8
  3. package/dist/cli/args.d.ts +26 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +72 -10
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/file-processor.d.ts.map +1 -1
  8. package/dist/cli/file-processor.js +2 -1
  9. package/dist/cli/file-processor.js.map +1 -1
  10. package/dist/cli/mcp-command.d.ts +9 -0
  11. package/dist/cli/mcp-command.d.ts.map +1 -0
  12. package/dist/cli/mcp-command.js +53 -0
  13. package/dist/cli/mcp-command.js.map +1 -0
  14. package/dist/cli/project-trust.d.ts.map +1 -1
  15. package/dist/cli/project-trust.js +3 -1
  16. package/dist/cli/project-trust.js.map +1 -1
  17. package/dist/cli/startup-ui.d.ts.map +1 -1
  18. package/dist/cli/startup-ui.js +2 -1
  19. package/dist/cli/startup-ui.js.map +1 -1
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +94 -5
  22. package/dist/cli.js.map +1 -1
  23. package/dist/config.d.ts +3 -2
  24. package/dist/config.d.ts.map +1 -1
  25. package/dist/config.js +45 -18
  26. package/dist/config.js.map +1 -1
  27. package/dist/core/agent-session-services.d.ts +3 -0
  28. package/dist/core/agent-session-services.d.ts.map +1 -1
  29. package/dist/core/agent-session-services.js +1 -0
  30. package/dist/core/agent-session-services.js.map +1 -1
  31. package/dist/core/agent-session.d.ts +183 -15
  32. package/dist/core/agent-session.d.ts.map +1 -1
  33. package/dist/core/agent-session.js +728 -117
  34. package/dist/core/agent-session.js.map +1 -1
  35. package/dist/core/auth-storage.d.ts.map +1 -1
  36. package/dist/core/auth-storage.js +6 -7
  37. package/dist/core/auth-storage.js.map +1 -1
  38. package/dist/core/bash-executor.d.ts.map +1 -1
  39. package/dist/core/bash-executor.js +7 -0
  40. package/dist/core/bash-executor.js.map +1 -1
  41. package/dist/core/checkpoints/git-checkpoints.d.ts +39 -0
  42. package/dist/core/checkpoints/git-checkpoints.d.ts.map +1 -0
  43. package/dist/core/checkpoints/git-checkpoints.js +251 -0
  44. package/dist/core/checkpoints/git-checkpoints.js.map +1 -0
  45. package/dist/core/checkpoints/session-checkpoints.d.ts +23 -0
  46. package/dist/core/checkpoints/session-checkpoints.d.ts.map +1 -0
  47. package/dist/core/checkpoints/session-checkpoints.js +36 -0
  48. package/dist/core/checkpoints/session-checkpoints.js.map +1 -0
  49. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  50. package/dist/core/compaction/branch-summarization.js +7 -3
  51. package/dist/core/compaction/branch-summarization.js.map +1 -1
  52. package/dist/core/compaction/compaction.d.ts +9 -3
  53. package/dist/core/compaction/compaction.d.ts.map +1 -1
  54. package/dist/core/compaction/compaction.js +59 -36
  55. package/dist/core/compaction/compaction.js.map +1 -1
  56. package/dist/core/defaults.d.ts +1 -0
  57. package/dist/core/defaults.d.ts.map +1 -1
  58. package/dist/core/defaults.js +9 -0
  59. package/dist/core/defaults.js.map +1 -1
  60. package/dist/core/delegation/runtime.d.ts +6 -5
  61. package/dist/core/delegation/runtime.d.ts.map +1 -1
  62. package/dist/core/delegation/runtime.js +4 -3
  63. package/dist/core/delegation/runtime.js.map +1 -1
  64. package/dist/core/durable-state/sqlite.d.ts.map +1 -1
  65. package/dist/core/durable-state/sqlite.js +26 -2
  66. package/dist/core/durable-state/sqlite.js.map +1 -1
  67. package/dist/core/exec.d.ts.map +1 -1
  68. package/dist/core/exec.js +5 -2
  69. package/dist/core/exec.js.map +1 -1
  70. package/dist/core/extensions/index.d.ts +2 -2
  71. package/dist/core/extensions/index.d.ts.map +1 -1
  72. package/dist/core/extensions/index.js +1 -1
  73. package/dist/core/extensions/index.js.map +1 -1
  74. package/dist/core/extensions/loader.d.ts.map +1 -1
  75. package/dist/core/extensions/loader.js +123 -50
  76. package/dist/core/extensions/loader.js.map +1 -1
  77. package/dist/core/extensions/runner.d.ts +5 -0
  78. package/dist/core/extensions/runner.d.ts.map +1 -1
  79. package/dist/core/extensions/runner.js +45 -1
  80. package/dist/core/extensions/runner.js.map +1 -1
  81. package/dist/core/extensions/source-runtime.d.ts +14 -0
  82. package/dist/core/extensions/source-runtime.d.ts.map +1 -0
  83. package/dist/core/extensions/source-runtime.js +56 -0
  84. package/dist/core/extensions/source-runtime.js.map +1 -0
  85. package/dist/core/extensions/types.d.ts +60 -11
  86. package/dist/core/extensions/types.d.ts.map +1 -1
  87. package/dist/core/extensions/types.js +3 -0
  88. package/dist/core/extensions/types.js.map +1 -1
  89. package/dist/core/footer-data-provider.d.ts +1 -1
  90. package/dist/core/footer-data-provider.d.ts.map +1 -1
  91. package/dist/core/footer-data-provider.js +1 -1
  92. package/dist/core/footer-data-provider.js.map +1 -1
  93. package/dist/core/formatter-lifecycle.d.ts +69 -0
  94. package/dist/core/formatter-lifecycle.d.ts.map +1 -0
  95. package/dist/core/formatter-lifecycle.js +360 -0
  96. package/dist/core/formatter-lifecycle.js.map +1 -0
  97. package/dist/core/hooks/command-handler.d.ts +19 -0
  98. package/dist/core/hooks/command-handler.d.ts.map +1 -0
  99. package/dist/core/hooks/command-handler.js +112 -0
  100. package/dist/core/hooks/command-handler.js.map +1 -0
  101. package/dist/core/hooks/http-handler.d.ts +10 -0
  102. package/dist/core/hooks/http-handler.d.ts.map +1 -0
  103. package/dist/core/hooks/http-handler.js +36 -0
  104. package/dist/core/hooks/http-handler.js.map +1 -0
  105. package/dist/core/hooks/loader.d.ts +29 -0
  106. package/dist/core/hooks/loader.d.ts.map +1 -0
  107. package/dist/core/hooks/loader.js +139 -0
  108. package/dist/core/hooks/loader.js.map +1 -0
  109. package/dist/core/hooks/runtime.d.ts +13 -0
  110. package/dist/core/hooks/runtime.d.ts.map +1 -0
  111. package/dist/core/hooks/runtime.js +63 -0
  112. package/dist/core/hooks/runtime.js.map +1 -0
  113. package/dist/core/hooks/types.d.ts +83 -0
  114. package/dist/core/hooks/types.d.ts.map +1 -0
  115. package/dist/core/hooks/types.js +24 -0
  116. package/dist/core/hooks/types.js.map +1 -0
  117. package/dist/core/keybindings.d.ts +20 -19
  118. package/dist/core/keybindings.d.ts.map +1 -1
  119. package/dist/core/keybindings.js +26 -5
  120. package/dist/core/keybindings.js.map +1 -1
  121. package/dist/core/mcp/config.d.ts +16 -0
  122. package/dist/core/mcp/config.d.ts.map +1 -0
  123. package/dist/core/mcp/config.js +166 -0
  124. package/dist/core/mcp/config.js.map +1 -0
  125. package/dist/core/mcp/connector.d.ts +35 -0
  126. package/dist/core/mcp/connector.d.ts.map +1 -0
  127. package/dist/core/mcp/connector.js +105 -0
  128. package/dist/core/mcp/connector.js.map +1 -0
  129. package/dist/core/mcp/contract.d.ts +19 -0
  130. package/dist/core/mcp/contract.d.ts.map +1 -0
  131. package/dist/core/mcp/contract.js +89 -0
  132. package/dist/core/mcp/contract.js.map +1 -0
  133. package/dist/core/mcp/mcp-tool.d.ts +32 -0
  134. package/dist/core/mcp/mcp-tool.d.ts.map +1 -0
  135. package/dist/core/mcp/mcp-tool.js +171 -0
  136. package/dist/core/mcp/mcp-tool.js.map +1 -0
  137. package/dist/core/mcp/metadata-cache.d.ts +29 -0
  138. package/dist/core/mcp/metadata-cache.d.ts.map +1 -0
  139. package/dist/core/mcp/metadata-cache.js +99 -0
  140. package/dist/core/mcp/metadata-cache.js.map +1 -0
  141. package/dist/core/mcp/oauth/authorize.d.ts +21 -0
  142. package/dist/core/mcp/oauth/authorize.d.ts.map +1 -0
  143. package/dist/core/mcp/oauth/authorize.js +32 -0
  144. package/dist/core/mcp/oauth/authorize.js.map +1 -0
  145. package/dist/core/mcp/oauth/discover.d.ts +27 -0
  146. package/dist/core/mcp/oauth/discover.d.ts.map +1 -0
  147. package/dist/core/mcp/oauth/discover.js +116 -0
  148. package/dist/core/mcp/oauth/discover.js.map +1 -0
  149. package/dist/core/mcp/oauth/flow.d.ts +31 -0
  150. package/dist/core/mcp/oauth/flow.d.ts.map +1 -0
  151. package/dist/core/mcp/oauth/flow.js +175 -0
  152. package/dist/core/mcp/oauth/flow.js.map +1 -0
  153. package/dist/core/mcp/oauth/mcp-token.d.ts +51 -0
  154. package/dist/core/mcp/oauth/mcp-token.d.ts.map +1 -0
  155. package/dist/core/mcp/oauth/mcp-token.js +129 -0
  156. package/dist/core/mcp/oauth/mcp-token.js.map +1 -0
  157. package/dist/core/mcp/oauth/pkce.d.ts +12 -0
  158. package/dist/core/mcp/oauth/pkce.d.ts.map +1 -0
  159. package/dist/core/mcp/oauth/pkce.js +11 -0
  160. package/dist/core/mcp/oauth/pkce.js.map +1 -0
  161. package/dist/core/mcp/oauth/register.d.ts +16 -0
  162. package/dist/core/mcp/oauth/register.d.ts.map +1 -0
  163. package/dist/core/mcp/oauth/register.js +42 -0
  164. package/dist/core/mcp/oauth/register.js.map +1 -0
  165. package/dist/core/mcp/oauth/token-client.d.ts +33 -0
  166. package/dist/core/mcp/oauth/token-client.d.ts.map +1 -0
  167. package/dist/core/mcp/oauth/token-client.js +65 -0
  168. package/dist/core/mcp/oauth/token-client.js.map +1 -0
  169. package/dist/core/mcp/runtime.d.ts +20 -0
  170. package/dist/core/mcp/runtime.d.ts.map +1 -0
  171. package/dist/core/mcp/runtime.js +37 -0
  172. package/dist/core/mcp/runtime.js.map +1 -0
  173. package/dist/core/mcp/schema.d.ts +22 -0
  174. package/dist/core/mcp/schema.d.ts.map +1 -0
  175. package/dist/core/mcp/schema.js +20 -0
  176. package/dist/core/mcp/schema.js.map +1 -0
  177. package/dist/core/mcp/server-manager.d.ts +43 -0
  178. package/dist/core/mcp/server-manager.d.ts.map +1 -0
  179. package/dist/core/mcp/server-manager.js +142 -0
  180. package/dist/core/mcp/server-manager.js.map +1 -0
  181. package/dist/core/mcp/types.d.ts +100 -0
  182. package/dist/core/mcp/types.d.ts.map +1 -0
  183. package/dist/core/mcp/types.js +8 -0
  184. package/dist/core/mcp/types.js.map +1 -0
  185. package/dist/core/model-config.d.ts +5 -0
  186. package/dist/core/model-config.d.ts.map +1 -1
  187. package/dist/core/model-config.js +3 -1
  188. package/dist/core/model-config.js.map +1 -1
  189. package/dist/core/model-resolver.d.ts +1 -0
  190. package/dist/core/model-resolver.d.ts.map +1 -1
  191. package/dist/core/model-resolver.js +14 -8
  192. package/dist/core/model-resolver.js.map +1 -1
  193. package/dist/core/models-store.d.ts.map +1 -1
  194. package/dist/core/models-store.js +2 -1
  195. package/dist/core/models-store.js.map +1 -1
  196. package/dist/core/package-manager.d.ts.map +1 -1
  197. package/dist/core/package-manager.js +22 -14
  198. package/dist/core/package-manager.js.map +1 -1
  199. package/dist/core/permissions/gate.d.ts.map +1 -1
  200. package/dist/core/permissions/gate.js +45 -4
  201. package/dist/core/permissions/gate.js.map +1 -1
  202. package/dist/core/permissions/operations.d.ts +20 -0
  203. package/dist/core/permissions/operations.d.ts.map +1 -0
  204. package/dist/core/permissions/operations.js +9 -0
  205. package/dist/core/permissions/operations.js.map +1 -0
  206. package/dist/core/permissions/policy-command.d.ts +42 -0
  207. package/dist/core/permissions/policy-command.d.ts.map +1 -0
  208. package/dist/core/permissions/policy-command.js +44 -0
  209. package/dist/core/permissions/policy-command.js.map +1 -0
  210. package/dist/core/permissions/responder.d.ts +49 -2
  211. package/dist/core/permissions/responder.d.ts.map +1 -1
  212. package/dist/core/permissions/responder.js +20 -8
  213. package/dist/core/permissions/responder.js.map +1 -1
  214. package/dist/core/permissions/rules.d.ts.map +1 -1
  215. package/dist/core/permissions/rules.js +11 -1
  216. package/dist/core/permissions/rules.js.map +1 -1
  217. package/dist/core/permissions/store.d.ts +18 -0
  218. package/dist/core/permissions/store.d.ts.map +1 -1
  219. package/dist/core/permissions/store.js +89 -8
  220. package/dist/core/permissions/store.js.map +1 -1
  221. package/dist/core/pi-manifest.d.ts.map +1 -1
  222. package/dist/core/pi-manifest.js +2 -1
  223. package/dist/core/pi-manifest.js.map +1 -1
  224. package/dist/core/policy-executor.d.ts +47 -0
  225. package/dist/core/policy-executor.d.ts.map +1 -0
  226. package/dist/core/policy-executor.js +228 -0
  227. package/dist/core/policy-executor.js.map +1 -0
  228. package/dist/core/policy-loader.d.ts +72 -0
  229. package/dist/core/policy-loader.d.ts.map +1 -0
  230. package/dist/core/policy-loader.js +195 -0
  231. package/dist/core/policy-loader.js.map +1 -0
  232. package/dist/core/project-trust.d.ts +1 -1
  233. package/dist/core/project-trust.d.ts.map +1 -1
  234. package/dist/core/project-trust.js.map +1 -1
  235. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  236. package/dist/core/remote-catalog-provider.js +2 -1
  237. package/dist/core/remote-catalog-provider.js.map +1 -1
  238. package/dist/core/resource-loader.d.ts.map +1 -1
  239. package/dist/core/resource-loader.js +3 -2
  240. package/dist/core/resource-loader.js.map +1 -1
  241. package/dist/core/sandbox/bwrap-arguments.d.ts +41 -0
  242. package/dist/core/sandbox/bwrap-arguments.d.ts.map +1 -0
  243. package/dist/core/sandbox/bwrap-arguments.js +136 -0
  244. package/dist/core/sandbox/bwrap-arguments.js.map +1 -0
  245. package/dist/core/sandbox/child-entry.d.ts.map +1 -1
  246. package/dist/core/sandbox/child-entry.js +3 -1
  247. package/dist/core/sandbox/child-entry.js.map +1 -1
  248. package/dist/core/sandbox/cli-launch.d.ts +84 -4
  249. package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
  250. package/dist/core/sandbox/cli-launch.js +115 -16
  251. package/dist/core/sandbox/cli-launch.js.map +1 -1
  252. package/dist/core/sandbox/cli-supervisor.d.ts +2 -0
  253. package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
  254. package/dist/core/sandbox/cli-supervisor.js +50 -2
  255. package/dist/core/sandbox/cli-supervisor.js.map +1 -1
  256. package/dist/core/sandbox/full-access.d.ts +28 -0
  257. package/dist/core/sandbox/full-access.d.ts.map +1 -0
  258. package/dist/core/sandbox/full-access.js +63 -0
  259. package/dist/core/sandbox/full-access.js.map +1 -0
  260. package/dist/core/sandbox/git-identity.d.ts +57 -0
  261. package/dist/core/sandbox/git-identity.d.ts.map +1 -0
  262. package/dist/core/sandbox/git-identity.js +78 -0
  263. package/dist/core/sandbox/git-identity.js.map +1 -0
  264. package/dist/core/sandbox/host-approval.d.ts +42 -0
  265. package/dist/core/sandbox/host-approval.d.ts.map +1 -0
  266. package/dist/core/sandbox/host-approval.js +99 -0
  267. package/dist/core/sandbox/host-approval.js.map +1 -0
  268. package/dist/core/sandbox/linux-backend.d.ts +14 -0
  269. package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
  270. package/dist/core/sandbox/linux-backend.js +158 -80
  271. package/dist/core/sandbox/linux-backend.js.map +1 -1
  272. package/dist/core/sandbox/macos-backend.d.ts +5 -0
  273. package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
  274. package/dist/core/sandbox/macos-backend.js +164 -5
  275. package/dist/core/sandbox/macos-backend.js.map +1 -1
  276. package/dist/core/sandbox/network-proxy.d.ts +19 -0
  277. package/dist/core/sandbox/network-proxy.d.ts.map +1 -1
  278. package/dist/core/sandbox/network-proxy.js +93 -19
  279. package/dist/core/sandbox/network-proxy.js.map +1 -1
  280. package/dist/core/sandbox/policy.d.ts +8 -0
  281. package/dist/core/sandbox/policy.d.ts.map +1 -1
  282. package/dist/core/sandbox/policy.js +29 -12
  283. package/dist/core/sandbox/policy.js.map +1 -1
  284. package/dist/core/sandbox/profiles.d.ts +25 -0
  285. package/dist/core/sandbox/profiles.d.ts.map +1 -0
  286. package/dist/core/sandbox/profiles.js +23 -0
  287. package/dist/core/sandbox/profiles.js.map +1 -0
  288. package/dist/core/sandbox/rpc/command-client.d.ts +28 -0
  289. package/dist/core/sandbox/rpc/command-client.d.ts.map +1 -0
  290. package/dist/core/sandbox/rpc/command-client.js +86 -0
  291. package/dist/core/sandbox/rpc/command-client.js.map +1 -0
  292. package/dist/core/sandbox/rpc/command-proxy.d.ts +48 -0
  293. package/dist/core/sandbox/rpc/command-proxy.d.ts.map +1 -0
  294. package/dist/core/sandbox/rpc/command-proxy.js +120 -0
  295. package/dist/core/sandbox/rpc/command-proxy.js.map +1 -0
  296. package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -1
  297. package/dist/core/sandbox/rpc/credential-proxy.js +2 -96
  298. package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -1
  299. package/dist/core/sandbox/rpc/framing.d.ts +35 -0
  300. package/dist/core/sandbox/rpc/framing.d.ts.map +1 -0
  301. package/dist/core/sandbox/rpc/framing.js +114 -0
  302. package/dist/core/sandbox/rpc/framing.js.map +1 -0
  303. package/dist/core/sandbox/rpc/git-credential-helper.d.ts +47 -0
  304. package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +1 -0
  305. package/dist/core/sandbox/rpc/git-credential-helper.js +222 -0
  306. package/dist/core/sandbox/rpc/git-credential-helper.js.map +1 -0
  307. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +66 -0
  308. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +1 -0
  309. package/dist/core/sandbox/rpc/git-credential-proxy.js +209 -0
  310. package/dist/core/sandbox/rpc/git-credential-proxy.js.map +1 -0
  311. package/dist/core/sandbox/supervisor-temp.d.ts +18 -0
  312. package/dist/core/sandbox/supervisor-temp.d.ts.map +1 -0
  313. package/dist/core/sandbox/supervisor-temp.js +21 -0
  314. package/dist/core/sandbox/supervisor-temp.js.map +1 -0
  315. package/dist/core/sandbox/supervisor.d.ts +12 -1
  316. package/dist/core/sandbox/supervisor.d.ts.map +1 -1
  317. package/dist/core/sandbox/supervisor.js +15 -1
  318. package/dist/core/sandbox/supervisor.js.map +1 -1
  319. package/dist/core/sandbox/terminal-handoff.d.ts +68 -0
  320. package/dist/core/sandbox/terminal-handoff.d.ts.map +1 -0
  321. package/dist/core/sandbox/terminal-handoff.js +221 -0
  322. package/dist/core/sandbox/terminal-handoff.js.map +1 -0
  323. package/dist/core/sandbox/terminal-size.d.ts +13 -0
  324. package/dist/core/sandbox/terminal-size.d.ts.map +1 -1
  325. package/dist/core/sandbox/terminal-size.js +23 -2
  326. package/dist/core/sandbox/terminal-size.js.map +1 -1
  327. package/dist/core/sdk.d.ts +24 -2
  328. package/dist/core/sdk.d.ts.map +1 -1
  329. package/dist/core/sdk.js +63 -4
  330. package/dist/core/sdk.js.map +1 -1
  331. package/dist/core/session-export.d.ts +4 -0
  332. package/dist/core/session-export.d.ts.map +1 -0
  333. package/dist/core/session-export.js +32 -0
  334. package/dist/core/session-export.js.map +1 -0
  335. package/dist/core/session-manager.d.ts +13 -1
  336. package/dist/core/session-manager.d.ts.map +1 -1
  337. package/dist/core/session-manager.js +34 -6
  338. package/dist/core/session-manager.js.map +1 -1
  339. package/dist/core/settings-diagnostics.d.ts +9 -0
  340. package/dist/core/settings-diagnostics.d.ts.map +1 -0
  341. package/dist/core/settings-diagnostics.js +21 -0
  342. package/dist/core/settings-diagnostics.js.map +1 -0
  343. package/dist/core/settings-manager.d.ts +172 -1
  344. package/dist/core/settings-manager.d.ts.map +1 -1
  345. package/dist/core/settings-manager.js +160 -11
  346. package/dist/core/settings-manager.js.map +1 -1
  347. package/dist/core/skills.d.ts.map +1 -1
  348. package/dist/core/skills.js +51 -33
  349. package/dist/core/skills.js.map +1 -1
  350. package/dist/core/slash-commands.d.ts.map +1 -1
  351. package/dist/core/slash-commands.js +4 -1
  352. package/dist/core/slash-commands.js.map +1 -1
  353. package/dist/core/system-prompt.d.ts.map +1 -1
  354. package/dist/core/system-prompt.js +8 -2
  355. package/dist/core/system-prompt.js.map +1 -1
  356. package/dist/core/tools/background-shell.d.ts +39 -0
  357. package/dist/core/tools/background-shell.d.ts.map +1 -0
  358. package/dist/core/tools/background-shell.js +81 -0
  359. package/dist/core/tools/background-shell.js.map +1 -0
  360. package/dist/core/tools/bash-command-segments.d.ts.map +1 -1
  361. package/dist/core/tools/bash-command-segments.js +2 -0
  362. package/dist/core/tools/bash-command-segments.js.map +1 -1
  363. package/dist/core/tools/bash.d.ts +44 -16
  364. package/dist/core/tools/bash.d.ts.map +1 -1
  365. package/dist/core/tools/bash.js +264 -32
  366. package/dist/core/tools/bash.js.map +1 -1
  367. package/dist/core/tools/contract-snapshot.d.ts +58 -0
  368. package/dist/core/tools/contract-snapshot.d.ts.map +1 -0
  369. package/dist/core/tools/contract-snapshot.js +51 -0
  370. package/dist/core/tools/contract-snapshot.js.map +1 -0
  371. package/dist/core/tools/contract.d.ts +53 -5
  372. package/dist/core/tools/contract.d.ts.map +1 -1
  373. package/dist/core/tools/contract.js +22 -2
  374. package/dist/core/tools/contract.js.map +1 -1
  375. package/dist/core/tools/delegate.d.ts +6 -4
  376. package/dist/core/tools/delegate.d.ts.map +1 -1
  377. package/dist/core/tools/delegate.js +21 -11
  378. package/dist/core/tools/delegate.js.map +1 -1
  379. package/dist/core/tools/edit-diff.d.ts +48 -5
  380. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  381. package/dist/core/tools/edit-diff.js +217 -8
  382. package/dist/core/tools/edit-diff.js.map +1 -1
  383. package/dist/core/tools/edit.d.ts +8 -0
  384. package/dist/core/tools/edit.d.ts.map +1 -1
  385. package/dist/core/tools/edit.js +72 -10
  386. package/dist/core/tools/edit.js.map +1 -1
  387. package/dist/core/tools/find.d.ts.map +1 -1
  388. package/dist/core/tools/find.js +5 -2
  389. package/dist/core/tools/find.js.map +1 -1
  390. package/dist/core/tools/grep.d.ts.map +1 -1
  391. package/dist/core/tools/grep.js +5 -2
  392. package/dist/core/tools/grep.js.map +1 -1
  393. package/dist/core/tools/index.d.ts +14 -2
  394. package/dist/core/tools/index.d.ts.map +1 -1
  395. package/dist/core/tools/index.js +12 -0
  396. package/dist/core/tools/index.js.map +1 -1
  397. package/dist/core/tools/ls.d.ts.map +1 -1
  398. package/dist/core/tools/ls.js +5 -2
  399. package/dist/core/tools/ls.js.map +1 -1
  400. package/dist/core/tools/lsp.d.ts +19 -1
  401. package/dist/core/tools/lsp.d.ts.map +1 -1
  402. package/dist/core/tools/lsp.js +163 -10
  403. package/dist/core/tools/lsp.js.map +1 -1
  404. package/dist/core/tools/path-permission.d.ts +3 -0
  405. package/dist/core/tools/path-permission.d.ts.map +1 -1
  406. package/dist/core/tools/path-permission.js +16 -4
  407. package/dist/core/tools/path-permission.js.map +1 -1
  408. package/dist/core/tools/path-utils.d.ts +27 -2
  409. package/dist/core/tools/path-utils.d.ts.map +1 -1
  410. package/dist/core/tools/path-utils.js +221 -4
  411. package/dist/core/tools/path-utils.js.map +1 -1
  412. package/dist/core/tools/powershell.d.ts +16 -0
  413. package/dist/core/tools/powershell.d.ts.map +1 -0
  414. package/dist/core/tools/powershell.js +39 -0
  415. package/dist/core/tools/powershell.js.map +1 -0
  416. package/dist/core/tools/read.d.ts.map +1 -1
  417. package/dist/core/tools/read.js +20 -11
  418. package/dist/core/tools/read.js.map +1 -1
  419. package/dist/core/tools/test.d.ts +54 -9
  420. package/dist/core/tools/test.d.ts.map +1 -1
  421. package/dist/core/tools/test.js +239 -19
  422. package/dist/core/tools/test.js.map +1 -1
  423. package/dist/core/tools/write.d.ts +7 -0
  424. package/dist/core/tools/write.d.ts.map +1 -1
  425. package/dist/core/tools/write.js +64 -9
  426. package/dist/core/tools/write.js.map +1 -1
  427. package/dist/core/trust-manager.d.ts.map +1 -1
  428. package/dist/core/trust-manager.js +2 -1
  429. package/dist/core/trust-manager.js.map +1 -1
  430. package/dist/core/verification-lifecycle.d.ts +77 -0
  431. package/dist/core/verification-lifecycle.d.ts.map +1 -0
  432. package/dist/core/verification-lifecycle.js +146 -0
  433. package/dist/core/verification-lifecycle.js.map +1 -0
  434. package/dist/core/workspace/artifacts.d.ts +45 -0
  435. package/dist/core/workspace/artifacts.d.ts.map +1 -0
  436. package/dist/core/workspace/artifacts.js +165 -0
  437. package/dist/core/workspace/artifacts.js.map +1 -0
  438. package/dist/core/workspace/comparison.d.ts +27 -0
  439. package/dist/core/workspace/comparison.d.ts.map +1 -0
  440. package/dist/core/workspace/comparison.js +94 -0
  441. package/dist/core/workspace/comparison.js.map +1 -0
  442. package/dist/core/workspace/git-observer.d.ts +48 -0
  443. package/dist/core/workspace/git-observer.d.ts.map +1 -0
  444. package/dist/core/workspace/git-observer.js +346 -0
  445. package/dist/core/workspace/git-observer.js.map +1 -0
  446. package/dist/core/workspace/projection.d.ts +3 -0
  447. package/dist/core/workspace/projection.d.ts.map +1 -0
  448. package/dist/core/workspace/projection.js +56 -0
  449. package/dist/core/workspace/projection.js.map +1 -0
  450. package/dist/core/workspace/state.d.ts +133 -0
  451. package/dist/core/workspace/state.d.ts.map +1 -0
  452. package/dist/core/workspace/state.js +96 -0
  453. package/dist/core/workspace/state.js.map +1 -0
  454. package/dist/extensions/llama/client.d.ts +6 -0
  455. package/dist/extensions/llama/client.d.ts.map +1 -1
  456. package/dist/extensions/llama/client.js +7 -0
  457. package/dist/extensions/llama/client.js.map +1 -1
  458. package/dist/extensions/llama/index.d.ts.map +1 -1
  459. package/dist/extensions/llama/index.js +2 -0
  460. package/dist/extensions/llama/index.js.map +1 -1
  461. package/dist/extensions/llama/provider.d.ts +3 -1
  462. package/dist/extensions/llama/provider.d.ts.map +1 -1
  463. package/dist/extensions/llama/provider.js +29 -4
  464. package/dist/extensions/llama/provider.js.map +1 -1
  465. package/dist/index.d.ts +8 -5
  466. package/dist/index.d.ts.map +1 -1
  467. package/dist/index.js +7 -4
  468. package/dist/index.js.map +1 -1
  469. package/dist/main.d.ts +6 -0
  470. package/dist/main.d.ts.map +1 -1
  471. package/dist/main.js +58 -23
  472. package/dist/main.js.map +1 -1
  473. package/dist/migrations.d.ts.map +1 -1
  474. package/dist/migrations.js +4 -3
  475. package/dist/migrations.js.map +1 -1
  476. package/dist/modes/acp/server.d.ts +55 -0
  477. package/dist/modes/acp/server.d.ts.map +1 -0
  478. package/dist/modes/acp/server.js +244 -0
  479. package/dist/modes/acp/server.js.map +1 -0
  480. package/dist/modes/acp/translate.d.ts +37 -0
  481. package/dist/modes/acp/translate.d.ts.map +1 -0
  482. package/dist/modes/acp/translate.js +50 -0
  483. package/dist/modes/acp/translate.js.map +1 -0
  484. package/dist/modes/index.d.ts +1 -0
  485. package/dist/modes/index.d.ts.map +1 -1
  486. package/dist/modes/index.js +1 -0
  487. package/dist/modes/index.js.map +1 -1
  488. package/dist/modes/interactive/components/assistant-message.d.ts +29 -0
  489. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  490. package/dist/modes/interactive/components/assistant-message.js +182 -23
  491. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  492. package/dist/modes/interactive/components/bash-execution.d.ts +7 -0
  493. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  494. package/dist/modes/interactive/components/bash-execution.js +20 -0
  495. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  496. package/dist/modes/interactive/components/custom-editor.d.ts +9 -4
  497. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  498. package/dist/modes/interactive/components/custom-editor.js +20 -9
  499. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  500. package/dist/modes/interactive/components/extension-selector.d.ts +7 -1
  501. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  502. package/dist/modes/interactive/components/extension-selector.js +7 -3
  503. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  504. package/dist/modes/interactive/components/footer.d.ts +19 -0
  505. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  506. package/dist/modes/interactive/components/footer.js +70 -10
  507. package/dist/modes/interactive/components/footer.js.map +1 -1
  508. package/dist/modes/interactive/components/model-selector.d.ts +10 -3
  509. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  510. package/dist/modes/interactive/components/model-selector.js +56 -14
  511. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  512. package/dist/modes/interactive/components/permission-preview.d.ts +15 -0
  513. package/dist/modes/interactive/components/permission-preview.d.ts.map +1 -0
  514. package/dist/modes/interactive/components/permission-preview.js +33 -0
  515. package/dist/modes/interactive/components/permission-preview.js.map +1 -0
  516. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  517. package/dist/modes/interactive/components/session-selector.js +2 -2
  518. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  519. package/dist/modes/interactive/components/settings-selector.d.ts +9 -2
  520. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  521. package/dist/modes/interactive/components/settings-selector.js +128 -56
  522. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  523. package/dist/modes/interactive/components/settings-submenu.d.ts +72 -0
  524. package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -0
  525. package/dist/modes/interactive/components/settings-submenu.js +165 -0
  526. package/dist/modes/interactive/components/settings-submenu.js.map +1 -0
  527. package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
  528. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  529. package/dist/modes/interactive/components/thinking-selector.js +74 -17
  530. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  531. package/dist/modes/interactive/components/tool-execution.d.ts +23 -1
  532. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  533. package/dist/modes/interactive/components/tool-execution.js +64 -10
  534. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  535. package/dist/modes/interactive/external-editor.d.ts.map +1 -1
  536. package/dist/modes/interactive/external-editor.js +2 -1
  537. package/dist/modes/interactive/external-editor.js.map +1 -1
  538. package/dist/modes/interactive/interactive-mode.d.ts +45 -0
  539. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  540. package/dist/modes/interactive/interactive-mode.js +395 -43
  541. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  542. package/dist/modes/interactive/theme/theme.d.ts +12 -0
  543. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  544. package/dist/modes/interactive/theme/theme.js +20 -9
  545. package/dist/modes/interactive/theme/theme.js.map +1 -1
  546. package/dist/modes/json-event.d.ts +16 -13
  547. package/dist/modes/json-event.d.ts.map +1 -1
  548. package/dist/modes/json-event.js +20 -6
  549. package/dist/modes/json-event.js.map +1 -1
  550. package/dist/modes/print-mode.d.ts.map +1 -1
  551. package/dist/modes/print-mode.js +27 -12
  552. package/dist/modes/print-mode.js.map +1 -1
  553. package/dist/modes/rpc/rpc-client.d.ts +7 -0
  554. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  555. package/dist/modes/rpc/rpc-client.js +7 -0
  556. package/dist/modes/rpc/rpc-client.js.map +1 -1
  557. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  558. package/dist/modes/rpc/rpc-mode.js +3 -0
  559. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  560. package/dist/modes/rpc/rpc-types.d.ts +12 -0
  561. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  562. package/dist/modes/rpc/rpc-types.js.map +1 -1
  563. package/dist/package-manager-cli.d.ts +1 -0
  564. package/dist/package-manager-cli.d.ts.map +1 -1
  565. package/dist/package-manager-cli.js +196 -10
  566. package/dist/package-manager-cli.js.map +1 -1
  567. package/dist/utils/frontmatter.d.ts.map +1 -1
  568. package/dist/utils/frontmatter.js +2 -1
  569. package/dist/utils/frontmatter.js.map +1 -1
  570. package/dist/utils/management-http.d.ts +5 -3
  571. package/dist/utils/management-http.d.ts.map +1 -1
  572. package/dist/utils/management-http.js +14 -10
  573. package/dist/utils/management-http.js.map +1 -1
  574. package/dist/utils/shell.d.ts +3 -0
  575. package/dist/utils/shell.d.ts.map +1 -1
  576. package/dist/utils/shell.js +23 -9
  577. package/dist/utils/shell.js.map +1 -1
  578. package/dist/utils/syntax-highlight.d.ts +1 -0
  579. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  580. package/dist/utils/syntax-highlight.js +60 -1
  581. package/dist/utils/syntax-highlight.js.map +1 -1
  582. package/dist/utils/text.d.ts +8 -0
  583. package/dist/utils/text.d.ts.map +1 -0
  584. package/dist/utils/text.js +9 -0
  585. package/dist/utils/text.js.map +1 -0
  586. package/dist/utils/version-check.d.ts.map +1 -1
  587. package/dist/utils/version-check.js +6 -4
  588. package/dist/utils/version-check.js.map +1 -1
  589. package/docs/compaction.md +19 -0
  590. package/docs/custom-provider.md +12 -9
  591. package/docs/environment-variables.md +1 -1
  592. package/docs/extensions.md +35 -7
  593. package/docs/json.md +7 -2
  594. package/docs/keybindings.md +9 -9
  595. package/docs/llama-cpp.md +2 -0
  596. package/docs/models.md +8 -2
  597. package/docs/packages.md +2 -2
  598. package/docs/quickstart.md +1 -1
  599. package/docs/rpc.md +32 -3
  600. package/docs/sdk.md +32 -2
  601. package/docs/settings.md +23 -7
  602. package/docs/skills.md +4 -3
  603. package/docs/terminal-setup.md +23 -7
  604. package/docs/usage.md +10 -5
  605. package/docs/windows.md +24 -2
  606. package/examples/extensions/border-status-editor.ts +1 -1
  607. package/examples/extensions/built-in-tool-renderer.ts +8 -4
  608. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  609. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  610. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  611. package/examples/extensions/git-checkpoint.ts +24 -36
  612. package/examples/extensions/gondolin/package-lock.json +2 -2
  613. package/examples/extensions/gondolin/package.json +1 -1
  614. package/examples/extensions/minimal-mode.ts +2 -2
  615. package/examples/extensions/notify.ts +3 -1
  616. package/examples/extensions/permission-gate.ts +1 -1
  617. package/examples/extensions/plan-mode/index.ts +1 -1
  618. package/examples/extensions/rpc-demo.ts +1 -1
  619. package/examples/extensions/sandbox/package-lock.json +2 -2
  620. package/examples/extensions/sandbox/package.json +1 -1
  621. package/examples/extensions/subagent/README.md +1 -1
  622. package/examples/extensions/subagent/index.ts +6 -1
  623. package/examples/extensions/titlebar-spinner.ts +1 -1
  624. package/examples/extensions/with-deps/package-lock.json +2 -2
  625. package/examples/extensions/with-deps/package.json +1 -1
  626. package/examples/rpc-extension-ui.ts +1 -1
  627. package/examples/sdk/06-extensions.ts +1 -1
  628. package/examples/sdk/README.md +1 -1
  629. package/npm-shrinkwrap.json +439 -405
  630. package/package.json +17 -17
  631. package/dist/server/create-harness.d.ts +0 -26
  632. package/dist/server/create-harness.d.ts.map +0 -1
  633. package/dist/server/create-harness.js +0 -107
  634. package/dist/server/create-harness.js.map +0 -1
@@ -1,10 +1,16 @@
1
1
  import { Editor, type EditorOptions, type EditorTheme, type TUI } from "@earendil-works/pi-tui";
2
2
  import type { AppKeybinding, KeybindingsManager } from "../../../core/keybindings.ts";
3
+ /** Which marker the dock draws. The set is closed so a mode cannot arrive without a marker. */
4
+ export type PromptMode = "agent" | "bash";
3
5
  export interface CustomEditorOptions extends EditorOptions {
4
6
  /** Marker rendered at the start of the first input line, e.g. `"> "`. */
5
7
  promptPrefix?: string;
6
8
  /** Styles the prompt prefix. Defaults to leaving it unstyled. */
7
9
  promptColor?: (text: string) => string;
10
+ /** Marker for bash mode. Defaults to `promptPrefix`, so mode is carried by colour alone. */
11
+ bashPromptPrefix?: string;
12
+ /** Styles the bash marker. Defaults to `promptColor`. */
13
+ bashPromptColor?: (text: string) => string;
8
14
  /** Shown in place of the empty input line. */
9
15
  placeholder?: string;
10
16
  /** Styles the placeholder. Defaults to leaving it unstyled. */
@@ -27,15 +33,14 @@ export interface CustomEditorOptions extends EditorOptions {
27
33
  */
28
34
  export declare class CustomEditor extends Editor {
29
35
  private keybindings;
30
- private readonly promptPrefix;
31
- private readonly promptColor;
36
+ private readonly prompts;
37
+ private promptMode;
32
38
  private readonly placeholderColor;
33
39
  private readonly commandColor;
34
40
  private readonly surfaceColor;
35
41
  private readonly placeholder;
36
42
  private readonly autocompleteRule;
37
43
  private readonly autocompleteFooter;
38
- private modeLabel;
39
44
  actionHandlers: Map<AppKeybinding, () => void>;
40
45
  onEscape?: () => void;
41
46
  onCtrlD?: () => void;
@@ -56,7 +61,7 @@ export declare class CustomEditor extends Editor {
56
61
  private withSurface;
57
62
  private editorBlockLineCount;
58
63
  private surfaceLine;
59
- setModeLabel(label: string | undefined): void;
64
+ setPromptMode(mode: PromptMode): void;
60
65
  private isBorderLine;
61
66
  /**
62
67
  * Tint a leading `/command` on the first input line.
@@ -1 +1 @@
1
- {"version":3,"file":"custom-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,MAAM,EACN,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,GAAG,EAGR,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGtF,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACzD,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACvC,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C,0EAA0E;IAC1E,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,iEAAiE;IACjE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,+EAA+E;IAC/E,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;CAClC;AAwHD;;;;;;GAMG;AACH,qBAAa,YAAa,SAAQ,MAAM;IACvC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;IACvD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA2B;IAC5D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyC;IACtE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyC;IACtE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA0C;IAC3E,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAChE,OAAO,CAAC,SAAS,CAAM;IAChB,cAAc,EAAE,GAAG,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC,CAAa;IAG3D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,2EAA2E;IACpE,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAEvD,YAAY,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,mBAAmB,EAWvG;IAEQ,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvC;IAED,OAAO,CAAC,YAAY;IAqCpB;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,WAAW;IAuCnB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAG5C;IAED,OAAO,CAAC,YAAY;IAKpB;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAyBxB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IA2BvB;;;;;;;;OAQG;IACH,OAAO,CAAC,sBAAsB;IAK9B;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAEzD;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CA2D9B;CACD","sourcesContent":["import {\n\tCURSOR_MARKER,\n\tEditor,\n\ttype EditorOptions,\n\ttype EditorTheme,\n\ttype TUI,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../../core/keybindings.ts\";\nimport { stripAnsi } from \"../../../utils/ansi.ts\";\n\nexport interface CustomEditorOptions extends EditorOptions {\n\t/** Marker rendered at the start of the first input line, e.g. `\"> \"`. */\n\tpromptPrefix?: string;\n\t/** Styles the prompt prefix. Defaults to leaving it unstyled. */\n\tpromptColor?: (text: string) => string;\n\t/** Shown in place of the empty input line. */\n\tplaceholder?: string;\n\t/** Styles the placeholder. Defaults to leaving it unstyled. */\n\tplaceholderColor?: (text: string) => string;\n\t/** Styles a leading `/command` token. Defaults to leaving it unstyled. */\n\tcommandColor?: (text: string) => string;\n\t/** Applies the filled prompt surface around the editor block. */\n\tsurfaceColor?: (text: string) => string;\n\t/** Rule drawn above the autocomplete rows. Omit for no rule. */\n\tautocompleteRule?: (width: number) => string;\n\t/** Keybinding footer drawn under the autocomplete rows. Omit for no footer. */\n\tautocompleteFooter?: () => string;\n}\n\n/** Box-drawing horizontal rule: the character the base Editor draws borders from. */\nconst BORDER_CHAR = \"─\";\n\n/**\n * The two shapes the base Editor draws a border in: a plain full-width rule, and\n * the scroll indicator (`─── ↑ 3 more ────`).\n *\n * Both are anchored deliberately. A prefix test would count any content line that\n * merely *starts* with the rule character — a pasted divider, `─notes` — as a\n * border, which is worse than it sounds: {@link CustomEditor.render} tells the\n * content region from the autocomplete region by counting borders, so one\n * miscount shifts every later line into the wrong region.\n */\nconst BORDER_RULE = /^─+$/;\nconst BORDER_SCROLL = /^─+ [↑↓] \\d+ more/;\n\n/**\n * A leading `/command` token: optional indent, a slash, then non-space.\n * Matched against the line's *visible* text, not the rendered string.\n */\nconst COMMAND_TOKEN = /^(\\s*)(\\/\\S+)/;\n\n/**\n * One ANSI control sequence: a CSI escape (`ESC [ … letter`) or the APC\n * hardware-cursor marker (`ESC _ … BEL`). Sticky, for position-by-position\n * scanning in {@link colorVisibleRange}.\n */\nconst CONTROL_SEQUENCE = /\\x1b\\[[0-9;]*[A-Za-z]|\\x1b_[^\\x07]*\\x07/y;\n\n/** The reverse-video cursor cell emitted by the inherited editor. */\nconst INVERTED_CURSOR_CELL = /\\x1b\\[7m[\\s\\S]\\x1b\\[(?:0|27)m/y;\n\nconst SURFACE_PADDING = 2;\n\n/**\n * ============================ ANSI measuring hazards ============================\n *\n * Two traps live in the rendered strings this file post-processes. Both produced\n * real bugs; both are cheap to re-introduce. Read this before touching the width\n * maths or any leading-sequence match.\n *\n * 1. DO NOT COMPOSE `stripAnsi` WITH `visibleWidth`.\n *\n * `stripAnsi` removes SGR colour codes but leaves the APC hardware-cursor\n * marker (`ESC _pi:c BEL`, see CURSOR_MARKER in packages/tui) fully intact —\n * all seven bytes of it. `visibleWidth` already handles that marker correctly\n * and scores it zero.\n *\n * So `visibleWidth(stripAnsi(line))` counts seven phantom columns on every\n * line carrying a cursor, which silently widened each placeholder line past\n * the terminal width. Measure with `visibleWidth` alone. Use `stripAnsi` only\n * to read text, never to measure it.\n *\n * 2. DO NOT MATCH THE LEADING SEQUENCE RUN GENERICALLY.\n *\n * The base Editor renders an empty line as:\n * CURSOR_MARKER ESC[7m <space> ESC[0m <padding>\n * The `ESC[7m` turns on reverse video for exactly the one-cell cursor block,\n * and `ESC[0m` turns it back off.\n *\n * A permissive prefix match such as `(?:\\x1b\\[[0-9;]*m)*` consumes that\n * `ESC[7m` as though it were ordinary leading styling. Whatever is appended\n * next then inherits reverse video, and because the matching `ESC[0m` was\n * left behind in the discarded remainder, the inversion runs on through the\n * placeholder and the rest of the frame. Match the reverse-video run\n * explicitly instead — see {@link CustomEditor.sliceLeadingCursorCell}.\n *\n * ==============================================================================\n */\n\n/**\n * Wrap the visible characters in `[start, end)` with `colorFn`, leaving every\n * control sequence in place.\n *\n * The range is in visible columns, so it is unaffected by however many escape\n * sequences are interleaved. That matters because the cursor cell can land in\n * the middle of the token being coloured: with the caret at column 3 of\n * `/model`, the line reads `/mo` + CURSOR_MARKER + `ESC[7m` + `d` + `ESC[0m` +\n * `el`. Colour is therefore re-opened after every control sequence rather than\n * once around the whole token, since the cursor cell's `ESC[0m` would otherwise\n * cancel it partway through.\n */\nfunction colorVisibleRange(line: string, start: number, end: number, colorFn: (text: string) => string): string {\n\tlet out = \"\";\n\tlet buffer = \"\";\n\tlet column = 0;\n\tlet index = 0;\n\n\tconst flush = () => {\n\t\tif (buffer) {\n\t\t\tout += colorFn(buffer);\n\t\t\tbuffer = \"\";\n\t\t}\n\t};\n\n\twhile (index < line.length) {\n\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\tif (control) {\n\t\t\tflush();\n\t\t\tout += control[0];\n\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\tcontinue;\n\t\t}\n\t\tconst char = line[index];\n\t\tif (column >= start && column < end) {\n\t\t\tbuffer += char;\n\t\t} else {\n\t\t\tflush();\n\t\t\tout += char;\n\t\t}\n\t\tcolumn += visibleWidth(char);\n\t\tindex += 1;\n\t}\n\tflush();\n\treturn out;\n}\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n *\n * The prompt prefix and placeholder are layered on in `render` rather than\n * inside the base Editor, because `packages/tui` is frozen under ADR 0001 and\n * its `EditorOptions` has no hook for either.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tprivate readonly promptPrefix: string;\n\tprivate readonly promptColor: (text: string) => string;\n\tprivate readonly placeholderColor: (text: string) => string;\n\tprivate readonly commandColor: ((text: string) => string) | undefined;\n\tprivate readonly surfaceColor: ((text: string) => string) | undefined;\n\tprivate readonly placeholder: string | undefined;\n\tprivate readonly autocompleteRule: ((width: number) => string) | undefined;\n\tprivate readonly autocompleteFooter: (() => string) | undefined;\n\tprivate modeLabel = \"\";\n\tpublic actionHandlers: Map<AppKeybinding, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: CustomEditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t\tthis.promptPrefix = options?.promptPrefix ?? \"\";\n\t\tthis.promptColor = options?.promptColor ?? ((text) => text);\n\t\tthis.placeholder = options?.placeholder;\n\t\tthis.placeholderColor = options?.placeholderColor ?? ((text) => text);\n\t\tthis.commandColor = options?.commandColor;\n\t\tthis.surfaceColor = options?.surfaceColor;\n\t\tthis.autocompleteRule = options?.autocompleteRule;\n\t\tthis.autocompleteFooter = options?.autocompleteFooter;\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst canRenderSurface = this.surfaceColor !== undefined && width > 0;\n\t\tconst surfacePadding = canRenderSurface ? Math.min(SURFACE_PADDING, Math.floor((width - 1) / 2)) : 0;\n\t\tconst editorWidth = canRenderSurface ? width - surfacePadding * 2 : width;\n\t\tconst lines = this.withAutocompleteChrome(this.renderEditor(editorWidth), editorWidth);\n\t\treturn canRenderSurface ? this.withSurface(lines, width, surfacePadding) : lines;\n\t}\n\n\tprivate renderEditor(width: number): string[] {\n\t\tconst promptPrefix = this.modeLabel ? `[${this.modeLabel}] ${this.promptPrefix}` : this.promptPrefix;\n\t\tconst prefixWidth = visibleWidth(promptPrefix);\n\t\t// Reserve the prefix out of the width handed to the base Editor, so its\n\t\t// wrapping, scrolling and cursor column all account for the space the\n\t\t// prefix occupies. Bail out entirely when the terminal cannot spare it.\n\t\tif (prefixWidth === 0 || width <= prefixWidth + 4) {\n\t\t\treturn this.withCommandColor(this.withPlaceholder(super.render(width)));\n\t\t}\n\n\t\tconst inner = this.withCommandColor(this.withPlaceholder(super.render(width - prefixWidth)));\n\t\tconst borderPad = this.borderColor(BORDER_CHAR.repeat(prefixWidth));\n\t\tconst blank = \" \".repeat(prefixWidth);\n\t\tconst styledPrefix = this.promptColor(promptPrefix);\n\n\t\t// Structure is: top border, content lines, bottom border, then optional\n\t\t// autocomplete rows. Counting borders tells the three regions apart\n\t\t// without depending on how many lines each contains.\n\t\tlet borders = 0;\n\t\tlet firstContentLine = true;\n\t\treturn inner.map((line) => {\n\t\t\tif (this.isBorderLine(line)) {\n\t\t\t\tborders++;\n\t\t\t\treturn borderPad + line;\n\t\t\t}\n\t\t\tif (borders !== 1) {\n\t\t\t\t// Autocomplete rows sit below the box; indent them to match.\n\t\t\t\treturn blank + line;\n\t\t\t}\n\t\t\tif (firstContentLine) {\n\t\t\t\tfirstContentLine = false;\n\t\t\t\treturn styledPrefix + line;\n\t\t\t}\n\t\t\treturn blank + line;\n\t\t});\n\t}\n\n\t/**\n\t * Brackets the autocomplete rows with a rule and a keybinding footer.\n\t *\n\t * The rows arrive after the editor's second border, so the same border count\n\t * that separates the box from its dropdown elsewhere in this file finds them\n\t * here. Nothing is added when the dropdown is closed.\n\t */\n\tprivate withAutocompleteChrome(lines: string[], width: number): string[] {\n\t\tif (!this.autocompleteRule && !this.autocompleteFooter) return lines;\n\t\tconst blockLineCount = this.editorBlockLineCount(lines);\n\t\tconst rows = lines.slice(blockLineCount);\n\t\tif (rows.length === 0) return lines;\n\n\t\tconst chrome = [...lines.slice(0, blockLineCount)];\n\t\tif (this.autocompleteRule) chrome.push(this.autocompleteRule(width));\n\t\tchrome.push(...rows);\n\t\tif (this.autocompleteFooter) chrome.push(this.autocompleteFooter());\n\t\treturn chrome;\n\t}\n\n\tprivate withSurface(lines: string[], width: number, padding: number): string[] {\n\t\tconst surfaceColor = this.surfaceColor;\n\t\tif (!surfaceColor) return lines;\n\n\t\tconst result = [this.surfaceLine(\" \".repeat(width), surfaceColor)];\n\t\tconst editorBlockLineCount = this.editorBlockLineCount(lines);\n\t\tconst editorContent = lines.slice(1, Math.max(1, editorBlockLineCount - 1));\n\t\tfor (const line of editorContent) {\n\t\t\tresult.push(this.surfaceLine(`${\" \".repeat(padding)}${line}${\" \".repeat(padding)}`, surfaceColor));\n\t\t}\n\t\tresult.push(this.surfaceLine(\" \".repeat(width), surfaceColor));\n\t\tfor (const line of lines.slice(editorBlockLineCount)) {\n\t\t\tresult.push(`${\" \".repeat(padding)}${line}`);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate editorBlockLineCount(lines: string[]): number {\n\t\tlet borders = 0;\n\t\tfor (let index = 0; index < lines.length; index++) {\n\t\t\tif (this.isBorderLine(lines[index])) borders++;\n\t\t\tif (borders === 2) return index + 1;\n\t\t}\n\t\treturn lines.length;\n\t}\n\n\tprivate surfaceLine(line: string, surfaceColor: (text: string) => string): string {\n\t\tlet output = \"\";\n\t\tlet visibleText = \"\";\n\t\tlet index = 0;\n\n\t\tconst flush = () => {\n\t\t\tif (visibleText) {\n\t\t\t\toutput += surfaceColor(visibleText);\n\t\t\t\tvisibleText = \"\";\n\t\t\t}\n\t\t};\n\n\t\twhile (index < line.length) {\n\t\t\tINVERTED_CURSOR_CELL.lastIndex = index;\n\t\t\tconst cursorCell = INVERTED_CURSOR_CELL.exec(line);\n\t\t\tif (cursorCell) {\n\t\t\t\tflush();\n\t\t\t\toutput += cursorCell[0];\n\t\t\t\tindex = INVERTED_CURSOR_CELL.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\t\tif (control) {\n\t\t\t\tflush();\n\t\t\t\toutput += control[0];\n\t\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvisibleText += line[index];\n\t\t\tindex += 1;\n\t\t}\n\n\t\tflush();\n\t\treturn output;\n\t}\n\n\tsetModeLabel(label: string | undefined): void {\n\t\tthis.modeLabel = label ?? \"\";\n\t\tthis.invalidate();\n\t}\n\n\tprivate isBorderLine(line: string): boolean {\n\t\tconst visible = stripAnsi(line.split(CURSOR_MARKER).join(\"\"));\n\t\treturn BORDER_RULE.test(visible) || BORDER_SCROLL.test(visible);\n\t}\n\n\t/**\n\t * Tint a leading `/command` on the first input line.\n\t *\n\t * The base Editor emits input text unstyled, so the only styling already\n\t * present on the line is the cursor cell — which `colorVisibleRange` steps\n\t * over. Only the command token is tinted; arguments after it stay plain, so\n\t * the highlight marks what is being invoked rather than the whole line.\n\t */\n\tprivate withCommandColor(lines: string[]): string[] {\n\t\tconst commandColor = this.commandColor;\n\t\tif (!commandColor || lines.length < 2) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Read the token from the model rather than the rendered line: the first\n\t\t// visual row is the start of the text only when the caret has not scrolled\n\t\t// the view down, and getText is unambiguous either way.\n\t\tconst firstLine = this.getText().split(\"\\n\", 1)[0] ?? \"\";\n\t\tconst match = COMMAND_TOKEN.exec(firstLine);\n\t\tif (!match) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst [, indent, token] = match;\n\t\t// The rendered row must still begin with that token, or the view has\n\t\t// scrolled and the highlight would land on unrelated text.\n\t\tif (!stripAnsi(lines[1].split(CURSOR_MARKER).join(\"\")).startsWith(`${indent}${token}`)) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst start = visibleWidth(indent);\n\t\tconst updated = [...lines];\n\t\tupdated[1] = colorVisibleRange(lines[1], start, start + visibleWidth(token), commandColor);\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Replace the sole content line with the placeholder while the input is\n\t * empty. Index 1 is always the first content line, and an empty editor has\n\t * exactly one, so no line classification is needed here.\n\t */\n\tprivate withPlaceholder(lines: string[]): string[] {\n\t\tconst placeholder = this.placeholder;\n\t\tif (!placeholder || lines.length < 2 || this.getText().length > 0 || this.isShowingAutocomplete()) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst contentLine = lines[1];\n\t\t// Preserve the cursor cell when focused; unfocused there is none, and the\n\t\t// placeholder simply starts at column zero.\n\t\tconst cursorCell = this.sliceLeadingCursorCell(contentLine);\n\t\t// Measure with visibleWidth alone — never visibleWidth(stripAnsi(...)),\n\t\t// which scores the cursor marker as seven columns. See hazard 1 at the top\n\t\t// of this file.\n\t\tconst available = visibleWidth(contentLine) - visibleWidth(cursorCell);\n\t\tif (available <= 0) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Cut by display width, not by String.slice: `available` counts terminal\n\t\t// columns, while slice counts UTF-16 code units. They only agree for\n\t\t// plain ASCII, and disagreeing splits a surrogate pair or overruns the\n\t\t// line on the first wide character.\n\t\tconst text = truncateToWidth(placeholder, available, \"\");\n\t\tconst padding = \" \".repeat(Math.max(0, available - visibleWidth(text)));\n\t\tconst updated = [...lines];\n\t\tupdated[1] = cursorCell + this.placeholderColor(text) + padding;\n\t\treturn updated;\n\t}\n\n\t/**\n\t * The base Editor renders an empty line as the hardware-cursor marker\n\t * followed by a reverse-video cell: `CURSOR_MARKER ESC[7m <space> ESC[0m`.\n\t * Both parts are kept verbatim.\n\t *\n\t * The `ESC[7m … ESC[0m` run is matched explicitly. Do not \"simplify\" this to\n\t * a general leading-SGR match such as `(?:\\x1b\\[[0-9;]*m)*` — see hazard 2 at\n\t * the top of this file for what that breaks.\n\t */\n\tprivate sliceLeadingCursorCell(line: string): string {\n\t\tconst match = /^(?:\\x1b_pi:c\\x07)?(?:\\x1b\\[7m.\\x1b\\[(?:0|27)m)?/.exec(line);\n\t\treturn match?.[0] ?? \"\";\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppKeybinding, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for clipboard paste keybinding\n\t\tif (this.keybindings.matches(data, \"app.clipboard.pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"app.interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"app.interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"app.exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"app.exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Explicit history bindings take precedence over app actions while the editor is focused.\n\t\t// This lets users bind Ctrl+P even though it cycles models by default.\n\t\tif (\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyPrevious\") ||\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyNext\")\n\t\t) {\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"app.interrupt\" && action !== \"app.exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
1
+ {"version":3,"file":"custom-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tCURSOR_MARKER,\n\tEditor,\n\ttype EditorOptions,\n\ttype EditorTheme,\n\ttype TUI,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../../core/keybindings.ts\";\nimport { stripAnsi } from \"../../../utils/ansi.ts\";\n\n/** Which marker the dock draws. The set is closed so a mode cannot arrive without a marker. */\nexport type PromptMode = \"agent\" | \"bash\";\n\nexport interface CustomEditorOptions extends EditorOptions {\n\t/** Marker rendered at the start of the first input line, e.g. `\"> \"`. */\n\tpromptPrefix?: string;\n\t/** Styles the prompt prefix. Defaults to leaving it unstyled. */\n\tpromptColor?: (text: string) => string;\n\t/** Marker for bash mode. Defaults to `promptPrefix`, so mode is carried by colour alone. */\n\tbashPromptPrefix?: string;\n\t/** Styles the bash marker. Defaults to `promptColor`. */\n\tbashPromptColor?: (text: string) => string;\n\t/** Shown in place of the empty input line. */\n\tplaceholder?: string;\n\t/** Styles the placeholder. Defaults to leaving it unstyled. */\n\tplaceholderColor?: (text: string) => string;\n\t/** Styles a leading `/command` token. Defaults to leaving it unstyled. */\n\tcommandColor?: (text: string) => string;\n\t/** Applies the filled prompt surface around the editor block. */\n\tsurfaceColor?: (text: string) => string;\n\t/** Rule drawn above the autocomplete rows. Omit for no rule. */\n\tautocompleteRule?: (width: number) => string;\n\t/** Keybinding footer drawn under the autocomplete rows. Omit for no footer. */\n\tautocompleteFooter?: () => string;\n}\n\n/** Box-drawing horizontal rule: the character the base Editor draws borders from. */\nconst BORDER_CHAR = \"─\";\n\n/**\n * The two shapes the base Editor draws a border in: a plain full-width rule, and\n * the scroll indicator (`─── ↑ 3 more ────`).\n *\n * Both are anchored deliberately. A prefix test would count any content line that\n * merely *starts* with the rule character — a pasted divider, `─notes` — as a\n * border, which is worse than it sounds: {@link CustomEditor.render} tells the\n * content region from the autocomplete region by counting borders, so one\n * miscount shifts every later line into the wrong region.\n */\nconst BORDER_RULE = /^─+$/;\nconst BORDER_SCROLL = /^─+ [↑↓] \\d+ more/;\n\n/**\n * A leading `/command` token: optional indent, a slash, then non-space.\n * Matched against the line's *visible* text, not the rendered string.\n */\nconst COMMAND_TOKEN = /^(\\s*)(\\/\\S+)/;\n\n/**\n * One ANSI control sequence: a CSI escape (`ESC [ … letter`) or the APC\n * hardware-cursor marker (`ESC _ … BEL`). Sticky, for position-by-position\n * scanning in {@link colorVisibleRange}.\n */\nconst CONTROL_SEQUENCE = /\\x1b\\[[0-9;]*[A-Za-z]|\\x1b_[^\\x07]*\\x07/y;\n\n/** The reverse-video cursor cell emitted by the inherited editor. */\nconst INVERTED_CURSOR_CELL = /\\x1b\\[7m[\\s\\S]\\x1b\\[(?:0|27)m/y;\n\nconst SURFACE_PADDING = 2;\n\n/**\n * ============================ ANSI measuring hazards ============================\n *\n * Two traps live in the rendered strings this file post-processes. Both produced\n * real bugs; both are cheap to re-introduce. Read this before touching the width\n * maths or any leading-sequence match.\n *\n * 1. DO NOT COMPOSE `stripAnsi` WITH `visibleWidth`.\n *\n * `stripAnsi` removes SGR colour codes but leaves the APC hardware-cursor\n * marker (`ESC _pi:c BEL`, see CURSOR_MARKER in packages/tui) fully intact —\n * all seven bytes of it. `visibleWidth` already handles that marker correctly\n * and scores it zero.\n *\n * So `visibleWidth(stripAnsi(line))` counts seven phantom columns on every\n * line carrying a cursor, which silently widened each placeholder line past\n * the terminal width. Measure with `visibleWidth` alone. Use `stripAnsi` only\n * to read text, never to measure it.\n *\n * 2. DO NOT MATCH THE LEADING SEQUENCE RUN GENERICALLY.\n *\n * The base Editor renders an empty line as:\n * CURSOR_MARKER ESC[7m <space> ESC[0m <padding>\n * The `ESC[7m` turns on reverse video for exactly the one-cell cursor block,\n * and `ESC[0m` turns it back off.\n *\n * A permissive prefix match such as `(?:\\x1b\\[[0-9;]*m)*` consumes that\n * `ESC[7m` as though it were ordinary leading styling. Whatever is appended\n * next then inherits reverse video, and because the matching `ESC[0m` was\n * left behind in the discarded remainder, the inversion runs on through the\n * placeholder and the rest of the frame. Match the reverse-video run\n * explicitly instead — see {@link CustomEditor.sliceLeadingCursorCell}.\n *\n * ==============================================================================\n */\n\n/**\n * Wrap the visible characters in `[start, end)` with `colorFn`, leaving every\n * control sequence in place.\n *\n * The range is in visible columns, so it is unaffected by however many escape\n * sequences are interleaved. That matters because the cursor cell can land in\n * the middle of the token being coloured: with the caret at column 3 of\n * `/model`, the line reads `/mo` + CURSOR_MARKER + `ESC[7m` + `d` + `ESC[0m` +\n * `el`. Colour is therefore re-opened after every control sequence rather than\n * once around the whole token, since the cursor cell's `ESC[0m` would otherwise\n * cancel it partway through.\n */\nfunction colorVisibleRange(line: string, start: number, end: number, colorFn: (text: string) => string): string {\n\tlet out = \"\";\n\tlet buffer = \"\";\n\tlet column = 0;\n\tlet index = 0;\n\n\tconst flush = () => {\n\t\tif (buffer) {\n\t\t\tout += colorFn(buffer);\n\t\t\tbuffer = \"\";\n\t\t}\n\t};\n\n\twhile (index < line.length) {\n\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\tif (control) {\n\t\t\tflush();\n\t\t\tout += control[0];\n\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\tcontinue;\n\t\t}\n\t\tconst char = line[index];\n\t\tif (column >= start && column < end) {\n\t\t\tbuffer += char;\n\t\t} else {\n\t\t\tflush();\n\t\t\tout += char;\n\t\t}\n\t\tcolumn += visibleWidth(char);\n\t\tindex += 1;\n\t}\n\tflush();\n\treturn out;\n}\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n *\n * The prompt prefix and placeholder are layered on in `render` rather than\n * inside the base Editor, because `packages/tui` is frozen under ADR 0001 and\n * its `EditorOptions` has no hook for either.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tprivate readonly prompts: Record<PromptMode, { prefix: string; color: (text: string) => string }>;\n\tprivate promptMode: PromptMode = \"agent\";\n\tprivate readonly placeholderColor: (text: string) => string;\n\tprivate readonly commandColor: ((text: string) => string) | undefined;\n\tprivate readonly surfaceColor: ((text: string) => string) | undefined;\n\tprivate readonly placeholder: string | undefined;\n\tprivate readonly autocompleteRule: ((width: number) => string) | undefined;\n\tprivate readonly autocompleteFooter: (() => string) | undefined;\n\tpublic actionHandlers: Map<AppKeybinding, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: CustomEditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t\tconst agentPrefix = options?.promptPrefix ?? \"\";\n\t\tconst agentColor = options?.promptColor ?? ((text) => text);\n\t\tconst bashPrefix = options?.bashPromptPrefix ?? agentPrefix;\n\t\t// Every marker occupies the same cells, so switching mode cannot move the\n\t\t// caret or reflow what is already typed. Padding rather than throwing keeps\n\t\t// a mismatched caller rendering instead of crashing its dock.\n\t\tconst cells = Math.max(visibleWidth(agentPrefix), visibleWidth(bashPrefix));\n\t\tconst pad = (text: string) => text + \" \".repeat(cells - visibleWidth(text));\n\t\tthis.prompts = {\n\t\t\tagent: { prefix: pad(agentPrefix), color: agentColor },\n\t\t\tbash: { prefix: pad(bashPrefix), color: options?.bashPromptColor ?? agentColor },\n\t\t};\n\t\tthis.placeholder = options?.placeholder;\n\t\tthis.placeholderColor = options?.placeholderColor ?? ((text) => text);\n\t\tthis.commandColor = options?.commandColor;\n\t\tthis.surfaceColor = options?.surfaceColor;\n\t\tthis.autocompleteRule = options?.autocompleteRule;\n\t\tthis.autocompleteFooter = options?.autocompleteFooter;\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst canRenderSurface = this.surfaceColor !== undefined && width > 0;\n\t\tconst surfacePadding = canRenderSurface ? Math.min(SURFACE_PADDING, Math.floor((width - 1) / 2)) : 0;\n\t\tconst editorWidth = canRenderSurface ? width - surfacePadding * 2 : width;\n\t\tconst lines = this.withAutocompleteChrome(this.renderEditor(editorWidth), editorWidth);\n\t\treturn canRenderSurface ? this.withSurface(lines, width, surfacePadding) : lines;\n\t}\n\n\tprivate renderEditor(width: number): string[] {\n\t\tconst { prefix: promptPrefix, color: promptColor } = this.prompts[this.promptMode];\n\t\tconst prefixWidth = visibleWidth(promptPrefix);\n\t\t// Reserve the prefix out of the width handed to the base Editor, so its\n\t\t// wrapping, scrolling and cursor column all account for the space the\n\t\t// prefix occupies. Bail out entirely when the terminal cannot spare it.\n\t\tif (prefixWidth === 0 || width <= prefixWidth + 4) {\n\t\t\treturn this.withCommandColor(this.withPlaceholder(super.render(width)));\n\t\t}\n\n\t\tconst inner = this.withCommandColor(this.withPlaceholder(super.render(width - prefixWidth)));\n\t\tconst borderPad = this.borderColor(BORDER_CHAR.repeat(prefixWidth));\n\t\tconst blank = \" \".repeat(prefixWidth);\n\t\tconst styledPrefix = promptColor(promptPrefix);\n\n\t\t// Structure is: top border, content lines, bottom border, then optional\n\t\t// autocomplete rows. Counting borders tells the three regions apart\n\t\t// without depending on how many lines each contains.\n\t\tlet borders = 0;\n\t\tlet firstContentLine = true;\n\t\treturn inner.map((line) => {\n\t\t\tif (this.isBorderLine(line)) {\n\t\t\t\tborders++;\n\t\t\t\treturn borderPad + line;\n\t\t\t}\n\t\t\tif (borders !== 1) {\n\t\t\t\t// Autocomplete rows sit below the box; indent them to match.\n\t\t\t\treturn blank + line;\n\t\t\t}\n\t\t\tif (firstContentLine) {\n\t\t\t\tfirstContentLine = false;\n\t\t\t\treturn styledPrefix + line;\n\t\t\t}\n\t\t\treturn blank + line;\n\t\t});\n\t}\n\n\t/**\n\t * Brackets the autocomplete rows with a rule and a keybinding footer.\n\t *\n\t * The rows arrive after the editor's second border, so the same border count\n\t * that separates the box from its dropdown elsewhere in this file finds them\n\t * here. Nothing is added when the dropdown is closed.\n\t */\n\tprivate withAutocompleteChrome(lines: string[], width: number): string[] {\n\t\tif (!this.autocompleteRule && !this.autocompleteFooter) return lines;\n\t\tconst blockLineCount = this.editorBlockLineCount(lines);\n\t\tconst rows = lines.slice(blockLineCount);\n\t\tif (rows.length === 0) return lines;\n\n\t\tconst chrome = [...lines.slice(0, blockLineCount)];\n\t\tif (this.autocompleteRule) chrome.push(this.autocompleteRule(width));\n\t\tchrome.push(...rows);\n\t\tif (this.autocompleteFooter) chrome.push(this.autocompleteFooter());\n\t\treturn chrome;\n\t}\n\n\tprivate withSurface(lines: string[], width: number, padding: number): string[] {\n\t\tconst surfaceColor = this.surfaceColor;\n\t\tif (!surfaceColor) return lines;\n\n\t\tconst result = [this.surfaceLine(\" \".repeat(width), surfaceColor)];\n\t\tconst editorBlockLineCount = this.editorBlockLineCount(lines);\n\t\tconst editorContent = lines.slice(1, Math.max(1, editorBlockLineCount - 1));\n\t\tfor (const line of editorContent) {\n\t\t\tresult.push(this.surfaceLine(`${\" \".repeat(padding)}${line}${\" \".repeat(padding)}`, surfaceColor));\n\t\t}\n\t\tresult.push(this.surfaceLine(\" \".repeat(width), surfaceColor));\n\t\tfor (const line of lines.slice(editorBlockLineCount)) {\n\t\t\tresult.push(`${\" \".repeat(padding)}${line}`);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate editorBlockLineCount(lines: string[]): number {\n\t\tlet borders = 0;\n\t\tfor (let index = 0; index < lines.length; index++) {\n\t\t\tif (this.isBorderLine(lines[index])) borders++;\n\t\t\tif (borders === 2) return index + 1;\n\t\t}\n\t\treturn lines.length;\n\t}\n\n\tprivate surfaceLine(line: string, surfaceColor: (text: string) => string): string {\n\t\tlet output = \"\";\n\t\tlet visibleText = \"\";\n\t\tlet index = 0;\n\n\t\tconst flush = () => {\n\t\t\tif (visibleText) {\n\t\t\t\toutput += surfaceColor(visibleText);\n\t\t\t\tvisibleText = \"\";\n\t\t\t}\n\t\t};\n\n\t\twhile (index < line.length) {\n\t\t\tINVERTED_CURSOR_CELL.lastIndex = index;\n\t\t\tconst cursorCell = INVERTED_CURSOR_CELL.exec(line);\n\t\t\tif (cursorCell) {\n\t\t\t\tflush();\n\t\t\t\toutput += cursorCell[0];\n\t\t\t\tindex = INVERTED_CURSOR_CELL.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\t\tif (control) {\n\t\t\t\tflush();\n\t\t\t\toutput += control[0];\n\t\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvisibleText += line[index];\n\t\t\tindex += 1;\n\t\t}\n\n\t\tflush();\n\t\treturn output;\n\t}\n\n\tsetPromptMode(mode: PromptMode): void {\n\t\tif (this.promptMode === mode) return;\n\t\tthis.promptMode = mode;\n\t\tthis.invalidate();\n\t}\n\n\tprivate isBorderLine(line: string): boolean {\n\t\tconst visible = stripAnsi(line.split(CURSOR_MARKER).join(\"\"));\n\t\treturn BORDER_RULE.test(visible) || BORDER_SCROLL.test(visible);\n\t}\n\n\t/**\n\t * Tint a leading `/command` on the first input line.\n\t *\n\t * The base Editor emits input text unstyled, so the only styling already\n\t * present on the line is the cursor cell — which `colorVisibleRange` steps\n\t * over. Only the command token is tinted; arguments after it stay plain, so\n\t * the highlight marks what is being invoked rather than the whole line.\n\t */\n\tprivate withCommandColor(lines: string[]): string[] {\n\t\tconst commandColor = this.commandColor;\n\t\tif (!commandColor || lines.length < 2) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Read the token from the model rather than the rendered line: the first\n\t\t// visual row is the start of the text only when the caret has not scrolled\n\t\t// the view down, and getText is unambiguous either way.\n\t\tconst firstLine = this.getText().split(\"\\n\", 1)[0] ?? \"\";\n\t\tconst match = COMMAND_TOKEN.exec(firstLine);\n\t\tif (!match) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst [, indent, token] = match;\n\t\t// The rendered row must still begin with that token, or the view has\n\t\t// scrolled and the highlight would land on unrelated text.\n\t\tif (!stripAnsi(lines[1].split(CURSOR_MARKER).join(\"\")).startsWith(`${indent}${token}`)) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst start = visibleWidth(indent);\n\t\tconst updated = [...lines];\n\t\tupdated[1] = colorVisibleRange(lines[1], start, start + visibleWidth(token), commandColor);\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Replace the sole content line with the placeholder while the input is\n\t * empty. Index 1 is always the first content line, and an empty editor has\n\t * exactly one, so no line classification is needed here.\n\t */\n\tprivate withPlaceholder(lines: string[]): string[] {\n\t\tconst placeholder = this.placeholder;\n\t\tif (!placeholder || lines.length < 2 || this.getText().length > 0 || this.isShowingAutocomplete()) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst contentLine = lines[1];\n\t\t// Preserve the cursor cell when focused; unfocused there is none, and the\n\t\t// placeholder simply starts at column zero.\n\t\tconst cursorCell = this.sliceLeadingCursorCell(contentLine);\n\t\t// Measure with visibleWidth alone — never visibleWidth(stripAnsi(...)),\n\t\t// which scores the cursor marker as seven columns. See hazard 1 at the top\n\t\t// of this file.\n\t\tconst available = visibleWidth(contentLine) - visibleWidth(cursorCell);\n\t\tif (available <= 0) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Cut by display width, not by String.slice: `available` counts terminal\n\t\t// columns, while slice counts UTF-16 code units. They only agree for\n\t\t// plain ASCII, and disagreeing splits a surrogate pair or overruns the\n\t\t// line on the first wide character.\n\t\tconst text = truncateToWidth(placeholder, available, \"\");\n\t\tconst padding = \" \".repeat(Math.max(0, available - visibleWidth(text)));\n\t\tconst updated = [...lines];\n\t\tupdated[1] = cursorCell + this.placeholderColor(text) + padding;\n\t\treturn updated;\n\t}\n\n\t/**\n\t * The base Editor renders an empty line as the hardware-cursor marker\n\t * followed by a reverse-video cell: `CURSOR_MARKER ESC[7m <space> ESC[0m`.\n\t * Both parts are kept verbatim.\n\t *\n\t * The `ESC[7m … ESC[0m` run is matched explicitly. Do not \"simplify\" this to\n\t * a general leading-SGR match such as `(?:\\x1b\\[[0-9;]*m)*` — see hazard 2 at\n\t * the top of this file for what that breaks.\n\t */\n\tprivate sliceLeadingCursorCell(line: string): string {\n\t\tconst match = /^(?:\\x1b_pi:c\\x07)?(?:\\x1b\\[7m.\\x1b\\[(?:0|27)m)?/.exec(line);\n\t\treturn match?.[0] ?? \"\";\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppKeybinding, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for clipboard paste keybinding\n\t\tif (this.keybindings.matches(data, \"app.clipboard.pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"app.interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"app.interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"app.exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"app.exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Explicit history bindings take precedence over app actions while the editor is focused.\n\t\t// This lets users bind Ctrl+P even though it cycles models by default.\n\t\tif (\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyPrevious\") ||\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyNext\")\n\t\t) {\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"app.interrupt\" && action !== \"app.exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
@@ -118,15 +118,14 @@ function colorVisibleRange(line, start, end, colorFn) {
118
118
  */
119
119
  export class CustomEditor extends Editor {
120
120
  keybindings;
121
- promptPrefix;
122
- promptColor;
121
+ prompts;
122
+ promptMode = "agent";
123
123
  placeholderColor;
124
124
  commandColor;
125
125
  surfaceColor;
126
126
  placeholder;
127
127
  autocompleteRule;
128
128
  autocompleteFooter;
129
- modeLabel = "";
130
129
  actionHandlers = new Map();
131
130
  // Special handlers that can be dynamically replaced
132
131
  onEscape;
@@ -137,8 +136,18 @@ export class CustomEditor extends Editor {
137
136
  constructor(tui, theme, keybindings, options) {
138
137
  super(tui, theme, options);
139
138
  this.keybindings = keybindings;
140
- this.promptPrefix = options?.promptPrefix ?? "";
141
- this.promptColor = options?.promptColor ?? ((text) => text);
139
+ const agentPrefix = options?.promptPrefix ?? "";
140
+ const agentColor = options?.promptColor ?? ((text) => text);
141
+ const bashPrefix = options?.bashPromptPrefix ?? agentPrefix;
142
+ // Every marker occupies the same cells, so switching mode cannot move the
143
+ // caret or reflow what is already typed. Padding rather than throwing keeps
144
+ // a mismatched caller rendering instead of crashing its dock.
145
+ const cells = Math.max(visibleWidth(agentPrefix), visibleWidth(bashPrefix));
146
+ const pad = (text) => text + " ".repeat(cells - visibleWidth(text));
147
+ this.prompts = {
148
+ agent: { prefix: pad(agentPrefix), color: agentColor },
149
+ bash: { prefix: pad(bashPrefix), color: options?.bashPromptColor ?? agentColor },
150
+ };
142
151
  this.placeholder = options?.placeholder;
143
152
  this.placeholderColor = options?.placeholderColor ?? ((text) => text);
144
153
  this.commandColor = options?.commandColor;
@@ -154,7 +163,7 @@ export class CustomEditor extends Editor {
154
163
  return canRenderSurface ? this.withSurface(lines, width, surfacePadding) : lines;
155
164
  }
156
165
  renderEditor(width) {
157
- const promptPrefix = this.modeLabel ? `[${this.modeLabel}] ${this.promptPrefix}` : this.promptPrefix;
166
+ const { prefix: promptPrefix, color: promptColor } = this.prompts[this.promptMode];
158
167
  const prefixWidth = visibleWidth(promptPrefix);
159
168
  // Reserve the prefix out of the width handed to the base Editor, so its
160
169
  // wrapping, scrolling and cursor column all account for the space the
@@ -165,7 +174,7 @@ export class CustomEditor extends Editor {
165
174
  const inner = this.withCommandColor(this.withPlaceholder(super.render(width - prefixWidth)));
166
175
  const borderPad = this.borderColor(BORDER_CHAR.repeat(prefixWidth));
167
176
  const blank = " ".repeat(prefixWidth);
168
- const styledPrefix = this.promptColor(promptPrefix);
177
+ const styledPrefix = promptColor(promptPrefix);
169
178
  // Structure is: top border, content lines, bottom border, then optional
170
179
  // autocomplete rows. Counting borders tells the three regions apart
171
180
  // without depending on how many lines each contains.
@@ -268,8 +277,10 @@ export class CustomEditor extends Editor {
268
277
  flush();
269
278
  return output;
270
279
  }
271
- setModeLabel(label) {
272
- this.modeLabel = label ?? "";
280
+ setPromptMode(mode) {
281
+ if (this.promptMode === mode)
282
+ return;
283
+ this.promptMode = mode;
273
284
  this.invalidate();
274
285
  }
275
286
  isBorderLine(line) {
@@ -1 +1 @@
1
- {"version":3,"file":"custom-editor.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,aAAa,EACb,MAAM,EAIN,eAAe,EACf,YAAY,GACZ,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAqBnD,qFAAqF;AACrF,MAAM,WAAW,GAAG,KAAG,CAAC;AAExB;;;;;;;;;GASG;AACH,MAAM,WAAW,GAAG,QAAM,CAAC;AAC3B,MAAM,aAAa,GAAG,yBAAmB,CAAC;AAE1C;;;GAGG;AACH,MAAM,aAAa,GAAG,eAAe,CAAC;AAEtC;;;;GAIG;AACH,MAAM,gBAAgB,GAAG,0CAA0C,CAAC;AAEpE,qEAAqE;AACrE,MAAM,oBAAoB,GAAG,gCAAgC,CAAC;AAE9D,MAAM,eAAe,GAAG,CAAC,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH;;;;;;;;;;;GAWG;AACH,SAAS,iBAAiB,CAAC,IAAY,EAAE,KAAa,EAAE,GAAW,EAAE,OAAiC,EAAU;IAC/G,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC;QACnB,IAAI,MAAM,EAAE,CAAC;YACZ,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;YACvB,MAAM,GAAG,EAAE,CAAC;QACb,CAAC;IAAA,CACD,CAAC;IAEF,OAAO,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,gBAAgB,CAAC,SAAS,GAAG,KAAK,CAAC;QACnC,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,OAAO,EAAE,CAAC;YACb,KAAK,EAAE,CAAC;YACR,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;YAClB,KAAK,GAAG,gBAAgB,CAAC,SAAS,CAAC;YACnC,SAAS;QACV,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,MAAM,IAAI,KAAK,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC;YACrC,MAAM,IAAI,IAAI,CAAC;QAChB,CAAC;aAAM,CAAC;YACP,KAAK,EAAE,CAAC;YACR,GAAG,IAAI,IAAI,CAAC;QACb,CAAC;QACD,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;QAC7B,KAAK,IAAI,CAAC,CAAC;IACZ,CAAC;IACD,KAAK,EAAE,CAAC;IACR,OAAO,GAAG,CAAC;AAAA,CACX;AAED;;;;;;GAMG;AACH,MAAM,OAAO,YAAa,SAAQ,MAAM;IAC/B,WAAW,CAAqB;IACvB,YAAY,CAAS;IACrB,WAAW,CAA2B;IACtC,gBAAgB,CAA2B;IAC3C,YAAY,CAAyC;IACrD,YAAY,CAAyC;IACrD,WAAW,CAAqB;IAChC,gBAAgB,CAA0C;IAC1D,kBAAkB,CAA6B;IACxD,SAAS,GAAG,EAAE,CAAC;IAChB,cAAc,GAAmC,IAAI,GAAG,EAAE,CAAC;IAElE,oDAAoD;IAC7C,QAAQ,CAAc;IACtB,OAAO,CAAc;IACrB,YAAY,CAAc;IACjC,2EAA2E;IACpE,mBAAmB,CAA6B;IAEvD,YAAY,GAAQ,EAAE,KAAkB,EAAE,WAA+B,EAAE,OAA6B,EAAE;QACzG,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,YAAY,GAAG,OAAO,EAAE,YAAY,IAAI,EAAE,CAAC;QAChD,IAAI,CAAC,WAAW,GAAG,OAAO,EAAE,WAAW,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;QAC5D,IAAI,CAAC,WAAW,GAAG,OAAO,EAAE,WAAW,CAAC;QACxC,IAAI,CAAC,gBAAgB,GAAG,OAAO,EAAE,gBAAgB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;QACtE,IAAI,CAAC,YAAY,GAAG,OAAO,EAAE,YAAY,CAAC;QAC1C,IAAI,CAAC,YAAY,GAAG,OAAO,EAAE,YAAY,CAAC;QAC1C,IAAI,CAAC,gBAAgB,GAAG,OAAO,EAAE,gBAAgB,CAAC;QAClD,IAAI,CAAC,kBAAkB,GAAG,OAAO,EAAE,kBAAkB,CAAC;IAAA,CACtD;IAEQ,MAAM,CAAC,KAAa,EAAY;QACxC,MAAM,gBAAgB,GAAG,IAAI,CAAC,YAAY,KAAK,SAAS,IAAI,KAAK,GAAG,CAAC,CAAC;QACtE,MAAM,cAAc,GAAG,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrG,MAAM,WAAW,GAAG,gBAAgB,CAAC,CAAC,CAAC,KAAK,GAAG,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,CAAC;QACvF,OAAO,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAAA,CACjF;IAEO,YAAY,CAAC,KAAa,EAAY;QAC7C,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC;QACrG,MAAM,WAAW,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;QAC/C,wEAAwE;QACxE,sEAAsE;QACtE,wEAAwE;QACxE,IAAI,WAAW,KAAK,CAAC,IAAI,KAAK,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;YACnD,OAAO,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACzE,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QAC7F,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;QACpE,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACtC,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;QAEpD,wEAAwE;QACxE,oEAAoE;QACpE,qDAAqD;QACrD,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,IAAI,gBAAgB,GAAG,IAAI,CAAC;QAC5B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAC1B,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7B,OAAO,EAAE,CAAC;gBACV,OAAO,SAAS,GAAG,IAAI,CAAC;YACzB,CAAC;YACD,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;gBACnB,6DAA6D;gBAC7D,OAAO,KAAK,GAAG,IAAI,CAAC;YACrB,CAAC;YACD,IAAI,gBAAgB,EAAE,CAAC;gBACtB,gBAAgB,GAAG,KAAK,CAAC;gBACzB,OAAO,YAAY,GAAG,IAAI,CAAC;YAC5B,CAAC;YACD,OAAO,KAAK,GAAG,IAAI,CAAC;QAAA,CACpB,CAAC,CAAC;IAAA,CACH;IAED;;;;;;OAMG;IACK,sBAAsB,CAAC,KAAe,EAAE,KAAa,EAAY;QACxE,IAAI,CAAC,IAAI,CAAC,gBAAgB,IAAI,CAAC,IAAI,CAAC,kBAAkB;YAAE,OAAO,KAAK,CAAC;QACrE,MAAM,cAAc,GAAG,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QACzC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAEpC,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC;QACnD,IAAI,IAAI,CAAC,gBAAgB;YAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC;QACrE,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QACrB,IAAI,IAAI,CAAC,kBAAkB;YAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,CAAC;QACpE,OAAO,MAAM,CAAC;IAAA,CACd;IAEO,WAAW,CAAC,KAAe,EAAE,KAAa,EAAE,OAAe,EAAY;QAC9E,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;QACvC,IAAI,CAAC,YAAY;YAAE,OAAO,KAAK,CAAC;QAEhC,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;QACnE,MAAM,oBAAoB,GAAG,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;QAC9D,MAAM,aAAa,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,oBAAoB,GAAG,CAAC,CAAC,CAAC,CAAC;QAC5E,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC;QACpG,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;QAC/D,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,oBAAoB,CAAC,EAAE,CAAC;YACtD,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,MAAM,CAAC;IAAA,CACd;IAEO,oBAAoB,CAAC,KAAe,EAAU;QACrD,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;YACnD,IAAI,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBAAE,OAAO,EAAE,CAAC;YAC/C,IAAI,OAAO,KAAK,CAAC;gBAAE,OAAO,KAAK,GAAG,CAAC,CAAC;QACrC,CAAC;QACD,OAAO,KAAK,CAAC,MAAM,CAAC;IAAA,CACpB;IAEO,WAAW,CAAC,IAAY,EAAE,YAAsC,EAAU;QACjF,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,WAAW,GAAG,EAAE,CAAC;QACrB,IAAI,KAAK,GAAG,CAAC,CAAC;QAEd,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC;YACnB,IAAI,WAAW,EAAE,CAAC;gBACjB,MAAM,IAAI,YAAY,CAAC,WAAW,CAAC,CAAC;gBACpC,WAAW,GAAG,EAAE,CAAC;YAClB,CAAC;QAAA,CACD,CAAC;QAEF,OAAO,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAC5B,oBAAoB,CAAC,SAAS,GAAG,KAAK,CAAC;YACvC,MAAM,UAAU,GAAG,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnD,IAAI,UAAU,EAAE,CAAC;gBAChB,KAAK,EAAE,CAAC;gBACR,MAAM,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;gBACxB,KAAK,GAAG,oBAAoB,CAAC,SAAS,CAAC;gBACvC,SAAS;YACV,CAAC;YAED,gBAAgB,CAAC,SAAS,GAAG,KAAK,CAAC;YACnC,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,OAAO,EAAE,CAAC;gBACb,KAAK,EAAE,CAAC;gBACR,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;gBACrB,KAAK,GAAG,gBAAgB,CAAC,SAAS,CAAC;gBACnC,SAAS;YACV,CAAC;YAED,WAAW,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;YAC3B,KAAK,IAAI,CAAC,CAAC;QACZ,CAAC;QAED,KAAK,EAAE,CAAC;QACR,OAAO,MAAM,CAAC;IAAA,CACd;IAED,YAAY,CAAC,KAAyB,EAAQ;QAC7C,IAAI,CAAC,SAAS,GAAG,KAAK,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,UAAU,EAAE,CAAC;IAAA,CAClB;IAEO,YAAY,CAAC,IAAY,EAAW;QAC3C,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9D,OAAO,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAAA,CAChE;IAED;;;;;;;OAOG;IACK,gBAAgB,CAAC,KAAe,EAAY;QACnD,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;QACvC,IAAI,CAAC,YAAY,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvC,OAAO,KAAK,CAAC;QACd,CAAC;QACD,yEAAyE;QACzE,2EAA2E;QAC3E,wDAAwD;QACxD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,OAAO,KAAK,CAAC;QACd,CAAC;QACD,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC;QAChC,qEAAqE;QACrE,2DAA2D;QAC3D,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,KAAK,EAAE,CAAC,EAAE,CAAC;YACxF,OAAO,KAAK,CAAC;QACd,CAAC;QACD,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;QAC3B,OAAO,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC;QAC3F,OAAO,OAAO,CAAC;IAAA,CACf;IAED;;;;OAIG;IACK,eAAe,CAAC,KAAe,EAAY;QAClD,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;QACrC,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;YACnG,OAAO,KAAK,CAAC;QACd,CAAC;QACD,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7B,0EAA0E;QAC1E,4CAA4C;QAC5C,MAAM,UAAU,GAAG,IAAI,CAAC,sBAAsB,CAAC,WAAW,CAAC,CAAC;QAC5D,0EAAwE;QACxE,2EAA2E;QAC3E,gBAAgB;QAChB,MAAM,SAAS,GAAG,YAAY,CAAC,WAAW,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;QACvE,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YACpB,OAAO,KAAK,CAAC;QACd,CAAC;QACD,yEAAyE;QACzE,qEAAqE;QACrE,uEAAuE;QACvE,oCAAoC;QACpC,MAAM,IAAI,GAAG,eAAe,CAAC,WAAW,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;QAC3B,OAAO,CAAC,CAAC,CAAC,GAAG,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;QAChE,OAAO,OAAO,CAAC;IAAA,CACf;IAED;;;;;;;;OAQG;IACK,sBAAsB,CAAC,IAAY,EAAU;QACpD,MAAM,KAAK,GAAG,kDAAkD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5E,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAAA,CACxB;IAED;;OAEG;IACH,QAAQ,CAAC,MAAqB,EAAE,OAAmB,EAAQ;QAC1D,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAA,CACzC;IAED,WAAW,CAAC,IAAY,EAAQ;QAC/B,6CAA6C;QAC7C,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,OAAO;QACR,CAAC;QAED,uCAAuC;QACvC,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,0BAA0B,CAAC,EAAE,CAAC;YAChE,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACtB,OAAO;QACR,CAAC;QAED,8BAA8B;QAE9B,wDAAwD;QACxD,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,eAAe,CAAC,EAAE,CAAC;YACrD,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;gBACnC,4DAA4D;gBAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;gBAC1E,IAAI,OAAO,EAAE,CAAC;oBACb,OAAO,EAAE,CAAC;oBACV,OAAO;gBACR,CAAC;YACF,CAAC;YACD,yDAAyD;YACzD,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YACxB,OAAO;QACR,CAAC;QAED,4CAA4C;QAC5C,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,CAAC;YAChD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACjC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBACpE,IAAI,OAAO;oBAAE,OAAO,EAAE,CAAC;gBACvB,OAAO;YACR,CAAC;YACD,yEAAyE;QAC1E,CAAC;QAED,0FAA0F;QAC1F,uEAAuE;QACvE,IACC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,4BAA4B,CAAC;YAC5D,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,wBAAwB,CAAC,EACvD,CAAC;YACF,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YACxB,OAAO;QACR,CAAC;QAED,8BAA8B;QAC9B,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACrD,IAAI,MAAM,KAAK,eAAe,IAAI,MAAM,KAAK,UAAU,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;gBACnG,OAAO,EAAE,CAAC;gBACV,OAAO;YACR,CAAC;QACF,CAAC;QAED,qCAAqC;QACrC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAAA,CACxB;CACD","sourcesContent":["import {\n\tCURSOR_MARKER,\n\tEditor,\n\ttype EditorOptions,\n\ttype EditorTheme,\n\ttype TUI,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../../core/keybindings.ts\";\nimport { stripAnsi } from \"../../../utils/ansi.ts\";\n\nexport interface CustomEditorOptions extends EditorOptions {\n\t/** Marker rendered at the start of the first input line, e.g. `\"> \"`. */\n\tpromptPrefix?: string;\n\t/** Styles the prompt prefix. Defaults to leaving it unstyled. */\n\tpromptColor?: (text: string) => string;\n\t/** Shown in place of the empty input line. */\n\tplaceholder?: string;\n\t/** Styles the placeholder. Defaults to leaving it unstyled. */\n\tplaceholderColor?: (text: string) => string;\n\t/** Styles a leading `/command` token. Defaults to leaving it unstyled. */\n\tcommandColor?: (text: string) => string;\n\t/** Applies the filled prompt surface around the editor block. */\n\tsurfaceColor?: (text: string) => string;\n\t/** Rule drawn above the autocomplete rows. Omit for no rule. */\n\tautocompleteRule?: (width: number) => string;\n\t/** Keybinding footer drawn under the autocomplete rows. Omit for no footer. */\n\tautocompleteFooter?: () => string;\n}\n\n/** Box-drawing horizontal rule: the character the base Editor draws borders from. */\nconst BORDER_CHAR = \"─\";\n\n/**\n * The two shapes the base Editor draws a border in: a plain full-width rule, and\n * the scroll indicator (`─── ↑ 3 more ────`).\n *\n * Both are anchored deliberately. A prefix test would count any content line that\n * merely *starts* with the rule character — a pasted divider, `─notes` — as a\n * border, which is worse than it sounds: {@link CustomEditor.render} tells the\n * content region from the autocomplete region by counting borders, so one\n * miscount shifts every later line into the wrong region.\n */\nconst BORDER_RULE = /^─+$/;\nconst BORDER_SCROLL = /^─+ [↑↓] \\d+ more/;\n\n/**\n * A leading `/command` token: optional indent, a slash, then non-space.\n * Matched against the line's *visible* text, not the rendered string.\n */\nconst COMMAND_TOKEN = /^(\\s*)(\\/\\S+)/;\n\n/**\n * One ANSI control sequence: a CSI escape (`ESC [ … letter`) or the APC\n * hardware-cursor marker (`ESC _ … BEL`). Sticky, for position-by-position\n * scanning in {@link colorVisibleRange}.\n */\nconst CONTROL_SEQUENCE = /\\x1b\\[[0-9;]*[A-Za-z]|\\x1b_[^\\x07]*\\x07/y;\n\n/** The reverse-video cursor cell emitted by the inherited editor. */\nconst INVERTED_CURSOR_CELL = /\\x1b\\[7m[\\s\\S]\\x1b\\[(?:0|27)m/y;\n\nconst SURFACE_PADDING = 2;\n\n/**\n * ============================ ANSI measuring hazards ============================\n *\n * Two traps live in the rendered strings this file post-processes. Both produced\n * real bugs; both are cheap to re-introduce. Read this before touching the width\n * maths or any leading-sequence match.\n *\n * 1. DO NOT COMPOSE `stripAnsi` WITH `visibleWidth`.\n *\n * `stripAnsi` removes SGR colour codes but leaves the APC hardware-cursor\n * marker (`ESC _pi:c BEL`, see CURSOR_MARKER in packages/tui) fully intact —\n * all seven bytes of it. `visibleWidth` already handles that marker correctly\n * and scores it zero.\n *\n * So `visibleWidth(stripAnsi(line))` counts seven phantom columns on every\n * line carrying a cursor, which silently widened each placeholder line past\n * the terminal width. Measure with `visibleWidth` alone. Use `stripAnsi` only\n * to read text, never to measure it.\n *\n * 2. DO NOT MATCH THE LEADING SEQUENCE RUN GENERICALLY.\n *\n * The base Editor renders an empty line as:\n * CURSOR_MARKER ESC[7m <space> ESC[0m <padding>\n * The `ESC[7m` turns on reverse video for exactly the one-cell cursor block,\n * and `ESC[0m` turns it back off.\n *\n * A permissive prefix match such as `(?:\\x1b\\[[0-9;]*m)*` consumes that\n * `ESC[7m` as though it were ordinary leading styling. Whatever is appended\n * next then inherits reverse video, and because the matching `ESC[0m` was\n * left behind in the discarded remainder, the inversion runs on through the\n * placeholder and the rest of the frame. Match the reverse-video run\n * explicitly instead — see {@link CustomEditor.sliceLeadingCursorCell}.\n *\n * ==============================================================================\n */\n\n/**\n * Wrap the visible characters in `[start, end)` with `colorFn`, leaving every\n * control sequence in place.\n *\n * The range is in visible columns, so it is unaffected by however many escape\n * sequences are interleaved. That matters because the cursor cell can land in\n * the middle of the token being coloured: with the caret at column 3 of\n * `/model`, the line reads `/mo` + CURSOR_MARKER + `ESC[7m` + `d` + `ESC[0m` +\n * `el`. Colour is therefore re-opened after every control sequence rather than\n * once around the whole token, since the cursor cell's `ESC[0m` would otherwise\n * cancel it partway through.\n */\nfunction colorVisibleRange(line: string, start: number, end: number, colorFn: (text: string) => string): string {\n\tlet out = \"\";\n\tlet buffer = \"\";\n\tlet column = 0;\n\tlet index = 0;\n\n\tconst flush = () => {\n\t\tif (buffer) {\n\t\t\tout += colorFn(buffer);\n\t\t\tbuffer = \"\";\n\t\t}\n\t};\n\n\twhile (index < line.length) {\n\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\tif (control) {\n\t\t\tflush();\n\t\t\tout += control[0];\n\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\tcontinue;\n\t\t}\n\t\tconst char = line[index];\n\t\tif (column >= start && column < end) {\n\t\t\tbuffer += char;\n\t\t} else {\n\t\t\tflush();\n\t\t\tout += char;\n\t\t}\n\t\tcolumn += visibleWidth(char);\n\t\tindex += 1;\n\t}\n\tflush();\n\treturn out;\n}\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n *\n * The prompt prefix and placeholder are layered on in `render` rather than\n * inside the base Editor, because `packages/tui` is frozen under ADR 0001 and\n * its `EditorOptions` has no hook for either.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tprivate readonly promptPrefix: string;\n\tprivate readonly promptColor: (text: string) => string;\n\tprivate readonly placeholderColor: (text: string) => string;\n\tprivate readonly commandColor: ((text: string) => string) | undefined;\n\tprivate readonly surfaceColor: ((text: string) => string) | undefined;\n\tprivate readonly placeholder: string | undefined;\n\tprivate readonly autocompleteRule: ((width: number) => string) | undefined;\n\tprivate readonly autocompleteFooter: (() => string) | undefined;\n\tprivate modeLabel = \"\";\n\tpublic actionHandlers: Map<AppKeybinding, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: CustomEditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t\tthis.promptPrefix = options?.promptPrefix ?? \"\";\n\t\tthis.promptColor = options?.promptColor ?? ((text) => text);\n\t\tthis.placeholder = options?.placeholder;\n\t\tthis.placeholderColor = options?.placeholderColor ?? ((text) => text);\n\t\tthis.commandColor = options?.commandColor;\n\t\tthis.surfaceColor = options?.surfaceColor;\n\t\tthis.autocompleteRule = options?.autocompleteRule;\n\t\tthis.autocompleteFooter = options?.autocompleteFooter;\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst canRenderSurface = this.surfaceColor !== undefined && width > 0;\n\t\tconst surfacePadding = canRenderSurface ? Math.min(SURFACE_PADDING, Math.floor((width - 1) / 2)) : 0;\n\t\tconst editorWidth = canRenderSurface ? width - surfacePadding * 2 : width;\n\t\tconst lines = this.withAutocompleteChrome(this.renderEditor(editorWidth), editorWidth);\n\t\treturn canRenderSurface ? this.withSurface(lines, width, surfacePadding) : lines;\n\t}\n\n\tprivate renderEditor(width: number): string[] {\n\t\tconst promptPrefix = this.modeLabel ? `[${this.modeLabel}] ${this.promptPrefix}` : this.promptPrefix;\n\t\tconst prefixWidth = visibleWidth(promptPrefix);\n\t\t// Reserve the prefix out of the width handed to the base Editor, so its\n\t\t// wrapping, scrolling and cursor column all account for the space the\n\t\t// prefix occupies. Bail out entirely when the terminal cannot spare it.\n\t\tif (prefixWidth === 0 || width <= prefixWidth + 4) {\n\t\t\treturn this.withCommandColor(this.withPlaceholder(super.render(width)));\n\t\t}\n\n\t\tconst inner = this.withCommandColor(this.withPlaceholder(super.render(width - prefixWidth)));\n\t\tconst borderPad = this.borderColor(BORDER_CHAR.repeat(prefixWidth));\n\t\tconst blank = \" \".repeat(prefixWidth);\n\t\tconst styledPrefix = this.promptColor(promptPrefix);\n\n\t\t// Structure is: top border, content lines, bottom border, then optional\n\t\t// autocomplete rows. Counting borders tells the three regions apart\n\t\t// without depending on how many lines each contains.\n\t\tlet borders = 0;\n\t\tlet firstContentLine = true;\n\t\treturn inner.map((line) => {\n\t\t\tif (this.isBorderLine(line)) {\n\t\t\t\tborders++;\n\t\t\t\treturn borderPad + line;\n\t\t\t}\n\t\t\tif (borders !== 1) {\n\t\t\t\t// Autocomplete rows sit below the box; indent them to match.\n\t\t\t\treturn blank + line;\n\t\t\t}\n\t\t\tif (firstContentLine) {\n\t\t\t\tfirstContentLine = false;\n\t\t\t\treturn styledPrefix + line;\n\t\t\t}\n\t\t\treturn blank + line;\n\t\t});\n\t}\n\n\t/**\n\t * Brackets the autocomplete rows with a rule and a keybinding footer.\n\t *\n\t * The rows arrive after the editor's second border, so the same border count\n\t * that separates the box from its dropdown elsewhere in this file finds them\n\t * here. Nothing is added when the dropdown is closed.\n\t */\n\tprivate withAutocompleteChrome(lines: string[], width: number): string[] {\n\t\tif (!this.autocompleteRule && !this.autocompleteFooter) return lines;\n\t\tconst blockLineCount = this.editorBlockLineCount(lines);\n\t\tconst rows = lines.slice(blockLineCount);\n\t\tif (rows.length === 0) return lines;\n\n\t\tconst chrome = [...lines.slice(0, blockLineCount)];\n\t\tif (this.autocompleteRule) chrome.push(this.autocompleteRule(width));\n\t\tchrome.push(...rows);\n\t\tif (this.autocompleteFooter) chrome.push(this.autocompleteFooter());\n\t\treturn chrome;\n\t}\n\n\tprivate withSurface(lines: string[], width: number, padding: number): string[] {\n\t\tconst surfaceColor = this.surfaceColor;\n\t\tif (!surfaceColor) return lines;\n\n\t\tconst result = [this.surfaceLine(\" \".repeat(width), surfaceColor)];\n\t\tconst editorBlockLineCount = this.editorBlockLineCount(lines);\n\t\tconst editorContent = lines.slice(1, Math.max(1, editorBlockLineCount - 1));\n\t\tfor (const line of editorContent) {\n\t\t\tresult.push(this.surfaceLine(`${\" \".repeat(padding)}${line}${\" \".repeat(padding)}`, surfaceColor));\n\t\t}\n\t\tresult.push(this.surfaceLine(\" \".repeat(width), surfaceColor));\n\t\tfor (const line of lines.slice(editorBlockLineCount)) {\n\t\t\tresult.push(`${\" \".repeat(padding)}${line}`);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate editorBlockLineCount(lines: string[]): number {\n\t\tlet borders = 0;\n\t\tfor (let index = 0; index < lines.length; index++) {\n\t\t\tif (this.isBorderLine(lines[index])) borders++;\n\t\t\tif (borders === 2) return index + 1;\n\t\t}\n\t\treturn lines.length;\n\t}\n\n\tprivate surfaceLine(line: string, surfaceColor: (text: string) => string): string {\n\t\tlet output = \"\";\n\t\tlet visibleText = \"\";\n\t\tlet index = 0;\n\n\t\tconst flush = () => {\n\t\t\tif (visibleText) {\n\t\t\t\toutput += surfaceColor(visibleText);\n\t\t\t\tvisibleText = \"\";\n\t\t\t}\n\t\t};\n\n\t\twhile (index < line.length) {\n\t\t\tINVERTED_CURSOR_CELL.lastIndex = index;\n\t\t\tconst cursorCell = INVERTED_CURSOR_CELL.exec(line);\n\t\t\tif (cursorCell) {\n\t\t\t\tflush();\n\t\t\t\toutput += cursorCell[0];\n\t\t\t\tindex = INVERTED_CURSOR_CELL.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\t\tif (control) {\n\t\t\t\tflush();\n\t\t\t\toutput += control[0];\n\t\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvisibleText += line[index];\n\t\t\tindex += 1;\n\t\t}\n\n\t\tflush();\n\t\treturn output;\n\t}\n\n\tsetModeLabel(label: string | undefined): void {\n\t\tthis.modeLabel = label ?? \"\";\n\t\tthis.invalidate();\n\t}\n\n\tprivate isBorderLine(line: string): boolean {\n\t\tconst visible = stripAnsi(line.split(CURSOR_MARKER).join(\"\"));\n\t\treturn BORDER_RULE.test(visible) || BORDER_SCROLL.test(visible);\n\t}\n\n\t/**\n\t * Tint a leading `/command` on the first input line.\n\t *\n\t * The base Editor emits input text unstyled, so the only styling already\n\t * present on the line is the cursor cell — which `colorVisibleRange` steps\n\t * over. Only the command token is tinted; arguments after it stay plain, so\n\t * the highlight marks what is being invoked rather than the whole line.\n\t */\n\tprivate withCommandColor(lines: string[]): string[] {\n\t\tconst commandColor = this.commandColor;\n\t\tif (!commandColor || lines.length < 2) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Read the token from the model rather than the rendered line: the first\n\t\t// visual row is the start of the text only when the caret has not scrolled\n\t\t// the view down, and getText is unambiguous either way.\n\t\tconst firstLine = this.getText().split(\"\\n\", 1)[0] ?? \"\";\n\t\tconst match = COMMAND_TOKEN.exec(firstLine);\n\t\tif (!match) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst [, indent, token] = match;\n\t\t// The rendered row must still begin with that token, or the view has\n\t\t// scrolled and the highlight would land on unrelated text.\n\t\tif (!stripAnsi(lines[1].split(CURSOR_MARKER).join(\"\")).startsWith(`${indent}${token}`)) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst start = visibleWidth(indent);\n\t\tconst updated = [...lines];\n\t\tupdated[1] = colorVisibleRange(lines[1], start, start + visibleWidth(token), commandColor);\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Replace the sole content line with the placeholder while the input is\n\t * empty. Index 1 is always the first content line, and an empty editor has\n\t * exactly one, so no line classification is needed here.\n\t */\n\tprivate withPlaceholder(lines: string[]): string[] {\n\t\tconst placeholder = this.placeholder;\n\t\tif (!placeholder || lines.length < 2 || this.getText().length > 0 || this.isShowingAutocomplete()) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst contentLine = lines[1];\n\t\t// Preserve the cursor cell when focused; unfocused there is none, and the\n\t\t// placeholder simply starts at column zero.\n\t\tconst cursorCell = this.sliceLeadingCursorCell(contentLine);\n\t\t// Measure with visibleWidth alone — never visibleWidth(stripAnsi(...)),\n\t\t// which scores the cursor marker as seven columns. See hazard 1 at the top\n\t\t// of this file.\n\t\tconst available = visibleWidth(contentLine) - visibleWidth(cursorCell);\n\t\tif (available <= 0) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Cut by display width, not by String.slice: `available` counts terminal\n\t\t// columns, while slice counts UTF-16 code units. They only agree for\n\t\t// plain ASCII, and disagreeing splits a surrogate pair or overruns the\n\t\t// line on the first wide character.\n\t\tconst text = truncateToWidth(placeholder, available, \"\");\n\t\tconst padding = \" \".repeat(Math.max(0, available - visibleWidth(text)));\n\t\tconst updated = [...lines];\n\t\tupdated[1] = cursorCell + this.placeholderColor(text) + padding;\n\t\treturn updated;\n\t}\n\n\t/**\n\t * The base Editor renders an empty line as the hardware-cursor marker\n\t * followed by a reverse-video cell: `CURSOR_MARKER ESC[7m <space> ESC[0m`.\n\t * Both parts are kept verbatim.\n\t *\n\t * The `ESC[7m … ESC[0m` run is matched explicitly. Do not \"simplify\" this to\n\t * a general leading-SGR match such as `(?:\\x1b\\[[0-9;]*m)*` — see hazard 2 at\n\t * the top of this file for what that breaks.\n\t */\n\tprivate sliceLeadingCursorCell(line: string): string {\n\t\tconst match = /^(?:\\x1b_pi:c\\x07)?(?:\\x1b\\[7m.\\x1b\\[(?:0|27)m)?/.exec(line);\n\t\treturn match?.[0] ?? \"\";\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppKeybinding, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for clipboard paste keybinding\n\t\tif (this.keybindings.matches(data, \"app.clipboard.pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"app.interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"app.interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"app.exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"app.exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Explicit history bindings take precedence over app actions while the editor is focused.\n\t\t// This lets users bind Ctrl+P even though it cycles models by default.\n\t\tif (\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyPrevious\") ||\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyNext\")\n\t\t) {\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"app.interrupt\" && action !== \"app.exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
1
+ {"version":3,"file":"custom-editor.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tCURSOR_MARKER,\n\tEditor,\n\ttype EditorOptions,\n\ttype EditorTheme,\n\ttype TUI,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../../core/keybindings.ts\";\nimport { stripAnsi } from \"../../../utils/ansi.ts\";\n\n/** Which marker the dock draws. The set is closed so a mode cannot arrive without a marker. */\nexport type PromptMode = \"agent\" | \"bash\";\n\nexport interface CustomEditorOptions extends EditorOptions {\n\t/** Marker rendered at the start of the first input line, e.g. `\"> \"`. */\n\tpromptPrefix?: string;\n\t/** Styles the prompt prefix. Defaults to leaving it unstyled. */\n\tpromptColor?: (text: string) => string;\n\t/** Marker for bash mode. Defaults to `promptPrefix`, so mode is carried by colour alone. */\n\tbashPromptPrefix?: string;\n\t/** Styles the bash marker. Defaults to `promptColor`. */\n\tbashPromptColor?: (text: string) => string;\n\t/** Shown in place of the empty input line. */\n\tplaceholder?: string;\n\t/** Styles the placeholder. Defaults to leaving it unstyled. */\n\tplaceholderColor?: (text: string) => string;\n\t/** Styles a leading `/command` token. Defaults to leaving it unstyled. */\n\tcommandColor?: (text: string) => string;\n\t/** Applies the filled prompt surface around the editor block. */\n\tsurfaceColor?: (text: string) => string;\n\t/** Rule drawn above the autocomplete rows. Omit for no rule. */\n\tautocompleteRule?: (width: number) => string;\n\t/** Keybinding footer drawn under the autocomplete rows. Omit for no footer. */\n\tautocompleteFooter?: () => string;\n}\n\n/** Box-drawing horizontal rule: the character the base Editor draws borders from. */\nconst BORDER_CHAR = \"─\";\n\n/**\n * The two shapes the base Editor draws a border in: a plain full-width rule, and\n * the scroll indicator (`─── ↑ 3 more ────`).\n *\n * Both are anchored deliberately. A prefix test would count any content line that\n * merely *starts* with the rule character — a pasted divider, `─notes` — as a\n * border, which is worse than it sounds: {@link CustomEditor.render} tells the\n * content region from the autocomplete region by counting borders, so one\n * miscount shifts every later line into the wrong region.\n */\nconst BORDER_RULE = /^─+$/;\nconst BORDER_SCROLL = /^─+ [↑↓] \\d+ more/;\n\n/**\n * A leading `/command` token: optional indent, a slash, then non-space.\n * Matched against the line's *visible* text, not the rendered string.\n */\nconst COMMAND_TOKEN = /^(\\s*)(\\/\\S+)/;\n\n/**\n * One ANSI control sequence: a CSI escape (`ESC [ … letter`) or the APC\n * hardware-cursor marker (`ESC _ … BEL`). Sticky, for position-by-position\n * scanning in {@link colorVisibleRange}.\n */\nconst CONTROL_SEQUENCE = /\\x1b\\[[0-9;]*[A-Za-z]|\\x1b_[^\\x07]*\\x07/y;\n\n/** The reverse-video cursor cell emitted by the inherited editor. */\nconst INVERTED_CURSOR_CELL = /\\x1b\\[7m[\\s\\S]\\x1b\\[(?:0|27)m/y;\n\nconst SURFACE_PADDING = 2;\n\n/**\n * ============================ ANSI measuring hazards ============================\n *\n * Two traps live in the rendered strings this file post-processes. Both produced\n * real bugs; both are cheap to re-introduce. Read this before touching the width\n * maths or any leading-sequence match.\n *\n * 1. DO NOT COMPOSE `stripAnsi` WITH `visibleWidth`.\n *\n * `stripAnsi` removes SGR colour codes but leaves the APC hardware-cursor\n * marker (`ESC _pi:c BEL`, see CURSOR_MARKER in packages/tui) fully intact —\n * all seven bytes of it. `visibleWidth` already handles that marker correctly\n * and scores it zero.\n *\n * So `visibleWidth(stripAnsi(line))` counts seven phantom columns on every\n * line carrying a cursor, which silently widened each placeholder line past\n * the terminal width. Measure with `visibleWidth` alone. Use `stripAnsi` only\n * to read text, never to measure it.\n *\n * 2. DO NOT MATCH THE LEADING SEQUENCE RUN GENERICALLY.\n *\n * The base Editor renders an empty line as:\n * CURSOR_MARKER ESC[7m <space> ESC[0m <padding>\n * The `ESC[7m` turns on reverse video for exactly the one-cell cursor block,\n * and `ESC[0m` turns it back off.\n *\n * A permissive prefix match such as `(?:\\x1b\\[[0-9;]*m)*` consumes that\n * `ESC[7m` as though it were ordinary leading styling. Whatever is appended\n * next then inherits reverse video, and because the matching `ESC[0m` was\n * left behind in the discarded remainder, the inversion runs on through the\n * placeholder and the rest of the frame. Match the reverse-video run\n * explicitly instead — see {@link CustomEditor.sliceLeadingCursorCell}.\n *\n * ==============================================================================\n */\n\n/**\n * Wrap the visible characters in `[start, end)` with `colorFn`, leaving every\n * control sequence in place.\n *\n * The range is in visible columns, so it is unaffected by however many escape\n * sequences are interleaved. That matters because the cursor cell can land in\n * the middle of the token being coloured: with the caret at column 3 of\n * `/model`, the line reads `/mo` + CURSOR_MARKER + `ESC[7m` + `d` + `ESC[0m` +\n * `el`. Colour is therefore re-opened after every control sequence rather than\n * once around the whole token, since the cursor cell's `ESC[0m` would otherwise\n * cancel it partway through.\n */\nfunction colorVisibleRange(line: string, start: number, end: number, colorFn: (text: string) => string): string {\n\tlet out = \"\";\n\tlet buffer = \"\";\n\tlet column = 0;\n\tlet index = 0;\n\n\tconst flush = () => {\n\t\tif (buffer) {\n\t\t\tout += colorFn(buffer);\n\t\t\tbuffer = \"\";\n\t\t}\n\t};\n\n\twhile (index < line.length) {\n\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\tif (control) {\n\t\t\tflush();\n\t\t\tout += control[0];\n\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\tcontinue;\n\t\t}\n\t\tconst char = line[index];\n\t\tif (column >= start && column < end) {\n\t\t\tbuffer += char;\n\t\t} else {\n\t\t\tflush();\n\t\t\tout += char;\n\t\t}\n\t\tcolumn += visibleWidth(char);\n\t\tindex += 1;\n\t}\n\tflush();\n\treturn out;\n}\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n *\n * The prompt prefix and placeholder are layered on in `render` rather than\n * inside the base Editor, because `packages/tui` is frozen under ADR 0001 and\n * its `EditorOptions` has no hook for either.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tprivate readonly prompts: Record<PromptMode, { prefix: string; color: (text: string) => string }>;\n\tprivate promptMode: PromptMode = \"agent\";\n\tprivate readonly placeholderColor: (text: string) => string;\n\tprivate readonly commandColor: ((text: string) => string) | undefined;\n\tprivate readonly surfaceColor: ((text: string) => string) | undefined;\n\tprivate readonly placeholder: string | undefined;\n\tprivate readonly autocompleteRule: ((width: number) => string) | undefined;\n\tprivate readonly autocompleteFooter: (() => string) | undefined;\n\tpublic actionHandlers: Map<AppKeybinding, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: CustomEditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t\tconst agentPrefix = options?.promptPrefix ?? \"\";\n\t\tconst agentColor = options?.promptColor ?? ((text) => text);\n\t\tconst bashPrefix = options?.bashPromptPrefix ?? agentPrefix;\n\t\t// Every marker occupies the same cells, so switching mode cannot move the\n\t\t// caret or reflow what is already typed. Padding rather than throwing keeps\n\t\t// a mismatched caller rendering instead of crashing its dock.\n\t\tconst cells = Math.max(visibleWidth(agentPrefix), visibleWidth(bashPrefix));\n\t\tconst pad = (text: string) => text + \" \".repeat(cells - visibleWidth(text));\n\t\tthis.prompts = {\n\t\t\tagent: { prefix: pad(agentPrefix), color: agentColor },\n\t\t\tbash: { prefix: pad(bashPrefix), color: options?.bashPromptColor ?? agentColor },\n\t\t};\n\t\tthis.placeholder = options?.placeholder;\n\t\tthis.placeholderColor = options?.placeholderColor ?? ((text) => text);\n\t\tthis.commandColor = options?.commandColor;\n\t\tthis.surfaceColor = options?.surfaceColor;\n\t\tthis.autocompleteRule = options?.autocompleteRule;\n\t\tthis.autocompleteFooter = options?.autocompleteFooter;\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst canRenderSurface = this.surfaceColor !== undefined && width > 0;\n\t\tconst surfacePadding = canRenderSurface ? Math.min(SURFACE_PADDING, Math.floor((width - 1) / 2)) : 0;\n\t\tconst editorWidth = canRenderSurface ? width - surfacePadding * 2 : width;\n\t\tconst lines = this.withAutocompleteChrome(this.renderEditor(editorWidth), editorWidth);\n\t\treturn canRenderSurface ? this.withSurface(lines, width, surfacePadding) : lines;\n\t}\n\n\tprivate renderEditor(width: number): string[] {\n\t\tconst { prefix: promptPrefix, color: promptColor } = this.prompts[this.promptMode];\n\t\tconst prefixWidth = visibleWidth(promptPrefix);\n\t\t// Reserve the prefix out of the width handed to the base Editor, so its\n\t\t// wrapping, scrolling and cursor column all account for the space the\n\t\t// prefix occupies. Bail out entirely when the terminal cannot spare it.\n\t\tif (prefixWidth === 0 || width <= prefixWidth + 4) {\n\t\t\treturn this.withCommandColor(this.withPlaceholder(super.render(width)));\n\t\t}\n\n\t\tconst inner = this.withCommandColor(this.withPlaceholder(super.render(width - prefixWidth)));\n\t\tconst borderPad = this.borderColor(BORDER_CHAR.repeat(prefixWidth));\n\t\tconst blank = \" \".repeat(prefixWidth);\n\t\tconst styledPrefix = promptColor(promptPrefix);\n\n\t\t// Structure is: top border, content lines, bottom border, then optional\n\t\t// autocomplete rows. Counting borders tells the three regions apart\n\t\t// without depending on how many lines each contains.\n\t\tlet borders = 0;\n\t\tlet firstContentLine = true;\n\t\treturn inner.map((line) => {\n\t\t\tif (this.isBorderLine(line)) {\n\t\t\t\tborders++;\n\t\t\t\treturn borderPad + line;\n\t\t\t}\n\t\t\tif (borders !== 1) {\n\t\t\t\t// Autocomplete rows sit below the box; indent them to match.\n\t\t\t\treturn blank + line;\n\t\t\t}\n\t\t\tif (firstContentLine) {\n\t\t\t\tfirstContentLine = false;\n\t\t\t\treturn styledPrefix + line;\n\t\t\t}\n\t\t\treturn blank + line;\n\t\t});\n\t}\n\n\t/**\n\t * Brackets the autocomplete rows with a rule and a keybinding footer.\n\t *\n\t * The rows arrive after the editor's second border, so the same border count\n\t * that separates the box from its dropdown elsewhere in this file finds them\n\t * here. Nothing is added when the dropdown is closed.\n\t */\n\tprivate withAutocompleteChrome(lines: string[], width: number): string[] {\n\t\tif (!this.autocompleteRule && !this.autocompleteFooter) return lines;\n\t\tconst blockLineCount = this.editorBlockLineCount(lines);\n\t\tconst rows = lines.slice(blockLineCount);\n\t\tif (rows.length === 0) return lines;\n\n\t\tconst chrome = [...lines.slice(0, blockLineCount)];\n\t\tif (this.autocompleteRule) chrome.push(this.autocompleteRule(width));\n\t\tchrome.push(...rows);\n\t\tif (this.autocompleteFooter) chrome.push(this.autocompleteFooter());\n\t\treturn chrome;\n\t}\n\n\tprivate withSurface(lines: string[], width: number, padding: number): string[] {\n\t\tconst surfaceColor = this.surfaceColor;\n\t\tif (!surfaceColor) return lines;\n\n\t\tconst result = [this.surfaceLine(\" \".repeat(width), surfaceColor)];\n\t\tconst editorBlockLineCount = this.editorBlockLineCount(lines);\n\t\tconst editorContent = lines.slice(1, Math.max(1, editorBlockLineCount - 1));\n\t\tfor (const line of editorContent) {\n\t\t\tresult.push(this.surfaceLine(`${\" \".repeat(padding)}${line}${\" \".repeat(padding)}`, surfaceColor));\n\t\t}\n\t\tresult.push(this.surfaceLine(\" \".repeat(width), surfaceColor));\n\t\tfor (const line of lines.slice(editorBlockLineCount)) {\n\t\t\tresult.push(`${\" \".repeat(padding)}${line}`);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate editorBlockLineCount(lines: string[]): number {\n\t\tlet borders = 0;\n\t\tfor (let index = 0; index < lines.length; index++) {\n\t\t\tif (this.isBorderLine(lines[index])) borders++;\n\t\t\tif (borders === 2) return index + 1;\n\t\t}\n\t\treturn lines.length;\n\t}\n\n\tprivate surfaceLine(line: string, surfaceColor: (text: string) => string): string {\n\t\tlet output = \"\";\n\t\tlet visibleText = \"\";\n\t\tlet index = 0;\n\n\t\tconst flush = () => {\n\t\t\tif (visibleText) {\n\t\t\t\toutput += surfaceColor(visibleText);\n\t\t\t\tvisibleText = \"\";\n\t\t\t}\n\t\t};\n\n\t\twhile (index < line.length) {\n\t\t\tINVERTED_CURSOR_CELL.lastIndex = index;\n\t\t\tconst cursorCell = INVERTED_CURSOR_CELL.exec(line);\n\t\t\tif (cursorCell) {\n\t\t\t\tflush();\n\t\t\t\toutput += cursorCell[0];\n\t\t\t\tindex = INVERTED_CURSOR_CELL.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\t\tif (control) {\n\t\t\t\tflush();\n\t\t\t\toutput += control[0];\n\t\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvisibleText += line[index];\n\t\t\tindex += 1;\n\t\t}\n\n\t\tflush();\n\t\treturn output;\n\t}\n\n\tsetPromptMode(mode: PromptMode): void {\n\t\tif (this.promptMode === mode) return;\n\t\tthis.promptMode = mode;\n\t\tthis.invalidate();\n\t}\n\n\tprivate isBorderLine(line: string): boolean {\n\t\tconst visible = stripAnsi(line.split(CURSOR_MARKER).join(\"\"));\n\t\treturn BORDER_RULE.test(visible) || BORDER_SCROLL.test(visible);\n\t}\n\n\t/**\n\t * Tint a leading `/command` on the first input line.\n\t *\n\t * The base Editor emits input text unstyled, so the only styling already\n\t * present on the line is the cursor cell — which `colorVisibleRange` steps\n\t * over. Only the command token is tinted; arguments after it stay plain, so\n\t * the highlight marks what is being invoked rather than the whole line.\n\t */\n\tprivate withCommandColor(lines: string[]): string[] {\n\t\tconst commandColor = this.commandColor;\n\t\tif (!commandColor || lines.length < 2) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Read the token from the model rather than the rendered line: the first\n\t\t// visual row is the start of the text only when the caret has not scrolled\n\t\t// the view down, and getText is unambiguous either way.\n\t\tconst firstLine = this.getText().split(\"\\n\", 1)[0] ?? \"\";\n\t\tconst match = COMMAND_TOKEN.exec(firstLine);\n\t\tif (!match) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst [, indent, token] = match;\n\t\t// The rendered row must still begin with that token, or the view has\n\t\t// scrolled and the highlight would land on unrelated text.\n\t\tif (!stripAnsi(lines[1].split(CURSOR_MARKER).join(\"\")).startsWith(`${indent}${token}`)) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst start = visibleWidth(indent);\n\t\tconst updated = [...lines];\n\t\tupdated[1] = colorVisibleRange(lines[1], start, start + visibleWidth(token), commandColor);\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Replace the sole content line with the placeholder while the input is\n\t * empty. Index 1 is always the first content line, and an empty editor has\n\t * exactly one, so no line classification is needed here.\n\t */\n\tprivate withPlaceholder(lines: string[]): string[] {\n\t\tconst placeholder = this.placeholder;\n\t\tif (!placeholder || lines.length < 2 || this.getText().length > 0 || this.isShowingAutocomplete()) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst contentLine = lines[1];\n\t\t// Preserve the cursor cell when focused; unfocused there is none, and the\n\t\t// placeholder simply starts at column zero.\n\t\tconst cursorCell = this.sliceLeadingCursorCell(contentLine);\n\t\t// Measure with visibleWidth alone — never visibleWidth(stripAnsi(...)),\n\t\t// which scores the cursor marker as seven columns. See hazard 1 at the top\n\t\t// of this file.\n\t\tconst available = visibleWidth(contentLine) - visibleWidth(cursorCell);\n\t\tif (available <= 0) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Cut by display width, not by String.slice: `available` counts terminal\n\t\t// columns, while slice counts UTF-16 code units. They only agree for\n\t\t// plain ASCII, and disagreeing splits a surrogate pair or overruns the\n\t\t// line on the first wide character.\n\t\tconst text = truncateToWidth(placeholder, available, \"\");\n\t\tconst padding = \" \".repeat(Math.max(0, available - visibleWidth(text)));\n\t\tconst updated = [...lines];\n\t\tupdated[1] = cursorCell + this.placeholderColor(text) + padding;\n\t\treturn updated;\n\t}\n\n\t/**\n\t * The base Editor renders an empty line as the hardware-cursor marker\n\t * followed by a reverse-video cell: `CURSOR_MARKER ESC[7m <space> ESC[0m`.\n\t * Both parts are kept verbatim.\n\t *\n\t * The `ESC[7m … ESC[0m` run is matched explicitly. Do not \"simplify\" this to\n\t * a general leading-SGR match such as `(?:\\x1b\\[[0-9;]*m)*` — see hazard 2 at\n\t * the top of this file for what that breaks.\n\t */\n\tprivate sliceLeadingCursorCell(line: string): string {\n\t\tconst match = /^(?:\\x1b_pi:c\\x07)?(?:\\x1b\\[7m.\\x1b\\[(?:0|27)m)?/.exec(line);\n\t\treturn match?.[0] ?? \"\";\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppKeybinding, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for clipboard paste keybinding\n\t\tif (this.keybindings.matches(data, \"app.clipboard.pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"app.interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"app.interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"app.exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"app.exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Explicit history bindings take precedence over app actions while the editor is focused.\n\t\t// This lets users bind Ctrl+P even though it cycles models by default.\n\t\tif (\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyPrevious\") ||\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyNext\")\n\t\t) {\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"app.interrupt\" && action !== \"app.exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
@@ -2,12 +2,18 @@
2
2
  * Generic selector component for extensions.
3
3
  * Displays a list of string options with keyboard navigation.
4
4
  */
5
- import { Container, type TUI } from "@earendil-works/pi-tui";
5
+ import { type Component, Container, type TUI } from "@earendil-works/pi-tui";
6
6
  export interface ExtensionSelectorOptions {
7
7
  tui?: TUI;
8
8
  timeout?: number;
9
9
  onToggleToolsExpanded?: () => void;
10
10
  enableSearch?: boolean;
11
+ /**
12
+ * Drawn under the title and above the rows, for a prompt whose choices only
13
+ * make sense beside the thing they act on. The caller owns its colouring, so
14
+ * the selector stays a list and gains no rendering of its own.
15
+ */
16
+ preamble?: Component;
11
17
  }
12
18
  export declare class ExtensionSelectorComponent extends Container {
13
19
  private options;
@@ -1 +1 @@
1
- {"version":3,"file":"extension-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/extension-selector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAoD,KAAK,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAM/G,MAAM,WAAW,wBAAwB;IACxC,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,qBAAa,0BAA2B,SAAQ,SAAS;IACxD,OAAO,CAAC,OAAO,CAAW;IAC1B,OAAO,CAAC,eAAe,CAAW;IAClC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,SAAS,CAAO;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAA6B;IAC9C,OAAO,CAAC,qBAAqB,CAA2B;IACxD,OAAO,CAAC,WAAW,CAAoB;IAEvC,YACC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EAAE,EACjB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,EAClC,QAAQ,EAAE,MAAM,IAAI,EACpB,IAAI,CAAC,EAAE,wBAAwB,EAkD/B;IAED,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,aAAa;IAOrB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAmBjC;IAED,OAAO,IAAI,IAAI,CAEd;CACD","sourcesContent":["/**\n * Generic selector component for extensions.\n * Displays a list of string options with keyboard navigation.\n */\n\nimport { Container, fuzzyFilter, getKeybindings, Input, Spacer, Text, type TUI } from \"@earendil-works/pi-tui\";\nimport { theme } from \"../theme/theme.ts\";\nimport { CountdownTimer } from \"./countdown-timer.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint, rawKeyHint } from \"./keybinding-hints.ts\";\n\nexport interface ExtensionSelectorOptions {\n\ttui?: TUI;\n\ttimeout?: number;\n\tonToggleToolsExpanded?: () => void;\n\tenableSearch?: boolean;\n}\n\nexport class ExtensionSelectorComponent extends Container {\n\tprivate options: string[];\n\tprivate filteredOptions: string[];\n\tprivate selectedIndex = 0;\n\tprivate listContainer: Container;\n\tprivate onSelectCallback: (option: string) => void;\n\tprivate onCancelCallback: () => void;\n\tprivate titleText: Text;\n\tprivate baseTitle: string;\n\tprivate countdown: CountdownTimer | undefined;\n\tprivate onToggleToolsExpanded: (() => void) | undefined;\n\tprivate searchInput: Input | undefined;\n\n\tconstructor(\n\t\ttitle: string,\n\t\toptions: string[],\n\t\tonSelect: (option: string) => void,\n\t\tonCancel: () => void,\n\t\topts?: ExtensionSelectorOptions,\n\t) {\n\t\tsuper();\n\n\t\tthis.options = options;\n\t\tthis.filteredOptions = options;\n\t\tthis.onSelectCallback = onSelect;\n\t\tthis.onCancelCallback = onCancel;\n\t\tthis.onToggleToolsExpanded = opts?.onToggleToolsExpanded;\n\t\tthis.baseTitle = title;\n\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.titleText = new Text(theme.fg(\"accent\", theme.bold(title)), 1, 0);\n\t\tthis.addChild(this.titleText);\n\t\tthis.addChild(new Spacer(1));\n\t\tif (opts?.enableSearch) {\n\t\t\tthis.searchInput = new Input();\n\t\t\tthis.addChild(this.searchInput);\n\t\t\tthis.addChild(new Spacer(1));\n\t\t}\n\n\t\tif (opts?.timeout && opts.timeout > 0 && opts.tui) {\n\t\t\tthis.countdown = new CountdownTimer(\n\t\t\t\topts.timeout,\n\t\t\t\topts.tui,\n\t\t\t\t(s) => this.titleText.setText(theme.fg(\"accent\", theme.bold(`${this.baseTitle} (${s}s)`))),\n\t\t\t\t() => this.onCancelCallback(),\n\t\t\t);\n\t\t}\n\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(\n\t\t\tnew Text(\n\t\t\t\trawKeyHint(\"↑↓\", \"navigate\") +\n\t\t\t\t\t\" \" +\n\t\t\t\t\tkeyHint(\"tui.select.confirm\", \"select\") +\n\t\t\t\t\t\" \" +\n\t\t\t\t\tkeyHint(\"tui.select.cancel\", \"cancel\"),\n\t\t\t\t1,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.updateList();\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\t\tfor (let i = 0; i < this.filteredOptions.length; i++) {\n\t\t\tconst isSelected = i === this.selectedIndex;\n\t\t\tconst text = isSelected\n\t\t\t\t? theme.fg(\"accent\", \"→ \") + theme.fg(\"accent\", this.filteredOptions[i])\n\t\t\t\t: ` ${theme.fg(\"text\", this.filteredOptions[i])}`;\n\t\t\tthis.listContainer.addChild(new Text(text, 1, 0));\n\t\t}\n\t}\n\n\tprivate filterOptions(): void {\n\t\tconst query = this.searchInput?.getValue() ?? \"\";\n\t\tthis.filteredOptions = query ? fuzzyFilter(this.options, query, (option) => option) : this.options;\n\t\tthis.selectedIndex = 0;\n\t\tthis.updateList();\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\tif (kb.matches(keyData, \"app.tools.expand\")) {\n\t\t\tthis.onToggleToolsExpanded?.();\n\t\t} else if (kb.matches(keyData, \"tui.select.up\") || keyData === \"k\") {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t\tthis.updateList();\n\t\t} else if (kb.matches(keyData, \"tui.select.down\") || keyData === \"j\") {\n\t\t\tthis.selectedIndex = Math.min(this.filteredOptions.length - 1, this.selectedIndex + 1);\n\t\t\tthis.updateList();\n\t\t} else if (kb.matches(keyData, \"tui.select.confirm\") || keyData === \"\\n\") {\n\t\t\tconst selected = this.filteredOptions[this.selectedIndex];\n\t\t\tif (selected) this.onSelectCallback(selected);\n\t\t} else if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t} else if (this.searchInput) {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterOptions();\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis.countdown?.dispose();\n\t}\n}\n"]}
1
+ {"version":3,"file":"extension-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/extension-selector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,KAAK,SAAS,EACd,SAAS,EAMT,KAAK,GAAG,EACR,MAAM,wBAAwB,CAAC;AAMhC,MAAM,WAAW,wBAAwB;IACxC,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACrB;AAED,qBAAa,0BAA2B,SAAQ,SAAS;IACxD,OAAO,CAAC,OAAO,CAAW;IAC1B,OAAO,CAAC,eAAe,CAAW;IAClC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,SAAS,CAAO;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAA6B;IAC9C,OAAO,CAAC,qBAAqB,CAA2B;IACxD,OAAO,CAAC,WAAW,CAAoB;IAEvC,YACC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EAAE,EACjB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,EAClC,QAAQ,EAAE,MAAM,IAAI,EACpB,IAAI,CAAC,EAAE,wBAAwB,EAsD/B;IAED,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,aAAa;IAOrB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAmBjC;IAED,OAAO,IAAI,IAAI,CAEd;CACD","sourcesContent":["/**\n * Generic selector component for extensions.\n * Displays a list of string options with keyboard navigation.\n */\n\nimport {\n\ttype Component,\n\tContainer,\n\tfuzzyFilter,\n\tgetKeybindings,\n\tInput,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@earendil-works/pi-tui\";\nimport { paintSelectedRow, theme } from \"../theme/theme.ts\";\nimport { CountdownTimer } from \"./countdown-timer.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint, rawKeyHint } from \"./keybinding-hints.ts\";\n\nexport interface ExtensionSelectorOptions {\n\ttui?: TUI;\n\ttimeout?: number;\n\tonToggleToolsExpanded?: () => void;\n\tenableSearch?: boolean;\n\t/**\n\t * Drawn under the title and above the rows, for a prompt whose choices only\n\t * make sense beside the thing they act on. The caller owns its colouring, so\n\t * the selector stays a list and gains no rendering of its own.\n\t */\n\tpreamble?: Component;\n}\n\nexport class ExtensionSelectorComponent extends Container {\n\tprivate options: string[];\n\tprivate filteredOptions: string[];\n\tprivate selectedIndex = 0;\n\tprivate listContainer: Container;\n\tprivate onSelectCallback: (option: string) => void;\n\tprivate onCancelCallback: () => void;\n\tprivate titleText: Text;\n\tprivate baseTitle: string;\n\tprivate countdown: CountdownTimer | undefined;\n\tprivate onToggleToolsExpanded: (() => void) | undefined;\n\tprivate searchInput: Input | undefined;\n\n\tconstructor(\n\t\ttitle: string,\n\t\toptions: string[],\n\t\tonSelect: (option: string) => void,\n\t\tonCancel: () => void,\n\t\topts?: ExtensionSelectorOptions,\n\t) {\n\t\tsuper();\n\n\t\tthis.options = options;\n\t\tthis.filteredOptions = options;\n\t\tthis.onSelectCallback = onSelect;\n\t\tthis.onCancelCallback = onCancel;\n\t\tthis.onToggleToolsExpanded = opts?.onToggleToolsExpanded;\n\t\tthis.baseTitle = title;\n\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.titleText = new Text(theme.fg(\"accent\", theme.bold(title)), 1, 0);\n\t\tthis.addChild(this.titleText);\n\t\tthis.addChild(new Spacer(1));\n\t\tif (opts?.preamble) {\n\t\t\tthis.addChild(opts.preamble);\n\t\t\tthis.addChild(new Spacer(1));\n\t\t}\n\t\tif (opts?.enableSearch) {\n\t\t\tthis.searchInput = new Input();\n\t\t\tthis.addChild(this.searchInput);\n\t\t\tthis.addChild(new Spacer(1));\n\t\t}\n\n\t\tif (opts?.timeout && opts.timeout > 0 && opts.tui) {\n\t\t\tthis.countdown = new CountdownTimer(\n\t\t\t\topts.timeout,\n\t\t\t\topts.tui,\n\t\t\t\t(s) => this.titleText.setText(theme.fg(\"accent\", theme.bold(`${this.baseTitle} (${s}s)`))),\n\t\t\t\t() => this.onCancelCallback(),\n\t\t\t);\n\t\t}\n\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(\n\t\t\tnew Text(\n\t\t\t\trawKeyHint(\"↑↓\", \"navigate\") +\n\t\t\t\t\t\" \" +\n\t\t\t\t\tkeyHint(\"tui.select.confirm\", \"select\") +\n\t\t\t\t\t\" \" +\n\t\t\t\t\tkeyHint(\"tui.select.cancel\", \"cancel\"),\n\t\t\t\t1,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.updateList();\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\t\tfor (let i = 0; i < this.filteredOptions.length; i++) {\n\t\t\tconst isSelected = i === this.selectedIndex;\n\t\t\tconst text = isSelected\n\t\t\t\t? paintSelectedRow(`→ ${this.filteredOptions[i]}`)\n\t\t\t\t: ` ${theme.fg(\"text\", this.filteredOptions[i])}`;\n\t\t\tthis.listContainer.addChild(new Text(text, 1, 0));\n\t\t}\n\t}\n\n\tprivate filterOptions(): void {\n\t\tconst query = this.searchInput?.getValue() ?? \"\";\n\t\tthis.filteredOptions = query ? fuzzyFilter(this.options, query, (option) => option) : this.options;\n\t\tthis.selectedIndex = 0;\n\t\tthis.updateList();\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\tif (kb.matches(keyData, \"app.tools.expand\")) {\n\t\t\tthis.onToggleToolsExpanded?.();\n\t\t} else if (kb.matches(keyData, \"tui.select.up\") || keyData === \"k\") {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t\tthis.updateList();\n\t\t} else if (kb.matches(keyData, \"tui.select.down\") || keyData === \"j\") {\n\t\t\tthis.selectedIndex = Math.min(this.filteredOptions.length - 1, this.selectedIndex + 1);\n\t\t\tthis.updateList();\n\t\t} else if (kb.matches(keyData, \"tui.select.confirm\") || keyData === \"\\n\") {\n\t\t\tconst selected = this.filteredOptions[this.selectedIndex];\n\t\t\tif (selected) this.onSelectCallback(selected);\n\t\t} else if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t} else if (this.searchInput) {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterOptions();\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis.countdown?.dispose();\n\t}\n}\n"]}
@@ -2,8 +2,8 @@
2
2
  * Generic selector component for extensions.
3
3
  * Displays a list of string options with keyboard navigation.
4
4
  */
5
- import { Container, fuzzyFilter, getKeybindings, Input, Spacer, Text } from "@earendil-works/pi-tui";
6
- import { theme } from "../theme/theme.js";
5
+ import { Container, fuzzyFilter, getKeybindings, Input, Spacer, Text, } from "@earendil-works/pi-tui";
6
+ import { paintSelectedRow, theme } from "../theme/theme.js";
7
7
  import { CountdownTimer } from "./countdown-timer.js";
8
8
  import { DynamicBorder } from "./dynamic-border.js";
9
9
  import { keyHint, rawKeyHint } from "./keybinding-hints.js";
@@ -32,6 +32,10 @@ export class ExtensionSelectorComponent extends Container {
32
32
  this.titleText = new Text(theme.fg("accent", theme.bold(title)), 1, 0);
33
33
  this.addChild(this.titleText);
34
34
  this.addChild(new Spacer(1));
35
+ if (opts?.preamble) {
36
+ this.addChild(opts.preamble);
37
+ this.addChild(new Spacer(1));
38
+ }
35
39
  if (opts?.enableSearch) {
36
40
  this.searchInput = new Input();
37
41
  this.addChild(this.searchInput);
@@ -57,7 +61,7 @@ export class ExtensionSelectorComponent extends Container {
57
61
  for (let i = 0; i < this.filteredOptions.length; i++) {
58
62
  const isSelected = i === this.selectedIndex;
59
63
  const text = isSelected
60
- ? theme.fg("accent", "→ ") + theme.fg("accent", this.filteredOptions[i])
64
+ ? paintSelectedRow(`→ ${this.filteredOptions[i]}`)
61
65
  : ` ${theme.fg("text", this.filteredOptions[i])}`;
62
66
  this.listContainer.addChild(new Text(text, 1, 0));
63
67
  }
@@ -1 +1 @@
1
- {"version":3,"file":"extension-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/extension-selector.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Generic selector component for extensions.\n * Displays a list of string options with keyboard navigation.\n */\n\nimport { Container, fuzzyFilter, getKeybindings, Input, Spacer, Text, type TUI } from \"@earendil-works/pi-tui\";\nimport { theme } from \"../theme/theme.ts\";\nimport { CountdownTimer } from \"./countdown-timer.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint, rawKeyHint } from \"./keybinding-hints.ts\";\n\nexport interface ExtensionSelectorOptions {\n\ttui?: TUI;\n\ttimeout?: number;\n\tonToggleToolsExpanded?: () => void;\n\tenableSearch?: boolean;\n}\n\nexport class ExtensionSelectorComponent extends Container {\n\tprivate options: string[];\n\tprivate filteredOptions: string[];\n\tprivate selectedIndex = 0;\n\tprivate listContainer: Container;\n\tprivate onSelectCallback: (option: string) => void;\n\tprivate onCancelCallback: () => void;\n\tprivate titleText: Text;\n\tprivate baseTitle: string;\n\tprivate countdown: CountdownTimer | undefined;\n\tprivate onToggleToolsExpanded: (() => void) | undefined;\n\tprivate searchInput: Input | undefined;\n\n\tconstructor(\n\t\ttitle: string,\n\t\toptions: string[],\n\t\tonSelect: (option: string) => void,\n\t\tonCancel: () => void,\n\t\topts?: ExtensionSelectorOptions,\n\t) {\n\t\tsuper();\n\n\t\tthis.options = options;\n\t\tthis.filteredOptions = options;\n\t\tthis.onSelectCallback = onSelect;\n\t\tthis.onCancelCallback = onCancel;\n\t\tthis.onToggleToolsExpanded = opts?.onToggleToolsExpanded;\n\t\tthis.baseTitle = title;\n\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.titleText = new Text(theme.fg(\"accent\", theme.bold(title)), 1, 0);\n\t\tthis.addChild(this.titleText);\n\t\tthis.addChild(new Spacer(1));\n\t\tif (opts?.enableSearch) {\n\t\t\tthis.searchInput = new Input();\n\t\t\tthis.addChild(this.searchInput);\n\t\t\tthis.addChild(new Spacer(1));\n\t\t}\n\n\t\tif (opts?.timeout && opts.timeout > 0 && opts.tui) {\n\t\t\tthis.countdown = new CountdownTimer(\n\t\t\t\topts.timeout,\n\t\t\t\topts.tui,\n\t\t\t\t(s) => this.titleText.setText(theme.fg(\"accent\", theme.bold(`${this.baseTitle} (${s}s)`))),\n\t\t\t\t() => this.onCancelCallback(),\n\t\t\t);\n\t\t}\n\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(\n\t\t\tnew Text(\n\t\t\t\trawKeyHint(\"↑↓\", \"navigate\") +\n\t\t\t\t\t\" \" +\n\t\t\t\t\tkeyHint(\"tui.select.confirm\", \"select\") +\n\t\t\t\t\t\" \" +\n\t\t\t\t\tkeyHint(\"tui.select.cancel\", \"cancel\"),\n\t\t\t\t1,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.updateList();\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\t\tfor (let i = 0; i < this.filteredOptions.length; i++) {\n\t\t\tconst isSelected = i === this.selectedIndex;\n\t\t\tconst text = isSelected\n\t\t\t\t? theme.fg(\"accent\", \"→ \") + theme.fg(\"accent\", this.filteredOptions[i])\n\t\t\t\t: ` ${theme.fg(\"text\", this.filteredOptions[i])}`;\n\t\t\tthis.listContainer.addChild(new Text(text, 1, 0));\n\t\t}\n\t}\n\n\tprivate filterOptions(): void {\n\t\tconst query = this.searchInput?.getValue() ?? \"\";\n\t\tthis.filteredOptions = query ? fuzzyFilter(this.options, query, (option) => option) : this.options;\n\t\tthis.selectedIndex = 0;\n\t\tthis.updateList();\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\tif (kb.matches(keyData, \"app.tools.expand\")) {\n\t\t\tthis.onToggleToolsExpanded?.();\n\t\t} else if (kb.matches(keyData, \"tui.select.up\") || keyData === \"k\") {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t\tthis.updateList();\n\t\t} else if (kb.matches(keyData, \"tui.select.down\") || keyData === \"j\") {\n\t\t\tthis.selectedIndex = Math.min(this.filteredOptions.length - 1, this.selectedIndex + 1);\n\t\t\tthis.updateList();\n\t\t} else if (kb.matches(keyData, \"tui.select.confirm\") || keyData === \"\\n\") {\n\t\t\tconst selected = this.filteredOptions[this.selectedIndex];\n\t\t\tif (selected) this.onSelectCallback(selected);\n\t\t} else if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t} else if (this.searchInput) {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterOptions();\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis.countdown?.dispose();\n\t}\n}\n"]}
1
+ {"version":3,"file":"extension-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/extension-selector.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Generic selector component for extensions.\n * Displays a list of string options with keyboard navigation.\n */\n\nimport {\n\ttype Component,\n\tContainer,\n\tfuzzyFilter,\n\tgetKeybindings,\n\tInput,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@earendil-works/pi-tui\";\nimport { paintSelectedRow, theme } from \"../theme/theme.ts\";\nimport { CountdownTimer } from \"./countdown-timer.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint, rawKeyHint } from \"./keybinding-hints.ts\";\n\nexport interface ExtensionSelectorOptions {\n\ttui?: TUI;\n\ttimeout?: number;\n\tonToggleToolsExpanded?: () => void;\n\tenableSearch?: boolean;\n\t/**\n\t * Drawn under the title and above the rows, for a prompt whose choices only\n\t * make sense beside the thing they act on. The caller owns its colouring, so\n\t * the selector stays a list and gains no rendering of its own.\n\t */\n\tpreamble?: Component;\n}\n\nexport class ExtensionSelectorComponent extends Container {\n\tprivate options: string[];\n\tprivate filteredOptions: string[];\n\tprivate selectedIndex = 0;\n\tprivate listContainer: Container;\n\tprivate onSelectCallback: (option: string) => void;\n\tprivate onCancelCallback: () => void;\n\tprivate titleText: Text;\n\tprivate baseTitle: string;\n\tprivate countdown: CountdownTimer | undefined;\n\tprivate onToggleToolsExpanded: (() => void) | undefined;\n\tprivate searchInput: Input | undefined;\n\n\tconstructor(\n\t\ttitle: string,\n\t\toptions: string[],\n\t\tonSelect: (option: string) => void,\n\t\tonCancel: () => void,\n\t\topts?: ExtensionSelectorOptions,\n\t) {\n\t\tsuper();\n\n\t\tthis.options = options;\n\t\tthis.filteredOptions = options;\n\t\tthis.onSelectCallback = onSelect;\n\t\tthis.onCancelCallback = onCancel;\n\t\tthis.onToggleToolsExpanded = opts?.onToggleToolsExpanded;\n\t\tthis.baseTitle = title;\n\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.titleText = new Text(theme.fg(\"accent\", theme.bold(title)), 1, 0);\n\t\tthis.addChild(this.titleText);\n\t\tthis.addChild(new Spacer(1));\n\t\tif (opts?.preamble) {\n\t\t\tthis.addChild(opts.preamble);\n\t\t\tthis.addChild(new Spacer(1));\n\t\t}\n\t\tif (opts?.enableSearch) {\n\t\t\tthis.searchInput = new Input();\n\t\t\tthis.addChild(this.searchInput);\n\t\t\tthis.addChild(new Spacer(1));\n\t\t}\n\n\t\tif (opts?.timeout && opts.timeout > 0 && opts.tui) {\n\t\t\tthis.countdown = new CountdownTimer(\n\t\t\t\topts.timeout,\n\t\t\t\topts.tui,\n\t\t\t\t(s) => this.titleText.setText(theme.fg(\"accent\", theme.bold(`${this.baseTitle} (${s}s)`))),\n\t\t\t\t() => this.onCancelCallback(),\n\t\t\t);\n\t\t}\n\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(\n\t\t\tnew Text(\n\t\t\t\trawKeyHint(\"↑↓\", \"navigate\") +\n\t\t\t\t\t\" \" +\n\t\t\t\t\tkeyHint(\"tui.select.confirm\", \"select\") +\n\t\t\t\t\t\" \" +\n\t\t\t\t\tkeyHint(\"tui.select.cancel\", \"cancel\"),\n\t\t\t\t1,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.updateList();\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\t\tfor (let i = 0; i < this.filteredOptions.length; i++) {\n\t\t\tconst isSelected = i === this.selectedIndex;\n\t\t\tconst text = isSelected\n\t\t\t\t? paintSelectedRow(`→ ${this.filteredOptions[i]}`)\n\t\t\t\t: ` ${theme.fg(\"text\", this.filteredOptions[i])}`;\n\t\t\tthis.listContainer.addChild(new Text(text, 1, 0));\n\t\t}\n\t}\n\n\tprivate filterOptions(): void {\n\t\tconst query = this.searchInput?.getValue() ?? \"\";\n\t\tthis.filteredOptions = query ? fuzzyFilter(this.options, query, (option) => option) : this.options;\n\t\tthis.selectedIndex = 0;\n\t\tthis.updateList();\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\tif (kb.matches(keyData, \"app.tools.expand\")) {\n\t\t\tthis.onToggleToolsExpanded?.();\n\t\t} else if (kb.matches(keyData, \"tui.select.up\") || keyData === \"k\") {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t\tthis.updateList();\n\t\t} else if (kb.matches(keyData, \"tui.select.down\") || keyData === \"j\") {\n\t\t\tthis.selectedIndex = Math.min(this.filteredOptions.length - 1, this.selectedIndex + 1);\n\t\t\tthis.updateList();\n\t\t} else if (kb.matches(keyData, \"tui.select.confirm\") || keyData === \"\\n\") {\n\t\t\tconst selected = this.filteredOptions[this.selectedIndex];\n\t\t\tif (selected) this.onSelectCallback(selected);\n\t\t} else if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t} else if (this.searchInput) {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterOptions();\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis.countdown?.dispose();\n\t}\n}\n"]}
@@ -35,10 +35,21 @@ export declare class FooterComponent implements Component {
35
35
  private session;
36
36
  private footerData;
37
37
  private accessibilitySettings;
38
+ private activity;
38
39
  constructor(session: AgentSession, footerData: ReadonlyFooterDataProvider, accessibilitySettings?: FooterAccessibilitySettings);
39
40
  setSession(session: AgentSession): void;
40
41
  setAutoCompactEnabled(enabled: boolean): void;
41
42
  setPermissionMode(mode: PermissionMode): void;
43
+ /**
44
+ * The working indicator, rendered inline in the tray instead of in its own
45
+ * rows above the editor.
46
+ *
47
+ * Taking the component rather than a string keeps the spinner animation, the
48
+ * extension-supplied `WorkingIndicatorOptions`, and disposal where they already
49
+ * live. This setter only moves where the line is drawn, so it cannot disturb
50
+ * the usage cache.
51
+ */
52
+ setActivity(activity: Component | undefined): void;
42
53
  /**
43
54
  * No-op: git branch caching now handled by provider.
44
55
  * Kept for compatibility with existing call sites in interactive-mode.
@@ -49,6 +60,14 @@ export declare class FooterComponent implements Component {
49
60
  * Git watcher cleanup now handled by provider.
50
61
  */
51
62
  dispose(): void;
63
+ /**
64
+ * Usage totals walked from all session entries. getEntries() copies the
65
+ * whole entry list, so this is cached against the session manager's
66
+ * entries version: without the cache the footer re-walked the entire
67
+ * transcript on every rendered frame.
68
+ */
69
+ private usageCache?;
70
+ private computeUsage;
52
71
  render(width: number): string[];
53
72
  }
54
73
  //# sourceMappingURL=footer.d.ts.map