apex-code 0.0.1-alpha.0 → 0.0.1-alpha.10

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 (739) hide show
  1. package/CHANGELOG.md +471 -0
  2. package/README.md +74 -680
  3. package/dist/cli/args.d.ts +21 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +119 -15
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/auth-command.d.ts.map +1 -1
  8. package/dist/cli/auth-command.js +3 -3
  9. package/dist/cli/auth-command.js.map +1 -1
  10. package/dist/cli/concurrent-session.d.ts +3 -0
  11. package/dist/cli/concurrent-session.d.ts.map +1 -0
  12. package/dist/cli/concurrent-session.js +13 -0
  13. package/dist/cli/concurrent-session.js.map +1 -0
  14. package/dist/cli/cost-command.d.ts +19 -0
  15. package/dist/cli/cost-command.d.ts.map +1 -0
  16. package/dist/cli/cost-command.js +107 -0
  17. package/dist/cli/cost-command.js.map +1 -0
  18. package/dist/cli/file-processor.d.ts.map +1 -1
  19. package/dist/cli/file-processor.js +2 -1
  20. package/dist/cli/file-processor.js.map +1 -1
  21. package/dist/cli/startup-ui.d.ts +1 -1
  22. package/dist/cli/startup-ui.d.ts.map +1 -1
  23. package/dist/cli/startup-ui.js +1 -2
  24. package/dist/cli/startup-ui.js.map +1 -1
  25. package/dist/cli.d.ts.map +1 -1
  26. package/dist/cli.js +99 -16
  27. package/dist/cli.js.map +1 -1
  28. package/dist/config.d.ts +5 -4
  29. package/dist/config.d.ts.map +1 -1
  30. package/dist/config.js +35 -20
  31. package/dist/config.js.map +1 -1
  32. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  33. package/dist/core/agent-session-runtime.js +2 -0
  34. package/dist/core/agent-session-runtime.js.map +1 -1
  35. package/dist/core/agent-session-services.d.ts +28 -0
  36. package/dist/core/agent-session-services.d.ts.map +1 -1
  37. package/dist/core/agent-session-services.js +140 -4
  38. package/dist/core/agent-session-services.js.map +1 -1
  39. package/dist/core/agent-session.d.ts +121 -9
  40. package/dist/core/agent-session.d.ts.map +1 -1
  41. package/dist/core/agent-session.js +364 -123
  42. package/dist/core/agent-session.js.map +1 -1
  43. package/dist/core/auth-storage.d.ts +4 -1
  44. package/dist/core/auth-storage.d.ts.map +1 -1
  45. package/dist/core/auth-storage.js +17 -14
  46. package/dist/core/auth-storage.js.map +1 -1
  47. package/dist/core/bash-executor.d.ts.map +1 -1
  48. package/dist/core/bash-executor.js +11 -10
  49. package/dist/core/bash-executor.js.map +1 -1
  50. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  51. package/dist/core/compaction/branch-summarization.js +3 -0
  52. package/dist/core/compaction/branch-summarization.js.map +1 -1
  53. package/dist/core/compaction/compaction.d.ts +4 -3
  54. package/dist/core/compaction/compaction.d.ts.map +1 -1
  55. package/dist/core/compaction/compaction.js +41 -32
  56. package/dist/core/compaction/compaction.js.map +1 -1
  57. package/dist/core/context/deferred-schemas.d.ts +92 -0
  58. package/dist/core/context/deferred-schemas.d.ts.map +1 -0
  59. package/dist/core/context/deferred-schemas.js +74 -0
  60. package/dist/core/context/deferred-schemas.js.map +1 -0
  61. package/dist/core/context/eviction.d.ts +47 -0
  62. package/dist/core/context/eviction.d.ts.map +1 -0
  63. package/dist/core/context/eviction.js +83 -0
  64. package/dist/core/context/eviction.js.map +1 -0
  65. package/dist/core/context/pipeline.d.ts +90 -0
  66. package/dist/core/context/pipeline.d.ts.map +1 -0
  67. package/dist/core/context/pipeline.js +109 -0
  68. package/dist/core/context/pipeline.js.map +1 -0
  69. package/dist/core/credential-failover.d.ts +30 -0
  70. package/dist/core/credential-failover.d.ts.map +1 -0
  71. package/dist/core/credential-failover.js +46 -0
  72. package/dist/core/credential-failover.js.map +1 -0
  73. package/dist/core/credential-pool.d.ts +60 -0
  74. package/dist/core/credential-pool.d.ts.map +1 -0
  75. package/dist/core/credential-pool.js +84 -0
  76. package/dist/core/credential-pool.js.map +1 -0
  77. package/dist/core/defaults.d.ts +1 -0
  78. package/dist/core/defaults.d.ts.map +1 -1
  79. package/dist/core/defaults.js +9 -0
  80. package/dist/core/defaults.js.map +1 -1
  81. package/dist/core/delegation/agents.d.ts +15 -0
  82. package/dist/core/delegation/agents.d.ts.map +1 -0
  83. package/dist/core/delegation/agents.js +55 -0
  84. package/dist/core/delegation/agents.js.map +1 -0
  85. package/dist/core/delegation/ceiling.d.ts +29 -0
  86. package/dist/core/delegation/ceiling.d.ts.map +1 -0
  87. package/dist/core/delegation/ceiling.js +34 -0
  88. package/dist/core/delegation/ceiling.js.map +1 -0
  89. package/dist/core/delegation/runtime.d.ts +94 -0
  90. package/dist/core/delegation/runtime.d.ts.map +1 -0
  91. package/dist/core/delegation/runtime.js +117 -0
  92. package/dist/core/delegation/runtime.js.map +1 -0
  93. package/dist/core/durable-state/daemon.d.ts +37 -0
  94. package/dist/core/durable-state/daemon.d.ts.map +1 -0
  95. package/dist/core/durable-state/daemon.js +62 -0
  96. package/dist/core/durable-state/daemon.js.map +1 -0
  97. package/dist/core/durable-state/provenance.d.ts +7 -0
  98. package/dist/core/durable-state/provenance.d.ts.map +1 -0
  99. package/dist/core/durable-state/provenance.js +24 -0
  100. package/dist/core/durable-state/provenance.js.map +1 -0
  101. package/dist/core/durable-state/sqlite.d.ts +84 -0
  102. package/dist/core/durable-state/sqlite.d.ts.map +1 -0
  103. package/dist/core/durable-state/sqlite.js +355 -0
  104. package/dist/core/durable-state/sqlite.js.map +1 -0
  105. package/dist/core/environment.d.ts +12 -0
  106. package/dist/core/environment.d.ts.map +1 -0
  107. package/dist/core/environment.js +69 -0
  108. package/dist/core/environment.js.map +1 -0
  109. package/dist/core/evidence-policy.d.ts +17 -0
  110. package/dist/core/evidence-policy.d.ts.map +1 -0
  111. package/dist/core/evidence-policy.js +25 -0
  112. package/dist/core/evidence-policy.js.map +1 -0
  113. package/dist/core/evidence.d.ts +16 -0
  114. package/dist/core/evidence.d.ts.map +1 -0
  115. package/dist/core/evidence.js +51 -0
  116. package/dist/core/evidence.js.map +1 -0
  117. package/dist/core/experimental.d.ts +4 -0
  118. package/dist/core/experimental.d.ts.map +1 -1
  119. package/dist/core/experimental.js +6 -1
  120. package/dist/core/experimental.js.map +1 -1
  121. package/dist/core/extensions/index.d.ts +2 -2
  122. package/dist/core/extensions/index.d.ts.map +1 -1
  123. package/dist/core/extensions/index.js +1 -1
  124. package/dist/core/extensions/index.js.map +1 -1
  125. package/dist/core/extensions/loader.d.ts.map +1 -1
  126. package/dist/core/extensions/loader.js +115 -48
  127. package/dist/core/extensions/loader.js.map +1 -1
  128. package/dist/core/extensions/types.d.ts +44 -9
  129. package/dist/core/extensions/types.d.ts.map +1 -1
  130. package/dist/core/extensions/types.js +3 -0
  131. package/dist/core/extensions/types.js.map +1 -1
  132. package/dist/core/footer-data-provider.d.ts +1 -1
  133. package/dist/core/footer-data-provider.d.ts.map +1 -1
  134. package/dist/core/footer-data-provider.js +1 -1
  135. package/dist/core/footer-data-provider.js.map +1 -1
  136. package/dist/core/http-dispatcher.d.ts +1 -2
  137. package/dist/core/http-dispatcher.d.ts.map +1 -1
  138. package/dist/core/http-dispatcher.js +2 -17
  139. package/dist/core/http-dispatcher.js.map +1 -1
  140. package/dist/core/http-idle-timeout.d.ts +12 -0
  141. package/dist/core/http-idle-timeout.d.ts.map +1 -0
  142. package/dist/core/http-idle-timeout.js +27 -0
  143. package/dist/core/http-idle-timeout.js.map +1 -0
  144. package/dist/core/index.d.ts +1 -0
  145. package/dist/core/index.d.ts.map +1 -1
  146. package/dist/core/index.js +1 -0
  147. package/dist/core/index.js.map +1 -1
  148. package/dist/core/keybindings.d.ts +38 -13
  149. package/dist/core/keybindings.d.ts.map +1 -1
  150. package/dist/core/keybindings.js +26 -5
  151. package/dist/core/keybindings.js.map +1 -1
  152. package/dist/core/lsp/client.d.ts +78 -0
  153. package/dist/core/lsp/client.d.ts.map +1 -0
  154. package/dist/core/lsp/client.js +494 -0
  155. package/dist/core/lsp/client.js.map +1 -0
  156. package/dist/core/lsp/diagnostics.d.ts +47 -0
  157. package/dist/core/lsp/diagnostics.d.ts.map +1 -0
  158. package/dist/core/lsp/diagnostics.js +224 -0
  159. package/dist/core/lsp/diagnostics.js.map +1 -0
  160. package/dist/core/lsp/pool.d.ts +48 -0
  161. package/dist/core/lsp/pool.d.ts.map +1 -0
  162. package/dist/core/lsp/pool.js +150 -0
  163. package/dist/core/lsp/pool.js.map +1 -0
  164. package/dist/core/lsp/registry.d.ts +49 -0
  165. package/dist/core/lsp/registry.d.ts.map +1 -0
  166. package/dist/core/lsp/registry.js +215 -0
  167. package/dist/core/lsp/registry.js.map +1 -0
  168. package/dist/core/lsp/status.d.ts +23 -0
  169. package/dist/core/lsp/status.d.ts.map +1 -0
  170. package/dist/core/lsp/status.js +35 -0
  171. package/dist/core/lsp/status.js.map +1 -0
  172. package/dist/core/lsp/sync.d.ts +13 -0
  173. package/dist/core/lsp/sync.d.ts.map +1 -0
  174. package/dist/core/lsp/sync.js +71 -0
  175. package/dist/core/lsp/sync.js.map +1 -0
  176. package/dist/core/model-config.d.ts +18 -0
  177. package/dist/core/model-config.d.ts.map +1 -1
  178. package/dist/core/model-config.js +25 -6
  179. package/dist/core/model-config.js.map +1 -1
  180. package/dist/core/model-resolver.d.ts +21 -0
  181. package/dist/core/model-resolver.d.ts.map +1 -1
  182. package/dist/core/model-resolver.js +73 -8
  183. package/dist/core/model-resolver.js.map +1 -1
  184. package/dist/core/model-runtime.d.ts +73 -0
  185. package/dist/core/model-runtime.d.ts.map +1 -1
  186. package/dist/core/model-runtime.js +225 -8
  187. package/dist/core/model-runtime.js.map +1 -1
  188. package/dist/core/models-store.d.ts.map +1 -1
  189. package/dist/core/models-store.js +2 -1
  190. package/dist/core/models-store.js.map +1 -1
  191. package/dist/core/observability/aggregate.d.ts +21 -0
  192. package/dist/core/observability/aggregate.d.ts.map +1 -0
  193. package/dist/core/observability/aggregate.js +36 -0
  194. package/dist/core/observability/aggregate.js.map +1 -0
  195. package/dist/core/observability/otlp.d.ts +66 -0
  196. package/dist/core/observability/otlp.d.ts.map +1 -0
  197. package/dist/core/observability/otlp.js +95 -0
  198. package/dist/core/observability/otlp.js.map +1 -0
  199. package/dist/core/package-manager.d.ts.map +1 -1
  200. package/dist/core/package-manager.js +40 -16
  201. package/dist/core/package-manager.js.map +1 -1
  202. package/dist/core/permissions/gate.d.ts +42 -0
  203. package/dist/core/permissions/gate.d.ts.map +1 -0
  204. package/dist/core/permissions/gate.js +72 -0
  205. package/dist/core/permissions/gate.js.map +1 -0
  206. package/dist/core/permissions/modes.d.ts +18 -0
  207. package/dist/core/permissions/modes.d.ts.map +1 -0
  208. package/dist/core/permissions/modes.js +57 -0
  209. package/dist/core/permissions/modes.js.map +1 -0
  210. package/dist/core/permissions/responder.d.ts +30 -0
  211. package/dist/core/permissions/responder.d.ts.map +1 -0
  212. package/dist/core/permissions/responder.js +32 -0
  213. package/dist/core/permissions/responder.js.map +1 -0
  214. package/dist/core/permissions/rules.d.ts +34 -0
  215. package/dist/core/permissions/rules.d.ts.map +1 -0
  216. package/dist/core/permissions/rules.js +74 -0
  217. package/dist/core/permissions/rules.js.map +1 -0
  218. package/dist/core/permissions/startup.d.ts +45 -0
  219. package/dist/core/permissions/startup.d.ts.map +1 -0
  220. package/dist/core/permissions/startup.js +63 -0
  221. package/dist/core/permissions/startup.js.map +1 -0
  222. package/dist/core/permissions/store.d.ts +114 -0
  223. package/dist/core/permissions/store.d.ts.map +1 -0
  224. package/dist/core/permissions/store.js +229 -0
  225. package/dist/core/permissions/store.js.map +1 -0
  226. package/dist/core/pi-manifest.d.ts.map +1 -1
  227. package/dist/core/pi-manifest.js +2 -1
  228. package/dist/core/pi-manifest.js.map +1 -1
  229. package/dist/core/project-trust.d.ts.map +1 -1
  230. package/dist/core/project-trust.js +1 -1
  231. package/dist/core/project-trust.js.map +1 -1
  232. package/dist/core/provider-attribution.d.ts.map +1 -1
  233. package/dist/core/provider-attribution.js +6 -7
  234. package/dist/core/provider-attribution.js.map +1 -1
  235. package/dist/core/remote-catalog-provider.d.ts +2 -2
  236. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  237. package/dist/core/remote-catalog-provider.js +22 -17
  238. package/dist/core/remote-catalog-provider.js.map +1 -1
  239. package/dist/core/resource-loader.d.ts.map +1 -1
  240. package/dist/core/resource-loader.js +3 -2
  241. package/dist/core/resource-loader.js.map +1 -1
  242. package/dist/core/sandbox/bwrap-arguments.d.ts +52 -0
  243. package/dist/core/sandbox/bwrap-arguments.d.ts.map +1 -0
  244. package/dist/core/sandbox/bwrap-arguments.js +86 -0
  245. package/dist/core/sandbox/bwrap-arguments.js.map +1 -0
  246. package/dist/core/sandbox/child-entry.d.ts +2 -0
  247. package/dist/core/sandbox/child-entry.d.ts.map +1 -0
  248. package/dist/core/sandbox/child-entry.js +22 -0
  249. package/dist/core/sandbox/child-entry.js.map +1 -0
  250. package/dist/core/sandbox/cli-launch.d.ts +88 -0
  251. package/dist/core/sandbox/cli-launch.d.ts.map +1 -0
  252. package/dist/core/sandbox/cli-launch.js +272 -0
  253. package/dist/core/sandbox/cli-launch.js.map +1 -0
  254. package/dist/core/sandbox/cli-supervisor.d.ts +30 -0
  255. package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -0
  256. package/dist/core/sandbox/cli-supervisor.js +123 -0
  257. package/dist/core/sandbox/cli-supervisor.js.map +1 -0
  258. package/dist/core/sandbox/default-hosts.d.ts +28 -0
  259. package/dist/core/sandbox/default-hosts.d.ts.map +1 -0
  260. package/dist/core/sandbox/default-hosts.js +69 -0
  261. package/dist/core/sandbox/default-hosts.js.map +1 -0
  262. package/dist/core/sandbox/full-access.d.ts +28 -0
  263. package/dist/core/sandbox/full-access.d.ts.map +1 -0
  264. package/dist/core/sandbox/full-access.js +63 -0
  265. package/dist/core/sandbox/full-access.js.map +1 -0
  266. package/dist/core/sandbox/git-identity.d.ts +57 -0
  267. package/dist/core/sandbox/git-identity.d.ts.map +1 -0
  268. package/dist/core/sandbox/git-identity.js +78 -0
  269. package/dist/core/sandbox/git-identity.js.map +1 -0
  270. package/dist/core/sandbox/host-approval.d.ts +42 -0
  271. package/dist/core/sandbox/host-approval.d.ts.map +1 -0
  272. package/dist/core/sandbox/host-approval.js +99 -0
  273. package/dist/core/sandbox/host-approval.js.map +1 -0
  274. package/dist/core/sandbox/linux-backend.d.ts +46 -0
  275. package/dist/core/sandbox/linux-backend.d.ts.map +1 -0
  276. package/dist/core/sandbox/linux-backend.js +304 -0
  277. package/dist/core/sandbox/linux-backend.js.map +1 -0
  278. package/dist/core/sandbox/macos-backend.d.ts +28 -0
  279. package/dist/core/sandbox/macos-backend.d.ts.map +1 -0
  280. package/dist/core/sandbox/macos-backend.js +313 -0
  281. package/dist/core/sandbox/macos-backend.js.map +1 -0
  282. package/dist/core/sandbox/network-proxy.d.ts +38 -0
  283. package/dist/core/sandbox/network-proxy.d.ts.map +1 -0
  284. package/dist/core/sandbox/network-proxy.js +153 -0
  285. package/dist/core/sandbox/network-proxy.js.map +1 -0
  286. package/dist/core/sandbox/network-refusal.d.ts +24 -0
  287. package/dist/core/sandbox/network-refusal.d.ts.map +1 -0
  288. package/dist/core/sandbox/network-refusal.js +72 -0
  289. package/dist/core/sandbox/network-refusal.js.map +1 -0
  290. package/dist/core/sandbox/policy.d.ts +40 -0
  291. package/dist/core/sandbox/policy.d.ts.map +1 -0
  292. package/dist/core/sandbox/policy.js +55 -0
  293. package/dist/core/sandbox/policy.js.map +1 -0
  294. package/dist/core/sandbox/profiles.d.ts +25 -0
  295. package/dist/core/sandbox/profiles.d.ts.map +1 -0
  296. package/dist/core/sandbox/profiles.js +23 -0
  297. package/dist/core/sandbox/profiles.js.map +1 -0
  298. package/dist/core/sandbox/rpc/command-client.d.ts +28 -0
  299. package/dist/core/sandbox/rpc/command-client.d.ts.map +1 -0
  300. package/dist/core/sandbox/rpc/command-client.js +86 -0
  301. package/dist/core/sandbox/rpc/command-client.js.map +1 -0
  302. package/dist/core/sandbox/rpc/command-proxy.d.ts +48 -0
  303. package/dist/core/sandbox/rpc/command-proxy.d.ts.map +1 -0
  304. package/dist/core/sandbox/rpc/command-proxy.js +120 -0
  305. package/dist/core/sandbox/rpc/command-proxy.js.map +1 -0
  306. package/dist/core/sandbox/rpc/credential-client.d.ts +20 -0
  307. package/dist/core/sandbox/rpc/credential-client.d.ts.map +1 -0
  308. package/dist/core/sandbox/rpc/credential-client.js +170 -0
  309. package/dist/core/sandbox/rpc/credential-client.js.map +1 -0
  310. package/dist/core/sandbox/rpc/credential-proxy.d.ts +44 -0
  311. package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -0
  312. package/dist/core/sandbox/rpc/credential-proxy.js +287 -0
  313. package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -0
  314. package/dist/core/sandbox/rpc/framing.d.ts +35 -0
  315. package/dist/core/sandbox/rpc/framing.d.ts.map +1 -0
  316. package/dist/core/sandbox/rpc/framing.js +114 -0
  317. package/dist/core/sandbox/rpc/framing.js.map +1 -0
  318. package/dist/core/sandbox/rpc/git-credential-helper.d.ts +40 -0
  319. package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +1 -0
  320. package/dist/core/sandbox/rpc/git-credential-helper.js +152 -0
  321. package/dist/core/sandbox/rpc/git-credential-helper.js.map +1 -0
  322. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +48 -0
  323. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +1 -0
  324. package/dist/core/sandbox/rpc/git-credential-proxy.js +142 -0
  325. package/dist/core/sandbox/rpc/git-credential-proxy.js.map +1 -0
  326. package/dist/core/sandbox/supervisor-temp.d.ts +18 -0
  327. package/dist/core/sandbox/supervisor-temp.d.ts.map +1 -0
  328. package/dist/core/sandbox/supervisor-temp.js +21 -0
  329. package/dist/core/sandbox/supervisor-temp.js.map +1 -0
  330. package/dist/core/sandbox/supervisor.d.ts +51 -0
  331. package/dist/core/sandbox/supervisor.d.ts.map +1 -0
  332. package/dist/core/sandbox/supervisor.js +18 -0
  333. package/dist/core/sandbox/supervisor.js.map +1 -0
  334. package/dist/core/sandbox/terminal-handoff.d.ts +45 -0
  335. package/dist/core/sandbox/terminal-handoff.d.ts.map +1 -0
  336. package/dist/core/sandbox/terminal-handoff.js +176 -0
  337. package/dist/core/sandbox/terminal-handoff.js.map +1 -0
  338. package/dist/core/sandbox/terminal-size.d.ts +40 -0
  339. package/dist/core/sandbox/terminal-size.d.ts.map +1 -0
  340. package/dist/core/sandbox/terminal-size.js +105 -0
  341. package/dist/core/sandbox/terminal-size.js.map +1 -0
  342. package/dist/core/sandbox/violations.d.ts +23 -0
  343. package/dist/core/sandbox/violations.d.ts.map +1 -0
  344. package/dist/core/sandbox/violations.js +30 -0
  345. package/dist/core/sandbox/violations.js.map +1 -0
  346. package/dist/core/sdk.d.ts +32 -41
  347. package/dist/core/sdk.d.ts.map +1 -1
  348. package/dist/core/sdk.js +161 -6
  349. package/dist/core/sdk.js.map +1 -1
  350. package/dist/core/session-export.d.ts +4 -0
  351. package/dist/core/session-export.d.ts.map +1 -0
  352. package/dist/core/session-export.js +32 -0
  353. package/dist/core/session-export.js.map +1 -0
  354. package/dist/core/session-lease.d.ts +31 -0
  355. package/dist/core/session-lease.d.ts.map +1 -0
  356. package/dist/core/session-lease.js +138 -0
  357. package/dist/core/session-lease.js.map +1 -0
  358. package/dist/core/session-manager.d.ts +46 -2
  359. package/dist/core/session-manager.d.ts.map +1 -1
  360. package/dist/core/session-manager.js +62 -4
  361. package/dist/core/session-manager.js.map +1 -1
  362. package/dist/core/session-share.d.ts +31 -0
  363. package/dist/core/session-share.d.ts.map +1 -0
  364. package/dist/core/session-share.js +45 -0
  365. package/dist/core/session-share.js.map +1 -0
  366. package/dist/core/settings-diagnostics.d.ts +9 -0
  367. package/dist/core/settings-diagnostics.d.ts.map +1 -0
  368. package/dist/core/settings-diagnostics.js +21 -0
  369. package/dist/core/settings-diagnostics.js.map +1 -0
  370. package/dist/core/settings-manager.d.ts +92 -9
  371. package/dist/core/settings-manager.d.ts.map +1 -1
  372. package/dist/core/settings-manager.js +138 -36
  373. package/dist/core/settings-manager.js.map +1 -1
  374. package/dist/core/skills.d.ts +19 -7
  375. package/dist/core/skills.d.ts.map +1 -1
  376. package/dist/core/skills.js +128 -52
  377. package/dist/core/skills.js.map +1 -1
  378. package/dist/core/slash-commands.d.ts.map +1 -1
  379. package/dist/core/slash-commands.js +5 -2
  380. package/dist/core/slash-commands.js.map +1 -1
  381. package/dist/core/system-prompt.d.ts +2 -2
  382. package/dist/core/system-prompt.d.ts.map +1 -1
  383. package/dist/core/system-prompt.js +59 -68
  384. package/dist/core/system-prompt.js.map +1 -1
  385. package/dist/core/timings.d.ts +1 -1
  386. package/dist/core/timings.d.ts.map +1 -1
  387. package/dist/core/timings.js +3 -2
  388. package/dist/core/timings.js.map +1 -1
  389. package/dist/core/tools/ask-user.d.ts +25 -0
  390. package/dist/core/tools/ask-user.d.ts.map +1 -0
  391. package/dist/core/tools/ask-user.js +49 -0
  392. package/dist/core/tools/ask-user.js.map +1 -0
  393. package/dist/core/tools/bash-command-segments.d.ts +22 -0
  394. package/dist/core/tools/bash-command-segments.d.ts.map +1 -0
  395. package/dist/core/tools/bash-command-segments.js +128 -0
  396. package/dist/core/tools/bash-command-segments.js.map +1 -0
  397. package/dist/core/tools/bash.d.ts +40 -4
  398. package/dist/core/tools/bash.d.ts.map +1 -1
  399. package/dist/core/tools/bash.js +183 -32
  400. package/dist/core/tools/bash.js.map +1 -1
  401. package/dist/core/tools/contract.d.ts +183 -0
  402. package/dist/core/tools/contract.d.ts.map +1 -0
  403. package/dist/core/tools/contract.js +48 -0
  404. package/dist/core/tools/contract.js.map +1 -0
  405. package/dist/core/tools/delegate.d.ts +38 -0
  406. package/dist/core/tools/delegate.d.ts.map +1 -0
  407. package/dist/core/tools/delegate.js +90 -0
  408. package/dist/core/tools/delegate.js.map +1 -0
  409. package/dist/core/tools/diagnostics.d.ts +39 -0
  410. package/dist/core/tools/diagnostics.d.ts.map +1 -0
  411. package/dist/core/tools/diagnostics.js +62 -0
  412. package/dist/core/tools/diagnostics.js.map +1 -0
  413. package/dist/core/tools/edit-diff.d.ts +0 -5
  414. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  415. package/dist/core/tools/edit-diff.js +2 -5
  416. package/dist/core/tools/edit-diff.js.map +1 -1
  417. package/dist/core/tools/edit.d.ts +12 -2
  418. package/dist/core/tools/edit.d.ts.map +1 -1
  419. package/dist/core/tools/edit.js +61 -10
  420. package/dist/core/tools/edit.js.map +1 -1
  421. package/dist/core/tools/find.d.ts +2 -2
  422. package/dist/core/tools/find.d.ts.map +1 -1
  423. package/dist/core/tools/find.js +13 -1
  424. package/dist/core/tools/find.js.map +1 -1
  425. package/dist/core/tools/grep.d.ts +4 -2
  426. package/dist/core/tools/grep.d.ts.map +1 -1
  427. package/dist/core/tools/grep.js +47 -10
  428. package/dist/core/tools/grep.js.map +1 -1
  429. package/dist/core/tools/index.d.ts +51 -6
  430. package/dist/core/tools/index.d.ts.map +1 -1
  431. package/dist/core/tools/index.js +145 -2
  432. package/dist/core/tools/index.js.map +1 -1
  433. package/dist/core/tools/ls.d.ts +4 -2
  434. package/dist/core/tools/ls.d.ts.map +1 -1
  435. package/dist/core/tools/ls.js +17 -1
  436. package/dist/core/tools/ls.js.map +1 -1
  437. package/dist/core/tools/lsp.d.ts +62 -0
  438. package/dist/core/tools/lsp.d.ts.map +1 -0
  439. package/dist/core/tools/lsp.js +216 -0
  440. package/dist/core/tools/lsp.js.map +1 -0
  441. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  442. package/dist/core/tools/output-accumulator.js +1 -1
  443. package/dist/core/tools/output-accumulator.js.map +1 -1
  444. package/dist/core/tools/path-permission.d.ts +18 -0
  445. package/dist/core/tools/path-permission.d.ts.map +1 -0
  446. package/dist/core/tools/path-permission.js +28 -0
  447. package/dist/core/tools/path-permission.js.map +1 -0
  448. package/dist/core/tools/plan-present.d.ts +27 -0
  449. package/dist/core/tools/plan-present.d.ts.map +1 -0
  450. package/dist/core/tools/plan-present.js +55 -0
  451. package/dist/core/tools/plan-present.js.map +1 -0
  452. package/dist/core/tools/powershell.d.ts +16 -0
  453. package/dist/core/tools/powershell.d.ts.map +1 -0
  454. package/dist/core/tools/powershell.js +39 -0
  455. package/dist/core/tools/powershell.js.map +1 -0
  456. package/dist/core/tools/read.d.ts +2 -2
  457. package/dist/core/tools/read.d.ts.map +1 -1
  458. package/dist/core/tools/read.js +14 -0
  459. package/dist/core/tools/read.js.map +1 -1
  460. package/dist/core/tools/skill-search.d.ts +32 -0
  461. package/dist/core/tools/skill-search.d.ts.map +1 -0
  462. package/dist/core/tools/skill-search.js +56 -0
  463. package/dist/core/tools/skill-search.js.map +1 -0
  464. package/dist/core/tools/test.d.ts +34 -0
  465. package/dist/core/tools/test.d.ts.map +1 -0
  466. package/dist/core/tools/test.js +75 -0
  467. package/dist/core/tools/test.js.map +1 -0
  468. package/dist/core/tools/todo-write.d.ts +31 -0
  469. package/dist/core/tools/todo-write.d.ts.map +1 -0
  470. package/dist/core/tools/todo-write.js +51 -0
  471. package/dist/core/tools/todo-write.js.map +1 -0
  472. package/dist/core/tools/tool-definition-wrapper.d.ts +13 -2
  473. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  474. package/dist/core/tools/tool-definition-wrapper.js +11 -1
  475. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  476. package/dist/core/tools/tool-schema.d.ts +22 -0
  477. package/dist/core/tools/tool-schema.d.ts.map +1 -0
  478. package/dist/core/tools/tool-schema.js +45 -0
  479. package/dist/core/tools/tool-schema.js.map +1 -0
  480. package/dist/core/tools/web-fetch.d.ts +26 -0
  481. package/dist/core/tools/web-fetch.d.ts.map +1 -0
  482. package/dist/core/tools/web-fetch.js +62 -0
  483. package/dist/core/tools/web-fetch.js.map +1 -0
  484. package/dist/core/tools/web-search-exa.d.ts +33 -0
  485. package/dist/core/tools/web-search-exa.d.ts.map +1 -0
  486. package/dist/core/tools/web-search-exa.js +128 -0
  487. package/dist/core/tools/web-search-exa.js.map +1 -0
  488. package/dist/core/tools/web-search.d.ts +26 -0
  489. package/dist/core/tools/web-search.d.ts.map +1 -0
  490. package/dist/core/tools/web-search.js +53 -0
  491. package/dist/core/tools/web-search.js.map +1 -0
  492. package/dist/core/tools/write.d.ts +16 -2
  493. package/dist/core/tools/write.d.ts.map +1 -1
  494. package/dist/core/tools/write.js +48 -2
  495. package/dist/core/tools/write.js.map +1 -1
  496. package/dist/core/trust-manager.d.ts.map +1 -1
  497. package/dist/core/trust-manager.js +2 -1
  498. package/dist/core/trust-manager.js.map +1 -1
  499. package/dist/core/usage-performance-store.d.ts +56 -0
  500. package/dist/core/usage-performance-store.d.ts.map +1 -0
  501. package/dist/core/usage-performance-store.js +85 -0
  502. package/dist/core/usage-performance-store.js.map +1 -0
  503. package/dist/core/web-search-provider.d.ts +64 -0
  504. package/dist/core/web-search-provider.d.ts.map +1 -0
  505. package/dist/core/web-search-provider.js +149 -0
  506. package/dist/core/web-search-provider.js.map +1 -0
  507. package/dist/extensions/llama/index.d.ts.map +1 -1
  508. package/dist/extensions/llama/index.js +2 -0
  509. package/dist/extensions/llama/index.js.map +1 -1
  510. package/dist/extensions/llama/provider.d.ts.map +1 -1
  511. package/dist/extensions/llama/provider.js +6 -2
  512. package/dist/extensions/llama/provider.js.map +1 -1
  513. package/dist/index.d.ts +7 -6
  514. package/dist/index.d.ts.map +1 -1
  515. package/dist/index.js +5 -4
  516. package/dist/index.js.map +1 -1
  517. package/dist/main.d.ts +5 -0
  518. package/dist/main.d.ts.map +1 -1
  519. package/dist/main.js +87 -22
  520. package/dist/main.js.map +1 -1
  521. package/dist/migrations.d.ts.map +1 -1
  522. package/dist/migrations.js +6 -5
  523. package/dist/migrations.js.map +1 -1
  524. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  525. package/dist/modes/interactive/components/config-selector.js +9 -6
  526. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  527. package/dist/modes/interactive/components/custom-editor.d.ts +72 -1
  528. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  529. package/dist/modes/interactive/components/custom-editor.js +338 -1
  530. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  531. package/dist/modes/interactive/components/dynamic-border.d.ts +7 -1
  532. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  533. package/dist/modes/interactive/components/dynamic-border.js +11 -2
  534. package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  535. package/dist/modes/interactive/components/extension-selector.d.ts +4 -0
  536. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  537. package/dist/modes/interactive/components/extension-selector.js +24 -6
  538. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  539. package/dist/modes/interactive/components/first-time-setup.d.ts +1 -4
  540. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
  541. package/dist/modes/interactive/components/first-time-setup.js +17 -41
  542. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  543. package/dist/modes/interactive/components/first-use-hints.d.ts +8 -0
  544. package/dist/modes/interactive/components/first-use-hints.d.ts.map +1 -0
  545. package/dist/modes/interactive/components/first-use-hints.js +22 -0
  546. package/dist/modes/interactive/components/first-use-hints.js.map +1 -0
  547. package/dist/modes/interactive/components/footer.d.ts +23 -1
  548. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  549. package/dist/modes/interactive/components/footer.js +182 -21
  550. package/dist/modes/interactive/components/footer.js.map +1 -1
  551. package/dist/modes/interactive/components/index.d.ts +2 -0
  552. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  553. package/dist/modes/interactive/components/index.js +2 -0
  554. package/dist/modes/interactive/components/index.js.map +1 -1
  555. package/dist/modes/interactive/components/model-selector.d.ts +31 -9
  556. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  557. package/dist/modes/interactive/components/model-selector.js +236 -96
  558. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  559. package/dist/modes/interactive/components/settings-selector.d.ts +25 -5
  560. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  561. package/dist/modes/interactive/components/settings-selector.js +226 -58
  562. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  563. package/dist/modes/interactive/components/settings-submenu.d.ts +72 -0
  564. package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -0
  565. package/dist/modes/interactive/components/settings-submenu.js +165 -0
  566. package/dist/modes/interactive/components/settings-submenu.js.map +1 -0
  567. package/dist/modes/interactive/components/splash-header.d.ts +66 -0
  568. package/dist/modes/interactive/components/splash-header.d.ts.map +1 -0
  569. package/dist/modes/interactive/components/splash-header.js +180 -0
  570. package/dist/modes/interactive/components/splash-header.js.map +1 -0
  571. package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
  572. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  573. package/dist/modes/interactive/components/thinking-selector.js +74 -17
  574. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  575. package/dist/modes/interactive/components/tool-execution.d.ts +16 -1
  576. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  577. package/dist/modes/interactive/components/tool-execution.js +113 -35
  578. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  579. package/dist/modes/interactive/components/tool-panel.d.ts +30 -0
  580. package/dist/modes/interactive/components/tool-panel.d.ts.map +1 -0
  581. package/dist/modes/interactive/components/tool-panel.js +130 -0
  582. package/dist/modes/interactive/components/tool-panel.js.map +1 -0
  583. package/dist/modes/interactive/external-editor.d.ts.map +1 -1
  584. package/dist/modes/interactive/external-editor.js +60 -10
  585. package/dist/modes/interactive/external-editor.js.map +1 -1
  586. package/dist/modes/interactive/interactive-mode.d.ts +61 -5
  587. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  588. package/dist/modes/interactive/interactive-mode.js +642 -203
  589. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  590. package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
  591. package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
  592. package/dist/modes/interactive/model-catalog-refresh.js +34 -0
  593. package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
  594. package/dist/modes/interactive/theme/apex.json +93 -0
  595. package/dist/modes/interactive/theme/dark.json +2 -0
  596. package/dist/modes/interactive/theme/light.json +3 -1
  597. package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
  598. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  599. package/dist/modes/interactive/theme/theme-controller.js +25 -11
  600. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  601. package/dist/modes/interactive/theme/theme-schema.json +11 -3
  602. package/dist/modes/interactive/theme/theme.d.ts +14 -3
  603. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  604. package/dist/modes/interactive/theme/theme.js +65 -10
  605. package/dist/modes/interactive/theme/theme.js.map +1 -1
  606. package/dist/modes/json-event.d.ts +18 -12
  607. package/dist/modes/json-event.d.ts.map +1 -1
  608. package/dist/modes/json-event.js +22 -5
  609. package/dist/modes/json-event.js.map +1 -1
  610. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  611. package/dist/modes/rpc/rpc-mode.js +2 -1
  612. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  613. package/dist/package-manager-cli.d.ts +1 -0
  614. package/dist/package-manager-cli.d.ts.map +1 -1
  615. package/dist/package-manager-cli.js +211 -14
  616. package/dist/package-manager-cli.js.map +1 -1
  617. package/dist/rpc-entry.d.ts.map +1 -1
  618. package/dist/rpc-entry.js +2 -1
  619. package/dist/rpc-entry.js.map +1 -1
  620. package/dist/server/create-harness.d.ts +1 -1
  621. package/dist/server/create-harness.d.ts.map +1 -1
  622. package/dist/server/create-harness.js +8 -5
  623. package/dist/server/create-harness.js.map +1 -1
  624. package/dist/testing/replay/metrics.d.ts +24 -0
  625. package/dist/testing/replay/metrics.d.ts.map +1 -0
  626. package/dist/testing/replay/metrics.js +35 -0
  627. package/dist/testing/replay/metrics.js.map +1 -0
  628. package/dist/testing/replay/recorded-provider.d.ts +10 -0
  629. package/dist/testing/replay/recorded-provider.d.ts.map +1 -0
  630. package/dist/testing/replay/recorded-provider.js +108 -0
  631. package/dist/testing/replay/recorded-provider.js.map +1 -0
  632. package/dist/testing/replay/runner.d.ts +56 -0
  633. package/dist/testing/replay/runner.d.ts.map +1 -0
  634. package/dist/testing/replay/runner.js +406 -0
  635. package/dist/testing/replay/runner.js.map +1 -0
  636. package/dist/themes/apex-logo.d.ts +41 -0
  637. package/dist/themes/apex-logo.d.ts.map +1 -0
  638. package/dist/themes/apex-logo.js +35 -0
  639. package/dist/themes/apex-logo.js.map +1 -0
  640. package/dist/utils/apex-code-user-agent.d.ts +2 -0
  641. package/dist/utils/apex-code-user-agent.d.ts.map +1 -0
  642. package/dist/utils/apex-code-user-agent.js +5 -0
  643. package/dist/utils/apex-code-user-agent.js.map +1 -0
  644. package/dist/utils/changelog.d.ts +7 -2
  645. package/dist/utils/changelog.d.ts.map +1 -1
  646. package/dist/utils/changelog.js +82 -13
  647. package/dist/utils/changelog.js.map +1 -1
  648. package/dist/utils/clipboard-image.d.ts.map +1 -1
  649. package/dist/utils/clipboard-image.js +1 -1
  650. package/dist/utils/clipboard-image.js.map +1 -1
  651. package/dist/utils/frontmatter.d.ts.map +1 -1
  652. package/dist/utils/frontmatter.js +2 -1
  653. package/dist/utils/frontmatter.js.map +1 -1
  654. package/dist/utils/management-http.d.ts +5 -3
  655. package/dist/utils/management-http.d.ts.map +1 -1
  656. package/dist/utils/management-http.js +14 -10
  657. package/dist/utils/management-http.js.map +1 -1
  658. package/dist/utils/shell.d.ts +3 -0
  659. package/dist/utils/shell.d.ts.map +1 -1
  660. package/dist/utils/shell.js +17 -5
  661. package/dist/utils/shell.js.map +1 -1
  662. package/dist/utils/syntax-highlight.d.ts +1 -0
  663. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  664. package/dist/utils/syntax-highlight.js +60 -1
  665. package/dist/utils/syntax-highlight.js.map +1 -1
  666. package/dist/utils/text.d.ts +8 -0
  667. package/dist/utils/text.d.ts.map +1 -0
  668. package/dist/utils/text.js +9 -0
  669. package/dist/utils/text.js.map +1 -0
  670. package/dist/utils/tools-manager.d.ts +70 -2
  671. package/dist/utils/tools-manager.d.ts.map +1 -1
  672. package/dist/utils/tools-manager.js +280 -124
  673. package/dist/utils/tools-manager.js.map +1 -1
  674. package/dist/utils/version-check.d.ts +7 -5
  675. package/dist/utils/version-check.d.ts.map +1 -1
  676. package/dist/utils/version-check.js +25 -14
  677. package/dist/utils/version-check.js.map +1 -1
  678. package/dist/utils/windows-self-update.d.ts.map +1 -1
  679. package/dist/utils/windows-self-update.js +1 -1
  680. package/dist/utils/windows-self-update.js.map +1 -1
  681. package/docs/compaction.md +39 -24
  682. package/docs/containerization.md +24 -21
  683. package/docs/custom-provider.md +15 -12
  684. package/docs/development.md +11 -11
  685. package/docs/environment-variables.md +37 -77
  686. package/docs/extensions.md +68 -54
  687. package/docs/index.md +12 -24
  688. package/docs/json.md +18 -11
  689. package/docs/keybindings.md +18 -12
  690. package/docs/llama-cpp.md +9 -7
  691. package/docs/models.md +17 -11
  692. package/docs/packages.md +47 -43
  693. package/docs/prompt-templates.md +3 -3
  694. package/docs/providers.md +10 -10
  695. package/docs/quickstart.md +49 -32
  696. package/docs/rpc.md +35 -18
  697. package/docs/sdk.md +20 -6
  698. package/docs/security.md +18 -18
  699. package/docs/session-format.md +9 -9
  700. package/docs/sessions.md +13 -13
  701. package/docs/settings.md +83 -16
  702. package/docs/shell-aliases.md +1 -1
  703. package/docs/skills.md +66 -15
  704. package/docs/terminal-setup.md +44 -19
  705. package/docs/termux.md +4 -4
  706. package/docs/themes.md +32 -11
  707. package/docs/tmux.md +4 -4
  708. package/docs/tui.md +5 -5
  709. package/docs/usage.md +61 -53
  710. package/docs/windows.md +24 -2
  711. package/examples/extensions/border-status-editor.ts +1 -1
  712. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  713. package/examples/extensions/custom-provider-anthropic/package.json +2 -2
  714. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  715. package/examples/extensions/git-checkpoint.ts +2 -2
  716. package/examples/extensions/gondolin/package-lock.json +2 -2
  717. package/examples/extensions/gondolin/package.json +1 -1
  718. package/examples/extensions/notify.ts +3 -1
  719. package/examples/extensions/sandbox/package-lock.json +2 -2
  720. package/examples/extensions/sandbox/package.json +1 -1
  721. package/examples/extensions/subagent/README.md +3 -1
  722. package/examples/extensions/subagent/agents.ts +40 -9
  723. package/examples/extensions/subagent/index.ts +27 -4
  724. package/examples/extensions/titlebar-spinner.ts +1 -1
  725. package/examples/extensions/with-deps/package-lock.json +2 -2
  726. package/examples/extensions/with-deps/package.json +1 -1
  727. package/examples/rpc-extension-ui.ts +1 -1
  728. package/examples/sdk/06-extensions.ts +1 -1
  729. package/examples/sdk/README.md +1 -1
  730. package/npm-shrinkwrap.json +280 -445
  731. package/package.json +9 -12
  732. package/dist/core/telemetry.d.ts +0 -3
  733. package/dist/core/telemetry.d.ts.map +0 -1
  734. package/dist/core/telemetry.js +0 -9
  735. package/dist/core/telemetry.js.map +0 -1
  736. package/dist/utils/pi-user-agent.d.ts +0 -2
  737. package/dist/utils/pi-user-agent.d.ts.map +0 -1
  738. package/dist/utils/pi-user-agent.js +0 -5
  739. package/dist/utils/pi-user-agent.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/utils/shell.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACpC;AAkDD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,WAAW,CAqDpE;AAED,wBAAgB,WAAW,IAAI,MAAM,CAAC,UAAU,CAY/C;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA8BxD;AAQD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAKlD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAyBjD","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { delimiter } from \"node:path\";\nimport { spawn, spawnSync } from \"child_process\";\nimport { getBinDir } from \"../config.ts\";\n\nexport interface ShellConfig {\n\tshell: string;\n\targs: string[];\n\tcommandTransport?: \"argv\" | \"stdin\";\n}\n\n/**\n * Find bash executable on PATH (cross-platform)\n */\nfunction isLegacyWslBashPath(path: string): boolean {\n\tconst normalized = path.replace(/\\//g, \"\\\\\").toLowerCase();\n\treturn /^[a-z]:\\\\windows\\\\(?:system32|sysnative)\\\\bash\\.exe$/.test(normalized);\n}\n\nfunction getBashShellConfig(shell: string): ShellConfig {\n\treturn isLegacyWslBashPath(shell) ? { shell, args: [\"-s\"], commandTransport: \"stdin\" } : { shell, args: [\"-c\"] };\n}\n\nfunction findBashOnPath(): string | null {\n\tif (process.platform === \"win32\") {\n\t\t// Windows: Use 'where' and verify file exists (where can return non-existent paths)\n\t\ttry {\n\t\t\tconst result = spawnSync(\"where\", [\"bash.exe\"], {\n\t\t\t\tencoding: \"utf-8\",\n\t\t\t\ttimeout: 5000,\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t\tif (result.status === 0 && result.stdout) {\n\t\t\t\tconst firstMatch = result.stdout.trim().split(/\\r?\\n/)[0];\n\t\t\t\tif (firstMatch && existsSync(firstMatch)) {\n\t\t\t\t\treturn firstMatch;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore errors\n\t\t}\n\t\treturn null;\n\t}\n\n\t// Unix: Use 'which' and trust its output (handles Termux and special filesystems)\n\ttry {\n\t\tconst result = spawnSync(\"which\", [\"bash\"], { encoding: \"utf-8\", timeout: 5000 });\n\t\tif (result.status === 0 && result.stdout) {\n\t\t\tconst firstMatch = result.stdout.trim().split(/\\r?\\n/)[0];\n\t\t\tif (firstMatch) {\n\t\t\t\treturn firstMatch;\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Ignore errors\n\t}\n\treturn null;\n}\n\n/**\n * Resolve shell configuration based on platform and an optional explicit shell path.\n * Resolution order:\n * 1. User-specified shellPath\n * 2. On Windows: Git Bash in known locations, then bash on PATH\n * 3. On Unix: /bin/bash, then bash on PATH, then fallback to sh\n */\nexport function getShellConfig(customShellPath?: string): ShellConfig {\n\t// 1. Check user-specified shell path\n\tif (customShellPath) {\n\t\tif (existsSync(customShellPath)) {\n\t\t\treturn getBashShellConfig(customShellPath);\n\t\t}\n\t\tthrow new Error(`Custom shell path not found: ${customShellPath}`);\n\t}\n\n\tif (process.platform === \"win32\") {\n\t\t// 2. Try Git Bash in known locations\n\t\tconst paths: string[] = [];\n\t\tconst programFiles = process.env.ProgramFiles;\n\t\tif (programFiles) {\n\t\t\tpaths.push(`${programFiles}\\\\Git\\\\bin\\\\bash.exe`);\n\t\t}\n\t\tconst programFilesX86 = process.env[\"ProgramFiles(x86)\"];\n\t\tif (programFilesX86) {\n\t\t\tpaths.push(`${programFilesX86}\\\\Git\\\\bin\\\\bash.exe`);\n\t\t}\n\n\t\tfor (const path of paths) {\n\t\t\tif (existsSync(path)) {\n\t\t\t\treturn getBashShellConfig(path);\n\t\t\t}\n\t\t}\n\n\t\t// 3. Fallback: search bash.exe on PATH (Cygwin, MSYS2, WSL, etc.)\n\t\tconst bashOnPath = findBashOnPath();\n\t\tif (bashOnPath) {\n\t\t\treturn getBashShellConfig(bashOnPath);\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`No bash shell found. Options:\\n` +\n\t\t\t\t` 1. Install Git for Windows: https://git-scm.com/download/win\\n` +\n\t\t\t\t` 2. Add your bash to PATH (Cygwin, MSYS2, etc.)\\n` +\n\t\t\t\t\" 3. Set shellPath in settings.json\\n\\n\" +\n\t\t\t\t`Searched Git Bash in:\\n${paths.map((p) => ` ${p}`).join(\"\\n\")}`,\n\t\t);\n\t}\n\n\t// Unix: try /bin/bash, then bash on PATH, then fallback to sh\n\tif (existsSync(\"/bin/bash\")) {\n\t\treturn getBashShellConfig(\"/bin/bash\");\n\t}\n\n\tconst bashOnPath = findBashOnPath();\n\tif (bashOnPath) {\n\t\treturn getBashShellConfig(bashOnPath);\n\t}\n\n\treturn { shell: \"sh\", args: [\"-c\"] };\n}\n\nexport function getShellEnv(): NodeJS.ProcessEnv {\n\tconst binDir = getBinDir();\n\tconst pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === \"path\") ?? \"PATH\";\n\tconst currentPath = process.env[pathKey] ?? \"\";\n\tconst pathEntries = currentPath.split(delimiter).filter(Boolean);\n\tconst hasBinDir = pathEntries.includes(binDir);\n\tconst updatedPath = hasBinDir ? currentPath : [binDir, currentPath].filter(Boolean).join(delimiter);\n\n\treturn {\n\t\t...process.env,\n\t\t[pathKey]: updatedPath,\n\t};\n}\n\n/**\n * Sanitize binary output for display/storage.\n * Removes characters that crash string-width or cause display issues:\n * - Control characters (except tab, newline, carriage return)\n * - Lone surrogates\n * - Unicode Format characters (crash string-width due to a bug)\n * - Characters with undefined code points\n */\nexport function sanitizeBinaryOutput(str: string): string {\n\t// Use Array.from to properly iterate over code points (not code units)\n\t// This handles surrogate pairs correctly and catches edge cases where\n\t// codePointAt() might return undefined\n\treturn Array.from(str)\n\t\t.filter((char) => {\n\t\t\t// Filter out characters that cause string-width to crash\n\t\t\t// This includes:\n\t\t\t// - Unicode format characters\n\t\t\t// - Lone surrogates (already filtered by Array.from)\n\t\t\t// - Control chars except \\t \\n \\r\n\t\t\t// - Characters with undefined code points\n\n\t\t\tconst code = char.codePointAt(0);\n\n\t\t\t// Skip if code point is undefined (edge case with invalid strings)\n\t\t\tif (code === undefined) return false;\n\n\t\t\t// Allow tab, newline, carriage return\n\t\t\tif (code === 0x09 || code === 0x0a || code === 0x0d) return true;\n\n\t\t\t// Filter out control characters (0x00-0x1F, except 0x09, 0x0a, 0x0x0d)\n\t\t\tif (code <= 0x1f) return false;\n\n\t\t\t// Filter out Unicode format characters\n\t\t\tif (code >= 0xfff9 && code <= 0xfffb) return false;\n\n\t\t\treturn true;\n\t\t})\n\t\t.join(\"\");\n}\n\n/**\n * Detached child processes must be tracked so they can be killed on parent\n * shutdown signals (SIGHUP/SIGTERM).\n */\nconst trackedDetachedChildPids = new Set<number>();\n\nexport function trackDetachedChildPid(pid: number): void {\n\ttrackedDetachedChildPids.add(pid);\n}\n\nexport function untrackDetachedChildPid(pid: number): void {\n\ttrackedDetachedChildPids.delete(pid);\n}\n\nexport function killTrackedDetachedChildren(): void {\n\tfor (const pid of trackedDetachedChildPids) {\n\t\tkillProcessTree(pid);\n\t}\n\ttrackedDetachedChildPids.clear();\n}\n\n/**\n * Kill a process and all its children (cross-platform)\n */\nexport function killProcessTree(pid: number): void {\n\tif (process.platform === \"win32\") {\n\t\t// Use taskkill on Windows to kill process tree\n\t\ttry {\n\t\t\tspawn(\"taskkill\", [\"/F\", \"/T\", \"/PID\", String(pid)], {\n\t\t\t\tstdio: \"ignore\",\n\t\t\t\tdetached: true,\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t} catch {\n\t\t\t// Ignore errors if taskkill fails\n\t\t}\n\t} else {\n\t\t// Use SIGKILL on Unix/Linux/Mac\n\t\ttry {\n\t\t\tprocess.kill(-pid, \"SIGKILL\");\n\t\t} catch {\n\t\t\t// Fallback to killing just the child if process group kill fails\n\t\t\ttry {\n\t\t\t\tprocess.kill(pid, \"SIGKILL\");\n\t\t\t} catch {\n\t\t\t\t// Process already dead\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/utils/shell.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACpC;AAkDD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,WAAW,CAqDpE;AAED,eAAO,MAAM,eAAe,sFAAuF,CAAC;AAEpH,6EAA6E;AAC7E,wBAAgB,mBAAmB,IAAI,WAAW,CAWjD;AAED,wBAAgB,WAAW,IAAI,MAAM,CAAC,UAAU,CAY/C;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA8BxD;AAQD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAKlD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAyBjD","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { delimiter } from \"node:path\";\nimport { spawn, spawnSync } from \"child_process\";\nimport { getBinDir } from \"../config.ts\";\n\nexport interface ShellConfig {\n\tshell: string;\n\targs: string[];\n\tcommandTransport?: \"argv\" | \"stdin\";\n}\n\n/**\n * Find bash executable on PATH (cross-platform)\n */\nfunction isLegacyWslBashPath(path: string): boolean {\n\tconst normalized = path.replace(/\\//g, \"\\\\\").toLowerCase();\n\treturn /^[a-z]:\\\\windows\\\\(?:system32|sysnative)\\\\bash\\.exe$/.test(normalized);\n}\n\nfunction getBashShellConfig(shell: string): ShellConfig {\n\treturn isLegacyWslBashPath(shell) ? { shell, args: [\"-s\"], commandTransport: \"stdin\" } : { shell, args: [\"-c\"] };\n}\n\nfunction findExecutableOnPath(executable: string): string | null {\n\tif (process.platform === \"win32\") {\n\t\t// Windows: Use 'where' and verify file exists (where can return non-existent paths)\n\t\ttry {\n\t\t\tconst result = spawnSync(\"where\", [executable], {\n\t\t\t\tencoding: \"utf-8\",\n\t\t\t\ttimeout: 5000,\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t\tif (result.status === 0 && result.stdout) {\n\t\t\t\tconst firstMatch = result.stdout.trim().split(/\\r?\\n/)[0];\n\t\t\t\tif (firstMatch && existsSync(firstMatch)) {\n\t\t\t\t\treturn firstMatch;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore errors\n\t\t}\n\t\treturn null;\n\t}\n\n\t// Unix: Use 'which' and trust its output (handles Termux and special filesystems)\n\ttry {\n\t\tconst result = spawnSync(\"which\", [executable], { encoding: \"utf-8\", timeout: 5000 });\n\t\tif (result.status === 0 && result.stdout) {\n\t\t\tconst firstMatch = result.stdout.trim().split(/\\r?\\n/)[0];\n\t\t\tif (firstMatch) {\n\t\t\t\treturn firstMatch;\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Ignore errors\n\t}\n\treturn null;\n}\n\n/**\n * Resolve shell configuration based on platform and an optional explicit shell path.\n * Resolution order:\n * 1. User-specified shellPath\n * 2. On Windows: Git Bash in known locations, then bash on PATH\n * 3. On Unix: /bin/bash, then bash on PATH, then fallback to sh\n */\nexport function getShellConfig(customShellPath?: string): ShellConfig {\n\t// 1. Check user-specified shell path\n\tif (customShellPath) {\n\t\tif (existsSync(customShellPath)) {\n\t\t\treturn getBashShellConfig(customShellPath);\n\t\t}\n\t\tthrow new Error(`Custom shell path not found: ${customShellPath}`);\n\t}\n\n\tif (process.platform === \"win32\") {\n\t\t// 2. Try Git Bash in known locations\n\t\tconst paths: string[] = [];\n\t\tconst programFiles = process.env.ProgramFiles;\n\t\tif (programFiles) {\n\t\t\tpaths.push(`${programFiles}\\\\Git\\\\bin\\\\bash.exe`);\n\t\t}\n\t\tconst programFilesX86 = process.env[\"ProgramFiles(x86)\"];\n\t\tif (programFilesX86) {\n\t\t\tpaths.push(`${programFilesX86}\\\\Git\\\\bin\\\\bash.exe`);\n\t\t}\n\n\t\tfor (const path of paths) {\n\t\t\tif (existsSync(path)) {\n\t\t\t\treturn getBashShellConfig(path);\n\t\t\t}\n\t\t}\n\n\t\t// 3. Fallback: search bash.exe on PATH (Cygwin, MSYS2, WSL, etc.)\n\t\tconst bashOnPath = findExecutableOnPath(\"bash.exe\");\n\t\tif (bashOnPath) {\n\t\t\treturn getBashShellConfig(bashOnPath);\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`No bash shell found. Options:\\n` +\n\t\t\t\t` 1. Install Git for Windows: https://git-scm.com/download/win\\n` +\n\t\t\t\t` 2. Add your bash to PATH (Cygwin, MSYS2, etc.)\\n` +\n\t\t\t\t\" 3. Set shellPath in settings.json\\n\\n\" +\n\t\t\t\t`Searched Git Bash in:\\n${paths.map((p) => ` ${p}`).join(\"\\n\")}`,\n\t\t);\n\t}\n\n\t// Unix: try /bin/bash, then bash on PATH, then fallback to sh\n\tif (existsSync(\"/bin/bash\")) {\n\t\treturn getBashShellConfig(\"/bin/bash\");\n\t}\n\n\tconst bashOnPath = findExecutableOnPath(\"bash\");\n\tif (bashOnPath) {\n\t\treturn getBashShellConfig(bashOnPath);\n\t}\n\n\treturn { shell: \"sh\", args: [\"-c\"] };\n}\n\nexport const POWERSHELL_ARGS = [\"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\"] as const;\n\n/** Resolve PowerShell on Windows, preferring PowerShell 7 when available. */\nexport function getPowerShellConfig(): ShellConfig {\n\tif (process.platform !== \"win32\") {\n\t\tthrow new Error(\"The powershell tool is only available on Windows.\");\n\t}\n\n\tconst shell = findExecutableOnPath(\"pwsh.exe\") ?? findExecutableOnPath(\"powershell.exe\");\n\tif (!shell) {\n\t\tthrow new Error(\"No PowerShell executable found. Install PowerShell or add powershell.exe/pwsh.exe to PATH.\");\n\t}\n\n\treturn { shell, args: [...POWERSHELL_ARGS] };\n}\n\nexport function getShellEnv(): NodeJS.ProcessEnv {\n\tconst binDir = getBinDir();\n\tconst pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === \"path\") ?? \"PATH\";\n\tconst currentPath = process.env[pathKey] ?? \"\";\n\tconst pathEntries = currentPath.split(delimiter).filter(Boolean);\n\tconst hasBinDir = pathEntries.includes(binDir);\n\tconst updatedPath = hasBinDir ? currentPath : [binDir, currentPath].filter(Boolean).join(delimiter);\n\n\treturn {\n\t\t...process.env,\n\t\t[pathKey]: updatedPath,\n\t};\n}\n\n/**\n * Sanitize binary output for display/storage.\n * Removes characters that crash string-width or cause display issues:\n * - Control characters (except tab, newline, carriage return)\n * - Lone surrogates\n * - Unicode Format characters (crash string-width due to a bug)\n * - Characters with undefined code points\n */\nexport function sanitizeBinaryOutput(str: string): string {\n\t// Use Array.from to properly iterate over code points (not code units)\n\t// This handles surrogate pairs correctly and catches edge cases where\n\t// codePointAt() might return undefined\n\treturn Array.from(str)\n\t\t.filter((char) => {\n\t\t\t// Filter out characters that cause string-width to crash\n\t\t\t// This includes:\n\t\t\t// - Unicode format characters\n\t\t\t// - Lone surrogates (already filtered by Array.from)\n\t\t\t// - Control chars except \\t \\n \\r\n\t\t\t// - Characters with undefined code points\n\n\t\t\tconst code = char.codePointAt(0);\n\n\t\t\t// Skip if code point is undefined (edge case with invalid strings)\n\t\t\tif (code === undefined) return false;\n\n\t\t\t// Allow tab, newline, carriage return\n\t\t\tif (code === 0x09 || code === 0x0a || code === 0x0d) return true;\n\n\t\t\t// Filter out control characters (0x00-0x1F, except 0x09, 0x0a, 0x0x0d)\n\t\t\tif (code <= 0x1f) return false;\n\n\t\t\t// Filter out Unicode format characters\n\t\t\tif (code >= 0xfff9 && code <= 0xfffb) return false;\n\n\t\t\treturn true;\n\t\t})\n\t\t.join(\"\");\n}\n\n/**\n * Detached child processes must be tracked so they can be killed on parent\n * shutdown signals (SIGHUP/SIGTERM).\n */\nconst trackedDetachedChildPids = new Set<number>();\n\nexport function trackDetachedChildPid(pid: number): void {\n\ttrackedDetachedChildPids.add(pid);\n}\n\nexport function untrackDetachedChildPid(pid: number): void {\n\ttrackedDetachedChildPids.delete(pid);\n}\n\nexport function killTrackedDetachedChildren(): void {\n\tfor (const pid of trackedDetachedChildPids) {\n\t\tkillProcessTree(pid);\n\t}\n\ttrackedDetachedChildPids.clear();\n}\n\n/**\n * Kill a process and all its children (cross-platform)\n */\nexport function killProcessTree(pid: number): void {\n\tif (process.platform === \"win32\") {\n\t\t// Use taskkill on Windows to kill process tree\n\t\ttry {\n\t\t\tspawn(\"taskkill\", [\"/F\", \"/T\", \"/PID\", String(pid)], {\n\t\t\t\tstdio: \"ignore\",\n\t\t\t\tdetached: true,\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t} catch {\n\t\t\t// Ignore errors if taskkill fails\n\t\t}\n\t} else {\n\t\t// Use SIGKILL on Unix/Linux/Mac\n\t\ttry {\n\t\t\tprocess.kill(-pid, \"SIGKILL\");\n\t\t} catch {\n\t\t\t// Fallback to killing just the child if process group kill fails\n\t\t\ttry {\n\t\t\t\tprocess.kill(pid, \"SIGKILL\");\n\t\t\t} catch {\n\t\t\t\t// Process already dead\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -12,11 +12,11 @@ function isLegacyWslBashPath(path) {
12
12
  function getBashShellConfig(shell) {
13
13
  return isLegacyWslBashPath(shell) ? { shell, args: ["-s"], commandTransport: "stdin" } : { shell, args: ["-c"] };
14
14
  }
15
- function findBashOnPath() {
15
+ function findExecutableOnPath(executable) {
16
16
  if (process.platform === "win32") {
17
17
  // Windows: Use 'where' and verify file exists (where can return non-existent paths)
18
18
  try {
19
- const result = spawnSync("where", ["bash.exe"], {
19
+ const result = spawnSync("where", [executable], {
20
20
  encoding: "utf-8",
21
21
  timeout: 5000,
22
22
  windowsHide: true,
@@ -35,7 +35,7 @@ function findBashOnPath() {
35
35
  }
36
36
  // Unix: Use 'which' and trust its output (handles Termux and special filesystems)
37
37
  try {
38
- const result = spawnSync("which", ["bash"], { encoding: "utf-8", timeout: 5000 });
38
+ const result = spawnSync("which", [executable], { encoding: "utf-8", timeout: 5000 });
39
39
  if (result.status === 0 && result.stdout) {
40
40
  const firstMatch = result.stdout.trim().split(/\r?\n/)[0];
41
41
  if (firstMatch) {
@@ -80,7 +80,7 @@ export function getShellConfig(customShellPath) {
80
80
  }
81
81
  }
82
82
  // 3. Fallback: search bash.exe on PATH (Cygwin, MSYS2, WSL, etc.)
83
- const bashOnPath = findBashOnPath();
83
+ const bashOnPath = findExecutableOnPath("bash.exe");
84
84
  if (bashOnPath) {
85
85
  return getBashShellConfig(bashOnPath);
86
86
  }
@@ -94,12 +94,24 @@ export function getShellConfig(customShellPath) {
94
94
  if (existsSync("/bin/bash")) {
95
95
  return getBashShellConfig("/bin/bash");
96
96
  }
97
- const bashOnPath = findBashOnPath();
97
+ const bashOnPath = findExecutableOnPath("bash");
98
98
  if (bashOnPath) {
99
99
  return getBashShellConfig(bashOnPath);
100
100
  }
101
101
  return { shell: "sh", args: ["-c"] };
102
102
  }
103
+ export const POWERSHELL_ARGS = ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command"];
104
+ /** Resolve PowerShell on Windows, preferring PowerShell 7 when available. */
105
+ export function getPowerShellConfig() {
106
+ if (process.platform !== "win32") {
107
+ throw new Error("The powershell tool is only available on Windows.");
108
+ }
109
+ const shell = findExecutableOnPath("pwsh.exe") ?? findExecutableOnPath("powershell.exe");
110
+ if (!shell) {
111
+ throw new Error("No PowerShell executable found. Install PowerShell or add powershell.exe/pwsh.exe to PATH.");
112
+ }
113
+ return { shell, args: [...POWERSHELL_ARGS] };
114
+ }
103
115
  export function getShellEnv() {
104
116
  const binDir = getBinDir();
105
117
  const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path") ?? "PATH";
@@ -1 +1 @@
1
- {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/utils/shell.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { delimiter } from \"node:path\";\nimport { spawn, spawnSync } from \"child_process\";\nimport { getBinDir } from \"../config.ts\";\n\nexport interface ShellConfig {\n\tshell: string;\n\targs: string[];\n\tcommandTransport?: \"argv\" | \"stdin\";\n}\n\n/**\n * Find bash executable on PATH (cross-platform)\n */\nfunction isLegacyWslBashPath(path: string): boolean {\n\tconst normalized = path.replace(/\\//g, \"\\\\\").toLowerCase();\n\treturn /^[a-z]:\\\\windows\\\\(?:system32|sysnative)\\\\bash\\.exe$/.test(normalized);\n}\n\nfunction getBashShellConfig(shell: string): ShellConfig {\n\treturn isLegacyWslBashPath(shell) ? { shell, args: [\"-s\"], commandTransport: \"stdin\" } : { shell, args: [\"-c\"] };\n}\n\nfunction findBashOnPath(): string | null {\n\tif (process.platform === \"win32\") {\n\t\t// Windows: Use 'where' and verify file exists (where can return non-existent paths)\n\t\ttry {\n\t\t\tconst result = spawnSync(\"where\", [\"bash.exe\"], {\n\t\t\t\tencoding: \"utf-8\",\n\t\t\t\ttimeout: 5000,\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t\tif (result.status === 0 && result.stdout) {\n\t\t\t\tconst firstMatch = result.stdout.trim().split(/\\r?\\n/)[0];\n\t\t\t\tif (firstMatch && existsSync(firstMatch)) {\n\t\t\t\t\treturn firstMatch;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore errors\n\t\t}\n\t\treturn null;\n\t}\n\n\t// Unix: Use 'which' and trust its output (handles Termux and special filesystems)\n\ttry {\n\t\tconst result = spawnSync(\"which\", [\"bash\"], { encoding: \"utf-8\", timeout: 5000 });\n\t\tif (result.status === 0 && result.stdout) {\n\t\t\tconst firstMatch = result.stdout.trim().split(/\\r?\\n/)[0];\n\t\t\tif (firstMatch) {\n\t\t\t\treturn firstMatch;\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Ignore errors\n\t}\n\treturn null;\n}\n\n/**\n * Resolve shell configuration based on platform and an optional explicit shell path.\n * Resolution order:\n * 1. User-specified shellPath\n * 2. On Windows: Git Bash in known locations, then bash on PATH\n * 3. On Unix: /bin/bash, then bash on PATH, then fallback to sh\n */\nexport function getShellConfig(customShellPath?: string): ShellConfig {\n\t// 1. Check user-specified shell path\n\tif (customShellPath) {\n\t\tif (existsSync(customShellPath)) {\n\t\t\treturn getBashShellConfig(customShellPath);\n\t\t}\n\t\tthrow new Error(`Custom shell path not found: ${customShellPath}`);\n\t}\n\n\tif (process.platform === \"win32\") {\n\t\t// 2. Try Git Bash in known locations\n\t\tconst paths: string[] = [];\n\t\tconst programFiles = process.env.ProgramFiles;\n\t\tif (programFiles) {\n\t\t\tpaths.push(`${programFiles}\\\\Git\\\\bin\\\\bash.exe`);\n\t\t}\n\t\tconst programFilesX86 = process.env[\"ProgramFiles(x86)\"];\n\t\tif (programFilesX86) {\n\t\t\tpaths.push(`${programFilesX86}\\\\Git\\\\bin\\\\bash.exe`);\n\t\t}\n\n\t\tfor (const path of paths) {\n\t\t\tif (existsSync(path)) {\n\t\t\t\treturn getBashShellConfig(path);\n\t\t\t}\n\t\t}\n\n\t\t// 3. Fallback: search bash.exe on PATH (Cygwin, MSYS2, WSL, etc.)\n\t\tconst bashOnPath = findBashOnPath();\n\t\tif (bashOnPath) {\n\t\t\treturn getBashShellConfig(bashOnPath);\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`No bash shell found. Options:\\n` +\n\t\t\t\t` 1. Install Git for Windows: https://git-scm.com/download/win\\n` +\n\t\t\t\t` 2. Add your bash to PATH (Cygwin, MSYS2, etc.)\\n` +\n\t\t\t\t\" 3. Set shellPath in settings.json\\n\\n\" +\n\t\t\t\t`Searched Git Bash in:\\n${paths.map((p) => ` ${p}`).join(\"\\n\")}`,\n\t\t);\n\t}\n\n\t// Unix: try /bin/bash, then bash on PATH, then fallback to sh\n\tif (existsSync(\"/bin/bash\")) {\n\t\treturn getBashShellConfig(\"/bin/bash\");\n\t}\n\n\tconst bashOnPath = findBashOnPath();\n\tif (bashOnPath) {\n\t\treturn getBashShellConfig(bashOnPath);\n\t}\n\n\treturn { shell: \"sh\", args: [\"-c\"] };\n}\n\nexport function getShellEnv(): NodeJS.ProcessEnv {\n\tconst binDir = getBinDir();\n\tconst pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === \"path\") ?? \"PATH\";\n\tconst currentPath = process.env[pathKey] ?? \"\";\n\tconst pathEntries = currentPath.split(delimiter).filter(Boolean);\n\tconst hasBinDir = pathEntries.includes(binDir);\n\tconst updatedPath = hasBinDir ? currentPath : [binDir, currentPath].filter(Boolean).join(delimiter);\n\n\treturn {\n\t\t...process.env,\n\t\t[pathKey]: updatedPath,\n\t};\n}\n\n/**\n * Sanitize binary output for display/storage.\n * Removes characters that crash string-width or cause display issues:\n * - Control characters (except tab, newline, carriage return)\n * - Lone surrogates\n * - Unicode Format characters (crash string-width due to a bug)\n * - Characters with undefined code points\n */\nexport function sanitizeBinaryOutput(str: string): string {\n\t// Use Array.from to properly iterate over code points (not code units)\n\t// This handles surrogate pairs correctly and catches edge cases where\n\t// codePointAt() might return undefined\n\treturn Array.from(str)\n\t\t.filter((char) => {\n\t\t\t// Filter out characters that cause string-width to crash\n\t\t\t// This includes:\n\t\t\t// - Unicode format characters\n\t\t\t// - Lone surrogates (already filtered by Array.from)\n\t\t\t// - Control chars except \\t \\n \\r\n\t\t\t// - Characters with undefined code points\n\n\t\t\tconst code = char.codePointAt(0);\n\n\t\t\t// Skip if code point is undefined (edge case with invalid strings)\n\t\t\tif (code === undefined) return false;\n\n\t\t\t// Allow tab, newline, carriage return\n\t\t\tif (code === 0x09 || code === 0x0a || code === 0x0d) return true;\n\n\t\t\t// Filter out control characters (0x00-0x1F, except 0x09, 0x0a, 0x0x0d)\n\t\t\tif (code <= 0x1f) return false;\n\n\t\t\t// Filter out Unicode format characters\n\t\t\tif (code >= 0xfff9 && code <= 0xfffb) return false;\n\n\t\t\treturn true;\n\t\t})\n\t\t.join(\"\");\n}\n\n/**\n * Detached child processes must be tracked so they can be killed on parent\n * shutdown signals (SIGHUP/SIGTERM).\n */\nconst trackedDetachedChildPids = new Set<number>();\n\nexport function trackDetachedChildPid(pid: number): void {\n\ttrackedDetachedChildPids.add(pid);\n}\n\nexport function untrackDetachedChildPid(pid: number): void {\n\ttrackedDetachedChildPids.delete(pid);\n}\n\nexport function killTrackedDetachedChildren(): void {\n\tfor (const pid of trackedDetachedChildPids) {\n\t\tkillProcessTree(pid);\n\t}\n\ttrackedDetachedChildPids.clear();\n}\n\n/**\n * Kill a process and all its children (cross-platform)\n */\nexport function killProcessTree(pid: number): void {\n\tif (process.platform === \"win32\") {\n\t\t// Use taskkill on Windows to kill process tree\n\t\ttry {\n\t\t\tspawn(\"taskkill\", [\"/F\", \"/T\", \"/PID\", String(pid)], {\n\t\t\t\tstdio: \"ignore\",\n\t\t\t\tdetached: true,\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t} catch {\n\t\t\t// Ignore errors if taskkill fails\n\t\t}\n\t} else {\n\t\t// Use SIGKILL on Unix/Linux/Mac\n\t\ttry {\n\t\t\tprocess.kill(-pid, \"SIGKILL\");\n\t\t} catch {\n\t\t\t// Fallback to killing just the child if process group kill fails\n\t\t\ttry {\n\t\t\t\tprocess.kill(pid, \"SIGKILL\");\n\t\t\t} catch {\n\t\t\t\t// Process already dead\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/utils/shell.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { delimiter } from \"node:path\";\nimport { spawn, spawnSync } from \"child_process\";\nimport { getBinDir } from \"../config.ts\";\n\nexport interface ShellConfig {\n\tshell: string;\n\targs: string[];\n\tcommandTransport?: \"argv\" | \"stdin\";\n}\n\n/**\n * Find bash executable on PATH (cross-platform)\n */\nfunction isLegacyWslBashPath(path: string): boolean {\n\tconst normalized = path.replace(/\\//g, \"\\\\\").toLowerCase();\n\treturn /^[a-z]:\\\\windows\\\\(?:system32|sysnative)\\\\bash\\.exe$/.test(normalized);\n}\n\nfunction getBashShellConfig(shell: string): ShellConfig {\n\treturn isLegacyWslBashPath(shell) ? { shell, args: [\"-s\"], commandTransport: \"stdin\" } : { shell, args: [\"-c\"] };\n}\n\nfunction findExecutableOnPath(executable: string): string | null {\n\tif (process.platform === \"win32\") {\n\t\t// Windows: Use 'where' and verify file exists (where can return non-existent paths)\n\t\ttry {\n\t\t\tconst result = spawnSync(\"where\", [executable], {\n\t\t\t\tencoding: \"utf-8\",\n\t\t\t\ttimeout: 5000,\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t\tif (result.status === 0 && result.stdout) {\n\t\t\t\tconst firstMatch = result.stdout.trim().split(/\\r?\\n/)[0];\n\t\t\t\tif (firstMatch && existsSync(firstMatch)) {\n\t\t\t\t\treturn firstMatch;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore errors\n\t\t}\n\t\treturn null;\n\t}\n\n\t// Unix: Use 'which' and trust its output (handles Termux and special filesystems)\n\ttry {\n\t\tconst result = spawnSync(\"which\", [executable], { encoding: \"utf-8\", timeout: 5000 });\n\t\tif (result.status === 0 && result.stdout) {\n\t\t\tconst firstMatch = result.stdout.trim().split(/\\r?\\n/)[0];\n\t\t\tif (firstMatch) {\n\t\t\t\treturn firstMatch;\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Ignore errors\n\t}\n\treturn null;\n}\n\n/**\n * Resolve shell configuration based on platform and an optional explicit shell path.\n * Resolution order:\n * 1. User-specified shellPath\n * 2. On Windows: Git Bash in known locations, then bash on PATH\n * 3. On Unix: /bin/bash, then bash on PATH, then fallback to sh\n */\nexport function getShellConfig(customShellPath?: string): ShellConfig {\n\t// 1. Check user-specified shell path\n\tif (customShellPath) {\n\t\tif (existsSync(customShellPath)) {\n\t\t\treturn getBashShellConfig(customShellPath);\n\t\t}\n\t\tthrow new Error(`Custom shell path not found: ${customShellPath}`);\n\t}\n\n\tif (process.platform === \"win32\") {\n\t\t// 2. Try Git Bash in known locations\n\t\tconst paths: string[] = [];\n\t\tconst programFiles = process.env.ProgramFiles;\n\t\tif (programFiles) {\n\t\t\tpaths.push(`${programFiles}\\\\Git\\\\bin\\\\bash.exe`);\n\t\t}\n\t\tconst programFilesX86 = process.env[\"ProgramFiles(x86)\"];\n\t\tif (programFilesX86) {\n\t\t\tpaths.push(`${programFilesX86}\\\\Git\\\\bin\\\\bash.exe`);\n\t\t}\n\n\t\tfor (const path of paths) {\n\t\t\tif (existsSync(path)) {\n\t\t\t\treturn getBashShellConfig(path);\n\t\t\t}\n\t\t}\n\n\t\t// 3. Fallback: search bash.exe on PATH (Cygwin, MSYS2, WSL, etc.)\n\t\tconst bashOnPath = findExecutableOnPath(\"bash.exe\");\n\t\tif (bashOnPath) {\n\t\t\treturn getBashShellConfig(bashOnPath);\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`No bash shell found. Options:\\n` +\n\t\t\t\t` 1. Install Git for Windows: https://git-scm.com/download/win\\n` +\n\t\t\t\t` 2. Add your bash to PATH (Cygwin, MSYS2, etc.)\\n` +\n\t\t\t\t\" 3. Set shellPath in settings.json\\n\\n\" +\n\t\t\t\t`Searched Git Bash in:\\n${paths.map((p) => ` ${p}`).join(\"\\n\")}`,\n\t\t);\n\t}\n\n\t// Unix: try /bin/bash, then bash on PATH, then fallback to sh\n\tif (existsSync(\"/bin/bash\")) {\n\t\treturn getBashShellConfig(\"/bin/bash\");\n\t}\n\n\tconst bashOnPath = findExecutableOnPath(\"bash\");\n\tif (bashOnPath) {\n\t\treturn getBashShellConfig(bashOnPath);\n\t}\n\n\treturn { shell: \"sh\", args: [\"-c\"] };\n}\n\nexport const POWERSHELL_ARGS = [\"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\"] as const;\n\n/** Resolve PowerShell on Windows, preferring PowerShell 7 when available. */\nexport function getPowerShellConfig(): ShellConfig {\n\tif (process.platform !== \"win32\") {\n\t\tthrow new Error(\"The powershell tool is only available on Windows.\");\n\t}\n\n\tconst shell = findExecutableOnPath(\"pwsh.exe\") ?? findExecutableOnPath(\"powershell.exe\");\n\tif (!shell) {\n\t\tthrow new Error(\"No PowerShell executable found. Install PowerShell or add powershell.exe/pwsh.exe to PATH.\");\n\t}\n\n\treturn { shell, args: [...POWERSHELL_ARGS] };\n}\n\nexport function getShellEnv(): NodeJS.ProcessEnv {\n\tconst binDir = getBinDir();\n\tconst pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === \"path\") ?? \"PATH\";\n\tconst currentPath = process.env[pathKey] ?? \"\";\n\tconst pathEntries = currentPath.split(delimiter).filter(Boolean);\n\tconst hasBinDir = pathEntries.includes(binDir);\n\tconst updatedPath = hasBinDir ? currentPath : [binDir, currentPath].filter(Boolean).join(delimiter);\n\n\treturn {\n\t\t...process.env,\n\t\t[pathKey]: updatedPath,\n\t};\n}\n\n/**\n * Sanitize binary output for display/storage.\n * Removes characters that crash string-width or cause display issues:\n * - Control characters (except tab, newline, carriage return)\n * - Lone surrogates\n * - Unicode Format characters (crash string-width due to a bug)\n * - Characters with undefined code points\n */\nexport function sanitizeBinaryOutput(str: string): string {\n\t// Use Array.from to properly iterate over code points (not code units)\n\t// This handles surrogate pairs correctly and catches edge cases where\n\t// codePointAt() might return undefined\n\treturn Array.from(str)\n\t\t.filter((char) => {\n\t\t\t// Filter out characters that cause string-width to crash\n\t\t\t// This includes:\n\t\t\t// - Unicode format characters\n\t\t\t// - Lone surrogates (already filtered by Array.from)\n\t\t\t// - Control chars except \\t \\n \\r\n\t\t\t// - Characters with undefined code points\n\n\t\t\tconst code = char.codePointAt(0);\n\n\t\t\t// Skip if code point is undefined (edge case with invalid strings)\n\t\t\tif (code === undefined) return false;\n\n\t\t\t// Allow tab, newline, carriage return\n\t\t\tif (code === 0x09 || code === 0x0a || code === 0x0d) return true;\n\n\t\t\t// Filter out control characters (0x00-0x1F, except 0x09, 0x0a, 0x0x0d)\n\t\t\tif (code <= 0x1f) return false;\n\n\t\t\t// Filter out Unicode format characters\n\t\t\tif (code >= 0xfff9 && code <= 0xfffb) return false;\n\n\t\t\treturn true;\n\t\t})\n\t\t.join(\"\");\n}\n\n/**\n * Detached child processes must be tracked so they can be killed on parent\n * shutdown signals (SIGHUP/SIGTERM).\n */\nconst trackedDetachedChildPids = new Set<number>();\n\nexport function trackDetachedChildPid(pid: number): void {\n\ttrackedDetachedChildPids.add(pid);\n}\n\nexport function untrackDetachedChildPid(pid: number): void {\n\ttrackedDetachedChildPids.delete(pid);\n}\n\nexport function killTrackedDetachedChildren(): void {\n\tfor (const pid of trackedDetachedChildPids) {\n\t\tkillProcessTree(pid);\n\t}\n\ttrackedDetachedChildPids.clear();\n}\n\n/**\n * Kill a process and all its children (cross-platform)\n */\nexport function killProcessTree(pid: number): void {\n\tif (process.platform === \"win32\") {\n\t\t// Use taskkill on Windows to kill process tree\n\t\ttry {\n\t\t\tspawn(\"taskkill\", [\"/F\", \"/T\", \"/PID\", String(pid)], {\n\t\t\t\tstdio: \"ignore\",\n\t\t\t\tdetached: true,\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t} catch {\n\t\t\t// Ignore errors if taskkill fails\n\t\t}\n\t} else {\n\t\t// Use SIGKILL on Unix/Linux/Mac\n\t\ttry {\n\t\t\tprocess.kill(-pid, \"SIGKILL\");\n\t\t} catch {\n\t\t\t// Fallback to killing just the child if process group kill fails\n\t\t\ttry {\n\t\t\t\tprocess.kill(pid, \"SIGKILL\");\n\t\t\t} catch {\n\t\t\t\t// Process already dead\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -1,3 +1,4 @@
1
+ export declare function loadAllHighlightLanguages(): Promise<void>;
1
2
  export type HighlightFormatter = (text: string) => string;
2
3
  export type HighlightTheme = Partial<Record<string, HighlightFormatter>>;
3
4
  export interface HighlightOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"syntax-highlight.d.ts","sourceRoot":"","sources":["../../src/utils/syntax-highlight.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;AAC1D,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAEzE,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,cAAc,CAAC;CACvB;AAoED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,cAAmB,GAAG,MAAM,CAoDtF;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAQ9E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD","sourcesContent":["import hljs from \"highlight.js/lib/index.js\";\nimport { decodeHtmlEntityAt } from \"./html.ts\";\n\nexport type HighlightFormatter = (text: string) => string;\nexport type HighlightTheme = Partial<Record<string, HighlightFormatter>>;\n\nexport interface HighlightOptions {\n\tlanguage?: string;\n\tignoreIllegals?: boolean;\n\tlanguageSubset?: string[];\n\ttheme?: HighlightTheme;\n}\n\nconst SPAN_CLOSE = \"</span>\";\nconst HIGHLIGHT_CLASS_PREFIX = \"hljs-\";\n\nfunction getScopeFromSpanTag(tag: string): string | undefined {\n\tconst match = /\\sclass\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/.exec(tag);\n\tconst classValue = match?.[1] ?? match?.[2];\n\tif (!classValue) {\n\t\treturn undefined;\n\t}\n\n\tfor (const className of classValue.split(/\\s+/)) {\n\t\tif (className.startsWith(HIGHLIGHT_CLASS_PREFIX)) {\n\t\t\treturn className.slice(HIGHLIGHT_CLASS_PREFIX.length);\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction getScopeFormatter(scope: string, theme: HighlightTheme): HighlightFormatter | undefined {\n\tconst exact = theme[scope];\n\tif (exact) {\n\t\treturn exact;\n\t}\n\n\tconst dotIndex = scope.indexOf(\".\");\n\tif (dotIndex !== -1) {\n\t\tconst prefixFormatter = theme[scope.slice(0, dotIndex)];\n\t\tif (prefixFormatter) {\n\t\t\treturn prefixFormatter;\n\t\t}\n\t}\n\n\tconst dashIndex = scope.indexOf(\"-\");\n\tif (dashIndex !== -1) {\n\t\tconst prefixFormatter = theme[scope.slice(0, dashIndex)];\n\t\tif (prefixFormatter) {\n\t\t\treturn prefixFormatter;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction getActiveFormatter(scopes: Array<string | undefined>, theme: HighlightTheme): HighlightFormatter | undefined {\n\tfor (let i = scopes.length - 1; i >= 0; i--) {\n\t\tconst scope = scopes[i];\n\t\tif (!scope) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst formatter = getScopeFormatter(scope, theme);\n\t\tif (formatter) {\n\t\t\treturn formatter;\n\t\t}\n\t}\n\treturn theme.default;\n}\n\nfunction isSpanOpenTagStart(html: string, index: number): boolean {\n\tif (!html.startsWith(\"<span\", index)) {\n\t\treturn false;\n\t}\n\tconst nextChar = html[index + \"<span\".length];\n\treturn nextChar === \">\" || nextChar === \" \" || nextChar === \"\\t\" || nextChar === \"\\n\" || nextChar === \"\\r\";\n}\n\nexport function renderHighlightedHtml(html: string, theme: HighlightTheme = {}): string {\n\tlet output = \"\";\n\tlet textBuffer = \"\";\n\tconst scopes: Array<string | undefined> = [];\n\n\tconst flushText = () => {\n\t\tif (!textBuffer) {\n\t\t\treturn;\n\t\t}\n\t\tconst formatter = getActiveFormatter(scopes, theme);\n\t\toutput += formatter ? formatter(textBuffer) : textBuffer;\n\t\ttextBuffer = \"\";\n\t};\n\n\tlet index = 0;\n\twhile (index < html.length) {\n\t\tif (isSpanOpenTagStart(html, index)) {\n\t\t\tconst tagEndIndex = html.indexOf(\">\", index + 5);\n\t\t\tif (tagEndIndex !== -1) {\n\t\t\t\tflushText();\n\t\t\t\tconst tag = html.slice(index, tagEndIndex + 1);\n\t\t\t\tconst scope = getScopeFromSpanTag(tag);\n\t\t\t\tscopes.push(scope);\n\t\t\t\tindex = tagEndIndex + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (html.startsWith(SPAN_CLOSE, index)) {\n\t\t\tflushText();\n\t\t\tif (scopes.length > 0) {\n\t\t\t\tscopes.pop();\n\t\t\t}\n\t\t\tindex += SPAN_CLOSE.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (html[index] === \"&\") {\n\t\t\tconst decoded = decodeHtmlEntityAt(html, index);\n\t\t\tif (decoded) {\n\t\t\t\ttextBuffer += decoded.text;\n\t\t\t\tindex += decoded.length;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\ttextBuffer += html[index];\n\t\tindex++;\n\t}\n\n\tflushText();\n\treturn output;\n}\n\nexport function highlight(code: string, options: HighlightOptions = {}): string {\n\tconst html = options.language\n\t\t? hljs.highlight(code, {\n\t\t\t\tlanguage: options.language,\n\t\t\t\tignoreIllegals: options.ignoreIllegals,\n\t\t\t}).value\n\t\t: hljs.highlightAuto(code, options.languageSubset).value;\n\treturn renderHighlightedHtml(html, options.theme);\n}\n\nexport function supportsLanguage(name: string): boolean {\n\treturn hljs.getLanguage(name) !== undefined;\n}\n"]}
1
+ {"version":3,"file":"syntax-highlight.d.ts","sourceRoot":"","sources":["../../src/utils/syntax-highlight.ts"],"names":[],"mappings":"AAoDA,wBAAgB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAezD;AAED,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;AAC1D,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAEzE,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,cAAc,CAAC;CACvB;AAoED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,cAAmB,GAAG,MAAM,CAoDtF;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAQ9E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD","sourcesContent":["import hljs from \"highlight.js/lib/core.js\";\nimport bash from \"highlight.js/lib/languages/bash.js\";\nimport c from \"highlight.js/lib/languages/c.js\";\nimport cpp from \"highlight.js/lib/languages/cpp.js\";\nimport csharp from \"highlight.js/lib/languages/csharp.js\";\nimport dart from \"highlight.js/lib/languages/dart.js\";\nimport go from \"highlight.js/lib/languages/go.js\";\nimport groovy from \"highlight.js/lib/languages/groovy.js\";\nimport java from \"highlight.js/lib/languages/java.js\";\nimport javascript from \"highlight.js/lib/languages/javascript.js\";\nimport kotlin from \"highlight.js/lib/languages/kotlin.js\";\nimport lua from \"highlight.js/lib/languages/lua.js\";\nimport nix from \"highlight.js/lib/languages/nix.js\";\nimport perl from \"highlight.js/lib/languages/perl.js\";\nimport php from \"highlight.js/lib/languages/php.js\";\nimport python from \"highlight.js/lib/languages/python.js\";\nimport ruby from \"highlight.js/lib/languages/ruby.js\";\nimport rust from \"highlight.js/lib/languages/rust.js\";\nimport scala from \"highlight.js/lib/languages/scala.js\";\nimport swift from \"highlight.js/lib/languages/swift.js\";\nimport typescript from \"highlight.js/lib/languages/typescript.js\";\nimport { decodeHtmlEntityAt } from \"./html.ts\";\n\nconst eagerLanguages = {\n\tpython,\n\tjava,\n\tgo,\n\tjavascript,\n\tcpp,\n\ttypescript,\n\tphp,\n\truby,\n\tc,\n\tcsharp,\n\tnix,\n\tbash,\n\trust,\n\tscala,\n\tkotlin,\n\tswift,\n\tdart,\n\tgroovy,\n\tperl,\n\tlua,\n};\n\nfor (const [name, language] of Object.entries(eagerLanguages)) {\n\thljs.registerLanguage(name, language);\n}\n\nlet allLanguagesPromise: Promise<void> | undefined;\n\nexport function loadAllHighlightLanguages(): Promise<void> {\n\tif (!allLanguagesPromise) {\n\t\tallLanguagesPromise = new Promise((resolve) => {\n\t\t\tsetImmediate(() => {\n\t\t\t\tvoid import(\"highlight.js/lib/index.js\").then(\n\t\t\t\t\t() => resolve(),\n\t\t\t\t\t() => {\n\t\t\t\t\t\t// Eager languages and plaintext fallback remain available.\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t});\n\t\t});\n\t}\n\treturn allLanguagesPromise;\n}\n\nexport type HighlightFormatter = (text: string) => string;\nexport type HighlightTheme = Partial<Record<string, HighlightFormatter>>;\n\nexport interface HighlightOptions {\n\tlanguage?: string;\n\tignoreIllegals?: boolean;\n\tlanguageSubset?: string[];\n\ttheme?: HighlightTheme;\n}\n\nconst SPAN_CLOSE = \"</span>\";\nconst HIGHLIGHT_CLASS_PREFIX = \"hljs-\";\n\nfunction getScopeFromSpanTag(tag: string): string | undefined {\n\tconst match = /\\sclass\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/.exec(tag);\n\tconst classValue = match?.[1] ?? match?.[2];\n\tif (!classValue) {\n\t\treturn undefined;\n\t}\n\n\tfor (const className of classValue.split(/\\s+/)) {\n\t\tif (className.startsWith(HIGHLIGHT_CLASS_PREFIX)) {\n\t\t\treturn className.slice(HIGHLIGHT_CLASS_PREFIX.length);\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction getScopeFormatter(scope: string, theme: HighlightTheme): HighlightFormatter | undefined {\n\tconst exact = theme[scope];\n\tif (exact) {\n\t\treturn exact;\n\t}\n\n\tconst dotIndex = scope.indexOf(\".\");\n\tif (dotIndex !== -1) {\n\t\tconst prefixFormatter = theme[scope.slice(0, dotIndex)];\n\t\tif (prefixFormatter) {\n\t\t\treturn prefixFormatter;\n\t\t}\n\t}\n\n\tconst dashIndex = scope.indexOf(\"-\");\n\tif (dashIndex !== -1) {\n\t\tconst prefixFormatter = theme[scope.slice(0, dashIndex)];\n\t\tif (prefixFormatter) {\n\t\t\treturn prefixFormatter;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction getActiveFormatter(scopes: Array<string | undefined>, theme: HighlightTheme): HighlightFormatter | undefined {\n\tfor (let i = scopes.length - 1; i >= 0; i--) {\n\t\tconst scope = scopes[i];\n\t\tif (!scope) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst formatter = getScopeFormatter(scope, theme);\n\t\tif (formatter) {\n\t\t\treturn formatter;\n\t\t}\n\t}\n\treturn theme.default;\n}\n\nfunction isSpanOpenTagStart(html: string, index: number): boolean {\n\tif (!html.startsWith(\"<span\", index)) {\n\t\treturn false;\n\t}\n\tconst nextChar = html[index + \"<span\".length];\n\treturn nextChar === \">\" || nextChar === \" \" || nextChar === \"\\t\" || nextChar === \"\\n\" || nextChar === \"\\r\";\n}\n\nexport function renderHighlightedHtml(html: string, theme: HighlightTheme = {}): string {\n\tlet output = \"\";\n\tlet textBuffer = \"\";\n\tconst scopes: Array<string | undefined> = [];\n\n\tconst flushText = () => {\n\t\tif (!textBuffer) {\n\t\t\treturn;\n\t\t}\n\t\tconst formatter = getActiveFormatter(scopes, theme);\n\t\toutput += formatter ? formatter(textBuffer) : textBuffer;\n\t\ttextBuffer = \"\";\n\t};\n\n\tlet index = 0;\n\twhile (index < html.length) {\n\t\tif (isSpanOpenTagStart(html, index)) {\n\t\t\tconst tagEndIndex = html.indexOf(\">\", index + 5);\n\t\t\tif (tagEndIndex !== -1) {\n\t\t\t\tflushText();\n\t\t\t\tconst tag = html.slice(index, tagEndIndex + 1);\n\t\t\t\tconst scope = getScopeFromSpanTag(tag);\n\t\t\t\tscopes.push(scope);\n\t\t\t\tindex = tagEndIndex + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (html.startsWith(SPAN_CLOSE, index)) {\n\t\t\tflushText();\n\t\t\tif (scopes.length > 0) {\n\t\t\t\tscopes.pop();\n\t\t\t}\n\t\t\tindex += SPAN_CLOSE.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (html[index] === \"&\") {\n\t\t\tconst decoded = decodeHtmlEntityAt(html, index);\n\t\t\tif (decoded) {\n\t\t\t\ttextBuffer += decoded.text;\n\t\t\t\tindex += decoded.length;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\ttextBuffer += html[index];\n\t\tindex++;\n\t}\n\n\tflushText();\n\treturn output;\n}\n\nexport function highlight(code: string, options: HighlightOptions = {}): string {\n\tconst html = options.language\n\t\t? hljs.highlight(code, {\n\t\t\t\tlanguage: options.language,\n\t\t\t\tignoreIllegals: options.ignoreIllegals,\n\t\t\t}).value\n\t\t: hljs.highlightAuto(code, options.languageSubset).value;\n\treturn renderHighlightedHtml(html, options.theme);\n}\n\nexport function supportsLanguage(name: string): boolean {\n\treturn hljs.getLanguage(name) !== undefined;\n}\n"]}
@@ -1,5 +1,64 @@
1
- import hljs from "highlight.js/lib/index.js";
1
+ import hljs from "highlight.js/lib/core.js";
2
+ import bash from "highlight.js/lib/languages/bash.js";
3
+ import c from "highlight.js/lib/languages/c.js";
4
+ import cpp from "highlight.js/lib/languages/cpp.js";
5
+ import csharp from "highlight.js/lib/languages/csharp.js";
6
+ import dart from "highlight.js/lib/languages/dart.js";
7
+ import go from "highlight.js/lib/languages/go.js";
8
+ import groovy from "highlight.js/lib/languages/groovy.js";
9
+ import java from "highlight.js/lib/languages/java.js";
10
+ import javascript from "highlight.js/lib/languages/javascript.js";
11
+ import kotlin from "highlight.js/lib/languages/kotlin.js";
12
+ import lua from "highlight.js/lib/languages/lua.js";
13
+ import nix from "highlight.js/lib/languages/nix.js";
14
+ import perl from "highlight.js/lib/languages/perl.js";
15
+ import php from "highlight.js/lib/languages/php.js";
16
+ import python from "highlight.js/lib/languages/python.js";
17
+ import ruby from "highlight.js/lib/languages/ruby.js";
18
+ import rust from "highlight.js/lib/languages/rust.js";
19
+ import scala from "highlight.js/lib/languages/scala.js";
20
+ import swift from "highlight.js/lib/languages/swift.js";
21
+ import typescript from "highlight.js/lib/languages/typescript.js";
2
22
  import { decodeHtmlEntityAt } from "./html.js";
23
+ const eagerLanguages = {
24
+ python,
25
+ java,
26
+ go,
27
+ javascript,
28
+ cpp,
29
+ typescript,
30
+ php,
31
+ ruby,
32
+ c,
33
+ csharp,
34
+ nix,
35
+ bash,
36
+ rust,
37
+ scala,
38
+ kotlin,
39
+ swift,
40
+ dart,
41
+ groovy,
42
+ perl,
43
+ lua,
44
+ };
45
+ for (const [name, language] of Object.entries(eagerLanguages)) {
46
+ hljs.registerLanguage(name, language);
47
+ }
48
+ let allLanguagesPromise;
49
+ export function loadAllHighlightLanguages() {
50
+ if (!allLanguagesPromise) {
51
+ allLanguagesPromise = new Promise((resolve) => {
52
+ setImmediate(() => {
53
+ void import("highlight.js/lib/index.js").then(() => resolve(), () => {
54
+ // Eager languages and plaintext fallback remain available.
55
+ resolve();
56
+ });
57
+ });
58
+ });
59
+ }
60
+ return allLanguagesPromise;
61
+ }
3
62
  const SPAN_CLOSE = "</span>";
4
63
  const HIGHLIGHT_CLASS_PREFIX = "hljs-";
5
64
  function getScopeFromSpanTag(tag) {
@@ -1 +1 @@
1
- {"version":3,"file":"syntax-highlight.js","sourceRoot":"","sources":["../../src/utils/syntax-highlight.ts"],"names":[],"mappings":"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","sourcesContent":["import hljs from \"highlight.js/lib/index.js\";\nimport { decodeHtmlEntityAt } from \"./html.ts\";\n\nexport type HighlightFormatter = (text: string) => string;\nexport type HighlightTheme = Partial<Record<string, HighlightFormatter>>;\n\nexport interface HighlightOptions {\n\tlanguage?: string;\n\tignoreIllegals?: boolean;\n\tlanguageSubset?: string[];\n\ttheme?: HighlightTheme;\n}\n\nconst SPAN_CLOSE = \"</span>\";\nconst HIGHLIGHT_CLASS_PREFIX = \"hljs-\";\n\nfunction getScopeFromSpanTag(tag: string): string | undefined {\n\tconst match = /\\sclass\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/.exec(tag);\n\tconst classValue = match?.[1] ?? match?.[2];\n\tif (!classValue) {\n\t\treturn undefined;\n\t}\n\n\tfor (const className of classValue.split(/\\s+/)) {\n\t\tif (className.startsWith(HIGHLIGHT_CLASS_PREFIX)) {\n\t\t\treturn className.slice(HIGHLIGHT_CLASS_PREFIX.length);\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction getScopeFormatter(scope: string, theme: HighlightTheme): HighlightFormatter | undefined {\n\tconst exact = theme[scope];\n\tif (exact) {\n\t\treturn exact;\n\t}\n\n\tconst dotIndex = scope.indexOf(\".\");\n\tif (dotIndex !== -1) {\n\t\tconst prefixFormatter = theme[scope.slice(0, dotIndex)];\n\t\tif (prefixFormatter) {\n\t\t\treturn prefixFormatter;\n\t\t}\n\t}\n\n\tconst dashIndex = scope.indexOf(\"-\");\n\tif (dashIndex !== -1) {\n\t\tconst prefixFormatter = theme[scope.slice(0, dashIndex)];\n\t\tif (prefixFormatter) {\n\t\t\treturn prefixFormatter;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction getActiveFormatter(scopes: Array<string | undefined>, theme: HighlightTheme): HighlightFormatter | undefined {\n\tfor (let i = scopes.length - 1; i >= 0; i--) {\n\t\tconst scope = scopes[i];\n\t\tif (!scope) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst formatter = getScopeFormatter(scope, theme);\n\t\tif (formatter) {\n\t\t\treturn formatter;\n\t\t}\n\t}\n\treturn theme.default;\n}\n\nfunction isSpanOpenTagStart(html: string, index: number): boolean {\n\tif (!html.startsWith(\"<span\", index)) {\n\t\treturn false;\n\t}\n\tconst nextChar = html[index + \"<span\".length];\n\treturn nextChar === \">\" || nextChar === \" \" || nextChar === \"\\t\" || nextChar === \"\\n\" || nextChar === \"\\r\";\n}\n\nexport function renderHighlightedHtml(html: string, theme: HighlightTheme = {}): string {\n\tlet output = \"\";\n\tlet textBuffer = \"\";\n\tconst scopes: Array<string | undefined> = [];\n\n\tconst flushText = () => {\n\t\tif (!textBuffer) {\n\t\t\treturn;\n\t\t}\n\t\tconst formatter = getActiveFormatter(scopes, theme);\n\t\toutput += formatter ? formatter(textBuffer) : textBuffer;\n\t\ttextBuffer = \"\";\n\t};\n\n\tlet index = 0;\n\twhile (index < html.length) {\n\t\tif (isSpanOpenTagStart(html, index)) {\n\t\t\tconst tagEndIndex = html.indexOf(\">\", index + 5);\n\t\t\tif (tagEndIndex !== -1) {\n\t\t\t\tflushText();\n\t\t\t\tconst tag = html.slice(index, tagEndIndex + 1);\n\t\t\t\tconst scope = getScopeFromSpanTag(tag);\n\t\t\t\tscopes.push(scope);\n\t\t\t\tindex = tagEndIndex + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (html.startsWith(SPAN_CLOSE, index)) {\n\t\t\tflushText();\n\t\t\tif (scopes.length > 0) {\n\t\t\t\tscopes.pop();\n\t\t\t}\n\t\t\tindex += SPAN_CLOSE.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (html[index] === \"&\") {\n\t\t\tconst decoded = decodeHtmlEntityAt(html, index);\n\t\t\tif (decoded) {\n\t\t\t\ttextBuffer += decoded.text;\n\t\t\t\tindex += decoded.length;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\ttextBuffer += html[index];\n\t\tindex++;\n\t}\n\n\tflushText();\n\treturn output;\n}\n\nexport function highlight(code: string, options: HighlightOptions = {}): string {\n\tconst html = options.language\n\t\t? hljs.highlight(code, {\n\t\t\t\tlanguage: options.language,\n\t\t\t\tignoreIllegals: options.ignoreIllegals,\n\t\t\t}).value\n\t\t: hljs.highlightAuto(code, options.languageSubset).value;\n\treturn renderHighlightedHtml(html, options.theme);\n}\n\nexport function supportsLanguage(name: string): boolean {\n\treturn hljs.getLanguage(name) !== undefined;\n}\n"]}
1
+ {"version":3,"file":"syntax-highlight.js","sourceRoot":"","sources":["../../src/utils/syntax-highlight.ts"],"names":[],"mappings":"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","sourcesContent":["import hljs from \"highlight.js/lib/core.js\";\nimport bash from \"highlight.js/lib/languages/bash.js\";\nimport c from \"highlight.js/lib/languages/c.js\";\nimport cpp from \"highlight.js/lib/languages/cpp.js\";\nimport csharp from \"highlight.js/lib/languages/csharp.js\";\nimport dart from \"highlight.js/lib/languages/dart.js\";\nimport go from \"highlight.js/lib/languages/go.js\";\nimport groovy from \"highlight.js/lib/languages/groovy.js\";\nimport java from \"highlight.js/lib/languages/java.js\";\nimport javascript from \"highlight.js/lib/languages/javascript.js\";\nimport kotlin from \"highlight.js/lib/languages/kotlin.js\";\nimport lua from \"highlight.js/lib/languages/lua.js\";\nimport nix from \"highlight.js/lib/languages/nix.js\";\nimport perl from \"highlight.js/lib/languages/perl.js\";\nimport php from \"highlight.js/lib/languages/php.js\";\nimport python from \"highlight.js/lib/languages/python.js\";\nimport ruby from \"highlight.js/lib/languages/ruby.js\";\nimport rust from \"highlight.js/lib/languages/rust.js\";\nimport scala from \"highlight.js/lib/languages/scala.js\";\nimport swift from \"highlight.js/lib/languages/swift.js\";\nimport typescript from \"highlight.js/lib/languages/typescript.js\";\nimport { decodeHtmlEntityAt } from \"./html.ts\";\n\nconst eagerLanguages = {\n\tpython,\n\tjava,\n\tgo,\n\tjavascript,\n\tcpp,\n\ttypescript,\n\tphp,\n\truby,\n\tc,\n\tcsharp,\n\tnix,\n\tbash,\n\trust,\n\tscala,\n\tkotlin,\n\tswift,\n\tdart,\n\tgroovy,\n\tperl,\n\tlua,\n};\n\nfor (const [name, language] of Object.entries(eagerLanguages)) {\n\thljs.registerLanguage(name, language);\n}\n\nlet allLanguagesPromise: Promise<void> | undefined;\n\nexport function loadAllHighlightLanguages(): Promise<void> {\n\tif (!allLanguagesPromise) {\n\t\tallLanguagesPromise = new Promise((resolve) => {\n\t\t\tsetImmediate(() => {\n\t\t\t\tvoid import(\"highlight.js/lib/index.js\").then(\n\t\t\t\t\t() => resolve(),\n\t\t\t\t\t() => {\n\t\t\t\t\t\t// Eager languages and plaintext fallback remain available.\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t});\n\t\t});\n\t}\n\treturn allLanguagesPromise;\n}\n\nexport type HighlightFormatter = (text: string) => string;\nexport type HighlightTheme = Partial<Record<string, HighlightFormatter>>;\n\nexport interface HighlightOptions {\n\tlanguage?: string;\n\tignoreIllegals?: boolean;\n\tlanguageSubset?: string[];\n\ttheme?: HighlightTheme;\n}\n\nconst SPAN_CLOSE = \"</span>\";\nconst HIGHLIGHT_CLASS_PREFIX = \"hljs-\";\n\nfunction getScopeFromSpanTag(tag: string): string | undefined {\n\tconst match = /\\sclass\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/.exec(tag);\n\tconst classValue = match?.[1] ?? match?.[2];\n\tif (!classValue) {\n\t\treturn undefined;\n\t}\n\n\tfor (const className of classValue.split(/\\s+/)) {\n\t\tif (className.startsWith(HIGHLIGHT_CLASS_PREFIX)) {\n\t\t\treturn className.slice(HIGHLIGHT_CLASS_PREFIX.length);\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction getScopeFormatter(scope: string, theme: HighlightTheme): HighlightFormatter | undefined {\n\tconst exact = theme[scope];\n\tif (exact) {\n\t\treturn exact;\n\t}\n\n\tconst dotIndex = scope.indexOf(\".\");\n\tif (dotIndex !== -1) {\n\t\tconst prefixFormatter = theme[scope.slice(0, dotIndex)];\n\t\tif (prefixFormatter) {\n\t\t\treturn prefixFormatter;\n\t\t}\n\t}\n\n\tconst dashIndex = scope.indexOf(\"-\");\n\tif (dashIndex !== -1) {\n\t\tconst prefixFormatter = theme[scope.slice(0, dashIndex)];\n\t\tif (prefixFormatter) {\n\t\t\treturn prefixFormatter;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction getActiveFormatter(scopes: Array<string | undefined>, theme: HighlightTheme): HighlightFormatter | undefined {\n\tfor (let i = scopes.length - 1; i >= 0; i--) {\n\t\tconst scope = scopes[i];\n\t\tif (!scope) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst formatter = getScopeFormatter(scope, theme);\n\t\tif (formatter) {\n\t\t\treturn formatter;\n\t\t}\n\t}\n\treturn theme.default;\n}\n\nfunction isSpanOpenTagStart(html: string, index: number): boolean {\n\tif (!html.startsWith(\"<span\", index)) {\n\t\treturn false;\n\t}\n\tconst nextChar = html[index + \"<span\".length];\n\treturn nextChar === \">\" || nextChar === \" \" || nextChar === \"\\t\" || nextChar === \"\\n\" || nextChar === \"\\r\";\n}\n\nexport function renderHighlightedHtml(html: string, theme: HighlightTheme = {}): string {\n\tlet output = \"\";\n\tlet textBuffer = \"\";\n\tconst scopes: Array<string | undefined> = [];\n\n\tconst flushText = () => {\n\t\tif (!textBuffer) {\n\t\t\treturn;\n\t\t}\n\t\tconst formatter = getActiveFormatter(scopes, theme);\n\t\toutput += formatter ? formatter(textBuffer) : textBuffer;\n\t\ttextBuffer = \"\";\n\t};\n\n\tlet index = 0;\n\twhile (index < html.length) {\n\t\tif (isSpanOpenTagStart(html, index)) {\n\t\t\tconst tagEndIndex = html.indexOf(\">\", index + 5);\n\t\t\tif (tagEndIndex !== -1) {\n\t\t\t\tflushText();\n\t\t\t\tconst tag = html.slice(index, tagEndIndex + 1);\n\t\t\t\tconst scope = getScopeFromSpanTag(tag);\n\t\t\t\tscopes.push(scope);\n\t\t\t\tindex = tagEndIndex + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (html.startsWith(SPAN_CLOSE, index)) {\n\t\t\tflushText();\n\t\t\tif (scopes.length > 0) {\n\t\t\t\tscopes.pop();\n\t\t\t}\n\t\t\tindex += SPAN_CLOSE.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (html[index] === \"&\") {\n\t\t\tconst decoded = decodeHtmlEntityAt(html, index);\n\t\t\tif (decoded) {\n\t\t\t\ttextBuffer += decoded.text;\n\t\t\t\tindex += decoded.length;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\ttextBuffer += html[index];\n\t\tindex++;\n\t}\n\n\tflushText();\n\treturn output;\n}\n\nexport function highlight(code: string, options: HighlightOptions = {}): string {\n\tconst html = options.language\n\t\t? hljs.highlight(code, {\n\t\t\t\tlanguage: options.language,\n\t\t\t\tignoreIllegals: options.ignoreIllegals,\n\t\t\t}).value\n\t\t: hljs.highlightAuto(code, options.languageSubset).value;\n\treturn renderHighlightedHtml(html, options.theme);\n}\n\nexport function supportsLanguage(name: string): boolean {\n\treturn hljs.getLanguage(name) !== undefined;\n}\n"]}
@@ -0,0 +1,8 @@
1
+ /** Split a leading UTF-8 byte order mark from decoded text. */
2
+ export declare function splitBom(content: string): {
3
+ bom: string;
4
+ text: string;
5
+ };
6
+ /** Remove a leading UTF-8 byte order mark from decoded text. */
7
+ export declare function stripBom(content: string): string;
8
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/utils/text.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAEvE;AAED,gEAAgE;AAChE,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEhD","sourcesContent":["/** Split a leading UTF-8 byte order mark from decoded text. */\nexport function splitBom(content: string): { bom: string; text: string } {\n\treturn content.startsWith(\"\\uFEFF\") ? { bom: \"\\uFEFF\", text: content.slice(1) } : { bom: \"\", text: content };\n}\n\n/** Remove a leading UTF-8 byte order mark from decoded text. */\nexport function stripBom(content: string): string {\n\treturn splitBom(content).text;\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /** Split a leading UTF-8 byte order mark from decoded text. */
2
+ export function splitBom(content) {
3
+ return content.startsWith("\uFEFF") ? { bom: "\uFEFF", text: content.slice(1) } : { bom: "", text: content };
4
+ }
5
+ /** Remove a leading UTF-8 byte order mark from decoded text. */
6
+ export function stripBom(content) {
7
+ return splitBom(content).text;
8
+ }
9
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/utils/text.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAiC;IACxE,OAAO,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAAA,CAC7G;AAED,gEAAgE;AAChE,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAU;IACjD,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;AAAA,CAC9B","sourcesContent":["/** Split a leading UTF-8 byte order mark from decoded text. */\nexport function splitBom(content: string): { bom: string; text: string } {\n\treturn content.startsWith(\"\\uFEFF\") ? { bom: \"\\uFEFF\", text: content.slice(1) } : { bom: \"\", text: content };\n}\n\n/** Remove a leading UTF-8 byte order mark from decoded text. */\nexport function stripBom(content: string): string {\n\treturn splitBom(content).text;\n}\n"]}
@@ -1,3 +1,71 @@
1
- export declare function getToolPath(tool: "fd" | "rg"): string | null;
2
- export declare function ensureTool(tool: "fd" | "rg", silent?: boolean): Promise<string | undefined>;
1
+ export declare function getToolPath(tool: "fd" | "rg", options?: {
2
+ toolsDirectory?: string;
3
+ }): string | null;
4
+ export interface ManagedToolArtifact {
5
+ version: string;
6
+ url: string;
7
+ sha256: string;
8
+ expectedBytes: number;
9
+ /** Bounded download ceiling (ADR 0017): four times the reviewed expected size. */
10
+ maxBytes: number;
11
+ binaryName: string;
12
+ archiveKind: "tar.gz" | "zip";
13
+ }
14
+ /** Resolve reviewed, pinned metadata for a managed tool. Never queries "latest". */
15
+ export declare function resolveManagedToolArtifact(options: {
16
+ tool: "fd" | "rg";
17
+ platform: NodeJS.Platform | string;
18
+ architecture: string;
19
+ }): ManagedToolArtifact;
20
+ export interface InstallManagedToolArchiveOptions {
21
+ archivePath: string;
22
+ destination: string;
23
+ binaryName: string;
24
+ archiveKind: "tar.gz" | "zip";
25
+ expectedSha256: string;
26
+ maxBytes: number;
27
+ }
28
+ /**
29
+ * Verify a downloaded tool archive against its pinned digest, extract it into a
30
+ * per-install quarantine directory, confirm the located binary did not escape that
31
+ * quarantine, and only then atomically promote it into the live tools directory
32
+ * (ADR 0017). Rejects a digest mismatch or an out-of-bounds archive before anything
33
+ * is written to `destination`.
34
+ */
35
+ export declare function installManagedToolArchive(options: InstallManagedToolArchiveOptions): Promise<string>;
36
+ export interface HostToolBinary {
37
+ /** File name the child looks for in its own tools directory ("fd", "rg", "fd.exe"). */
38
+ name: string;
39
+ /** Absolute host path to the executable backing that name. */
40
+ path: string;
41
+ }
42
+ /**
43
+ * Absolute host path for a managed tool, or undefined when it is installed nowhere.
44
+ *
45
+ * `getToolPath` may return a bare command name because its caller spawns through PATH.
46
+ * A sandboxed child has no such luxury: its PATH still lists host directories that the
47
+ * OS boundary has already replaced, so the supervisor must resolve a real path here and
48
+ * project it in (ADR 0017, and the sandbox spec's tool-projection amendment).
49
+ */
50
+ export declare function resolveHostToolBinary(tool: "fd" | "rg", options?: {
51
+ toolsDirectory?: string;
52
+ pathValue?: string;
53
+ platform?: NodeJS.Platform;
54
+ }): string | undefined;
55
+ /**
56
+ * Install any missing managed tool on the host, then report absolute paths for the ones
57
+ * that exist. Downloading here rather than inside the child means one install serves every
58
+ * workspace, and it happens on the side of the boundary that still has network access.
59
+ */
60
+ export declare function prepareHostToolBinaries(silent?: boolean): Promise<HostToolBinary[]>;
61
+ export interface ToolStatus {
62
+ type: "info" | "warning";
63
+ message: string;
64
+ }
65
+ /**
66
+ * Ensure a tool is available, downloading if necessary.
67
+ * Reports progress through `onStatus`; status messages are otherwise silent.
68
+ * Returns the tool path, or undefined if unavailable.
69
+ */
70
+ export declare function ensureTool(tool: "fd" | "rg", onStatus?: (status: ToolStatus) => void): Promise<string | undefined>;
3
71
  //# sourceMappingURL=tools-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools-manager.d.ts","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"AAqFA,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAmB5D;AA+ND,wBAAsB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA2CxG","sourcesContent":["import chalk from \"chalk\";\nimport { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport { chmodSync, createWriteStream, existsSync, mkdirSync, readdirSync, renameSync, rmSync } from \"fs\";\nimport { arch, platform } from \"os\";\nimport { join } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { APP_NAME, getBinDir } from \"../config.ts\";\nimport { fetchWithRetry } from \"./management-http.ts\";\n\nconst TOOLS_DIR = getBinDir();\nconst NETWORK_TIMEOUT_MS = 10_000;\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = process.env.PI_OFFLINE;\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\tsystemBinaryNames?: string[]; // Alternative system command names to try before downloading\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n\tgetAssetName: (version: string, plat: string, architecture: string) => string | null;\n}\n\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\tsystemBinaryNames: [\"fd\", \"fdfind\"],\n\t\ttagPrefix: \"v\",\n\t\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tif (architecture === \"arm64\") {\n\t\t\t\t\treturn `ripgrep-${version}-aarch64-unknown-linux-gnu.tar.gz`;\n\t\t\t\t}\n\t\t\t\treturn `ripgrep-${version}-x86_64-unknown-linux-musl.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Get the path to a tool (system-wide or in our tools dir)\nexport function getToolPath(tool: \"fd\" | \"rg\"): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Check our tools directory first\n\tconst localPath = join(TOOLS_DIR, config.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"));\n\tif (existsSync(localPath)) {\n\t\treturn localPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tconst systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n\tfor (const systemBinaryName of systemBinaryNames) {\n\t\tif (commandExists(systemBinaryName)) {\n\t\t\treturn systemBinaryName;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n// Fetch latest release version from GitHub\nasync function getLatestVersion(repo: string): Promise<string> {\n\tconst response = await fetchWithRetry(\n\t\t`https://api.github.com/repos/${repo}/releases/latest`,\n\t\t{\n\t\t\theaders: { \"User-Agent\": `${APP_NAME}-coding-agent` },\n\t\t},\n\t\t{ timeoutMs: NETWORK_TIMEOUT_MS },\n\t);\n\n\tif (!response.ok) {\n\t\tthrow new Error(`GitHub API error: ${response.status}`);\n\t}\n\n\tconst data = (await response.json()) as { tag_name: string };\n\treturn data.tag_name.replace(/^v/, \"\");\n}\n\n// Download a file from URL\nasync function downloadFile(url: string, dest: string): Promise<void> {\n\tconst response = await fetchWithRetry(url, undefined, { timeoutMs: DOWNLOAD_TIMEOUT_MS });\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new Error(\"No response body\");\n\t}\n\n\tconst fileStream = createWriteStream(dest);\n\tawait pipeline(Readable.fromWeb(response.body as any), fileStream);\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {\n\tconst stack: string[] = [rootDir];\n\n\twhile (stack.length > 0) {\n\t\tconst currentDir = stack.pop();\n\t\tif (!currentDir) continue;\n\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tif (entry.isFile() && entry.name === binaryFileName) {\n\t\t\t\treturn fullPath;\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(fullPath);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {\n\tif (result.error?.message) {\n\t\treturn result.error.message;\n\t}\n\tconst stderr = result.stderr?.toString().trim();\n\tif (stderr) {\n\t\treturn stderr;\n\t}\n\tconst stdout = result.stdout?.toString().trim();\n\tif (stdout) {\n\t\treturn stdout;\n\t}\n\treturn `exit status ${result.status ?? \"unknown\"}`;\n}\n\nfunction runExtractionCommand(command: string, args: string[]): string | null {\n\tconst result = spawnSync(command, args, { stdio: \"pipe\" });\n\tif (!result.error && result.status === 0) {\n\t\treturn null;\n\t}\n\treturn `${command}: ${formatSpawnFailure(result)}`;\n}\n\nfunction extractTarGzArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failure = runExtractionCommand(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir]);\n\tif (failure) {\n\t\tthrow new Error(`Failed to extract ${assetName}: ${failure}`);\n\t}\n}\n\nfunction getWindowsTarCommand(): string {\n\tconst systemRoot = process.env.SystemRoot ?? process.env.WINDIR;\n\tif (systemRoot) {\n\t\tconst systemTar = join(systemRoot, \"System32\", \"tar.exe\");\n\t\tif (existsSync(systemTar)) {\n\t\t\treturn systemTar;\n\t\t}\n\t}\n\treturn \"tar.exe\";\n}\n\nfunction extractZipArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failures: string[] = [];\n\n\tif (platform() === \"win32\") {\n\t\t// Windows ships bsdtar as tar.exe, which supports zip files. Prefer the\n\t\t// System32 binary over Git Bash's GNU tar, which does not handle zip archives.\n\t\tconst tarFailure = runExtractionCommand(getWindowsTarCommand(), [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\n\t\tconst script =\n\t\t\t\"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }\";\n\t\tconst powershellFailure = runExtractionCommand(\"powershell.exe\", [\n\t\t\t\"-NoLogo\",\n\t\t\t\"-NoProfile\",\n\t\t\t\"-NonInteractive\",\n\t\t\t\"-ExecutionPolicy\",\n\t\t\t\"Bypass\",\n\t\t\t\"-Command\",\n\t\t\tscript,\n\t\t\tarchivePath,\n\t\t\textractDir,\n\t\t]);\n\t\tif (!powershellFailure) return;\n\t\tfailures.push(powershellFailure);\n\t} else {\n\t\tconst unzipFailure = runExtractionCommand(\"unzip\", [\"-q\", archivePath, \"-d\", extractDir]);\n\t\tif (!unzipFailure) return;\n\t\tfailures.push(unzipFailure);\n\n\t\tconst tarFailure = runExtractionCommand(\"tar\", [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\t}\n\n\tthrow new Error(`Failed to extract ${assetName}: ${failures.join(\"; \")}`);\n}\n\n// Download and install a tool\nasync function downloadTool(tool: \"fd\" | \"rg\"): Promise<string> {\n\tconst config = TOOLS[tool];\n\tif (!config) throw new Error(`Unknown tool: ${tool}`);\n\n\tconst plat = platform();\n\tconst architecture = arch();\n\n\t// Get latest version\n\tlet version = await getLatestVersion(config.repo);\n\tif (tool === \"fd\" && plat === \"darwin\" && architecture === \"x64\") {\n\t\tversion = \"10.3.0\";\n\t}\n\n\t// Get asset name for this platform\n\tconst assetName = config.getAssetName(version, plat, architecture);\n\tif (!assetName) {\n\t\tthrow new Error(`Unsupported platform: ${plat}/${architecture}`);\n\t}\n\n\t// Create tools directory\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\tconst downloadUrl = `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${version}/${assetName}`;\n\tconst archivePath = join(TOOLS_DIR, assetName);\n\tconst binaryExt = plat === \"win32\" ? \".exe\" : \"\";\n\tconst binaryPath = join(TOOLS_DIR, config.binaryName + binaryExt);\n\n\t// Download\n\tawait downloadFile(downloadUrl, archivePath);\n\n\t// Extract into a unique temp directory. fd and rg downloads can run concurrently\n\t// during startup, so sharing a fixed directory causes races.\n\tconst extractDir = join(\n\t\tTOOLS_DIR,\n\t\t`extract_tmp_${config.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\tmkdirSync(extractDir, { recursive: true });\n\n\ttry {\n\t\tif (assetName.endsWith(\".tar.gz\")) {\n\t\t\textractTarGzArchive(archivePath, extractDir, assetName);\n\t\t} else if (assetName.endsWith(\".zip\")) {\n\t\t\textractZipArchive(archivePath, extractDir, assetName);\n\t\t} else {\n\t\t\tthrow new Error(`Unsupported archive format: ${assetName}`);\n\t\t}\n\n\t\t// Find the binary in extracted files. Some archives contain files directly\n\t\t// at root, others nest under a versioned subdirectory.\n\t\tconst binaryFileName = config.binaryName + binaryExt;\n\t\tconst extractedDir = join(extractDir, assetName.replace(/\\.(tar\\.gz|zip)$/, \"\"));\n\t\tconst extractedBinaryCandidates = [join(extractedDir, binaryFileName), join(extractDir, binaryFileName)];\n\t\tlet extractedBinary = extractedBinaryCandidates.find((candidate) => existsSync(candidate));\n\n\t\tif (!extractedBinary) {\n\t\t\textractedBinary = findBinaryRecursively(extractDir, binaryFileName) ?? undefined;\n\t\t}\n\n\t\tif (extractedBinary) {\n\t\t\trenameSync(extractedBinary, binaryPath);\n\t\t} else {\n\t\t\tthrow new Error(`Binary not found in archive: expected ${binaryFileName} under ${extractDir}`);\n\t\t}\n\n\t\t// Make executable (Unix only)\n\t\tif (plat !== \"win32\") {\n\t\t\tchmodSync(binaryPath, 0o755);\n\t\t}\n\t} finally {\n\t\t// Cleanup\n\t\trmSync(archivePath, { force: true });\n\t\trmSync(extractDir, { recursive: true, force: true });\n\t}\n\n\treturn binaryPath;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n\tfd: \"fd\",\n\trg: \"ripgrep\",\n};\n\n// Ensure a tool is available, downloading if necessary\n// Returns the path to the tool, or null if unavailable\nexport async function ensureTool(tool: \"fd\" | \"rg\", silent: boolean = false): Promise<string | undefined> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tif (isOfflineModeEnabled()) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Offline mode enabled, skipping download.`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n\t// Users must install via pkg.\n\tif (platform() === \"android\") {\n\t\tconst pkgName = TERMUX_PACKAGES[tool] ?? tool;\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Install with: pkg install ${pkgName}`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// Tool not found - download it\n\tif (!silent) {\n\t\tconsole.log(chalk.dim(`${config.name} not found. Downloading...`));\n\t}\n\n\ttry {\n\t\tconst path = await downloadTool(tool);\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.dim(`${config.name} installed to ${path}`));\n\t\t}\n\t\treturn path;\n\t} catch (e) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`));\n\t\t}\n\t\treturn undefined;\n\t}\n}\n"]}
1
+ {"version":3,"file":"tools-manager.d.ts","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"AAoEA,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,GAAG,IAAI,CAwBvG;AA6GD,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,QAAQ,GAAG,KAAK,CAAC;CAC9B;AAED,oFAAoF;AACpF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE;IACnD,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;CACrB,GAAG,mBAAmB,CAetB;AAmJD,MAAM,WAAW,gCAAgC;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,gCAAgC,GAAG,OAAO,CAAC,MAAM,CAAC,CAiD1G;AAmCD,MAAM,WAAW,cAAc;IAC9B,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;CACb;AAaD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;CAAO,GACvF,MAAM,GAAG,SAAS,CA0BpB;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAUhG;AAQD,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAC/B,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,GACrC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAoC7B","sourcesContent":["import { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport { createHash } from \"crypto\";\nimport {\n\tchmodSync,\n\tcreateReadStream,\n\tcreateWriteStream,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { basename, join, relative, resolve as resolvePath } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { getBinDir } from \"../config.ts\";\nimport { getApexEnvironment } from \"../core/environment.ts\";\nimport { fetchWithRetry } from \"./management-http.ts\";\n\nconst TOOLS_DIR = getBinDir();\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = getApexEnvironment(\"APEX_CODE_OFFLINE\");\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\tsystemBinaryNames?: string[]; // Alternative system command names to try before downloading\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n}\n\n// Per-platform/architecture asset names live in MANAGED_TOOL_PINS (ADR 0017) as the\n// single reviewed source of truth, not derived here.\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\tsystemBinaryNames: [\"fd\", \"fdfind\"],\n\t\ttagPrefix: \"v\",\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Get the path to a tool (system-wide or in our tools dir)\nexport function getToolPath(tool: \"fd\" | \"rg\", options: { toolsDirectory?: string } = {}): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Check our tools directory first. Existence alone is not enough: the sandbox\n\t// projects host tools by bind-mounting over a file here, and bwrap leaves that\n\t// mountpoint behind as an empty, non-executable stub once the namespace is gone.\n\tconst managedPath = join(\n\t\toptions.toolsDirectory ?? TOOLS_DIR,\n\t\tconfig.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"),\n\t);\n\tif (isExecutableFile(managedPath, platform())) {\n\t\treturn managedPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tconst systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n\tfor (const systemBinaryName of systemBinaryNames) {\n\t\tif (commandExists(systemBinaryName)) {\n\t\t\treturn systemBinaryName;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Reviewed, pinned release metadata for auto-downloaded executable tools (ADR 0017).\n * Never resolved from a mutable \"latest\" API call. Updating a version, asset name, or\n * digest here is itself the reviewed change that authorizes installing a new release.\n *\n * Every digest and byte size below was established by downloading the artifact,\n * computing its SHA-256 locally, and cross-checking that value against the\n * upstream-published digest (ripgrep's own `.sha256` release sidecars; GitHub's own\n * per-asset `digest` field for both projects) — not invented, and not taken from a\n * single unverified source.\n */\ninterface ManagedToolPin {\n\tversion: string;\n\tassetName: string;\n\tsha256: string;\n\texpectedBytes: number;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\nconst MANAGED_TOOL_PINS: Record<\"fd\" | \"rg\", Partial<Record<string, ManagedToolPin>>> = {\n\trg: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz\",\n\t\t\tsha256: \"4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e\",\n\t\t\texpectedBytes: 2566310,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"c827481c4ff4ea10c9dc7a4022c8de5db34a5737cb74484d62eb94a95841ab2f\",\n\t\t\texpectedBytes: 2047405,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"fc87e78f7cb3fea12d69072e7ef3b21509754717b746368fd40d88963630e2b3\",\n\t\t\texpectedBytes: 2082672,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"24ad76777745fbff131c8fbc466742b011f925bfa4fffa2ded6def23b5b937be\",\n\t\t\texpectedBytes: 1787248,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"d0f534024c42afd6cb4d38907c25cd2b249b79bbe6cc1dbee8e3e37c2b6e25a1\",\n\t\t\texpectedBytes: 2058893,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t// No upstream win32/arm64 ripgrep release asset exists at 14.1.1; resolution\n\t\t// fails closed for that combination rather than constructing a URL that 404s.\n\t},\n\tfd: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"def59805cd14b5651b68990855f426ad087f3b96881296d963910431ba3143c8\",\n\t\t\texpectedBytes: 1700779,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"6c51f7c5446b3338b1e401ff15dc194c590bb2fa64fd43ff3278300f073adec5\",\n\t\t\texpectedBytes: 1559490,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t// Upstream stopped shipping an Intel macOS fd binary after 10.3.0 (see\n\t\t// 7afd80d78, \"pin fd for macos x64\"); preserved here rather than special-cased.\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"10.3.0\",\n\t\t\tassetName: \"fd-v10.3.0-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"50d30f13fe3d5914b14c4fff5abcbd4d0cdab4b855970a6956f4f006c17117a3\",\n\t\t\texpectedBytes: 1430203,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"623dc0afc81b92e4d4606b380d7bc91916ba7b97814263e554d50923a39e480a\",\n\t\t\texpectedBytes: 1328933,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"b2816e506390a89941c63c9187d58a3cc10e9a55f2ef0685f9ea0eccaf7c98c8\",\n\t\t\texpectedBytes: 1525898,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t\"win32:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"4f9110c2d5b33a7f760bfa5510f4c113d828109f7277d421b1053a9943c0fc92\",\n\t\t\texpectedBytes: 1370473,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t},\n};\n\nexport interface ManagedToolArtifact {\n\tversion: string;\n\turl: string;\n\tsha256: string;\n\texpectedBytes: number;\n\t/** Bounded download ceiling (ADR 0017): four times the reviewed expected size. */\n\tmaxBytes: number;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\n/** Resolve reviewed, pinned metadata for a managed tool. Never queries \"latest\". */\nexport function resolveManagedToolArtifact(options: {\n\ttool: \"fd\" | \"rg\";\n\tplatform: NodeJS.Platform | string;\n\tarchitecture: string;\n}): ManagedToolArtifact {\n\tconst config = TOOLS[options.tool];\n\tconst pin = MANAGED_TOOL_PINS[options.tool]?.[`${options.platform}:${options.architecture}`];\n\tif (!config || !pin) {\n\t\tthrow new Error(`No reviewed pinned artifact for ${options.tool} on ${options.platform}/${options.architecture}`);\n\t}\n\treturn {\n\t\tversion: pin.version,\n\t\turl: `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${pin.version}/${pin.assetName}`,\n\t\tsha256: pin.sha256,\n\t\texpectedBytes: pin.expectedBytes,\n\t\tmaxBytes: pin.expectedBytes * 4,\n\t\tbinaryName: config.binaryName,\n\t\tarchiveKind: pin.archiveKind,\n\t};\n}\n\n// Download a file from URL, aborting if the declared or received size exceeds the\n// bounded ceiling (ADR 0017) rather than writing an unbounded stream to disk.\nasync function downloadFile(url: string, dest: string, maxBytes: number): Promise<void> {\n\tconst response = await fetchWithRetry(url, undefined, { timeoutMs: DOWNLOAD_TIMEOUT_MS });\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new Error(\"No response body\");\n\t}\n\n\tconst declaredLength = Number(response.headers.get(\"content-length\") ?? Number.NaN);\n\tif (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n\t\tthrow new Error(`Refusing download: declared size ${declaredLength} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tlet received = 0;\n\tconst source = Readable.fromWeb(response.body as any);\n\tsource.on(\"data\", (chunk: Buffer) => {\n\t\treceived += chunk.length;\n\t\tif (received > maxBytes) {\n\t\t\tsource.destroy(new Error(`Refusing download: exceeded bounded limit ${maxBytes} bytes`));\n\t\t}\n\t});\n\n\tconst fileStream = createWriteStream(dest);\n\ttry {\n\t\tawait pipeline(source, fileStream);\n\t} catch (error) {\n\t\trmSync(dest, { force: true });\n\t\tthrow error;\n\t}\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {\n\tconst stack: string[] = [rootDir];\n\n\twhile (stack.length > 0) {\n\t\tconst currentDir = stack.pop();\n\t\tif (!currentDir) continue;\n\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tif (entry.isFile() && entry.name === binaryFileName) {\n\t\t\t\treturn fullPath;\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(fullPath);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {\n\tif (result.error?.message) {\n\t\treturn result.error.message;\n\t}\n\tconst stderr = result.stderr?.toString().trim();\n\tif (stderr) {\n\t\treturn stderr;\n\t}\n\tconst stdout = result.stdout?.toString().trim();\n\tif (stdout) {\n\t\treturn stdout;\n\t}\n\treturn `exit status ${result.status ?? \"unknown\"}`;\n}\n\nfunction runExtractionCommand(command: string, args: string[]): string | null {\n\tconst result = spawnSync(command, args, { stdio: \"pipe\" });\n\tif (!result.error && result.status === 0) {\n\t\treturn null;\n\t}\n\treturn `${command}: ${formatSpawnFailure(result)}`;\n}\n\nfunction extractTarGzArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failure = runExtractionCommand(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir]);\n\tif (failure) {\n\t\tthrow new Error(`Failed to extract ${assetName}: ${failure}`);\n\t}\n}\n\nfunction getWindowsTarCommand(): string {\n\tconst systemRoot = process.env.SystemRoot ?? process.env.WINDIR;\n\tif (systemRoot) {\n\t\tconst systemTar = join(systemRoot, \"System32\", \"tar.exe\");\n\t\tif (existsSync(systemTar)) {\n\t\t\treturn systemTar;\n\t\t}\n\t}\n\treturn \"tar.exe\";\n}\n\nfunction extractZipArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failures: string[] = [];\n\n\tif (platform() === \"win32\") {\n\t\t// Windows ships bsdtar as tar.exe, which supports zip files. Prefer the\n\t\t// System32 binary over Git Bash's GNU tar, which does not handle zip archives.\n\t\tconst tarFailure = runExtractionCommand(getWindowsTarCommand(), [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\n\t\tconst script =\n\t\t\t\"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }\";\n\t\tconst powershellFailure = runExtractionCommand(\"powershell.exe\", [\n\t\t\t\"-NoLogo\",\n\t\t\t\"-NoProfile\",\n\t\t\t\"-NonInteractive\",\n\t\t\t\"-ExecutionPolicy\",\n\t\t\t\"Bypass\",\n\t\t\t\"-Command\",\n\t\t\tscript,\n\t\t\tarchivePath,\n\t\t\textractDir,\n\t\t]);\n\t\tif (!powershellFailure) return;\n\t\tfailures.push(powershellFailure);\n\t} else {\n\t\tconst unzipFailure = runExtractionCommand(\"unzip\", [\"-q\", archivePath, \"-d\", extractDir]);\n\t\tif (!unzipFailure) return;\n\t\tfailures.push(unzipFailure);\n\n\t\tconst tarFailure = runExtractionCommand(\"tar\", [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\t}\n\n\tthrow new Error(`Failed to extract ${assetName}: ${failures.join(\"; \")}`);\n}\n\nasync function sha256File(path: string): Promise<string> {\n\tconst hash = createHash(\"sha256\");\n\tfor await (const chunk of createReadStream(path)) {\n\t\thash.update(chunk as Buffer);\n\t}\n\treturn hash.digest(\"hex\");\n}\n\nexport interface InstallManagedToolArchiveOptions {\n\tarchivePath: string;\n\tdestination: string;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n\texpectedSha256: string;\n\tmaxBytes: number;\n}\n\n/**\n * Verify a downloaded tool archive against its pinned digest, extract it into a\n * per-install quarantine directory, confirm the located binary did not escape that\n * quarantine, and only then atomically promote it into the live tools directory\n * (ADR 0017). Rejects a digest mismatch or an out-of-bounds archive before anything\n * is written to `destination`.\n */\nexport async function installManagedToolArchive(options: InstallManagedToolArchiveOptions): Promise<string> {\n\tconst { archivePath, destination, binaryName, archiveKind, expectedSha256, maxBytes } = options;\n\n\tconst { size } = statSync(archivePath);\n\tif (size > maxBytes) {\n\t\tthrow new Error(`Refusing install: archive size ${size} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tconst actualSha256 = await sha256File(archivePath);\n\tif (actualSha256.toLowerCase() !== expectedSha256.toLowerCase()) {\n\t\tthrow new Error(`Refusing install: digest mismatch for ${basename(archivePath)}`);\n\t}\n\n\tmkdirSync(destination, { recursive: true });\n\tconst quarantineDirectory = join(\n\t\tdestination,\n\t\t`.quarantine_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\tmkdirSync(quarantineDirectory, { recursive: true });\n\n\ttry {\n\t\tconst assetName = basename(archivePath);\n\t\tif (archiveKind === \"tar.gz\") {\n\t\t\textractTarGzArchive(archivePath, quarantineDirectory, assetName);\n\t\t} else {\n\t\t\textractZipArchive(archivePath, quarantineDirectory, assetName);\n\t\t}\n\n\t\tconst binaryFileName = binaryName + (platform() === \"win32\" ? \".exe\" : \"\");\n\t\tconst extractedBinary = findBinaryRecursively(quarantineDirectory, binaryFileName);\n\t\tif (!extractedBinary) {\n\t\t\tthrow new Error(`Binary not found in verified archive: expected ${binaryFileName}`);\n\t\t}\n\n\t\tconst resolvedBinary = resolvePath(extractedBinary);\n\t\tconst containmentPath = relative(resolvePath(quarantineDirectory), resolvedBinary);\n\t\tif (containmentPath.startsWith(\"..\") || resolvePath(containmentPath) === containmentPath) {\n\t\t\tthrow new Error(`Refusing install: archive entry escaped its quarantine directory: ${extractedBinary}`);\n\t\t}\n\n\t\tconst finalPath = join(destination, binaryFileName);\n\t\trenameSync(resolvedBinary, finalPath);\n\t\tif (platform() !== \"win32\") {\n\t\t\tchmodSync(finalPath, 0o755);\n\t\t}\n\t\treturn finalPath;\n\t} finally {\n\t\trmSync(quarantineDirectory, { recursive: true, force: true });\n\t}\n}\n\n// Download and install a tool from reviewed, pinned metadata (ADR 0017): bounded\n// download, digest verification, quarantine extraction, then atomic promotion.\nasync function downloadTool(tool: \"fd\" | \"rg\"): Promise<string> {\n\tconst plat = platform();\n\tconst architecture = arch();\n\tconst artifact = resolveManagedToolArtifact({ tool, platform: plat, architecture });\n\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\t// Unique download filename: fd and rg downloads can run concurrently during\n\t// startup, so sharing a fixed path causes races.\n\tconst archivePath = join(\n\t\tTOOLS_DIR,\n\t\t`download_${artifact.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}${\n\t\t\tartifact.archiveKind === \"zip\" ? \".zip\" : \".tar.gz\"\n\t\t}`,\n\t);\n\n\ttry {\n\t\tawait downloadFile(artifact.url, archivePath, artifact.maxBytes);\n\t\treturn await installManagedToolArchive({\n\t\t\tarchivePath,\n\t\t\tdestination: TOOLS_DIR,\n\t\t\tbinaryName: artifact.binaryName,\n\t\t\tarchiveKind: artifact.archiveKind,\n\t\t\texpectedSha256: artifact.sha256,\n\t\t\tmaxBytes: artifact.maxBytes,\n\t\t});\n\t} finally {\n\t\trmSync(archivePath, { force: true });\n\t}\n}\n\nexport interface HostToolBinary {\n\t/** File name the child looks for in its own tools directory (\"fd\", \"rg\", \"fd.exe\"). */\n\tname: string;\n\t/** Absolute host path to the executable backing that name. */\n\tpath: string;\n}\n\nfunction isExecutableFile(candidate: string, targetPlatform: NodeJS.Platform): boolean {\n\ttry {\n\t\tconst stats = statSync(candidate);\n\t\tif (!stats.isFile()) return false;\n\t\t// Windows has no POSIX execute bit; presence of the file is the only signal.\n\t\treturn targetPlatform === \"win32\" || (stats.mode & 0o111) !== 0;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Absolute host path for a managed tool, or undefined when it is installed nowhere.\n *\n * `getToolPath` may return a bare command name because its caller spawns through PATH.\n * A sandboxed child has no such luxury: its PATH still lists host directories that the\n * OS boundary has already replaced, so the supervisor must resolve a real path here and\n * project it in (ADR 0017, and the sandbox spec's tool-projection amendment).\n */\nexport function resolveHostToolBinary(\n\ttool: \"fd\" | \"rg\",\n\toptions: { toolsDirectory?: string; pathValue?: string; platform?: NodeJS.Platform } = {},\n): string | undefined {\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tconst targetPlatform = options.platform ?? platform();\n\tconst executableSuffix = targetPlatform === \"win32\" ? \".exe\" : \"\";\n\tconst managedPath = join(options.toolsDirectory ?? TOOLS_DIR, config.binaryName + executableSuffix);\n\tif (isExecutableFile(managedPath, targetPlatform)) {\n\t\treturn managedPath;\n\t}\n\n\tconst pathEntries = (options.pathValue ?? process.env.PATH ?? \"\")\n\t\t.split(targetPlatform === \"win32\" ? \";\" : \":\")\n\t\t.filter((entry) => entry.length > 0);\n\t// Preference runs by name first, matching getToolPath: a real `fd` anywhere on PATH\n\t// beats a `fdfind` alias, rather than whichever directory happens to come first.\n\tfor (const systemBinaryName of config.systemBinaryNames ?? [config.binaryName]) {\n\t\tfor (const entry of pathEntries) {\n\t\t\tconst candidate = join(entry, systemBinaryName + executableSuffix);\n\t\t\tif (isExecutableFile(candidate, targetPlatform)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Install any missing managed tool on the host, then report absolute paths for the ones\n * that exist. Downloading here rather than inside the child means one install serves every\n * workspace, and it happens on the side of the boundary that still has network access.\n */\nexport async function prepareHostToolBinaries(silent: boolean = false): Promise<HostToolBinary[]> {\n\tconst binaries: HostToolBinary[] = [];\n\tfor (const tool of [\"fd\", \"rg\"] as const) {\n\t\tawait ensureTool(tool, silent ? undefined : ({ message }) => console.error(message));\n\t\tconst hostPath = resolveHostToolBinary(tool);\n\t\tif (hostPath) {\n\t\t\tbinaries.push({ name: TOOLS[tool].binaryName + (platform() === \"win32\" ? \".exe\" : \"\"), path: hostPath });\n\t\t}\n\t}\n\treturn binaries;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n\tfd: \"fd\",\n\trg: \"ripgrep\",\n};\n\nexport interface ToolStatus {\n\ttype: \"info\" | \"warning\";\n\tmessage: string;\n}\n\n/**\n * Ensure a tool is available, downloading if necessary.\n * Reports progress through `onStatus`; status messages are otherwise silent.\n * Returns the tool path, or undefined if unavailable.\n */\nexport async function ensureTool(\n\ttool: \"fd\" | \"rg\",\n\tonStatus?: (status: ToolStatus) => void,\n): Promise<string | undefined> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tif (isOfflineModeEnabled()) {\n\t\tonStatus?.({ type: \"warning\", message: `${config.name} not found. Offline mode enabled, skipping download.` });\n\t\treturn undefined;\n\t}\n\n\t// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n\t// Users must install via pkg.\n\tif (platform() === \"android\") {\n\t\tconst pkgName = TERMUX_PACKAGES[tool] ?? tool;\n\t\tonStatus?.({ type: \"warning\", message: `${config.name} not found. Install with: pkg install ${pkgName}` });\n\t\treturn undefined;\n\t}\n\n\t// Tool not found - download it\n\tonStatus?.({ type: \"info\", message: `${config.name} not found. Downloading...` });\n\n\ttry {\n\t\tconst path = await downloadTool(tool);\n\t\tonStatus?.({ type: \"info\", message: `${config.name} installed to ${path}` });\n\t\treturn path;\n\t} catch (e) {\n\t\tonStatus?.({\n\t\t\ttype: \"warning\",\n\t\t\tmessage: `Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`,\n\t\t});\n\t\treturn undefined;\n\t}\n}\n"]}