@bastani/atomic 0.8.11 → 0.8.12-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (514) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/builtin/intercom/package.json +1 -1
  3. package/dist/builtin/mcp/package.json +1 -1
  4. package/dist/builtin/subagents/CHANGELOG.md +3 -0
  5. package/dist/builtin/subagents/package.json +1 -1
  6. package/dist/builtin/subagents/src/agents/agent-serializer.ts +3 -2
  7. package/dist/builtin/subagents/src/agents/agents.ts +1 -1
  8. package/dist/builtin/subagents/src/extension/index.ts +597 -471
  9. package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +16 -8
  10. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +9 -13
  11. package/dist/builtin/subagents/src/runs/foreground/execution.ts +7 -3
  12. package/dist/builtin/subagents/src/shared/formatters.ts +8 -3
  13. package/dist/builtin/subagents/src/slash/slash-commands.ts +625 -468
  14. package/dist/builtin/subagents/src/tui/render.ts +342 -158
  15. package/dist/builtin/web-access/package.json +1 -1
  16. package/dist/builtin/workflows/package.json +1 -1
  17. package/dist/builtin/workflows/src/runs/foreground/executor.ts +15 -4
  18. package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +75 -33
  19. package/dist/builtin/workflows/src/shared/store-types.ts +3 -4
  20. package/dist/builtin/workflows/src/shared/store.ts +2 -3
  21. package/dist/builtin/workflows/src/tui/graph-view.ts +12 -1
  22. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +1 -2
  23. package/dist/builtin/workflows/src/tui/status-helpers.ts +1 -1
  24. package/dist/bun/cli.d.ts.map +1 -1
  25. package/dist/bun/cli.js.map +1 -1
  26. package/dist/cli/args.d.ts +1 -1
  27. package/dist/cli/args.d.ts.map +1 -1
  28. package/dist/cli/args.js.map +1 -1
  29. package/dist/cli/config-selector.d.ts +2 -2
  30. package/dist/cli/config-selector.d.ts.map +1 -1
  31. package/dist/cli/config-selector.js.map +1 -1
  32. package/dist/cli/file-processor.d.ts.map +1 -1
  33. package/dist/cli/file-processor.js.map +1 -1
  34. package/dist/cli/initial-message.d.ts +1 -1
  35. package/dist/cli/initial-message.d.ts.map +1 -1
  36. package/dist/cli/initial-message.js.map +1 -1
  37. package/dist/cli/list-models.d.ts +1 -1
  38. package/dist/cli/list-models.d.ts.map +1 -1
  39. package/dist/cli/list-models.js.map +1 -1
  40. package/dist/cli/session-picker.d.ts +1 -1
  41. package/dist/cli/session-picker.d.ts.map +1 -1
  42. package/dist/cli/session-picker.js.map +1 -1
  43. package/dist/cli.d.ts.map +1 -1
  44. package/dist/cli.js +2 -6
  45. package/dist/cli.js.map +1 -1
  46. package/dist/config.d.ts.map +1 -1
  47. package/dist/config.js +45 -22
  48. package/dist/config.js.map +1 -1
  49. package/dist/core/agent-session-runtime.d.ts +9 -9
  50. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  51. package/dist/core/agent-session-runtime.js +2 -3
  52. package/dist/core/agent-session-runtime.js.map +1 -1
  53. package/dist/core/agent-session-services.d.ts +7 -7
  54. package/dist/core/agent-session-services.d.ts.map +1 -1
  55. package/dist/core/agent-session-services.js.map +1 -1
  56. package/dist/core/agent-session.d.ts +10 -10
  57. package/dist/core/agent-session.d.ts.map +1 -1
  58. package/dist/core/agent-session.js.map +1 -1
  59. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  60. package/dist/core/atomic-guide-command.js.map +1 -1
  61. package/dist/core/auth-guidance.d.ts.map +1 -1
  62. package/dist/core/auth-guidance.js.map +1 -1
  63. package/dist/core/auth-storage.d.ts +1 -1
  64. package/dist/core/auth-storage.d.ts.map +1 -1
  65. package/dist/core/auth-storage.js +1 -1
  66. package/dist/core/auth-storage.js.map +1 -1
  67. package/dist/core/bash-executor.d.ts +1 -1
  68. package/dist/core/bash-executor.d.ts.map +1 -1
  69. package/dist/core/bash-executor.js.map +1 -1
  70. package/dist/core/builtin-packages.d.ts.map +1 -1
  71. package/dist/core/builtin-packages.js.map +1 -1
  72. package/dist/core/compaction/branch-summarization.d.ts +3 -3
  73. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  74. package/dist/core/compaction/branch-summarization.js.map +1 -1
  75. package/dist/core/compaction/compaction.d.ts +2 -2
  76. package/dist/core/compaction/compaction.d.ts.map +1 -1
  77. package/dist/core/compaction/compaction.js.map +1 -1
  78. package/dist/core/compaction/index.d.ts +3 -3
  79. package/dist/core/compaction/index.d.ts.map +1 -1
  80. package/dist/core/compaction/index.js.map +1 -1
  81. package/dist/core/exec.d.ts.map +1 -1
  82. package/dist/core/exec.js.map +1 -1
  83. package/dist/core/export-html/index.d.ts +1 -1
  84. package/dist/core/export-html/index.d.ts.map +1 -1
  85. package/dist/core/export-html/index.js.map +1 -1
  86. package/dist/core/export-html/tool-renderer.d.ts +2 -2
  87. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  88. package/dist/core/export-html/tool-renderer.js.map +1 -1
  89. package/dist/core/extensions/index.d.ts +8 -8
  90. package/dist/core/extensions/index.d.ts.map +1 -1
  91. package/dist/core/extensions/index.js.map +1 -1
  92. package/dist/core/extensions/loader.d.ts +3 -3
  93. package/dist/core/extensions/loader.d.ts.map +1 -1
  94. package/dist/core/extensions/loader.js.map +1 -1
  95. package/dist/core/extensions/runner.d.ts +6 -6
  96. package/dist/core/extensions/runner.d.ts.map +1 -1
  97. package/dist/core/extensions/runner.js.map +1 -1
  98. package/dist/core/extensions/types.d.ts +20 -20
  99. package/dist/core/extensions/types.d.ts.map +1 -1
  100. package/dist/core/extensions/types.js.map +1 -1
  101. package/dist/core/extensions/wrapper.d.ts +2 -2
  102. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  103. package/dist/core/extensions/wrapper.js.map +1 -1
  104. package/dist/core/footer-data-provider.d.ts.map +1 -1
  105. package/dist/core/footer-data-provider.js.map +1 -1
  106. package/dist/core/http-dispatcher.d.ts +32 -0
  107. package/dist/core/http-dispatcher.d.ts.map +1 -0
  108. package/dist/core/http-dispatcher.js +43 -0
  109. package/dist/core/http-dispatcher.js.map +1 -0
  110. package/dist/core/index.d.ts +8 -8
  111. package/dist/core/index.d.ts.map +1 -1
  112. package/dist/core/index.js.map +1 -1
  113. package/dist/core/keybindings.d.ts.map +1 -1
  114. package/dist/core/keybindings.js.map +1 -1
  115. package/dist/core/model-registry.d.ts +4 -4
  116. package/dist/core/model-registry.d.ts.map +1 -1
  117. package/dist/core/model-registry.js +2 -2
  118. package/dist/core/model-registry.js.map +1 -1
  119. package/dist/core/model-resolver.d.ts +1 -1
  120. package/dist/core/model-resolver.d.ts.map +1 -1
  121. package/dist/core/model-resolver.js.map +1 -1
  122. package/dist/core/package-manager.d.ts +1 -1
  123. package/dist/core/package-manager.d.ts.map +1 -1
  124. package/dist/core/package-manager.js +10 -11
  125. package/dist/core/package-manager.js.map +1 -1
  126. package/dist/core/prompt-templates.d.ts +1 -1
  127. package/dist/core/prompt-templates.d.ts.map +1 -1
  128. package/dist/core/prompt-templates.js.map +1 -1
  129. package/dist/core/resolve-config-value.d.ts.map +1 -1
  130. package/dist/core/resolve-config-value.js.map +1 -1
  131. package/dist/core/resource-loader.d.ts +9 -9
  132. package/dist/core/resource-loader.d.ts.map +1 -1
  133. package/dist/core/resource-loader.js.map +1 -1
  134. package/dist/core/sdk.d.ts +13 -13
  135. package/dist/core/sdk.d.ts.map +1 -1
  136. package/dist/core/sdk.js.map +1 -1
  137. package/dist/core/session-manager.d.ts +1 -1
  138. package/dist/core/session-manager.d.ts.map +1 -1
  139. package/dist/core/session-manager.js.map +1 -1
  140. package/dist/core/settings-manager.d.ts +3 -0
  141. package/dist/core/settings-manager.d.ts.map +1 -1
  142. package/dist/core/settings-manager.js +21 -0
  143. package/dist/core/settings-manager.js.map +1 -1
  144. package/dist/core/skills.d.ts +2 -2
  145. package/dist/core/skills.d.ts.map +1 -1
  146. package/dist/core/skills.js.map +1 -1
  147. package/dist/core/slash-commands.d.ts +1 -1
  148. package/dist/core/slash-commands.d.ts.map +1 -1
  149. package/dist/core/slash-commands.js.map +1 -1
  150. package/dist/core/source-info.d.ts +1 -1
  151. package/dist/core/source-info.d.ts.map +1 -1
  152. package/dist/core/source-info.js.map +1 -1
  153. package/dist/core/system-prompt.d.ts +1 -1
  154. package/dist/core/system-prompt.d.ts.map +1 -1
  155. package/dist/core/system-prompt.js +7 -8
  156. package/dist/core/system-prompt.js.map +1 -1
  157. package/dist/core/telemetry.d.ts +1 -1
  158. package/dist/core/telemetry.d.ts.map +1 -1
  159. package/dist/core/telemetry.js.map +1 -1
  160. package/dist/core/timings.d.ts.map +1 -1
  161. package/dist/core/timings.js.map +1 -1
  162. package/dist/core/tools/ask-user-question/ask-user-question.d.ts +4 -4
  163. package/dist/core/tools/ask-user-question/ask-user-question.d.ts.map +1 -1
  164. package/dist/core/tools/ask-user-question/ask-user-question.js.map +1 -1
  165. package/dist/core/tools/ask-user-question/index.d.ts +1 -1
  166. package/dist/core/tools/ask-user-question/index.d.ts.map +1 -1
  167. package/dist/core/tools/ask-user-question/index.js.map +1 -1
  168. package/dist/core/tools/ask-user-question/state/build-questionnaire.d.ts +5 -5
  169. package/dist/core/tools/ask-user-question/state/build-questionnaire.d.ts.map +1 -1
  170. package/dist/core/tools/ask-user-question/state/build-questionnaire.js.map +1 -1
  171. package/dist/core/tools/ask-user-question/state/key-router.d.ts +2 -2
  172. package/dist/core/tools/ask-user-question/state/key-router.d.ts.map +1 -1
  173. package/dist/core/tools/ask-user-question/state/key-router.js.map +1 -1
  174. package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts +3 -3
  175. package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts.map +1 -1
  176. package/dist/core/tools/ask-user-question/state/questionnaire-session.js.map +1 -1
  177. package/dist/core/tools/ask-user-question/state/row-intent.d.ts +2 -2
  178. package/dist/core/tools/ask-user-question/state/row-intent.d.ts.map +1 -1
  179. package/dist/core/tools/ask-user-question/state/row-intent.js.map +1 -1
  180. package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts +6 -6
  181. package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts.map +1 -1
  182. package/dist/core/tools/ask-user-question/state/selectors/contract.js.map +1 -1
  183. package/dist/core/tools/ask-user-question/state/selectors/derivations.d.ts +2 -2
  184. package/dist/core/tools/ask-user-question/state/selectors/derivations.d.ts.map +1 -1
  185. package/dist/core/tools/ask-user-question/state/selectors/derivations.js.map +1 -1
  186. package/dist/core/tools/ask-user-question/state/selectors/focus.d.ts +1 -1
  187. package/dist/core/tools/ask-user-question/state/selectors/focus.d.ts.map +1 -1
  188. package/dist/core/tools/ask-user-question/state/selectors/focus.js.map +1 -1
  189. package/dist/core/tools/ask-user-question/state/selectors/projections.d.ts +8 -8
  190. package/dist/core/tools/ask-user-question/state/selectors/projections.d.ts.map +1 -1
  191. package/dist/core/tools/ask-user-question/state/selectors/projections.js.map +1 -1
  192. package/dist/core/tools/ask-user-question/state/state-reducer.d.ts +4 -4
  193. package/dist/core/tools/ask-user-question/state/state-reducer.d.ts.map +1 -1
  194. package/dist/core/tools/ask-user-question/state/state-reducer.js.map +1 -1
  195. package/dist/core/tools/ask-user-question/state/state.d.ts +2 -2
  196. package/dist/core/tools/ask-user-question/state/state.d.ts.map +1 -1
  197. package/dist/core/tools/ask-user-question/state/state.js.map +1 -1
  198. package/dist/core/tools/ask-user-question/tool/format-answer.d.ts +1 -1
  199. package/dist/core/tools/ask-user-question/tool/format-answer.d.ts.map +1 -1
  200. package/dist/core/tools/ask-user-question/tool/format-answer.js.map +1 -1
  201. package/dist/core/tools/ask-user-question/tool/response-envelope.d.ts +1 -1
  202. package/dist/core/tools/ask-user-question/tool/response-envelope.d.ts.map +1 -1
  203. package/dist/core/tools/ask-user-question/tool/response-envelope.js.map +1 -1
  204. package/dist/core/tools/ask-user-question/tool/types.d.ts.map +1 -1
  205. package/dist/core/tools/ask-user-question/tool/types.js.map +1 -1
  206. package/dist/core/tools/ask-user-question/tool/validate-questionnaire.d.ts +1 -1
  207. package/dist/core/tools/ask-user-question/tool/validate-questionnaire.d.ts.map +1 -1
  208. package/dist/core/tools/ask-user-question/tool/validate-questionnaire.js.map +1 -1
  209. package/dist/core/tools/ask-user-question/view/body-residual-spacer.d.ts.map +1 -1
  210. package/dist/core/tools/ask-user-question/view/body-residual-spacer.js.map +1 -1
  211. package/dist/core/tools/ask-user-question/view/component-binding.d.ts +4 -4
  212. package/dist/core/tools/ask-user-question/view/component-binding.d.ts.map +1 -1
  213. package/dist/core/tools/ask-user-question/view/component-binding.js.map +1 -1
  214. package/dist/core/tools/ask-user-question/view/components/chat-row-view.d.ts +2 -2
  215. package/dist/core/tools/ask-user-question/view/components/chat-row-view.d.ts.map +1 -1
  216. package/dist/core/tools/ask-user-question/view/components/chat-row-view.js.map +1 -1
  217. package/dist/core/tools/ask-user-question/view/components/multi-select-view.d.ts +4 -4
  218. package/dist/core/tools/ask-user-question/view/components/multi-select-view.d.ts.map +1 -1
  219. package/dist/core/tools/ask-user-question/view/components/multi-select-view.js.map +1 -1
  220. package/dist/core/tools/ask-user-question/view/components/option-list-view.d.ts +2 -2
  221. package/dist/core/tools/ask-user-question/view/components/option-list-view.d.ts.map +1 -1
  222. package/dist/core/tools/ask-user-question/view/components/option-list-view.js.map +1 -1
  223. package/dist/core/tools/ask-user-question/view/components/preview/markdown-content-cache.d.ts +2 -2
  224. package/dist/core/tools/ask-user-question/view/components/preview/markdown-content-cache.d.ts.map +1 -1
  225. package/dist/core/tools/ask-user-question/view/components/preview/markdown-content-cache.js.map +1 -1
  226. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts +3 -3
  227. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts.map +1 -1
  228. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js.map +1 -1
  229. package/dist/core/tools/ask-user-question/view/components/preview/preview-layout-decider.d.ts +2 -2
  230. package/dist/core/tools/ask-user-question/view/components/preview/preview-layout-decider.d.ts.map +1 -1
  231. package/dist/core/tools/ask-user-question/view/components/preview/preview-layout-decider.js.map +1 -1
  232. package/dist/core/tools/ask-user-question/view/components/preview/preview-pane.d.ts +8 -8
  233. package/dist/core/tools/ask-user-question/view/components/preview/preview-pane.d.ts.map +1 -1
  234. package/dist/core/tools/ask-user-question/view/components/preview/preview-pane.js.map +1 -1
  235. package/dist/core/tools/ask-user-question/view/components/submit-picker.d.ts +3 -3
  236. package/dist/core/tools/ask-user-question/view/components/submit-picker.d.ts.map +1 -1
  237. package/dist/core/tools/ask-user-question/view/components/submit-picker.js.map +1 -1
  238. package/dist/core/tools/ask-user-question/view/components/tab-bar.d.ts +3 -3
  239. package/dist/core/tools/ask-user-question/view/components/tab-bar.d.ts.map +1 -1
  240. package/dist/core/tools/ask-user-question/view/components/tab-bar.js.map +1 -1
  241. package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts +8 -8
  242. package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts.map +1 -1
  243. package/dist/core/tools/ask-user-question/view/dialog-builder.js.map +1 -1
  244. package/dist/core/tools/ask-user-question/view/props-adapter.d.ts +5 -5
  245. package/dist/core/tools/ask-user-question/view/props-adapter.d.ts.map +1 -1
  246. package/dist/core/tools/ask-user-question/view/props-adapter.js.map +1 -1
  247. package/dist/core/tools/ask-user-question/view/tab-components.d.ts +4 -4
  248. package/dist/core/tools/ask-user-question/view/tab-components.d.ts.map +1 -1
  249. package/dist/core/tools/ask-user-question/view/tab-components.js.map +1 -1
  250. package/dist/core/tools/ask-user-question/view/tab-content-strategy.d.ts +9 -9
  251. package/dist/core/tools/ask-user-question/view/tab-content-strategy.d.ts.map +1 -1
  252. package/dist/core/tools/ask-user-question/view/tab-content-strategy.js +2 -2
  253. package/dist/core/tools/ask-user-question/view/tab-content-strategy.js.map +1 -1
  254. package/dist/core/tools/bash.d.ts +2 -2
  255. package/dist/core/tools/bash.d.ts.map +1 -1
  256. package/dist/core/tools/bash.js +9 -1
  257. package/dist/core/tools/bash.js.map +1 -1
  258. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  259. package/dist/core/tools/edit-diff.js.map +1 -1
  260. package/dist/core/tools/edit.d.ts +2 -2
  261. package/dist/core/tools/edit.d.ts.map +1 -1
  262. package/dist/core/tools/edit.js.map +1 -1
  263. package/dist/core/tools/find.d.ts +2 -2
  264. package/dist/core/tools/find.d.ts.map +1 -1
  265. package/dist/core/tools/find.js.map +1 -1
  266. package/dist/core/tools/grep.d.ts +2 -2
  267. package/dist/core/tools/grep.d.ts.map +1 -1
  268. package/dist/core/tools/grep.js.map +1 -1
  269. package/dist/core/tools/index.d.ts +19 -19
  270. package/dist/core/tools/index.d.ts.map +1 -1
  271. package/dist/core/tools/index.js.map +1 -1
  272. package/dist/core/tools/ls.d.ts +2 -2
  273. package/dist/core/tools/ls.d.ts.map +1 -1
  274. package/dist/core/tools/ls.js.map +1 -1
  275. package/dist/core/tools/output-accumulator.d.ts +1 -1
  276. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  277. package/dist/core/tools/output-accumulator.js.map +1 -1
  278. package/dist/core/tools/read.d.ts +2 -2
  279. package/dist/core/tools/read.d.ts.map +1 -1
  280. package/dist/core/tools/read.js.map +1 -1
  281. package/dist/core/tools/render-utils.d.ts +1 -1
  282. package/dist/core/tools/render-utils.d.ts.map +1 -1
  283. package/dist/core/tools/render-utils.js.map +1 -1
  284. package/dist/core/tools/todos.d.ts +1 -1
  285. package/dist/core/tools/todos.d.ts.map +1 -1
  286. package/dist/core/tools/todos.js.map +1 -1
  287. package/dist/core/tools/tool-definition-wrapper.d.ts +1 -1
  288. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  289. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  290. package/dist/core/tools/write.d.ts +1 -1
  291. package/dist/core/tools/write.d.ts.map +1 -1
  292. package/dist/core/tools/write.js.map +1 -1
  293. package/dist/index.d.ts +30 -29
  294. package/dist/index.d.ts.map +1 -1
  295. package/dist/index.js +1 -0
  296. package/dist/index.js.map +1 -1
  297. package/dist/main.d.ts +1 -1
  298. package/dist/main.d.ts.map +1 -1
  299. package/dist/main.js +2 -0
  300. package/dist/main.js.map +1 -1
  301. package/dist/migrations.d.ts.map +1 -1
  302. package/dist/migrations.js.map +1 -1
  303. package/dist/modes/index.d.ts +5 -5
  304. package/dist/modes/index.d.ts.map +1 -1
  305. package/dist/modes/index.js.map +1 -1
  306. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  307. package/dist/modes/interactive/components/armin.js.map +1 -1
  308. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  309. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  310. package/dist/modes/interactive/components/atomic-banner.d.ts +1 -1
  311. package/dist/modes/interactive/components/atomic-banner.d.ts.map +1 -1
  312. package/dist/modes/interactive/components/atomic-banner.js.map +1 -1
  313. package/dist/modes/interactive/components/bash-execution.d.ts +1 -1
  314. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  315. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  316. package/dist/modes/interactive/components/bordered-loader.d.ts +1 -1
  317. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  318. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  319. package/dist/modes/interactive/components/branch-summary-message.d.ts +1 -1
  320. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  321. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  322. package/dist/modes/interactive/components/chat-message-renderer.d.ts +3 -3
  323. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  324. package/dist/modes/interactive/components/chat-message-renderer.js +1 -1
  325. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  326. package/dist/modes/interactive/components/chat-transcript.d.ts.map +1 -1
  327. package/dist/modes/interactive/components/chat-transcript.js.map +1 -1
  328. package/dist/modes/interactive/components/compaction-summary-message.d.ts +1 -1
  329. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  330. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  331. package/dist/modes/interactive/components/config-selector.d.ts +2 -2
  332. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  333. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  334. package/dist/modes/interactive/components/countdown-timer.d.ts +2 -2
  335. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
  336. package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
  337. package/dist/modes/interactive/components/custom-editor.d.ts +1 -1
  338. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  339. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  340. package/dist/modes/interactive/components/custom-message.d.ts +2 -2
  341. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  342. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  343. package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -1
  344. package/dist/modes/interactive/components/daxnuts.js.map +1 -1
  345. package/dist/modes/interactive/components/diff.d.ts.map +1 -1
  346. package/dist/modes/interactive/components/diff.js.map +1 -1
  347. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  348. package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  349. package/dist/modes/interactive/components/earendil-announcement.d.ts.map +1 -1
  350. package/dist/modes/interactive/components/earendil-announcement.js.map +1 -1
  351. package/dist/modes/interactive/components/extension-editor.d.ts +1 -1
  352. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  353. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  354. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  355. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  356. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  357. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  358. package/dist/modes/interactive/components/footer.d.ts +3 -3
  359. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  360. package/dist/modes/interactive/components/footer.js +1 -1
  361. package/dist/modes/interactive/components/footer.js.map +1 -1
  362. package/dist/modes/interactive/components/index.d.ts +34 -34
  363. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  364. package/dist/modes/interactive/components/index.js.map +1 -1
  365. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  366. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  367. package/dist/modes/interactive/components/login-dialog.d.ts +1 -1
  368. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  369. package/dist/modes/interactive/components/login-dialog.js +1 -1
  370. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  371. package/dist/modes/interactive/components/model-selector.d.ts +2 -2
  372. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  373. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  374. package/dist/modes/interactive/components/oauth-selector.d.ts +1 -1
  375. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  376. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  377. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  378. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  379. package/dist/modes/interactive/components/session-selector-search.d.ts +1 -1
  380. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  381. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  382. package/dist/modes/interactive/components/session-selector.d.ts +3 -3
  383. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  384. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  385. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  386. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  387. package/dist/modes/interactive/components/settings-selector.js +13 -0
  388. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  389. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -1
  390. package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
  391. package/dist/modes/interactive/components/skill-invocation-message.d.ts +1 -1
  392. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  393. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  394. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -1
  395. package/dist/modes/interactive/components/theme-selector.js.map +1 -1
  396. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  397. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  398. package/dist/modes/interactive/components/tool-execution.d.ts +1 -1
  399. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  400. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  401. package/dist/modes/interactive/components/tree-selector.d.ts +1 -1
  402. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  403. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  404. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  405. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  406. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  407. package/dist/modes/interactive/components/user-message.js.map +1 -1
  408. package/dist/modes/interactive/components/working-status.d.ts.map +1 -1
  409. package/dist/modes/interactive/components/working-status.js.map +1 -1
  410. package/dist/modes/interactive/interactive-mode.d.ts +2 -2
  411. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  412. package/dist/modes/interactive/interactive-mode.js +7 -1
  413. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  414. package/dist/modes/interactive/theme/theme.d.ts +1 -1
  415. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  416. package/dist/modes/interactive/theme/theme.js.map +1 -1
  417. package/dist/modes/print-mode.d.ts +1 -1
  418. package/dist/modes/print-mode.d.ts.map +1 -1
  419. package/dist/modes/print-mode.js.map +1 -1
  420. package/dist/modes/rpc/rpc-client.d.ts +5 -5
  421. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  422. package/dist/modes/rpc/rpc-client.js +1 -1
  423. package/dist/modes/rpc/rpc-client.js.map +1 -1
  424. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  425. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  426. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  427. package/dist/modes/rpc/rpc-types.d.ts +4 -4
  428. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  429. package/dist/modes/rpc/rpc-types.js.map +1 -1
  430. package/dist/package-manager-cli.d.ts.map +1 -1
  431. package/dist/package-manager-cli.js +22 -6
  432. package/dist/package-manager-cli.js.map +1 -1
  433. package/dist/utils/changelog.d.ts +1 -1
  434. package/dist/utils/changelog.d.ts.map +1 -1
  435. package/dist/utils/changelog.js.map +1 -1
  436. package/dist/utils/child-process.d.ts +5 -2
  437. package/dist/utils/child-process.d.ts.map +1 -1
  438. package/dist/utils/child-process.js +9 -7
  439. package/dist/utils/child-process.js.map +1 -1
  440. package/dist/utils/clipboard-image.d.ts.map +1 -1
  441. package/dist/utils/clipboard-image.js.map +1 -1
  442. package/dist/utils/clipboard.d.ts.map +1 -1
  443. package/dist/utils/clipboard.js.map +1 -1
  444. package/dist/utils/exif-orientation.d.ts +1 -1
  445. package/dist/utils/exif-orientation.d.ts.map +1 -1
  446. package/dist/utils/exif-orientation.js.map +1 -1
  447. package/dist/utils/image-convert.d.ts.map +1 -1
  448. package/dist/utils/image-convert.js.map +1 -1
  449. package/dist/utils/image-resize.d.ts.map +1 -1
  450. package/dist/utils/image-resize.js.map +1 -1
  451. package/dist/utils/shell.d.ts.map +1 -1
  452. package/dist/utils/shell.js.map +1 -1
  453. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  454. package/dist/utils/syntax-highlight.js.map +1 -1
  455. package/dist/utils/tools-manager.d.ts.map +1 -1
  456. package/dist/utils/tools-manager.js.map +1 -1
  457. package/dist/utils/version-check.d.ts.map +1 -1
  458. package/dist/utils/version-check.js.map +1 -1
  459. package/dist/utils/windows-self-update.d.ts +3 -0
  460. package/dist/utils/windows-self-update.d.ts.map +1 -0
  461. package/dist/utils/windows-self-update.js +78 -0
  462. package/dist/utils/windows-self-update.js.map +1 -0
  463. package/docs/compaction.md +5 -5
  464. package/docs/custom-provider.md +2 -2
  465. package/docs/development.md +22 -16
  466. package/docs/docs.json +6 -2
  467. package/docs/extensions.md +25 -25
  468. package/docs/index.md +11 -14
  469. package/docs/keybindings.md +5 -5
  470. package/docs/models.md +6 -6
  471. package/docs/packages.md +55 -48
  472. package/docs/prompt-templates.md +5 -5
  473. package/docs/providers.md +10 -10
  474. package/docs/quickstart.md +32 -30
  475. package/docs/rpc.md +9 -9
  476. package/docs/sdk.md +1 -1
  477. package/docs/session-format.md +3 -3
  478. package/docs/sessions.md +11 -11
  479. package/docs/settings.md +18 -15
  480. package/docs/shell-aliases.md +2 -2
  481. package/docs/skills.md +11 -11
  482. package/docs/terminal-setup.md +8 -8
  483. package/docs/termux.md +6 -6
  484. package/docs/themes.md +10 -10
  485. package/docs/tui.md +5 -4
  486. package/docs/usage.md +50 -50
  487. package/docs/windows.md +2 -2
  488. package/docs/workflows.md +695 -0
  489. package/examples/extensions/custom-provider-gitlab-duo/test.ts +1 -1
  490. package/examples/extensions/doom-overlay/doom-component.ts +2 -2
  491. package/examples/extensions/doom-overlay/index.ts +3 -3
  492. package/examples/extensions/overlay-qa-tests.ts +116 -33
  493. package/examples/extensions/overlay-test.ts +9 -3
  494. package/examples/extensions/plan-mode/index.ts +1 -1
  495. package/examples/extensions/subagent/index.ts +1159 -903
  496. package/package.json +6 -4
  497. package/dist/builtin/workflows/skills/workflow/SKILL.md +0 -322
  498. package/dist/builtin/workflows/skills/workflow/references/context-engineering/advanced-evaluation.md +0 -404
  499. package/dist/builtin/workflows/skills/workflow/references/context-engineering/bdi-mental-states.md +0 -313
  500. package/dist/builtin/workflows/skills/workflow/references/context-engineering/context-compression.md +0 -274
  501. package/dist/builtin/workflows/skills/workflow/references/context-engineering/context-degradation.md +0 -208
  502. package/dist/builtin/workflows/skills/workflow/references/context-engineering/context-fundamentals.md +0 -203
  503. package/dist/builtin/workflows/skills/workflow/references/context-engineering/context-optimization.md +0 -197
  504. package/dist/builtin/workflows/skills/workflow/references/context-engineering/evaluation.md +0 -253
  505. package/dist/builtin/workflows/skills/workflow/references/context-engineering/filesystem-context.md +0 -289
  506. package/dist/builtin/workflows/skills/workflow/references/context-engineering/hosted-agents.md +0 -262
  507. package/dist/builtin/workflows/skills/workflow/references/context-engineering/memory-systems.md +0 -221
  508. package/dist/builtin/workflows/skills/workflow/references/context-engineering/multi-agent-patterns.md +0 -259
  509. package/dist/builtin/workflows/skills/workflow/references/context-engineering/project-development.md +0 -293
  510. package/dist/builtin/workflows/skills/workflow/references/context-engineering/tool-design.md +0 -273
  511. package/dist/builtin/workflows/skills/workflow/references/context-engineering.md +0 -23
  512. package/dist/builtin/workflows/skills/workflow/references/design-checklist.md +0 -83
  513. package/dist/builtin/workflows/skills/workflow/references/running-workflows.md +0 -159
  514. package/dist/builtin/workflows/skills/workflow/references/sdk-authoring.md +0 -260
@@ -1 +1 @@
1
- {"version":3,"file":"exif-orientation.d.ts","sourceRoot":"","sources":["../../src/utils/exif-orientation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,KAAK,MAAM,GAAG,cAAc,4BAA4B,CAAC,CAAC;AAgJ1D,wBAAgB,oBAAoB,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,eAAe,EACtB,aAAa,EAAE,UAAU,GACvB,eAAe,CAgCjB","sourcesContent":["import type { PhotonImageType } from \"./photon.js\";\n\ntype Photon = typeof import(\"@silvia-odwyer/photon-node\");\n\nfunction readOrientationFromTiff(bytes: Uint8Array, tiffStart: number): number {\n\tif (tiffStart + 8 > bytes.length) return 1;\n\n\tconst byteOrder = (bytes[tiffStart] << 8) | bytes[tiffStart + 1];\n\tconst le = byteOrder === 0x4949;\n\n\tconst read16 = (pos: number): number => {\n\t\tif (le) return bytes[pos] | (bytes[pos + 1] << 8);\n\t\treturn (bytes[pos] << 8) | bytes[pos + 1];\n\t};\n\n\tconst read32 = (pos: number): number => {\n\t\tif (le) return bytes[pos] | (bytes[pos + 1] << 8) | (bytes[pos + 2] << 16) | (bytes[pos + 3] << 24);\n\t\treturn ((bytes[pos] << 24) | (bytes[pos + 1] << 16) | (bytes[pos + 2] << 8) | bytes[pos + 3]) >>> 0;\n\t};\n\n\tconst ifdOffset = read32(tiffStart + 4);\n\tconst ifdStart = tiffStart + ifdOffset;\n\tif (ifdStart + 2 > bytes.length) return 1;\n\n\tconst entryCount = read16(ifdStart);\n\tfor (let i = 0; i < entryCount; i++) {\n\t\tconst entryPos = ifdStart + 2 + i * 12;\n\t\tif (entryPos + 12 > bytes.length) return 1;\n\n\t\tif (read16(entryPos) === 0x0112) {\n\t\t\tconst value = read16(entryPos + 8);\n\t\t\treturn value >= 1 && value <= 8 ? value : 1;\n\t\t}\n\t}\n\n\treturn 1;\n}\n\nfunction findJpegTiffOffset(bytes: Uint8Array): number {\n\tlet offset = 2;\n\twhile (offset < bytes.length - 1) {\n\t\tif (bytes[offset] !== 0xff) return -1;\n\t\tconst marker = bytes[offset + 1];\n\t\tif (marker === 0xff) {\n\t\t\toffset++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (marker === 0xe1) {\n\t\t\tif (offset + 4 >= bytes.length) return -1;\n\t\t\tconst segmentStart = offset + 4;\n\t\t\tif (segmentStart + 6 > bytes.length) return -1;\n\t\t\tif (!hasExifHeader(bytes, segmentStart)) return -1;\n\t\t\treturn segmentStart + 6;\n\t\t}\n\n\t\tif (offset + 4 > bytes.length) return -1;\n\t\tconst length = (bytes[offset + 2] << 8) | bytes[offset + 3];\n\t\toffset += 2 + length;\n\t}\n\n\treturn -1;\n}\n\nfunction findWebpTiffOffset(bytes: Uint8Array): number {\n\tlet offset = 12;\n\twhile (offset + 8 <= bytes.length) {\n\t\tconst chunkId = String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);\n\t\tconst chunkSize =\n\t\t\tbytes[offset + 4] | (bytes[offset + 5] << 8) | (bytes[offset + 6] << 16) | (bytes[offset + 7] << 24);\n\t\tconst dataStart = offset + 8;\n\n\t\tif (chunkId === \"EXIF\") {\n\t\t\tif (dataStart + chunkSize > bytes.length) return -1;\n\t\t\t// Some WebP files have \"Exif\\0\\0\" prefix before the TIFF header\n\t\t\tconst tiffStart = chunkSize >= 6 && hasExifHeader(bytes, dataStart) ? dataStart + 6 : dataStart;\n\t\t\treturn tiffStart;\n\t\t}\n\n\t\t// RIFF chunks are padded to even size\n\t\toffset = dataStart + chunkSize + (chunkSize % 2);\n\t}\n\n\treturn -1;\n}\n\nfunction hasExifHeader(bytes: Uint8Array, offset: number): boolean {\n\treturn (\n\t\tbytes[offset] === 0x45 &&\n\t\tbytes[offset + 1] === 0x78 &&\n\t\tbytes[offset + 2] === 0x69 &&\n\t\tbytes[offset + 3] === 0x66 &&\n\t\tbytes[offset + 4] === 0x00 &&\n\t\tbytes[offset + 5] === 0x00\n\t);\n}\n\nfunction getExifOrientation(bytes: Uint8Array): number {\n\tlet tiffOffset = -1;\n\n\t// JPEG: starts with FF D8\n\tif (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xd8) {\n\t\ttiffOffset = findJpegTiffOffset(bytes);\n\t}\n\t// WebP: starts with RIFF....WEBP\n\telse if (\n\t\tbytes.length >= 12 &&\n\t\tbytes[0] === 0x52 &&\n\t\tbytes[1] === 0x49 &&\n\t\tbytes[2] === 0x46 &&\n\t\tbytes[3] === 0x46 &&\n\t\tbytes[8] === 0x57 &&\n\t\tbytes[9] === 0x45 &&\n\t\tbytes[10] === 0x42 &&\n\t\tbytes[11] === 0x50\n\t) {\n\t\ttiffOffset = findWebpTiffOffset(bytes);\n\t}\n\n\tif (tiffOffset === -1) return 1;\n\treturn readOrientationFromTiff(bytes, tiffOffset);\n}\n\ntype DstIndexFn = (x: number, y: number, w: number, h: number) => number;\n\nfunction rotate90(photon: Photon, image: PhotonImageType, dstIndex: DstIndexFn): PhotonImageType {\n\tconst w = image.get_width();\n\tconst h = image.get_height();\n\tconst src = image.get_raw_pixels();\n\tconst dst = new Uint8Array(src.length);\n\n\tfor (let y = 0; y < h; y++) {\n\t\tfor (let x = 0; x < w; x++) {\n\t\t\tconst srcIdx = (y * w + x) * 4;\n\t\t\tconst dstIdx = dstIndex(x, y, w, h) * 4;\n\t\t\tdst[dstIdx] = src[srcIdx];\n\t\t\tdst[dstIdx + 1] = src[srcIdx + 1];\n\t\t\tdst[dstIdx + 2] = src[srcIdx + 2];\n\t\t\tdst[dstIdx + 3] = src[srcIdx + 3];\n\t\t}\n\t}\n\n\treturn new photon.PhotonImage(dst, h, w);\n}\n\n// Flip orientations mutate in-place. Rotations return a new image (caller must free the old one if different).\nexport function applyExifOrientation(\n\tphoton: Photon,\n\timage: PhotonImageType,\n\toriginalBytes: Uint8Array,\n): PhotonImageType {\n\tconst orientation = getExifOrientation(originalBytes);\n\tif (orientation === 1) return image;\n\n\tswitch (orientation) {\n\t\tcase 2:\n\t\t\tphoton.fliph(image);\n\t\t\treturn image;\n\t\tcase 3:\n\t\t\tphoton.fliph(image);\n\t\t\tphoton.flipv(image);\n\t\t\treturn image;\n\t\tcase 4:\n\t\t\tphoton.flipv(image);\n\t\t\treturn image;\n\t\tcase 5: {\n\t\t\tconst rotated = rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));\n\t\t\tphoton.fliph(rotated);\n\t\t\treturn rotated;\n\t\t}\n\t\tcase 6:\n\t\t\treturn rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));\n\t\tcase 7: {\n\t\t\tconst rotated = rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);\n\t\t\tphoton.fliph(rotated);\n\t\t\treturn rotated;\n\t\t}\n\t\tcase 8:\n\t\t\treturn rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);\n\t\tdefault:\n\t\t\treturn image;\n\t}\n}\n"]}
1
+ {"version":3,"file":"exif-orientation.d.ts","sourceRoot":"","sources":["../../src/utils/exif-orientation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,KAAK,MAAM,GAAG,cAAc,4BAA4B,CAAC,CAAC;AAgJ1D,wBAAgB,oBAAoB,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,eAAe,EACtB,aAAa,EAAE,UAAU,GACvB,eAAe,CAgCjB","sourcesContent":["import type { PhotonImageType } from \"./photon.ts\";\n\ntype Photon = typeof import(\"@silvia-odwyer/photon-node\");\n\nfunction readOrientationFromTiff(bytes: Uint8Array, tiffStart: number): number {\n\tif (tiffStart + 8 > bytes.length) return 1;\n\n\tconst byteOrder = (bytes[tiffStart] << 8) | bytes[tiffStart + 1];\n\tconst le = byteOrder === 0x4949;\n\n\tconst read16 = (pos: number): number => {\n\t\tif (le) return bytes[pos] | (bytes[pos + 1] << 8);\n\t\treturn (bytes[pos] << 8) | bytes[pos + 1];\n\t};\n\n\tconst read32 = (pos: number): number => {\n\t\tif (le) return bytes[pos] | (bytes[pos + 1] << 8) | (bytes[pos + 2] << 16) | (bytes[pos + 3] << 24);\n\t\treturn ((bytes[pos] << 24) | (bytes[pos + 1] << 16) | (bytes[pos + 2] << 8) | bytes[pos + 3]) >>> 0;\n\t};\n\n\tconst ifdOffset = read32(tiffStart + 4);\n\tconst ifdStart = tiffStart + ifdOffset;\n\tif (ifdStart + 2 > bytes.length) return 1;\n\n\tconst entryCount = read16(ifdStart);\n\tfor (let i = 0; i < entryCount; i++) {\n\t\tconst entryPos = ifdStart + 2 + i * 12;\n\t\tif (entryPos + 12 > bytes.length) return 1;\n\n\t\tif (read16(entryPos) === 0x0112) {\n\t\t\tconst value = read16(entryPos + 8);\n\t\t\treturn value >= 1 && value <= 8 ? value : 1;\n\t\t}\n\t}\n\n\treturn 1;\n}\n\nfunction findJpegTiffOffset(bytes: Uint8Array): number {\n\tlet offset = 2;\n\twhile (offset < bytes.length - 1) {\n\t\tif (bytes[offset] !== 0xff) return -1;\n\t\tconst marker = bytes[offset + 1];\n\t\tif (marker === 0xff) {\n\t\t\toffset++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (marker === 0xe1) {\n\t\t\tif (offset + 4 >= bytes.length) return -1;\n\t\t\tconst segmentStart = offset + 4;\n\t\t\tif (segmentStart + 6 > bytes.length) return -1;\n\t\t\tif (!hasExifHeader(bytes, segmentStart)) return -1;\n\t\t\treturn segmentStart + 6;\n\t\t}\n\n\t\tif (offset + 4 > bytes.length) return -1;\n\t\tconst length = (bytes[offset + 2] << 8) | bytes[offset + 3];\n\t\toffset += 2 + length;\n\t}\n\n\treturn -1;\n}\n\nfunction findWebpTiffOffset(bytes: Uint8Array): number {\n\tlet offset = 12;\n\twhile (offset + 8 <= bytes.length) {\n\t\tconst chunkId = String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);\n\t\tconst chunkSize =\n\t\t\tbytes[offset + 4] | (bytes[offset + 5] << 8) | (bytes[offset + 6] << 16) | (bytes[offset + 7] << 24);\n\t\tconst dataStart = offset + 8;\n\n\t\tif (chunkId === \"EXIF\") {\n\t\t\tif (dataStart + chunkSize > bytes.length) return -1;\n\t\t\t// Some WebP files have \"Exif\\0\\0\" prefix before the TIFF header\n\t\t\tconst tiffStart = chunkSize >= 6 && hasExifHeader(bytes, dataStart) ? dataStart + 6 : dataStart;\n\t\t\treturn tiffStart;\n\t\t}\n\n\t\t// RIFF chunks are padded to even size\n\t\toffset = dataStart + chunkSize + (chunkSize % 2);\n\t}\n\n\treturn -1;\n}\n\nfunction hasExifHeader(bytes: Uint8Array, offset: number): boolean {\n\treturn (\n\t\tbytes[offset] === 0x45 &&\n\t\tbytes[offset + 1] === 0x78 &&\n\t\tbytes[offset + 2] === 0x69 &&\n\t\tbytes[offset + 3] === 0x66 &&\n\t\tbytes[offset + 4] === 0x00 &&\n\t\tbytes[offset + 5] === 0x00\n\t);\n}\n\nfunction getExifOrientation(bytes: Uint8Array): number {\n\tlet tiffOffset = -1;\n\n\t// JPEG: starts with FF D8\n\tif (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xd8) {\n\t\ttiffOffset = findJpegTiffOffset(bytes);\n\t}\n\t// WebP: starts with RIFF....WEBP\n\telse if (\n\t\tbytes.length >= 12 &&\n\t\tbytes[0] === 0x52 &&\n\t\tbytes[1] === 0x49 &&\n\t\tbytes[2] === 0x46 &&\n\t\tbytes[3] === 0x46 &&\n\t\tbytes[8] === 0x57 &&\n\t\tbytes[9] === 0x45 &&\n\t\tbytes[10] === 0x42 &&\n\t\tbytes[11] === 0x50\n\t) {\n\t\ttiffOffset = findWebpTiffOffset(bytes);\n\t}\n\n\tif (tiffOffset === -1) return 1;\n\treturn readOrientationFromTiff(bytes, tiffOffset);\n}\n\ntype DstIndexFn = (x: number, y: number, w: number, h: number) => number;\n\nfunction rotate90(photon: Photon, image: PhotonImageType, dstIndex: DstIndexFn): PhotonImageType {\n\tconst w = image.get_width();\n\tconst h = image.get_height();\n\tconst src = image.get_raw_pixels();\n\tconst dst = new Uint8Array(src.length);\n\n\tfor (let y = 0; y < h; y++) {\n\t\tfor (let x = 0; x < w; x++) {\n\t\t\tconst srcIdx = (y * w + x) * 4;\n\t\t\tconst dstIdx = dstIndex(x, y, w, h) * 4;\n\t\t\tdst[dstIdx] = src[srcIdx];\n\t\t\tdst[dstIdx + 1] = src[srcIdx + 1];\n\t\t\tdst[dstIdx + 2] = src[srcIdx + 2];\n\t\t\tdst[dstIdx + 3] = src[srcIdx + 3];\n\t\t}\n\t}\n\n\treturn new photon.PhotonImage(dst, h, w);\n}\n\n// Flip orientations mutate in-place. Rotations return a new image (caller must free the old one if different).\nexport function applyExifOrientation(\n\tphoton: Photon,\n\timage: PhotonImageType,\n\toriginalBytes: Uint8Array,\n): PhotonImageType {\n\tconst orientation = getExifOrientation(originalBytes);\n\tif (orientation === 1) return image;\n\n\tswitch (orientation) {\n\t\tcase 2:\n\t\t\tphoton.fliph(image);\n\t\t\treturn image;\n\t\tcase 3:\n\t\t\tphoton.fliph(image);\n\t\t\tphoton.flipv(image);\n\t\t\treturn image;\n\t\tcase 4:\n\t\t\tphoton.flipv(image);\n\t\t\treturn image;\n\t\tcase 5: {\n\t\t\tconst rotated = rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));\n\t\t\tphoton.fliph(rotated);\n\t\t\treturn rotated;\n\t\t}\n\t\tcase 6:\n\t\t\treturn rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));\n\t\tcase 7: {\n\t\t\tconst rotated = rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);\n\t\t\tphoton.fliph(rotated);\n\t\t\treturn rotated;\n\t\t}\n\t\tcase 8:\n\t\t\treturn rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);\n\t\tdefault:\n\t\t\treturn image;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"exif-orientation.js","sourceRoot":"","sources":["../../src/utils/exif-orientation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PhotonImageType } from \"./photon.js\";\n\ntype Photon = typeof import(\"@silvia-odwyer/photon-node\");\n\nfunction readOrientationFromTiff(bytes: Uint8Array, tiffStart: number): number {\n\tif (tiffStart + 8 > bytes.length) return 1;\n\n\tconst byteOrder = (bytes[tiffStart] << 8) | bytes[tiffStart + 1];\n\tconst le = byteOrder === 0x4949;\n\n\tconst read16 = (pos: number): number => {\n\t\tif (le) return bytes[pos] | (bytes[pos + 1] << 8);\n\t\treturn (bytes[pos] << 8) | bytes[pos + 1];\n\t};\n\n\tconst read32 = (pos: number): number => {\n\t\tif (le) return bytes[pos] | (bytes[pos + 1] << 8) | (bytes[pos + 2] << 16) | (bytes[pos + 3] << 24);\n\t\treturn ((bytes[pos] << 24) | (bytes[pos + 1] << 16) | (bytes[pos + 2] << 8) | bytes[pos + 3]) >>> 0;\n\t};\n\n\tconst ifdOffset = read32(tiffStart + 4);\n\tconst ifdStart = tiffStart + ifdOffset;\n\tif (ifdStart + 2 > bytes.length) return 1;\n\n\tconst entryCount = read16(ifdStart);\n\tfor (let i = 0; i < entryCount; i++) {\n\t\tconst entryPos = ifdStart + 2 + i * 12;\n\t\tif (entryPos + 12 > bytes.length) return 1;\n\n\t\tif (read16(entryPos) === 0x0112) {\n\t\t\tconst value = read16(entryPos + 8);\n\t\t\treturn value >= 1 && value <= 8 ? value : 1;\n\t\t}\n\t}\n\n\treturn 1;\n}\n\nfunction findJpegTiffOffset(bytes: Uint8Array): number {\n\tlet offset = 2;\n\twhile (offset < bytes.length - 1) {\n\t\tif (bytes[offset] !== 0xff) return -1;\n\t\tconst marker = bytes[offset + 1];\n\t\tif (marker === 0xff) {\n\t\t\toffset++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (marker === 0xe1) {\n\t\t\tif (offset + 4 >= bytes.length) return -1;\n\t\t\tconst segmentStart = offset + 4;\n\t\t\tif (segmentStart + 6 > bytes.length) return -1;\n\t\t\tif (!hasExifHeader(bytes, segmentStart)) return -1;\n\t\t\treturn segmentStart + 6;\n\t\t}\n\n\t\tif (offset + 4 > bytes.length) return -1;\n\t\tconst length = (bytes[offset + 2] << 8) | bytes[offset + 3];\n\t\toffset += 2 + length;\n\t}\n\n\treturn -1;\n}\n\nfunction findWebpTiffOffset(bytes: Uint8Array): number {\n\tlet offset = 12;\n\twhile (offset + 8 <= bytes.length) {\n\t\tconst chunkId = String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);\n\t\tconst chunkSize =\n\t\t\tbytes[offset + 4] | (bytes[offset + 5] << 8) | (bytes[offset + 6] << 16) | (bytes[offset + 7] << 24);\n\t\tconst dataStart = offset + 8;\n\n\t\tif (chunkId === \"EXIF\") {\n\t\t\tif (dataStart + chunkSize > bytes.length) return -1;\n\t\t\t// Some WebP files have \"Exif\\0\\0\" prefix before the TIFF header\n\t\t\tconst tiffStart = chunkSize >= 6 && hasExifHeader(bytes, dataStart) ? dataStart + 6 : dataStart;\n\t\t\treturn tiffStart;\n\t\t}\n\n\t\t// RIFF chunks are padded to even size\n\t\toffset = dataStart + chunkSize + (chunkSize % 2);\n\t}\n\n\treturn -1;\n}\n\nfunction hasExifHeader(bytes: Uint8Array, offset: number): boolean {\n\treturn (\n\t\tbytes[offset] === 0x45 &&\n\t\tbytes[offset + 1] === 0x78 &&\n\t\tbytes[offset + 2] === 0x69 &&\n\t\tbytes[offset + 3] === 0x66 &&\n\t\tbytes[offset + 4] === 0x00 &&\n\t\tbytes[offset + 5] === 0x00\n\t);\n}\n\nfunction getExifOrientation(bytes: Uint8Array): number {\n\tlet tiffOffset = -1;\n\n\t// JPEG: starts with FF D8\n\tif (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xd8) {\n\t\ttiffOffset = findJpegTiffOffset(bytes);\n\t}\n\t// WebP: starts with RIFF....WEBP\n\telse if (\n\t\tbytes.length >= 12 &&\n\t\tbytes[0] === 0x52 &&\n\t\tbytes[1] === 0x49 &&\n\t\tbytes[2] === 0x46 &&\n\t\tbytes[3] === 0x46 &&\n\t\tbytes[8] === 0x57 &&\n\t\tbytes[9] === 0x45 &&\n\t\tbytes[10] === 0x42 &&\n\t\tbytes[11] === 0x50\n\t) {\n\t\ttiffOffset = findWebpTiffOffset(bytes);\n\t}\n\n\tif (tiffOffset === -1) return 1;\n\treturn readOrientationFromTiff(bytes, tiffOffset);\n}\n\ntype DstIndexFn = (x: number, y: number, w: number, h: number) => number;\n\nfunction rotate90(photon: Photon, image: PhotonImageType, dstIndex: DstIndexFn): PhotonImageType {\n\tconst w = image.get_width();\n\tconst h = image.get_height();\n\tconst src = image.get_raw_pixels();\n\tconst dst = new Uint8Array(src.length);\n\n\tfor (let y = 0; y < h; y++) {\n\t\tfor (let x = 0; x < w; x++) {\n\t\t\tconst srcIdx = (y * w + x) * 4;\n\t\t\tconst dstIdx = dstIndex(x, y, w, h) * 4;\n\t\t\tdst[dstIdx] = src[srcIdx];\n\t\t\tdst[dstIdx + 1] = src[srcIdx + 1];\n\t\t\tdst[dstIdx + 2] = src[srcIdx + 2];\n\t\t\tdst[dstIdx + 3] = src[srcIdx + 3];\n\t\t}\n\t}\n\n\treturn new photon.PhotonImage(dst, h, w);\n}\n\n// Flip orientations mutate in-place. Rotations return a new image (caller must free the old one if different).\nexport function applyExifOrientation(\n\tphoton: Photon,\n\timage: PhotonImageType,\n\toriginalBytes: Uint8Array,\n): PhotonImageType {\n\tconst orientation = getExifOrientation(originalBytes);\n\tif (orientation === 1) return image;\n\n\tswitch (orientation) {\n\t\tcase 2:\n\t\t\tphoton.fliph(image);\n\t\t\treturn image;\n\t\tcase 3:\n\t\t\tphoton.fliph(image);\n\t\t\tphoton.flipv(image);\n\t\t\treturn image;\n\t\tcase 4:\n\t\t\tphoton.flipv(image);\n\t\t\treturn image;\n\t\tcase 5: {\n\t\t\tconst rotated = rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));\n\t\t\tphoton.fliph(rotated);\n\t\t\treturn rotated;\n\t\t}\n\t\tcase 6:\n\t\t\treturn rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));\n\t\tcase 7: {\n\t\t\tconst rotated = rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);\n\t\t\tphoton.fliph(rotated);\n\t\t\treturn rotated;\n\t\t}\n\t\tcase 8:\n\t\t\treturn rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);\n\t\tdefault:\n\t\t\treturn image;\n\t}\n}\n"]}
1
+ {"version":3,"file":"exif-orientation.js","sourceRoot":"","sources":["../../src/utils/exif-orientation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PhotonImageType } from \"./photon.ts\";\n\ntype Photon = typeof import(\"@silvia-odwyer/photon-node\");\n\nfunction readOrientationFromTiff(bytes: Uint8Array, tiffStart: number): number {\n\tif (tiffStart + 8 > bytes.length) return 1;\n\n\tconst byteOrder = (bytes[tiffStart] << 8) | bytes[tiffStart + 1];\n\tconst le = byteOrder === 0x4949;\n\n\tconst read16 = (pos: number): number => {\n\t\tif (le) return bytes[pos] | (bytes[pos + 1] << 8);\n\t\treturn (bytes[pos] << 8) | bytes[pos + 1];\n\t};\n\n\tconst read32 = (pos: number): number => {\n\t\tif (le) return bytes[pos] | (bytes[pos + 1] << 8) | (bytes[pos + 2] << 16) | (bytes[pos + 3] << 24);\n\t\treturn ((bytes[pos] << 24) | (bytes[pos + 1] << 16) | (bytes[pos + 2] << 8) | bytes[pos + 3]) >>> 0;\n\t};\n\n\tconst ifdOffset = read32(tiffStart + 4);\n\tconst ifdStart = tiffStart + ifdOffset;\n\tif (ifdStart + 2 > bytes.length) return 1;\n\n\tconst entryCount = read16(ifdStart);\n\tfor (let i = 0; i < entryCount; i++) {\n\t\tconst entryPos = ifdStart + 2 + i * 12;\n\t\tif (entryPos + 12 > bytes.length) return 1;\n\n\t\tif (read16(entryPos) === 0x0112) {\n\t\t\tconst value = read16(entryPos + 8);\n\t\t\treturn value >= 1 && value <= 8 ? value : 1;\n\t\t}\n\t}\n\n\treturn 1;\n}\n\nfunction findJpegTiffOffset(bytes: Uint8Array): number {\n\tlet offset = 2;\n\twhile (offset < bytes.length - 1) {\n\t\tif (bytes[offset] !== 0xff) return -1;\n\t\tconst marker = bytes[offset + 1];\n\t\tif (marker === 0xff) {\n\t\t\toffset++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (marker === 0xe1) {\n\t\t\tif (offset + 4 >= bytes.length) return -1;\n\t\t\tconst segmentStart = offset + 4;\n\t\t\tif (segmentStart + 6 > bytes.length) return -1;\n\t\t\tif (!hasExifHeader(bytes, segmentStart)) return -1;\n\t\t\treturn segmentStart + 6;\n\t\t}\n\n\t\tif (offset + 4 > bytes.length) return -1;\n\t\tconst length = (bytes[offset + 2] << 8) | bytes[offset + 3];\n\t\toffset += 2 + length;\n\t}\n\n\treturn -1;\n}\n\nfunction findWebpTiffOffset(bytes: Uint8Array): number {\n\tlet offset = 12;\n\twhile (offset + 8 <= bytes.length) {\n\t\tconst chunkId = String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);\n\t\tconst chunkSize =\n\t\t\tbytes[offset + 4] | (bytes[offset + 5] << 8) | (bytes[offset + 6] << 16) | (bytes[offset + 7] << 24);\n\t\tconst dataStart = offset + 8;\n\n\t\tif (chunkId === \"EXIF\") {\n\t\t\tif (dataStart + chunkSize > bytes.length) return -1;\n\t\t\t// Some WebP files have \"Exif\\0\\0\" prefix before the TIFF header\n\t\t\tconst tiffStart = chunkSize >= 6 && hasExifHeader(bytes, dataStart) ? dataStart + 6 : dataStart;\n\t\t\treturn tiffStart;\n\t\t}\n\n\t\t// RIFF chunks are padded to even size\n\t\toffset = dataStart + chunkSize + (chunkSize % 2);\n\t}\n\n\treturn -1;\n}\n\nfunction hasExifHeader(bytes: Uint8Array, offset: number): boolean {\n\treturn (\n\t\tbytes[offset] === 0x45 &&\n\t\tbytes[offset + 1] === 0x78 &&\n\t\tbytes[offset + 2] === 0x69 &&\n\t\tbytes[offset + 3] === 0x66 &&\n\t\tbytes[offset + 4] === 0x00 &&\n\t\tbytes[offset + 5] === 0x00\n\t);\n}\n\nfunction getExifOrientation(bytes: Uint8Array): number {\n\tlet tiffOffset = -1;\n\n\t// JPEG: starts with FF D8\n\tif (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xd8) {\n\t\ttiffOffset = findJpegTiffOffset(bytes);\n\t}\n\t// WebP: starts with RIFF....WEBP\n\telse if (\n\t\tbytes.length >= 12 &&\n\t\tbytes[0] === 0x52 &&\n\t\tbytes[1] === 0x49 &&\n\t\tbytes[2] === 0x46 &&\n\t\tbytes[3] === 0x46 &&\n\t\tbytes[8] === 0x57 &&\n\t\tbytes[9] === 0x45 &&\n\t\tbytes[10] === 0x42 &&\n\t\tbytes[11] === 0x50\n\t) {\n\t\ttiffOffset = findWebpTiffOffset(bytes);\n\t}\n\n\tif (tiffOffset === -1) return 1;\n\treturn readOrientationFromTiff(bytes, tiffOffset);\n}\n\ntype DstIndexFn = (x: number, y: number, w: number, h: number) => number;\n\nfunction rotate90(photon: Photon, image: PhotonImageType, dstIndex: DstIndexFn): PhotonImageType {\n\tconst w = image.get_width();\n\tconst h = image.get_height();\n\tconst src = image.get_raw_pixels();\n\tconst dst = new Uint8Array(src.length);\n\n\tfor (let y = 0; y < h; y++) {\n\t\tfor (let x = 0; x < w; x++) {\n\t\t\tconst srcIdx = (y * w + x) * 4;\n\t\t\tconst dstIdx = dstIndex(x, y, w, h) * 4;\n\t\t\tdst[dstIdx] = src[srcIdx];\n\t\t\tdst[dstIdx + 1] = src[srcIdx + 1];\n\t\t\tdst[dstIdx + 2] = src[srcIdx + 2];\n\t\t\tdst[dstIdx + 3] = src[srcIdx + 3];\n\t\t}\n\t}\n\n\treturn new photon.PhotonImage(dst, h, w);\n}\n\n// Flip orientations mutate in-place. Rotations return a new image (caller must free the old one if different).\nexport function applyExifOrientation(\n\tphoton: Photon,\n\timage: PhotonImageType,\n\toriginalBytes: Uint8Array,\n): PhotonImageType {\n\tconst orientation = getExifOrientation(originalBytes);\n\tif (orientation === 1) return image;\n\n\tswitch (orientation) {\n\t\tcase 2:\n\t\t\tphoton.fliph(image);\n\t\t\treturn image;\n\t\tcase 3:\n\t\t\tphoton.fliph(image);\n\t\t\tphoton.flipv(image);\n\t\t\treturn image;\n\t\tcase 4:\n\t\t\tphoton.flipv(image);\n\t\t\treturn image;\n\t\tcase 5: {\n\t\t\tconst rotated = rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));\n\t\t\tphoton.fliph(rotated);\n\t\t\treturn rotated;\n\t\t}\n\t\tcase 6:\n\t\t\treturn rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));\n\t\tcase 7: {\n\t\t\tconst rotated = rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);\n\t\t\tphoton.fliph(rotated);\n\t\t\treturn rotated;\n\t\t}\n\t\tcase 8:\n\t\t\treturn rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);\n\t\tdefault:\n\t\t\treturn image;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"image-convert.d.ts","sourceRoot":"","sources":["../../src/utils/image-convert.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAsB,YAAY,CACjC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA8BpD","sourcesContent":["import { applyExifOrientation } from \"./exif-orientation.js\";\nimport { loadPhoton } from \"./photon.js\";\n\n/**\n * Convert image to PNG format for terminal display.\n * Kitty graphics protocol requires PNG format (f=100).\n */\nexport async function convertToPng(\n\tbase64Data: string,\n\tmimeType: string,\n): Promise<{ data: string; mimeType: string } | null> {\n\t// Already PNG, no conversion needed\n\tif (mimeType === \"image/png\") {\n\t\treturn { data: base64Data, mimeType };\n\t}\n\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\t// Photon not available, can't convert\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst bytes = new Uint8Array(Buffer.from(base64Data, \"base64\"));\n\t\tconst rawImage = photon.PhotonImage.new_from_byteslice(bytes);\n\t\tconst image = applyExifOrientation(photon, rawImage, bytes);\n\t\tif (image !== rawImage) rawImage.free();\n\t\ttry {\n\t\t\tconst pngBuffer = image.get_bytes();\n\t\t\treturn {\n\t\t\t\tdata: Buffer.from(pngBuffer).toString(\"base64\"),\n\t\t\t\tmimeType: \"image/png\",\n\t\t\t};\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\t// Conversion failed\n\t\treturn null;\n\t}\n}\n"]}
1
+ {"version":3,"file":"image-convert.d.ts","sourceRoot":"","sources":["../../src/utils/image-convert.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAsB,YAAY,CACjC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA8BpD","sourcesContent":["import { applyExifOrientation } from \"./exif-orientation.ts\";\nimport { loadPhoton } from \"./photon.ts\";\n\n/**\n * Convert image to PNG format for terminal display.\n * Kitty graphics protocol requires PNG format (f=100).\n */\nexport async function convertToPng(\n\tbase64Data: string,\n\tmimeType: string,\n): Promise<{ data: string; mimeType: string } | null> {\n\t// Already PNG, no conversion needed\n\tif (mimeType === \"image/png\") {\n\t\treturn { data: base64Data, mimeType };\n\t}\n\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\t// Photon not available, can't convert\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst bytes = new Uint8Array(Buffer.from(base64Data, \"base64\"));\n\t\tconst rawImage = photon.PhotonImage.new_from_byteslice(bytes);\n\t\tconst image = applyExifOrientation(photon, rawImage, bytes);\n\t\tif (image !== rawImage) rawImage.free();\n\t\ttry {\n\t\t\tconst pngBuffer = image.get_bytes();\n\t\t\treturn {\n\t\t\t\tdata: Buffer.from(pngBuffer).toString(\"base64\"),\n\t\t\t\tmimeType: \"image/png\",\n\t\t\t};\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\t// Conversion failed\n\t\treturn null;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"image-convert.js","sourceRoot":"","sources":["../../src/utils/image-convert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,UAAkB,EAClB,QAAgB;IAEhB,oCAAoC;IACpC,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvC,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,sCAAsC;QACtC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,oBAAoB,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC5D,IAAI,KAAK,KAAK,QAAQ;YAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC;YACJ,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YACpC,OAAO;gBACN,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAC/C,QAAQ,EAAE,WAAW;aACrB,CAAC;QACH,CAAC;gBAAS,CAAC;YACV,KAAK,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,oBAAoB;QACpB,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC","sourcesContent":["import { applyExifOrientation } from \"./exif-orientation.js\";\nimport { loadPhoton } from \"./photon.js\";\n\n/**\n * Convert image to PNG format for terminal display.\n * Kitty graphics protocol requires PNG format (f=100).\n */\nexport async function convertToPng(\n\tbase64Data: string,\n\tmimeType: string,\n): Promise<{ data: string; mimeType: string } | null> {\n\t// Already PNG, no conversion needed\n\tif (mimeType === \"image/png\") {\n\t\treturn { data: base64Data, mimeType };\n\t}\n\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\t// Photon not available, can't convert\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst bytes = new Uint8Array(Buffer.from(base64Data, \"base64\"));\n\t\tconst rawImage = photon.PhotonImage.new_from_byteslice(bytes);\n\t\tconst image = applyExifOrientation(photon, rawImage, bytes);\n\t\tif (image !== rawImage) rawImage.free();\n\t\ttry {\n\t\t\tconst pngBuffer = image.get_bytes();\n\t\t\treturn {\n\t\t\t\tdata: Buffer.from(pngBuffer).toString(\"base64\"),\n\t\t\t\tmimeType: \"image/png\",\n\t\t\t};\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\t// Conversion failed\n\t\treturn null;\n\t}\n}\n"]}
1
+ {"version":3,"file":"image-convert.js","sourceRoot":"","sources":["../../src/utils/image-convert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,UAAkB,EAClB,QAAgB;IAEhB,oCAAoC;IACpC,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvC,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,sCAAsC;QACtC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,oBAAoB,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC5D,IAAI,KAAK,KAAK,QAAQ;YAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC;YACJ,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YACpC,OAAO;gBACN,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAC/C,QAAQ,EAAE,WAAW;aACrB,CAAC;QACH,CAAC;gBAAS,CAAC;YACV,KAAK,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,oBAAoB;QACpB,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC","sourcesContent":["import { applyExifOrientation } from \"./exif-orientation.ts\";\nimport { loadPhoton } from \"./photon.ts\";\n\n/**\n * Convert image to PNG format for terminal display.\n * Kitty graphics protocol requires PNG format (f=100).\n */\nexport async function convertToPng(\n\tbase64Data: string,\n\tmimeType: string,\n): Promise<{ data: string; mimeType: string } | null> {\n\t// Already PNG, no conversion needed\n\tif (mimeType === \"image/png\") {\n\t\treturn { data: base64Data, mimeType };\n\t}\n\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\t// Photon not available, can't convert\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst bytes = new Uint8Array(Buffer.from(base64Data, \"base64\"));\n\t\tconst rawImage = photon.PhotonImage.new_from_byteslice(bytes);\n\t\tconst image = applyExifOrientation(photon, rawImage, bytes);\n\t\tif (image !== rawImage) rawImage.free();\n\t\ttry {\n\t\t\tconst pngBuffer = image.get_bytes();\n\t\t\treturn {\n\t\t\t\tdata: Buffer.from(pngBuffer).toString(\"base64\"),\n\t\t\t\tmimeType: \"image/png\",\n\t\t\t};\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\t// Conversion failed\n\t\treturn null;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"image-resize.d.ts","sourceRoot":"","sources":["../../src/utils/image-resize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAI1D,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;CACpB;AA2BD;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAuG/G;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAO5E","sourcesContent":["import type { ImageContent } from \"@earendil-works/pi-ai\";\nimport { applyExifOrientation } from \"./exif-orientation.js\";\nimport { loadPhoton } from \"./photon.js\";\n\nexport interface ImageResizeOptions {\n\tmaxWidth?: number; // Default: 2000\n\tmaxHeight?: number; // Default: 2000\n\tmaxBytes?: number; // Default: 4.5MB of base64 payload (below Anthropic's 5MB limit)\n\tjpegQuality?: number; // Default: 80\n}\n\nexport interface ResizedImage {\n\tdata: string; // base64\n\tmimeType: string;\n\toriginalWidth: number;\n\toriginalHeight: number;\n\twidth: number;\n\theight: number;\n\twasResized: boolean;\n}\n\n// 4.5MB of base64 payload. Provides headroom below Anthropic's 5MB limit.\nconst DEFAULT_MAX_BYTES = 4.5 * 1024 * 1024;\n\nconst DEFAULT_OPTIONS: Required<ImageResizeOptions> = {\n\tmaxWidth: 2000,\n\tmaxHeight: 2000,\n\tmaxBytes: DEFAULT_MAX_BYTES,\n\tjpegQuality: 80,\n};\n\ninterface EncodedCandidate {\n\tdata: string;\n\tencodedSize: number;\n\tmimeType: string;\n}\n\nfunction encodeCandidate(buffer: Uint8Array, mimeType: string): EncodedCandidate {\n\tconst data = Buffer.from(buffer).toString(\"base64\");\n\treturn {\n\t\tdata,\n\t\tencodedSize: Buffer.byteLength(data, \"utf-8\"),\n\t\tmimeType,\n\t};\n}\n\n/**\n * Resize an image to fit within the specified max dimensions and encoded file size.\n * Returns null if the image cannot be resized below maxBytes.\n *\n * Uses Photon (Rust/WASM) for image processing. If Photon is not available,\n * returns null.\n *\n * Strategy for staying under maxBytes:\n * 1. First resize to maxWidth/maxHeight\n * 2. Try both PNG and JPEG formats, pick the smaller one\n * 3. If still too large, try JPEG with decreasing quality\n * 4. If still too large, progressively reduce dimensions until 1x1\n */\nexport async function resizeImage(img: ImageContent, options?: ImageResizeOptions): Promise<ResizedImage | null> {\n\tconst opts = { ...DEFAULT_OPTIONS, ...options };\n\tconst inputBuffer = Buffer.from(img.data, \"base64\");\n\tconst inputBase64Size = Buffer.byteLength(img.data, \"utf-8\");\n\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\tlet image: ReturnType<typeof photon.PhotonImage.new_from_byteslice> | undefined;\n\ttry {\n\t\tconst inputBytes = new Uint8Array(inputBuffer);\n\t\tconst rawImage = photon.PhotonImage.new_from_byteslice(inputBytes);\n\t\timage = applyExifOrientation(photon, rawImage, inputBytes);\n\t\tif (image !== rawImage) rawImage.free();\n\n\t\tconst originalWidth = image.get_width();\n\t\tconst originalHeight = image.get_height();\n\t\tconst format = img.mimeType?.split(\"/\")[1] ?? \"png\";\n\n\t\t// Check if already within all limits (dimensions AND encoded size)\n\t\tif (originalWidth <= opts.maxWidth && originalHeight <= opts.maxHeight && inputBase64Size < opts.maxBytes) {\n\t\t\treturn {\n\t\t\t\tdata: img.data,\n\t\t\t\tmimeType: img.mimeType ?? `image/${format}`,\n\t\t\t\toriginalWidth,\n\t\t\t\toriginalHeight,\n\t\t\t\twidth: originalWidth,\n\t\t\t\theight: originalHeight,\n\t\t\t\twasResized: false,\n\t\t\t};\n\t\t}\n\n\t\t// Calculate initial dimensions respecting max limits\n\t\tlet targetWidth = originalWidth;\n\t\tlet targetHeight = originalHeight;\n\n\t\tif (targetWidth > opts.maxWidth) {\n\t\t\ttargetHeight = Math.round((targetHeight * opts.maxWidth) / targetWidth);\n\t\t\ttargetWidth = opts.maxWidth;\n\t\t}\n\t\tif (targetHeight > opts.maxHeight) {\n\t\t\ttargetWidth = Math.round((targetWidth * opts.maxHeight) / targetHeight);\n\t\t\ttargetHeight = opts.maxHeight;\n\t\t}\n\n\t\tfunction tryEncodings(width: number, height: number, jpegQualities: number[]): EncodedCandidate[] {\n\t\t\tconst resized = photon!.resize(image!, width, height, photon!.SamplingFilter.Lanczos3);\n\n\t\t\ttry {\n\t\t\t\tconst candidates: EncodedCandidate[] = [encodeCandidate(resized.get_bytes(), \"image/png\")];\n\t\t\t\tfor (const quality of jpegQualities) {\n\t\t\t\t\tcandidates.push(encodeCandidate(resized.get_bytes_jpeg(quality), \"image/jpeg\"));\n\t\t\t\t}\n\t\t\t\treturn candidates;\n\t\t\t} finally {\n\t\t\t\tresized.free();\n\t\t\t}\n\t\t}\n\n\t\tconst qualitySteps = Array.from(new Set([opts.jpegQuality, 85, 70, 55, 40]));\n\t\tlet currentWidth = targetWidth;\n\t\tlet currentHeight = targetHeight;\n\n\t\twhile (true) {\n\t\t\tconst candidates = tryEncodings(currentWidth, currentHeight, qualitySteps);\n\t\t\tfor (const candidate of candidates) {\n\t\t\t\tif (candidate.encodedSize < opts.maxBytes) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdata: candidate.data,\n\t\t\t\t\t\tmimeType: candidate.mimeType,\n\t\t\t\t\t\toriginalWidth,\n\t\t\t\t\t\toriginalHeight,\n\t\t\t\t\t\twidth: currentWidth,\n\t\t\t\t\t\theight: currentHeight,\n\t\t\t\t\t\twasResized: true,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (currentWidth === 1 && currentHeight === 1) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tconst nextWidth = currentWidth === 1 ? 1 : Math.max(1, Math.floor(currentWidth * 0.75));\n\t\t\tconst nextHeight = currentHeight === 1 ? 1 : Math.max(1, Math.floor(currentHeight * 0.75));\n\t\t\tif (nextWidth === currentWidth && nextHeight === currentHeight) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcurrentWidth = nextWidth;\n\t\t\tcurrentHeight = nextHeight;\n\t\t}\n\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\tif (image) {\n\t\t\timage.free();\n\t\t}\n\t}\n}\n\n/**\n * Format a dimension note for resized images.\n * This helps the model understand the coordinate mapping.\n */\nexport function formatDimensionNote(result: ResizedImage): string | undefined {\n\tif (!result.wasResized) {\n\t\treturn undefined;\n\t}\n\n\tconst scale = result.originalWidth / result.width;\n\treturn `[Image: original ${result.originalWidth}x${result.originalHeight}, displayed at ${result.width}x${result.height}. Multiply coordinates by ${scale.toFixed(2)} to map to original image.]`;\n}\n"]}
1
+ {"version":3,"file":"image-resize.d.ts","sourceRoot":"","sources":["../../src/utils/image-resize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAI1D,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;CACpB;AA2BD;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAuG/G;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAO5E","sourcesContent":["import type { ImageContent } from \"@earendil-works/pi-ai\";\nimport { applyExifOrientation } from \"./exif-orientation.ts\";\nimport { loadPhoton } from \"./photon.ts\";\n\nexport interface ImageResizeOptions {\n\tmaxWidth?: number; // Default: 2000\n\tmaxHeight?: number; // Default: 2000\n\tmaxBytes?: number; // Default: 4.5MB of base64 payload (below Anthropic's 5MB limit)\n\tjpegQuality?: number; // Default: 80\n}\n\nexport interface ResizedImage {\n\tdata: string; // base64\n\tmimeType: string;\n\toriginalWidth: number;\n\toriginalHeight: number;\n\twidth: number;\n\theight: number;\n\twasResized: boolean;\n}\n\n// 4.5MB of base64 payload. Provides headroom below Anthropic's 5MB limit.\nconst DEFAULT_MAX_BYTES = 4.5 * 1024 * 1024;\n\nconst DEFAULT_OPTIONS: Required<ImageResizeOptions> = {\n\tmaxWidth: 2000,\n\tmaxHeight: 2000,\n\tmaxBytes: DEFAULT_MAX_BYTES,\n\tjpegQuality: 80,\n};\n\ninterface EncodedCandidate {\n\tdata: string;\n\tencodedSize: number;\n\tmimeType: string;\n}\n\nfunction encodeCandidate(buffer: Uint8Array, mimeType: string): EncodedCandidate {\n\tconst data = Buffer.from(buffer).toString(\"base64\");\n\treturn {\n\t\tdata,\n\t\tencodedSize: Buffer.byteLength(data, \"utf-8\"),\n\t\tmimeType,\n\t};\n}\n\n/**\n * Resize an image to fit within the specified max dimensions and encoded file size.\n * Returns null if the image cannot be resized below maxBytes.\n *\n * Uses Photon (Rust/WASM) for image processing. If Photon is not available,\n * returns null.\n *\n * Strategy for staying under maxBytes:\n * 1. First resize to maxWidth/maxHeight\n * 2. Try both PNG and JPEG formats, pick the smaller one\n * 3. If still too large, try JPEG with decreasing quality\n * 4. If still too large, progressively reduce dimensions until 1x1\n */\nexport async function resizeImage(img: ImageContent, options?: ImageResizeOptions): Promise<ResizedImage | null> {\n\tconst opts = { ...DEFAULT_OPTIONS, ...options };\n\tconst inputBuffer = Buffer.from(img.data, \"base64\");\n\tconst inputBase64Size = Buffer.byteLength(img.data, \"utf-8\");\n\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\tlet image: ReturnType<typeof photon.PhotonImage.new_from_byteslice> | undefined;\n\ttry {\n\t\tconst inputBytes = new Uint8Array(inputBuffer);\n\t\tconst rawImage = photon.PhotonImage.new_from_byteslice(inputBytes);\n\t\timage = applyExifOrientation(photon, rawImage, inputBytes);\n\t\tif (image !== rawImage) rawImage.free();\n\n\t\tconst originalWidth = image.get_width();\n\t\tconst originalHeight = image.get_height();\n\t\tconst format = img.mimeType?.split(\"/\")[1] ?? \"png\";\n\n\t\t// Check if already within all limits (dimensions AND encoded size)\n\t\tif (originalWidth <= opts.maxWidth && originalHeight <= opts.maxHeight && inputBase64Size < opts.maxBytes) {\n\t\t\treturn {\n\t\t\t\tdata: img.data,\n\t\t\t\tmimeType: img.mimeType ?? `image/${format}`,\n\t\t\t\toriginalWidth,\n\t\t\t\toriginalHeight,\n\t\t\t\twidth: originalWidth,\n\t\t\t\theight: originalHeight,\n\t\t\t\twasResized: false,\n\t\t\t};\n\t\t}\n\n\t\t// Calculate initial dimensions respecting max limits\n\t\tlet targetWidth = originalWidth;\n\t\tlet targetHeight = originalHeight;\n\n\t\tif (targetWidth > opts.maxWidth) {\n\t\t\ttargetHeight = Math.round((targetHeight * opts.maxWidth) / targetWidth);\n\t\t\ttargetWidth = opts.maxWidth;\n\t\t}\n\t\tif (targetHeight > opts.maxHeight) {\n\t\t\ttargetWidth = Math.round((targetWidth * opts.maxHeight) / targetHeight);\n\t\t\ttargetHeight = opts.maxHeight;\n\t\t}\n\n\t\tfunction tryEncodings(width: number, height: number, jpegQualities: number[]): EncodedCandidate[] {\n\t\t\tconst resized = photon!.resize(image!, width, height, photon!.SamplingFilter.Lanczos3);\n\n\t\t\ttry {\n\t\t\t\tconst candidates: EncodedCandidate[] = [encodeCandidate(resized.get_bytes(), \"image/png\")];\n\t\t\t\tfor (const quality of jpegQualities) {\n\t\t\t\t\tcandidates.push(encodeCandidate(resized.get_bytes_jpeg(quality), \"image/jpeg\"));\n\t\t\t\t}\n\t\t\t\treturn candidates;\n\t\t\t} finally {\n\t\t\t\tresized.free();\n\t\t\t}\n\t\t}\n\n\t\tconst qualitySteps = Array.from(new Set([opts.jpegQuality, 85, 70, 55, 40]));\n\t\tlet currentWidth = targetWidth;\n\t\tlet currentHeight = targetHeight;\n\n\t\twhile (true) {\n\t\t\tconst candidates = tryEncodings(currentWidth, currentHeight, qualitySteps);\n\t\t\tfor (const candidate of candidates) {\n\t\t\t\tif (candidate.encodedSize < opts.maxBytes) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdata: candidate.data,\n\t\t\t\t\t\tmimeType: candidate.mimeType,\n\t\t\t\t\t\toriginalWidth,\n\t\t\t\t\t\toriginalHeight,\n\t\t\t\t\t\twidth: currentWidth,\n\t\t\t\t\t\theight: currentHeight,\n\t\t\t\t\t\twasResized: true,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (currentWidth === 1 && currentHeight === 1) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tconst nextWidth = currentWidth === 1 ? 1 : Math.max(1, Math.floor(currentWidth * 0.75));\n\t\t\tconst nextHeight = currentHeight === 1 ? 1 : Math.max(1, Math.floor(currentHeight * 0.75));\n\t\t\tif (nextWidth === currentWidth && nextHeight === currentHeight) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcurrentWidth = nextWidth;\n\t\t\tcurrentHeight = nextHeight;\n\t\t}\n\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\tif (image) {\n\t\t\timage.free();\n\t\t}\n\t}\n}\n\n/**\n * Format a dimension note for resized images.\n * This helps the model understand the coordinate mapping.\n */\nexport function formatDimensionNote(result: ResizedImage): string | undefined {\n\tif (!result.wasResized) {\n\t\treturn undefined;\n\t}\n\n\tconst scale = result.originalWidth / result.width;\n\treturn `[Image: original ${result.originalWidth}x${result.originalHeight}, displayed at ${result.width}x${result.height}. Multiply coordinates by ${scale.toFixed(2)} to map to original image.]`;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"image-resize.js","sourceRoot":"","sources":["../../src/utils/image-resize.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ImageContent } from \"@earendil-works/pi-ai\";\nimport { applyExifOrientation } from \"./exif-orientation.js\";\nimport { loadPhoton } from \"./photon.js\";\n\nexport interface ImageResizeOptions {\n\tmaxWidth?: number; // Default: 2000\n\tmaxHeight?: number; // Default: 2000\n\tmaxBytes?: number; // Default: 4.5MB of base64 payload (below Anthropic's 5MB limit)\n\tjpegQuality?: number; // Default: 80\n}\n\nexport interface ResizedImage {\n\tdata: string; // base64\n\tmimeType: string;\n\toriginalWidth: number;\n\toriginalHeight: number;\n\twidth: number;\n\theight: number;\n\twasResized: boolean;\n}\n\n// 4.5MB of base64 payload. Provides headroom below Anthropic's 5MB limit.\nconst DEFAULT_MAX_BYTES = 4.5 * 1024 * 1024;\n\nconst DEFAULT_OPTIONS: Required<ImageResizeOptions> = {\n\tmaxWidth: 2000,\n\tmaxHeight: 2000,\n\tmaxBytes: DEFAULT_MAX_BYTES,\n\tjpegQuality: 80,\n};\n\ninterface EncodedCandidate {\n\tdata: string;\n\tencodedSize: number;\n\tmimeType: string;\n}\n\nfunction encodeCandidate(buffer: Uint8Array, mimeType: string): EncodedCandidate {\n\tconst data = Buffer.from(buffer).toString(\"base64\");\n\treturn {\n\t\tdata,\n\t\tencodedSize: Buffer.byteLength(data, \"utf-8\"),\n\t\tmimeType,\n\t};\n}\n\n/**\n * Resize an image to fit within the specified max dimensions and encoded file size.\n * Returns null if the image cannot be resized below maxBytes.\n *\n * Uses Photon (Rust/WASM) for image processing. If Photon is not available,\n * returns null.\n *\n * Strategy for staying under maxBytes:\n * 1. First resize to maxWidth/maxHeight\n * 2. Try both PNG and JPEG formats, pick the smaller one\n * 3. If still too large, try JPEG with decreasing quality\n * 4. If still too large, progressively reduce dimensions until 1x1\n */\nexport async function resizeImage(img: ImageContent, options?: ImageResizeOptions): Promise<ResizedImage | null> {\n\tconst opts = { ...DEFAULT_OPTIONS, ...options };\n\tconst inputBuffer = Buffer.from(img.data, \"base64\");\n\tconst inputBase64Size = Buffer.byteLength(img.data, \"utf-8\");\n\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\tlet image: ReturnType<typeof photon.PhotonImage.new_from_byteslice> | undefined;\n\ttry {\n\t\tconst inputBytes = new Uint8Array(inputBuffer);\n\t\tconst rawImage = photon.PhotonImage.new_from_byteslice(inputBytes);\n\t\timage = applyExifOrientation(photon, rawImage, inputBytes);\n\t\tif (image !== rawImage) rawImage.free();\n\n\t\tconst originalWidth = image.get_width();\n\t\tconst originalHeight = image.get_height();\n\t\tconst format = img.mimeType?.split(\"/\")[1] ?? \"png\";\n\n\t\t// Check if already within all limits (dimensions AND encoded size)\n\t\tif (originalWidth <= opts.maxWidth && originalHeight <= opts.maxHeight && inputBase64Size < opts.maxBytes) {\n\t\t\treturn {\n\t\t\t\tdata: img.data,\n\t\t\t\tmimeType: img.mimeType ?? `image/${format}`,\n\t\t\t\toriginalWidth,\n\t\t\t\toriginalHeight,\n\t\t\t\twidth: originalWidth,\n\t\t\t\theight: originalHeight,\n\t\t\t\twasResized: false,\n\t\t\t};\n\t\t}\n\n\t\t// Calculate initial dimensions respecting max limits\n\t\tlet targetWidth = originalWidth;\n\t\tlet targetHeight = originalHeight;\n\n\t\tif (targetWidth > opts.maxWidth) {\n\t\t\ttargetHeight = Math.round((targetHeight * opts.maxWidth) / targetWidth);\n\t\t\ttargetWidth = opts.maxWidth;\n\t\t}\n\t\tif (targetHeight > opts.maxHeight) {\n\t\t\ttargetWidth = Math.round((targetWidth * opts.maxHeight) / targetHeight);\n\t\t\ttargetHeight = opts.maxHeight;\n\t\t}\n\n\t\tfunction tryEncodings(width: number, height: number, jpegQualities: number[]): EncodedCandidate[] {\n\t\t\tconst resized = photon!.resize(image!, width, height, photon!.SamplingFilter.Lanczos3);\n\n\t\t\ttry {\n\t\t\t\tconst candidates: EncodedCandidate[] = [encodeCandidate(resized.get_bytes(), \"image/png\")];\n\t\t\t\tfor (const quality of jpegQualities) {\n\t\t\t\t\tcandidates.push(encodeCandidate(resized.get_bytes_jpeg(quality), \"image/jpeg\"));\n\t\t\t\t}\n\t\t\t\treturn candidates;\n\t\t\t} finally {\n\t\t\t\tresized.free();\n\t\t\t}\n\t\t}\n\n\t\tconst qualitySteps = Array.from(new Set([opts.jpegQuality, 85, 70, 55, 40]));\n\t\tlet currentWidth = targetWidth;\n\t\tlet currentHeight = targetHeight;\n\n\t\twhile (true) {\n\t\t\tconst candidates = tryEncodings(currentWidth, currentHeight, qualitySteps);\n\t\t\tfor (const candidate of candidates) {\n\t\t\t\tif (candidate.encodedSize < opts.maxBytes) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdata: candidate.data,\n\t\t\t\t\t\tmimeType: candidate.mimeType,\n\t\t\t\t\t\toriginalWidth,\n\t\t\t\t\t\toriginalHeight,\n\t\t\t\t\t\twidth: currentWidth,\n\t\t\t\t\t\theight: currentHeight,\n\t\t\t\t\t\twasResized: true,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (currentWidth === 1 && currentHeight === 1) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tconst nextWidth = currentWidth === 1 ? 1 : Math.max(1, Math.floor(currentWidth * 0.75));\n\t\t\tconst nextHeight = currentHeight === 1 ? 1 : Math.max(1, Math.floor(currentHeight * 0.75));\n\t\t\tif (nextWidth === currentWidth && nextHeight === currentHeight) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcurrentWidth = nextWidth;\n\t\t\tcurrentHeight = nextHeight;\n\t\t}\n\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\tif (image) {\n\t\t\timage.free();\n\t\t}\n\t}\n}\n\n/**\n * Format a dimension note for resized images.\n * This helps the model understand the coordinate mapping.\n */\nexport function formatDimensionNote(result: ResizedImage): string | undefined {\n\tif (!result.wasResized) {\n\t\treturn undefined;\n\t}\n\n\tconst scale = result.originalWidth / result.width;\n\treturn `[Image: original ${result.originalWidth}x${result.originalHeight}, displayed at ${result.width}x${result.height}. Multiply coordinates by ${scale.toFixed(2)} to map to original image.]`;\n}\n"]}
1
+ {"version":3,"file":"image-resize.js","sourceRoot":"","sources":["../../src/utils/image-resize.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ImageContent } from \"@earendil-works/pi-ai\";\nimport { applyExifOrientation } from \"./exif-orientation.ts\";\nimport { loadPhoton } from \"./photon.ts\";\n\nexport interface ImageResizeOptions {\n\tmaxWidth?: number; // Default: 2000\n\tmaxHeight?: number; // Default: 2000\n\tmaxBytes?: number; // Default: 4.5MB of base64 payload (below Anthropic's 5MB limit)\n\tjpegQuality?: number; // Default: 80\n}\n\nexport interface ResizedImage {\n\tdata: string; // base64\n\tmimeType: string;\n\toriginalWidth: number;\n\toriginalHeight: number;\n\twidth: number;\n\theight: number;\n\twasResized: boolean;\n}\n\n// 4.5MB of base64 payload. Provides headroom below Anthropic's 5MB limit.\nconst DEFAULT_MAX_BYTES = 4.5 * 1024 * 1024;\n\nconst DEFAULT_OPTIONS: Required<ImageResizeOptions> = {\n\tmaxWidth: 2000,\n\tmaxHeight: 2000,\n\tmaxBytes: DEFAULT_MAX_BYTES,\n\tjpegQuality: 80,\n};\n\ninterface EncodedCandidate {\n\tdata: string;\n\tencodedSize: number;\n\tmimeType: string;\n}\n\nfunction encodeCandidate(buffer: Uint8Array, mimeType: string): EncodedCandidate {\n\tconst data = Buffer.from(buffer).toString(\"base64\");\n\treturn {\n\t\tdata,\n\t\tencodedSize: Buffer.byteLength(data, \"utf-8\"),\n\t\tmimeType,\n\t};\n}\n\n/**\n * Resize an image to fit within the specified max dimensions and encoded file size.\n * Returns null if the image cannot be resized below maxBytes.\n *\n * Uses Photon (Rust/WASM) for image processing. If Photon is not available,\n * returns null.\n *\n * Strategy for staying under maxBytes:\n * 1. First resize to maxWidth/maxHeight\n * 2. Try both PNG and JPEG formats, pick the smaller one\n * 3. If still too large, try JPEG with decreasing quality\n * 4. If still too large, progressively reduce dimensions until 1x1\n */\nexport async function resizeImage(img: ImageContent, options?: ImageResizeOptions): Promise<ResizedImage | null> {\n\tconst opts = { ...DEFAULT_OPTIONS, ...options };\n\tconst inputBuffer = Buffer.from(img.data, \"base64\");\n\tconst inputBase64Size = Buffer.byteLength(img.data, \"utf-8\");\n\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\tlet image: ReturnType<typeof photon.PhotonImage.new_from_byteslice> | undefined;\n\ttry {\n\t\tconst inputBytes = new Uint8Array(inputBuffer);\n\t\tconst rawImage = photon.PhotonImage.new_from_byteslice(inputBytes);\n\t\timage = applyExifOrientation(photon, rawImage, inputBytes);\n\t\tif (image !== rawImage) rawImage.free();\n\n\t\tconst originalWidth = image.get_width();\n\t\tconst originalHeight = image.get_height();\n\t\tconst format = img.mimeType?.split(\"/\")[1] ?? \"png\";\n\n\t\t// Check if already within all limits (dimensions AND encoded size)\n\t\tif (originalWidth <= opts.maxWidth && originalHeight <= opts.maxHeight && inputBase64Size < opts.maxBytes) {\n\t\t\treturn {\n\t\t\t\tdata: img.data,\n\t\t\t\tmimeType: img.mimeType ?? `image/${format}`,\n\t\t\t\toriginalWidth,\n\t\t\t\toriginalHeight,\n\t\t\t\twidth: originalWidth,\n\t\t\t\theight: originalHeight,\n\t\t\t\twasResized: false,\n\t\t\t};\n\t\t}\n\n\t\t// Calculate initial dimensions respecting max limits\n\t\tlet targetWidth = originalWidth;\n\t\tlet targetHeight = originalHeight;\n\n\t\tif (targetWidth > opts.maxWidth) {\n\t\t\ttargetHeight = Math.round((targetHeight * opts.maxWidth) / targetWidth);\n\t\t\ttargetWidth = opts.maxWidth;\n\t\t}\n\t\tif (targetHeight > opts.maxHeight) {\n\t\t\ttargetWidth = Math.round((targetWidth * opts.maxHeight) / targetHeight);\n\t\t\ttargetHeight = opts.maxHeight;\n\t\t}\n\n\t\tfunction tryEncodings(width: number, height: number, jpegQualities: number[]): EncodedCandidate[] {\n\t\t\tconst resized = photon!.resize(image!, width, height, photon!.SamplingFilter.Lanczos3);\n\n\t\t\ttry {\n\t\t\t\tconst candidates: EncodedCandidate[] = [encodeCandidate(resized.get_bytes(), \"image/png\")];\n\t\t\t\tfor (const quality of jpegQualities) {\n\t\t\t\t\tcandidates.push(encodeCandidate(resized.get_bytes_jpeg(quality), \"image/jpeg\"));\n\t\t\t\t}\n\t\t\t\treturn candidates;\n\t\t\t} finally {\n\t\t\t\tresized.free();\n\t\t\t}\n\t\t}\n\n\t\tconst qualitySteps = Array.from(new Set([opts.jpegQuality, 85, 70, 55, 40]));\n\t\tlet currentWidth = targetWidth;\n\t\tlet currentHeight = targetHeight;\n\n\t\twhile (true) {\n\t\t\tconst candidates = tryEncodings(currentWidth, currentHeight, qualitySteps);\n\t\t\tfor (const candidate of candidates) {\n\t\t\t\tif (candidate.encodedSize < opts.maxBytes) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdata: candidate.data,\n\t\t\t\t\t\tmimeType: candidate.mimeType,\n\t\t\t\t\t\toriginalWidth,\n\t\t\t\t\t\toriginalHeight,\n\t\t\t\t\t\twidth: currentWidth,\n\t\t\t\t\t\theight: currentHeight,\n\t\t\t\t\t\twasResized: true,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (currentWidth === 1 && currentHeight === 1) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tconst nextWidth = currentWidth === 1 ? 1 : Math.max(1, Math.floor(currentWidth * 0.75));\n\t\t\tconst nextHeight = currentHeight === 1 ? 1 : Math.max(1, Math.floor(currentHeight * 0.75));\n\t\t\tif (nextWidth === currentWidth && nextHeight === currentHeight) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcurrentWidth = nextWidth;\n\t\t\tcurrentHeight = nextHeight;\n\t\t}\n\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\tif (image) {\n\t\t\timage.free();\n\t\t}\n\t}\n}\n\n/**\n * Format a dimension note for resized images.\n * This helps the model understand the coordinate mapping.\n */\nexport function formatDimensionNote(result: ResizedImage): string | undefined {\n\tif (!result.wasResized) {\n\t\treturn undefined;\n\t}\n\n\tconst scale = result.originalWidth / result.width;\n\treturn `[Image: original ${result.originalWidth}x${result.originalHeight}, displayed at ${result.width}x${result.height}. Multiply coordinates by ${scale.toFixed(2)} to map to original image.]`;\n}\n"]}
@@ -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;CACf;AAqCD;;;;;;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,CAwBjD","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { delimiter } from \"node:path\";\nimport { spawn, spawnSync } from \"child_process\";\nimport { getBinDir } from \"../config.js\";\n\nexport interface ShellConfig {\n\tshell: string;\n\targs: string[];\n}\n\n/**\n * Find bash executable on PATH (cross-platform)\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\"], { encoding: \"utf-8\", timeout: 5000 });\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 { shell: customShellPath, args: [\"-c\"] };\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 { shell: path, args: [\"-c\"] };\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 { shell: bashOnPath, args: [\"-c\"] };\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 { shell: \"/bin/bash\", args: [\"-c\"] };\n\t}\n\n\tconst bashOnPath = findBashOnPath();\n\tif (bashOnPath) {\n\t\treturn { shell: bashOnPath, args: [\"-c\"] };\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});\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;CACf;AAqCD;;;;;;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,CAwBjD","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}\n\n/**\n * Find bash executable on PATH (cross-platform)\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\"], { encoding: \"utf-8\", timeout: 5000 });\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 { shell: customShellPath, args: [\"-c\"] };\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 { shell: path, args: [\"-c\"] };\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 { shell: bashOnPath, args: [\"-c\"] };\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 { shell: \"/bin/bash\", args: [\"-c\"] };\n\t}\n\n\tconst bashOnPath = findBashOnPath();\n\tif (bashOnPath) {\n\t\treturn { shell: bashOnPath, args: [\"-c\"] };\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});\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 +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.js\";\n\nexport interface ShellConfig {\n\tshell: string;\n\targs: string[];\n}\n\n/**\n * Find bash executable on PATH (cross-platform)\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\"], { encoding: \"utf-8\", timeout: 5000 });\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 { shell: customShellPath, args: [\"-c\"] };\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 { shell: path, args: [\"-c\"] };\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 { shell: bashOnPath, args: [\"-c\"] };\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 { shell: \"/bin/bash\", args: [\"-c\"] };\n\t}\n\n\tconst bashOnPath = findBashOnPath();\n\tif (bashOnPath) {\n\t\treturn { shell: bashOnPath, args: [\"-c\"] };\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});\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}\n\n/**\n * Find bash executable on PATH (cross-platform)\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\"], { encoding: \"utf-8\", timeout: 5000 });\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 { shell: customShellPath, args: [\"-c\"] };\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 { shell: path, args: [\"-c\"] };\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 { shell: bashOnPath, args: [\"-c\"] };\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 { shell: \"/bin/bash\", args: [\"-c\"] };\n\t}\n\n\tconst bashOnPath = findBashOnPath();\n\tif (bashOnPath) {\n\t\treturn { shell: bashOnPath, args: [\"-c\"] };\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});\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 +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.js\";\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":"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 +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.js\";\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/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 +1 @@
1
- {"version":3,"file":"tools-manager.d.ts","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"AAoGA,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAsB5D;AAwQD,wBAAsB,UAAU,CAC9B,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,MAAM,GAAE,OAAe,GACtB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAuD7B","sourcesContent":["import chalk from \"chalk\";\nimport { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport {\n chmodSync,\n createWriteStream,\n existsSync,\n mkdirSync,\n readdirSync,\n renameSync,\n rmSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { join } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { APP_NAME, ENV_OFFLINE, getBinDir, getEnvValue } from \"../config.js\";\n\nconst TOOLS_DIR = getBinDir();\nconst NETWORK_TIMEOUT_MS = 10_000;\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n const value = getEnvValue(ENV_OFFLINE);\n if (!value) return false;\n return (\n value === \"1\" ||\n value.toLowerCase() === \"true\" ||\n value.toLowerCase() === \"yes\"\n );\n}\n\ninterface ToolConfig {\n name: string;\n repo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n binaryName: string; // Name of the binary inside the archive\n systemBinaryNames?: string[]; // Alternative system command names to try before downloading\n tagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n getAssetName: (\n version: string,\n plat: string,\n architecture: string,\n ) => string | null;\n}\n\nconst TOOLS: Record<string, ToolConfig> = {\n fd: {\n name: \"fd\",\n repo: \"sharkdp/fd\",\n binaryName: \"fd\",\n systemBinaryNames: [\"fd\", \"fdfind\"],\n tagPrefix: \"v\",\n getAssetName: (version, plat, architecture) => {\n if (plat === \"darwin\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `fd-v${version}-${archStr}-apple-darwin.tar.gz`;\n } else if (plat === \"linux\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `fd-v${version}-${archStr}-unknown-linux-gnu.tar.gz`;\n } else if (plat === \"win32\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `fd-v${version}-${archStr}-pc-windows-msvc.zip`;\n }\n return null;\n },\n },\n rg: {\n name: \"ripgrep\",\n repo: \"BurntSushi/ripgrep\",\n binaryName: \"rg\",\n tagPrefix: \"\",\n getAssetName: (version, plat, architecture) => {\n if (plat === \"darwin\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `ripgrep-${version}-${archStr}-apple-darwin.tar.gz`;\n } else if (plat === \"linux\") {\n if (architecture === \"arm64\") {\n return `ripgrep-${version}-aarch64-unknown-linux-gnu.tar.gz`;\n }\n return `ripgrep-${version}-x86_64-unknown-linux-musl.tar.gz`;\n } else if (plat === \"win32\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `ripgrep-${version}-${archStr}-pc-windows-msvc.zip`;\n }\n return null;\n },\n },\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n try {\n const result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n // Check for ENOENT error (command not found)\n return result.error === undefined || result.error === null;\n } catch {\n return false;\n }\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 const config = TOOLS[tool];\n if (!config) return null;\n\n // Check our tools directory first\n const localPath = join(\n TOOLS_DIR,\n config.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"),\n );\n if (existsSync(localPath)) {\n return localPath;\n }\n\n // Check system PATH - if found, just return the command name (it's in PATH)\n const systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n for (const systemBinaryName of systemBinaryNames) {\n if (commandExists(systemBinaryName)) {\n return systemBinaryName;\n }\n }\n\n return null;\n}\n\n// Fetch latest release version from GitHub\nasync function getLatestVersion(repo: string): Promise<string> {\n const response = await fetch(\n `https://api.github.com/repos/${repo}/releases/latest`,\n {\n headers: { \"User-Agent\": APP_NAME },\n signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),\n },\n );\n\n if (!response.ok) {\n throw new Error(`GitHub API error: ${response.status}`);\n }\n\n const data = (await response.json()) as { tag_name: string };\n return data.tag_name.replace(/^v/, \"\");\n}\n\n// Download a file from URL\nasync function downloadFile(url: string, dest: string): Promise<void> {\n const response = await fetch(url, {\n signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to download: ${response.status}`);\n }\n\n if (!response.body) {\n throw new Error(\"No response body\");\n }\n\n const fileStream = createWriteStream(dest);\n await pipeline(Readable.fromWeb(response.body), fileStream);\n}\n\nfunction findBinaryRecursively(\n rootDir: string,\n binaryFileName: string,\n): string | null {\n const stack: string[] = [rootDir];\n\n while (stack.length > 0) {\n const currentDir = stack.pop();\n if (!currentDir) continue;\n\n const entries = readdirSync(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(currentDir, entry.name);\n if (entry.isFile() && entry.name === binaryFileName) {\n return fullPath;\n }\n if (entry.isDirectory()) {\n stack.push(fullPath);\n }\n }\n }\n\n return null;\n}\n\nfunction formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {\n if (result.error?.message) {\n return result.error.message;\n }\n const stderr = result.stderr?.toString().trim();\n if (stderr) {\n return stderr;\n }\n const stdout = result.stdout?.toString().trim();\n if (stdout) {\n return stdout;\n }\n return `exit status ${result.status ?? \"unknown\"}`;\n}\n\nfunction runExtractionCommand(command: string, args: string[]): string | null {\n const result = spawnSync(command, args, { stdio: \"pipe\" });\n if (!result.error && result.status === 0) {\n return null;\n }\n return `${command}: ${formatSpawnFailure(result)}`;\n}\n\nfunction extractTarGzArchive(\n archivePath: string,\n extractDir: string,\n assetName: string,\n): void {\n const failure = runExtractionCommand(\"tar\", [\n \"xzf\",\n archivePath,\n \"-C\",\n extractDir,\n ]);\n if (failure) {\n throw new Error(`Failed to extract ${assetName}: ${failure}`);\n }\n}\n\nfunction getWindowsTarCommand(): string {\n const systemRoot = process.env.SystemRoot ?? process.env.WINDIR;\n if (systemRoot) {\n const systemTar = join(systemRoot, \"System32\", \"tar.exe\");\n if (existsSync(systemTar)) {\n return systemTar;\n }\n }\n return \"tar.exe\";\n}\n\nfunction extractZipArchive(\n archivePath: string,\n extractDir: string,\n assetName: string,\n): void {\n const failures: string[] = [];\n\n if (platform() === \"win32\") {\n // Windows ships bsdtar as tar.exe, which supports zip files. Prefer the\n // System32 binary over Git Bash's GNU tar, which does not handle zip archives.\n const tarFailure = runExtractionCommand(getWindowsTarCommand(), [\n \"xf\",\n archivePath,\n \"-C\",\n extractDir,\n ]);\n if (!tarFailure) return;\n failures.push(tarFailure);\n\n const script =\n \"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }\";\n const powershellFailure = runExtractionCommand(\"powershell.exe\", [\n \"-NoLogo\",\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n script,\n archivePath,\n extractDir,\n ]);\n if (!powershellFailure) return;\n failures.push(powershellFailure);\n } else {\n const unzipFailure = runExtractionCommand(\"unzip\", [\n \"-q\",\n archivePath,\n \"-d\",\n extractDir,\n ]);\n if (!unzipFailure) return;\n failures.push(unzipFailure);\n\n const tarFailure = runExtractionCommand(\"tar\", [\n \"xf\",\n archivePath,\n \"-C\",\n extractDir,\n ]);\n if (!tarFailure) return;\n failures.push(tarFailure);\n }\n\n throw 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 const config = TOOLS[tool];\n if (!config) throw new Error(`Unknown tool: ${tool}`);\n\n const plat = platform();\n const architecture = arch();\n\n // Get latest version\n const version = await getLatestVersion(config.repo);\n\n // Get asset name for this platform\n const assetName = config.getAssetName(version, plat, architecture);\n if (!assetName) {\n throw new Error(`Unsupported platform: ${plat}/${architecture}`);\n }\n\n // Create tools directory\n mkdirSync(TOOLS_DIR, { recursive: true });\n\n const downloadUrl = `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${version}/${assetName}`;\n const archivePath = join(TOOLS_DIR, assetName);\n const binaryExt = plat === \"win32\" ? \".exe\" : \"\";\n const binaryPath = join(TOOLS_DIR, config.binaryName + binaryExt);\n\n // Download\n await downloadFile(downloadUrl, archivePath);\n\n // Extract into a unique temp directory. fd and rg downloads can run concurrently\n // during startup, so sharing a fixed directory causes races.\n const extractDir = join(\n TOOLS_DIR,\n `extract_tmp_${config.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n );\n mkdirSync(extractDir, { recursive: true });\n\n try {\n if (assetName.endsWith(\".tar.gz\")) {\n extractTarGzArchive(archivePath, extractDir, assetName);\n } else if (assetName.endsWith(\".zip\")) {\n extractZipArchive(archivePath, extractDir, assetName);\n } else {\n throw new Error(`Unsupported archive format: ${assetName}`);\n }\n\n // Find the binary in extracted files. Some archives contain files directly\n // at root, others nest under a versioned subdirectory.\n const binaryFileName = config.binaryName + binaryExt;\n const extractedDir = join(\n extractDir,\n assetName.replace(/\\.(tar\\.gz|zip)$/, \"\"),\n );\n const extractedBinaryCandidates = [\n join(extractedDir, binaryFileName),\n join(extractDir, binaryFileName),\n ];\n let extractedBinary = extractedBinaryCandidates.find((candidate) =>\n existsSync(candidate),\n );\n\n if (!extractedBinary) {\n extractedBinary =\n findBinaryRecursively(extractDir, binaryFileName) ?? undefined;\n }\n\n if (extractedBinary) {\n renameSync(extractedBinary, binaryPath);\n } else {\n throw new Error(\n `Binary not found in archive: expected ${binaryFileName} under ${extractDir}`,\n );\n }\n\n // Make executable (Unix only)\n if (plat !== \"win32\") {\n chmodSync(binaryPath, 0o755);\n }\n } finally {\n // Cleanup\n rmSync(archivePath, { force: true });\n rmSync(extractDir, { recursive: true, force: true });\n }\n\n return binaryPath;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n fd: \"fd\",\n rg: \"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(\n tool: \"fd\" | \"rg\",\n silent: boolean = false,\n): Promise<string | undefined> {\n const existingPath = getToolPath(tool);\n if (existingPath) {\n return existingPath;\n }\n\n const config = TOOLS[tool];\n if (!config) return undefined;\n\n if (isOfflineModeEnabled()) {\n if (!silent) {\n console.log(\n chalk.yellow(\n `${config.name} not found. Offline mode enabled, skipping download.`,\n ),\n );\n }\n return undefined;\n }\n\n // On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n // Users must install via pkg.\n if (platform() === \"android\") {\n const pkgName = TERMUX_PACKAGES[tool] ?? tool;\n if (!silent) {\n console.log(\n chalk.yellow(\n `${config.name} not found. Install with: pkg install ${pkgName}`,\n ),\n );\n }\n return undefined;\n }\n\n // Tool not found - download it\n if (!silent) {\n console.log(chalk.dim(`${config.name} not found. Downloading...`));\n }\n\n try {\n const path = await downloadTool(tool);\n if (!silent) {\n console.log(chalk.dim(`${config.name} installed to ${path}`));\n }\n return path;\n } catch (e) {\n if (!silent) {\n console.log(\n chalk.yellow(\n `Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`,\n ),\n );\n }\n return undefined;\n }\n}\n"]}
1
+ {"version":3,"file":"tools-manager.d.ts","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"AAoGA,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAsB5D;AAwQD,wBAAsB,UAAU,CAC9B,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,MAAM,GAAE,OAAe,GACtB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAuD7B","sourcesContent":["import chalk from \"chalk\";\nimport { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport {\n chmodSync,\n createWriteStream,\n existsSync,\n mkdirSync,\n readdirSync,\n renameSync,\n rmSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { join } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { APP_NAME, ENV_OFFLINE, getBinDir, getEnvValue } from \"../config.ts\";\n\nconst TOOLS_DIR = getBinDir();\nconst NETWORK_TIMEOUT_MS = 10_000;\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n const value = getEnvValue(ENV_OFFLINE);\n if (!value) return false;\n return (\n value === \"1\" ||\n value.toLowerCase() === \"true\" ||\n value.toLowerCase() === \"yes\"\n );\n}\n\ninterface ToolConfig {\n name: string;\n repo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n binaryName: string; // Name of the binary inside the archive\n systemBinaryNames?: string[]; // Alternative system command names to try before downloading\n tagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n getAssetName: (\n version: string,\n plat: string,\n architecture: string,\n ) => string | null;\n}\n\nconst TOOLS: Record<string, ToolConfig> = {\n fd: {\n name: \"fd\",\n repo: \"sharkdp/fd\",\n binaryName: \"fd\",\n systemBinaryNames: [\"fd\", \"fdfind\"],\n tagPrefix: \"v\",\n getAssetName: (version, plat, architecture) => {\n if (plat === \"darwin\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `fd-v${version}-${archStr}-apple-darwin.tar.gz`;\n } else if (plat === \"linux\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `fd-v${version}-${archStr}-unknown-linux-gnu.tar.gz`;\n } else if (plat === \"win32\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `fd-v${version}-${archStr}-pc-windows-msvc.zip`;\n }\n return null;\n },\n },\n rg: {\n name: \"ripgrep\",\n repo: \"BurntSushi/ripgrep\",\n binaryName: \"rg\",\n tagPrefix: \"\",\n getAssetName: (version, plat, architecture) => {\n if (plat === \"darwin\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `ripgrep-${version}-${archStr}-apple-darwin.tar.gz`;\n } else if (plat === \"linux\") {\n if (architecture === \"arm64\") {\n return `ripgrep-${version}-aarch64-unknown-linux-gnu.tar.gz`;\n }\n return `ripgrep-${version}-x86_64-unknown-linux-musl.tar.gz`;\n } else if (plat === \"win32\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `ripgrep-${version}-${archStr}-pc-windows-msvc.zip`;\n }\n return null;\n },\n },\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n try {\n const result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n // Check for ENOENT error (command not found)\n return result.error === undefined || result.error === null;\n } catch {\n return false;\n }\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 const config = TOOLS[tool];\n if (!config) return null;\n\n // Check our tools directory first\n const localPath = join(\n TOOLS_DIR,\n config.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"),\n );\n if (existsSync(localPath)) {\n return localPath;\n }\n\n // Check system PATH - if found, just return the command name (it's in PATH)\n const systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n for (const systemBinaryName of systemBinaryNames) {\n if (commandExists(systemBinaryName)) {\n return systemBinaryName;\n }\n }\n\n return null;\n}\n\n// Fetch latest release version from GitHub\nasync function getLatestVersion(repo: string): Promise<string> {\n const response = await fetch(\n `https://api.github.com/repos/${repo}/releases/latest`,\n {\n headers: { \"User-Agent\": APP_NAME },\n signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),\n },\n );\n\n if (!response.ok) {\n throw new Error(`GitHub API error: ${response.status}`);\n }\n\n const data = (await response.json()) as { tag_name: string };\n return data.tag_name.replace(/^v/, \"\");\n}\n\n// Download a file from URL\nasync function downloadFile(url: string, dest: string): Promise<void> {\n const response = await fetch(url, {\n signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to download: ${response.status}`);\n }\n\n if (!response.body) {\n throw new Error(\"No response body\");\n }\n\n const fileStream = createWriteStream(dest);\n await pipeline(Readable.fromWeb(response.body), fileStream);\n}\n\nfunction findBinaryRecursively(\n rootDir: string,\n binaryFileName: string,\n): string | null {\n const stack: string[] = [rootDir];\n\n while (stack.length > 0) {\n const currentDir = stack.pop();\n if (!currentDir) continue;\n\n const entries = readdirSync(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(currentDir, entry.name);\n if (entry.isFile() && entry.name === binaryFileName) {\n return fullPath;\n }\n if (entry.isDirectory()) {\n stack.push(fullPath);\n }\n }\n }\n\n return null;\n}\n\nfunction formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {\n if (result.error?.message) {\n return result.error.message;\n }\n const stderr = result.stderr?.toString().trim();\n if (stderr) {\n return stderr;\n }\n const stdout = result.stdout?.toString().trim();\n if (stdout) {\n return stdout;\n }\n return `exit status ${result.status ?? \"unknown\"}`;\n}\n\nfunction runExtractionCommand(command: string, args: string[]): string | null {\n const result = spawnSync(command, args, { stdio: \"pipe\" });\n if (!result.error && result.status === 0) {\n return null;\n }\n return `${command}: ${formatSpawnFailure(result)}`;\n}\n\nfunction extractTarGzArchive(\n archivePath: string,\n extractDir: string,\n assetName: string,\n): void {\n const failure = runExtractionCommand(\"tar\", [\n \"xzf\",\n archivePath,\n \"-C\",\n extractDir,\n ]);\n if (failure) {\n throw new Error(`Failed to extract ${assetName}: ${failure}`);\n }\n}\n\nfunction getWindowsTarCommand(): string {\n const systemRoot = process.env.SystemRoot ?? process.env.WINDIR;\n if (systemRoot) {\n const systemTar = join(systemRoot, \"System32\", \"tar.exe\");\n if (existsSync(systemTar)) {\n return systemTar;\n }\n }\n return \"tar.exe\";\n}\n\nfunction extractZipArchive(\n archivePath: string,\n extractDir: string,\n assetName: string,\n): void {\n const failures: string[] = [];\n\n if (platform() === \"win32\") {\n // Windows ships bsdtar as tar.exe, which supports zip files. Prefer the\n // System32 binary over Git Bash's GNU tar, which does not handle zip archives.\n const tarFailure = runExtractionCommand(getWindowsTarCommand(), [\n \"xf\",\n archivePath,\n \"-C\",\n extractDir,\n ]);\n if (!tarFailure) return;\n failures.push(tarFailure);\n\n const script =\n \"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }\";\n const powershellFailure = runExtractionCommand(\"powershell.exe\", [\n \"-NoLogo\",\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n script,\n archivePath,\n extractDir,\n ]);\n if (!powershellFailure) return;\n failures.push(powershellFailure);\n } else {\n const unzipFailure = runExtractionCommand(\"unzip\", [\n \"-q\",\n archivePath,\n \"-d\",\n extractDir,\n ]);\n if (!unzipFailure) return;\n failures.push(unzipFailure);\n\n const tarFailure = runExtractionCommand(\"tar\", [\n \"xf\",\n archivePath,\n \"-C\",\n extractDir,\n ]);\n if (!tarFailure) return;\n failures.push(tarFailure);\n }\n\n throw 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 const config = TOOLS[tool];\n if (!config) throw new Error(`Unknown tool: ${tool}`);\n\n const plat = platform();\n const architecture = arch();\n\n // Get latest version\n const version = await getLatestVersion(config.repo);\n\n // Get asset name for this platform\n const assetName = config.getAssetName(version, plat, architecture);\n if (!assetName) {\n throw new Error(`Unsupported platform: ${plat}/${architecture}`);\n }\n\n // Create tools directory\n mkdirSync(TOOLS_DIR, { recursive: true });\n\n const downloadUrl = `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${version}/${assetName}`;\n const archivePath = join(TOOLS_DIR, assetName);\n const binaryExt = plat === \"win32\" ? \".exe\" : \"\";\n const binaryPath = join(TOOLS_DIR, config.binaryName + binaryExt);\n\n // Download\n await downloadFile(downloadUrl, archivePath);\n\n // Extract into a unique temp directory. fd and rg downloads can run concurrently\n // during startup, so sharing a fixed directory causes races.\n const extractDir = join(\n TOOLS_DIR,\n `extract_tmp_${config.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n );\n mkdirSync(extractDir, { recursive: true });\n\n try {\n if (assetName.endsWith(\".tar.gz\")) {\n extractTarGzArchive(archivePath, extractDir, assetName);\n } else if (assetName.endsWith(\".zip\")) {\n extractZipArchive(archivePath, extractDir, assetName);\n } else {\n throw new Error(`Unsupported archive format: ${assetName}`);\n }\n\n // Find the binary in extracted files. Some archives contain files directly\n // at root, others nest under a versioned subdirectory.\n const binaryFileName = config.binaryName + binaryExt;\n const extractedDir = join(\n extractDir,\n assetName.replace(/\\.(tar\\.gz|zip)$/, \"\"),\n );\n const extractedBinaryCandidates = [\n join(extractedDir, binaryFileName),\n join(extractDir, binaryFileName),\n ];\n let extractedBinary = extractedBinaryCandidates.find((candidate) =>\n existsSync(candidate),\n );\n\n if (!extractedBinary) {\n extractedBinary =\n findBinaryRecursively(extractDir, binaryFileName) ?? undefined;\n }\n\n if (extractedBinary) {\n renameSync(extractedBinary, binaryPath);\n } else {\n throw new Error(\n `Binary not found in archive: expected ${binaryFileName} under ${extractDir}`,\n );\n }\n\n // Make executable (Unix only)\n if (plat !== \"win32\") {\n chmodSync(binaryPath, 0o755);\n }\n } finally {\n // Cleanup\n rmSync(archivePath, { force: true });\n rmSync(extractDir, { recursive: true, force: true });\n }\n\n return binaryPath;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n fd: \"fd\",\n rg: \"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(\n tool: \"fd\" | \"rg\",\n silent: boolean = false,\n): Promise<string | undefined> {\n const existingPath = getToolPath(tool);\n if (existingPath) {\n return existingPath;\n }\n\n const config = TOOLS[tool];\n if (!config) return undefined;\n\n if (isOfflineModeEnabled()) {\n if (!silent) {\n console.log(\n chalk.yellow(\n `${config.name} not found. Offline mode enabled, skipping download.`,\n ),\n );\n }\n return undefined;\n }\n\n // On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n // Users must install via pkg.\n if (platform() === \"android\") {\n const pkgName = TERMUX_PACKAGES[tool] ?? tool;\n if (!silent) {\n console.log(\n chalk.yellow(\n `${config.name} not found. Install with: pkg install ${pkgName}`,\n ),\n );\n }\n return undefined;\n }\n\n // Tool not found - download it\n if (!silent) {\n console.log(chalk.dim(`${config.name} not found. Downloading...`));\n }\n\n try {\n const path = await downloadTool(tool);\n if (!silent) {\n console.log(chalk.dim(`${config.name} installed to ${path}`));\n }\n return path;\n } catch (e) {\n if (!silent) {\n console.log(\n chalk.yellow(\n `Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`,\n ),\n );\n }\n return undefined;\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"tools-manager.js","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAyB,SAAS,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,MAAM,GACP,MAAM,IAAI,CAAC;AACZ,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE7E,MAAM,SAAS,GAAG,SAAS,EAAE,CAAC;AAC9B,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAEpC,SAAS,oBAAoB;IAC3B,MAAM,KAAK,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,OAAO,CACL,KAAK,KAAK,GAAG;QACb,KAAK,CAAC,WAAW,EAAE,KAAK,MAAM;QAC9B,KAAK,CAAC,WAAW,EAAE,KAAK,KAAK,CAC9B,CAAC;AACJ,CAAC;AAeD,MAAM,KAAK,GAA+B;IACxC,EAAE,EAAE;QACF,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE,IAAI;QAChB,iBAAiB,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC;QACnC,SAAS,EAAE,GAAG;QACd,YAAY,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE;YAC5C,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACtB,MAAM,OAAO,GAAG,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;gBAChE,OAAO,OAAO,OAAO,IAAI,OAAO,sBAAsB,CAAC;YACzD,CAAC;iBAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC5B,MAAM,OAAO,GAAG,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;gBAChE,OAAO,OAAO,OAAO,IAAI,OAAO,2BAA2B,CAAC;YAC9D,CAAC;iBAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC5B,MAAM,OAAO,GAAG,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;gBAChE,OAAO,OAAO,OAAO,IAAI,OAAO,sBAAsB,CAAC;YACzD,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;KACF;IACD,EAAE,EAAE;QACF,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,oBAAoB;QAC1B,UAAU,EAAE,IAAI;QAChB,SAAS,EAAE,EAAE;QACb,YAAY,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE;YAC5C,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACtB,MAAM,OAAO,GAAG,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;gBAChE,OAAO,WAAW,OAAO,IAAI,OAAO,sBAAsB,CAAC;YAC7D,CAAC;iBAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC5B,IAAI,YAAY,KAAK,OAAO,EAAE,CAAC;oBAC7B,OAAO,WAAW,OAAO,mCAAmC,CAAC;gBAC/D,CAAC;gBACD,OAAO,WAAW,OAAO,mCAAmC,CAAC;YAC/D,CAAC;iBAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC5B,MAAM,OAAO,GAAG,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;gBAChE,OAAO,WAAW,OAAO,IAAI,OAAO,sBAAsB,CAAC;YAC7D,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;KACF;CACF,CAAC;AAEF,wDAAwD;AACxD,SAAS,aAAa,CAAC,GAAW;IAChC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QAChE,6CAA6C;QAC7C,OAAO,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,WAAW,CAAC,IAAiB;IAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,kCAAkC;IAClC,MAAM,SAAS,GAAG,IAAI,CACpB,SAAS,EACT,MAAM,CAAC,UAAU,GAAG,CAAC,QAAQ,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAC3D,CAAC;IACF,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,4EAA4E;IAC5E,MAAM,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC1E,KAAK,MAAM,gBAAgB,IAAI,iBAAiB,EAAE,CAAC;QACjD,IAAI,aAAa,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACpC,OAAO,gBAAgB,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2CAA2C;AAC3C,KAAK,UAAU,gBAAgB,CAAC,IAAY;IAC1C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAC1B,gCAAgC,IAAI,kBAAkB,EACtD;QACE,OAAO,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE;QACnC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,kBAAkB,CAAC;KAChD,CACF,CAAC;IAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,qBAAqB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAyB,CAAC;IAC7D,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,2BAA2B;AAC3B,KAAK,UAAU,YAAY,CAAC,GAAW,EAAE,IAAY;IACnD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAChC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,mBAAmB,CAAC;KACjD,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,qBAAqB,CAC5B,OAAe,EACf,cAAsB;IAEtB,MAAM,KAAK,GAAa,CAAC,OAAO,CAAC,CAAC;IAElC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QAC/B,IAAI,CAAC,UAAU;YAAE,SAAS;QAE1B,MAAM,OAAO,GAAG,WAAW,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACjE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC9C,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;gBACpD,OAAO,QAAQ,CAAC;YAClB,CAAC;YACD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,kBAAkB,CAAC,MAAgC;IAC1D,IAAI,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;QAC1B,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;IAC9B,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;IAChD,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;IAChD,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,eAAe,MAAM,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;AACrD,CAAC;AAED,SAAS,oBAAoB,CAAC,OAAe,EAAE,IAAc;IAC3D,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3D,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,GAAG,OAAO,KAAK,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;AACrD,CAAC;AAED,SAAS,mBAAmB,CAC1B,WAAmB,EACnB,UAAkB,EAClB,SAAiB;IAEjB,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,EAAE;QAC1C,KAAK;QACL,WAAW;QACX,IAAI;QACJ,UAAU;KACX,CAAC,CAAC;IACH,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,qBAAqB,SAAS,KAAK,OAAO,EAAE,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB;IAC3B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;IAChE,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;QAC1D,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,iBAAiB,CACxB,WAAmB,EACnB,UAAkB,EAClB,SAAiB;IAEjB,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,IAAI,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC;QAC3B,wEAAwE;QACxE,+EAA+E;QAC/E,MAAM,UAAU,GAAG,oBAAoB,CAAC,oBAAoB,EAAE,EAAE;YAC9D,IAAI;YACJ,WAAW;YACX,IAAI;YACJ,UAAU;SACX,CAAC,CAAC;QACH,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAE1B,MAAM,MAAM,GACV,gJAAgJ,CAAC;QACnJ,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,gBAAgB,EAAE;YAC/D,SAAS;YACT,YAAY;YACZ,iBAAiB;YACjB,kBAAkB;YAClB,QAAQ;YACR,UAAU;YACV,MAAM;YACN,WAAW;YACX,UAAU;SACX,CAAC,CAAC;QACH,IAAI,CAAC,iBAAiB;YAAE,OAAO;QAC/B,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACnC,CAAC;SAAM,CAAC;QACN,MAAM,YAAY,GAAG,oBAAoB,CAAC,OAAO,EAAE;YACjD,IAAI;YACJ,WAAW;YACX,IAAI;YACJ,UAAU;SACX,CAAC,CAAC;QACH,IAAI,CAAC,YAAY;YAAE,OAAO;QAC1B,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAE5B,MAAM,UAAU,GAAG,oBAAoB,CAAC,KAAK,EAAE;YAC7C,IAAI;YACJ,WAAW;YACX,IAAI;YACJ,UAAU;SACX,CAAC,CAAC;QACH,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC5B,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,qBAAqB,SAAS,KAAK,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,8BAA8B;AAC9B,KAAK,UAAU,YAAY,CAAC,IAAiB;IAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;IAEtD,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;IACxB,MAAM,YAAY,GAAG,IAAI,EAAE,CAAC;IAE5B,qBAAqB;IACrB,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAEpD,mCAAmC;IACnC,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;IACnE,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,IAAI,YAAY,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,yBAAyB;IACzB,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1C,MAAM,WAAW,GAAG,sBAAsB,MAAM,CAAC,IAAI,sBAAsB,MAAM,CAAC,SAAS,GAAG,OAAO,IAAI,SAAS,EAAE,CAAC;IACrH,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACjD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC;IAElE,WAAW;IACX,MAAM,YAAY,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IAE7C,iFAAiF;IACjF,6DAA6D;IAC7D,MAAM,UAAU,GAAG,IAAI,CACrB,SAAS,EACT,eAAe,MAAM,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAC3G,CAAC;IACF,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE3C,IAAI,CAAC;QACH,IAAI,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAClC,mBAAmB,CAAC,WAAW,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;QAC1D,CAAC;aAAM,IAAI,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACtC,iBAAiB,CAAC,WAAW,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;QACxD,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,+BAA+B,SAAS,EAAE,CAAC,CAAC;QAC9D,CAAC;QAED,2EAA2E;QAC3E,uDAAuD;QACvD,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC;QACrD,MAAM,YAAY,GAAG,IAAI,CACvB,UAAU,EACV,SAAS,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAC1C,CAAC;QACF,MAAM,yBAAyB,GAAG;YAChC,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC;YAClC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC;SACjC,CAAC;QACF,IAAI,eAAe,GAAG,yBAAyB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CACjE,UAAU,CAAC,SAAS,CAAC,CACtB,CAAC;QAEF,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,eAAe;gBACb,qBAAqB,CAAC,UAAU,EAAE,cAAc,CAAC,IAAI,SAAS,CAAC;QACnE,CAAC;QAED,IAAI,eAAe,EAAE,CAAC;YACpB,UAAU,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAC1C,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CACb,yCAAyC,cAAc,UAAU,UAAU,EAAE,CAC9E,CAAC;QACJ,CAAC;QAED,8BAA8B;QAC9B,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACrB,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;YAAS,CAAC;QACT,UAAU;QACV,MAAM,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACrC,MAAM,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,iCAAiC;AACjC,MAAM,eAAe,GAA2B;IAC9C,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,SAAS;CACd,CAAC;AAEF,uDAAuD;AACvD,uDAAuD;AACvD,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAiB,EACjB,MAAM,GAAY,KAAK;IAEvB,MAAM,YAAY,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAE9B,IAAI,oBAAoB,EAAE,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CACV,GAAG,MAAM,CAAC,IAAI,sDAAsD,CACrE,CACF,CAAC;QACJ,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,mFAAmF;IACnF,8BAA8B;IAC9B,IAAI,QAAQ,EAAE,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;QAC9C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CACV,GAAG,MAAM,CAAC,IAAI,yCAAyC,OAAO,EAAE,CACjE,CACF,CAAC;QACJ,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,+BAA+B;IAC/B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,4BAA4B,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,iBAAiB,IAAI,EAAE,CAAC,CAAC,CAAC;QAChE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CACV,sBAAsB,MAAM,CAAC,IAAI,KAAK,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAC3E,CACF,CAAC;QACJ,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC","sourcesContent":["import chalk from \"chalk\";\nimport { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport {\n chmodSync,\n createWriteStream,\n existsSync,\n mkdirSync,\n readdirSync,\n renameSync,\n rmSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { join } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { APP_NAME, ENV_OFFLINE, getBinDir, getEnvValue } from \"../config.js\";\n\nconst TOOLS_DIR = getBinDir();\nconst NETWORK_TIMEOUT_MS = 10_000;\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n const value = getEnvValue(ENV_OFFLINE);\n if (!value) return false;\n return (\n value === \"1\" ||\n value.toLowerCase() === \"true\" ||\n value.toLowerCase() === \"yes\"\n );\n}\n\ninterface ToolConfig {\n name: string;\n repo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n binaryName: string; // Name of the binary inside the archive\n systemBinaryNames?: string[]; // Alternative system command names to try before downloading\n tagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n getAssetName: (\n version: string,\n plat: string,\n architecture: string,\n ) => string | null;\n}\n\nconst TOOLS: Record<string, ToolConfig> = {\n fd: {\n name: \"fd\",\n repo: \"sharkdp/fd\",\n binaryName: \"fd\",\n systemBinaryNames: [\"fd\", \"fdfind\"],\n tagPrefix: \"v\",\n getAssetName: (version, plat, architecture) => {\n if (plat === \"darwin\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `fd-v${version}-${archStr}-apple-darwin.tar.gz`;\n } else if (plat === \"linux\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `fd-v${version}-${archStr}-unknown-linux-gnu.tar.gz`;\n } else if (plat === \"win32\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `fd-v${version}-${archStr}-pc-windows-msvc.zip`;\n }\n return null;\n },\n },\n rg: {\n name: \"ripgrep\",\n repo: \"BurntSushi/ripgrep\",\n binaryName: \"rg\",\n tagPrefix: \"\",\n getAssetName: (version, plat, architecture) => {\n if (plat === \"darwin\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `ripgrep-${version}-${archStr}-apple-darwin.tar.gz`;\n } else if (plat === \"linux\") {\n if (architecture === \"arm64\") {\n return `ripgrep-${version}-aarch64-unknown-linux-gnu.tar.gz`;\n }\n return `ripgrep-${version}-x86_64-unknown-linux-musl.tar.gz`;\n } else if (plat === \"win32\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `ripgrep-${version}-${archStr}-pc-windows-msvc.zip`;\n }\n return null;\n },\n },\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n try {\n const result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n // Check for ENOENT error (command not found)\n return result.error === undefined || result.error === null;\n } catch {\n return false;\n }\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 const config = TOOLS[tool];\n if (!config) return null;\n\n // Check our tools directory first\n const localPath = join(\n TOOLS_DIR,\n config.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"),\n );\n if (existsSync(localPath)) {\n return localPath;\n }\n\n // Check system PATH - if found, just return the command name (it's in PATH)\n const systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n for (const systemBinaryName of systemBinaryNames) {\n if (commandExists(systemBinaryName)) {\n return systemBinaryName;\n }\n }\n\n return null;\n}\n\n// Fetch latest release version from GitHub\nasync function getLatestVersion(repo: string): Promise<string> {\n const response = await fetch(\n `https://api.github.com/repos/${repo}/releases/latest`,\n {\n headers: { \"User-Agent\": APP_NAME },\n signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),\n },\n );\n\n if (!response.ok) {\n throw new Error(`GitHub API error: ${response.status}`);\n }\n\n const data = (await response.json()) as { tag_name: string };\n return data.tag_name.replace(/^v/, \"\");\n}\n\n// Download a file from URL\nasync function downloadFile(url: string, dest: string): Promise<void> {\n const response = await fetch(url, {\n signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to download: ${response.status}`);\n }\n\n if (!response.body) {\n throw new Error(\"No response body\");\n }\n\n const fileStream = createWriteStream(dest);\n await pipeline(Readable.fromWeb(response.body), fileStream);\n}\n\nfunction findBinaryRecursively(\n rootDir: string,\n binaryFileName: string,\n): string | null {\n const stack: string[] = [rootDir];\n\n while (stack.length > 0) {\n const currentDir = stack.pop();\n if (!currentDir) continue;\n\n const entries = readdirSync(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(currentDir, entry.name);\n if (entry.isFile() && entry.name === binaryFileName) {\n return fullPath;\n }\n if (entry.isDirectory()) {\n stack.push(fullPath);\n }\n }\n }\n\n return null;\n}\n\nfunction formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {\n if (result.error?.message) {\n return result.error.message;\n }\n const stderr = result.stderr?.toString().trim();\n if (stderr) {\n return stderr;\n }\n const stdout = result.stdout?.toString().trim();\n if (stdout) {\n return stdout;\n }\n return `exit status ${result.status ?? \"unknown\"}`;\n}\n\nfunction runExtractionCommand(command: string, args: string[]): string | null {\n const result = spawnSync(command, args, { stdio: \"pipe\" });\n if (!result.error && result.status === 0) {\n return null;\n }\n return `${command}: ${formatSpawnFailure(result)}`;\n}\n\nfunction extractTarGzArchive(\n archivePath: string,\n extractDir: string,\n assetName: string,\n): void {\n const failure = runExtractionCommand(\"tar\", [\n \"xzf\",\n archivePath,\n \"-C\",\n extractDir,\n ]);\n if (failure) {\n throw new Error(`Failed to extract ${assetName}: ${failure}`);\n }\n}\n\nfunction getWindowsTarCommand(): string {\n const systemRoot = process.env.SystemRoot ?? process.env.WINDIR;\n if (systemRoot) {\n const systemTar = join(systemRoot, \"System32\", \"tar.exe\");\n if (existsSync(systemTar)) {\n return systemTar;\n }\n }\n return \"tar.exe\";\n}\n\nfunction extractZipArchive(\n archivePath: string,\n extractDir: string,\n assetName: string,\n): void {\n const failures: string[] = [];\n\n if (platform() === \"win32\") {\n // Windows ships bsdtar as tar.exe, which supports zip files. Prefer the\n // System32 binary over Git Bash's GNU tar, which does not handle zip archives.\n const tarFailure = runExtractionCommand(getWindowsTarCommand(), [\n \"xf\",\n archivePath,\n \"-C\",\n extractDir,\n ]);\n if (!tarFailure) return;\n failures.push(tarFailure);\n\n const script =\n \"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }\";\n const powershellFailure = runExtractionCommand(\"powershell.exe\", [\n \"-NoLogo\",\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n script,\n archivePath,\n extractDir,\n ]);\n if (!powershellFailure) return;\n failures.push(powershellFailure);\n } else {\n const unzipFailure = runExtractionCommand(\"unzip\", [\n \"-q\",\n archivePath,\n \"-d\",\n extractDir,\n ]);\n if (!unzipFailure) return;\n failures.push(unzipFailure);\n\n const tarFailure = runExtractionCommand(\"tar\", [\n \"xf\",\n archivePath,\n \"-C\",\n extractDir,\n ]);\n if (!tarFailure) return;\n failures.push(tarFailure);\n }\n\n throw 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 const config = TOOLS[tool];\n if (!config) throw new Error(`Unknown tool: ${tool}`);\n\n const plat = platform();\n const architecture = arch();\n\n // Get latest version\n const version = await getLatestVersion(config.repo);\n\n // Get asset name for this platform\n const assetName = config.getAssetName(version, plat, architecture);\n if (!assetName) {\n throw new Error(`Unsupported platform: ${plat}/${architecture}`);\n }\n\n // Create tools directory\n mkdirSync(TOOLS_DIR, { recursive: true });\n\n const downloadUrl = `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${version}/${assetName}`;\n const archivePath = join(TOOLS_DIR, assetName);\n const binaryExt = plat === \"win32\" ? \".exe\" : \"\";\n const binaryPath = join(TOOLS_DIR, config.binaryName + binaryExt);\n\n // Download\n await downloadFile(downloadUrl, archivePath);\n\n // Extract into a unique temp directory. fd and rg downloads can run concurrently\n // during startup, so sharing a fixed directory causes races.\n const extractDir = join(\n TOOLS_DIR,\n `extract_tmp_${config.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n );\n mkdirSync(extractDir, { recursive: true });\n\n try {\n if (assetName.endsWith(\".tar.gz\")) {\n extractTarGzArchive(archivePath, extractDir, assetName);\n } else if (assetName.endsWith(\".zip\")) {\n extractZipArchive(archivePath, extractDir, assetName);\n } else {\n throw new Error(`Unsupported archive format: ${assetName}`);\n }\n\n // Find the binary in extracted files. Some archives contain files directly\n // at root, others nest under a versioned subdirectory.\n const binaryFileName = config.binaryName + binaryExt;\n const extractedDir = join(\n extractDir,\n assetName.replace(/\\.(tar\\.gz|zip)$/, \"\"),\n );\n const extractedBinaryCandidates = [\n join(extractedDir, binaryFileName),\n join(extractDir, binaryFileName),\n ];\n let extractedBinary = extractedBinaryCandidates.find((candidate) =>\n existsSync(candidate),\n );\n\n if (!extractedBinary) {\n extractedBinary =\n findBinaryRecursively(extractDir, binaryFileName) ?? undefined;\n }\n\n if (extractedBinary) {\n renameSync(extractedBinary, binaryPath);\n } else {\n throw new Error(\n `Binary not found in archive: expected ${binaryFileName} under ${extractDir}`,\n );\n }\n\n // Make executable (Unix only)\n if (plat !== \"win32\") {\n chmodSync(binaryPath, 0o755);\n }\n } finally {\n // Cleanup\n rmSync(archivePath, { force: true });\n rmSync(extractDir, { recursive: true, force: true });\n }\n\n return binaryPath;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n fd: \"fd\",\n rg: \"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(\n tool: \"fd\" | \"rg\",\n silent: boolean = false,\n): Promise<string | undefined> {\n const existingPath = getToolPath(tool);\n if (existingPath) {\n return existingPath;\n }\n\n const config = TOOLS[tool];\n if (!config) return undefined;\n\n if (isOfflineModeEnabled()) {\n if (!silent) {\n console.log(\n chalk.yellow(\n `${config.name} not found. Offline mode enabled, skipping download.`,\n ),\n );\n }\n return undefined;\n }\n\n // On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n // Users must install via pkg.\n if (platform() === \"android\") {\n const pkgName = TERMUX_PACKAGES[tool] ?? tool;\n if (!silent) {\n console.log(\n chalk.yellow(\n `${config.name} not found. Install with: pkg install ${pkgName}`,\n ),\n );\n }\n return undefined;\n }\n\n // Tool not found - download it\n if (!silent) {\n console.log(chalk.dim(`${config.name} not found. Downloading...`));\n }\n\n try {\n const path = await downloadTool(tool);\n if (!silent) {\n console.log(chalk.dim(`${config.name} installed to ${path}`));\n }\n return path;\n } catch (e) {\n if (!silent) {\n console.log(\n chalk.yellow(\n `Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`,\n ),\n );\n }\n return undefined;\n }\n}\n"]}
1
+ {"version":3,"file":"tools-manager.js","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from \"chalk\";\nimport { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport {\n chmodSync,\n createWriteStream,\n existsSync,\n mkdirSync,\n readdirSync,\n renameSync,\n rmSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { join } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { APP_NAME, ENV_OFFLINE, getBinDir, getEnvValue } from \"../config.ts\";\n\nconst TOOLS_DIR = getBinDir();\nconst NETWORK_TIMEOUT_MS = 10_000;\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n const value = getEnvValue(ENV_OFFLINE);\n if (!value) return false;\n return (\n value === \"1\" ||\n value.toLowerCase() === \"true\" ||\n value.toLowerCase() === \"yes\"\n );\n}\n\ninterface ToolConfig {\n name: string;\n repo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n binaryName: string; // Name of the binary inside the archive\n systemBinaryNames?: string[]; // Alternative system command names to try before downloading\n tagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n getAssetName: (\n version: string,\n plat: string,\n architecture: string,\n ) => string | null;\n}\n\nconst TOOLS: Record<string, ToolConfig> = {\n fd: {\n name: \"fd\",\n repo: \"sharkdp/fd\",\n binaryName: \"fd\",\n systemBinaryNames: [\"fd\", \"fdfind\"],\n tagPrefix: \"v\",\n getAssetName: (version, plat, architecture) => {\n if (plat === \"darwin\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `fd-v${version}-${archStr}-apple-darwin.tar.gz`;\n } else if (plat === \"linux\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `fd-v${version}-${archStr}-unknown-linux-gnu.tar.gz`;\n } else if (plat === \"win32\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `fd-v${version}-${archStr}-pc-windows-msvc.zip`;\n }\n return null;\n },\n },\n rg: {\n name: \"ripgrep\",\n repo: \"BurntSushi/ripgrep\",\n binaryName: \"rg\",\n tagPrefix: \"\",\n getAssetName: (version, plat, architecture) => {\n if (plat === \"darwin\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `ripgrep-${version}-${archStr}-apple-darwin.tar.gz`;\n } else if (plat === \"linux\") {\n if (architecture === \"arm64\") {\n return `ripgrep-${version}-aarch64-unknown-linux-gnu.tar.gz`;\n }\n return `ripgrep-${version}-x86_64-unknown-linux-musl.tar.gz`;\n } else if (plat === \"win32\") {\n const archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n return `ripgrep-${version}-${archStr}-pc-windows-msvc.zip`;\n }\n return null;\n },\n },\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n try {\n const result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n // Check for ENOENT error (command not found)\n return result.error === undefined || result.error === null;\n } catch {\n return false;\n }\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 const config = TOOLS[tool];\n if (!config) return null;\n\n // Check our tools directory first\n const localPath = join(\n TOOLS_DIR,\n config.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"),\n );\n if (existsSync(localPath)) {\n return localPath;\n }\n\n // Check system PATH - if found, just return the command name (it's in PATH)\n const systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n for (const systemBinaryName of systemBinaryNames) {\n if (commandExists(systemBinaryName)) {\n return systemBinaryName;\n }\n }\n\n return null;\n}\n\n// Fetch latest release version from GitHub\nasync function getLatestVersion(repo: string): Promise<string> {\n const response = await fetch(\n `https://api.github.com/repos/${repo}/releases/latest`,\n {\n headers: { \"User-Agent\": APP_NAME },\n signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),\n },\n );\n\n if (!response.ok) {\n throw new Error(`GitHub API error: ${response.status}`);\n }\n\n const data = (await response.json()) as { tag_name: string };\n return data.tag_name.replace(/^v/, \"\");\n}\n\n// Download a file from URL\nasync function downloadFile(url: string, dest: string): Promise<void> {\n const response = await fetch(url, {\n signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to download: ${response.status}`);\n }\n\n if (!response.body) {\n throw new Error(\"No response body\");\n }\n\n const fileStream = createWriteStream(dest);\n await pipeline(Readable.fromWeb(response.body), fileStream);\n}\n\nfunction findBinaryRecursively(\n rootDir: string,\n binaryFileName: string,\n): string | null {\n const stack: string[] = [rootDir];\n\n while (stack.length > 0) {\n const currentDir = stack.pop();\n if (!currentDir) continue;\n\n const entries = readdirSync(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(currentDir, entry.name);\n if (entry.isFile() && entry.name === binaryFileName) {\n return fullPath;\n }\n if (entry.isDirectory()) {\n stack.push(fullPath);\n }\n }\n }\n\n return null;\n}\n\nfunction formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {\n if (result.error?.message) {\n return result.error.message;\n }\n const stderr = result.stderr?.toString().trim();\n if (stderr) {\n return stderr;\n }\n const stdout = result.stdout?.toString().trim();\n if (stdout) {\n return stdout;\n }\n return `exit status ${result.status ?? \"unknown\"}`;\n}\n\nfunction runExtractionCommand(command: string, args: string[]): string | null {\n const result = spawnSync(command, args, { stdio: \"pipe\" });\n if (!result.error && result.status === 0) {\n return null;\n }\n return `${command}: ${formatSpawnFailure(result)}`;\n}\n\nfunction extractTarGzArchive(\n archivePath: string,\n extractDir: string,\n assetName: string,\n): void {\n const failure = runExtractionCommand(\"tar\", [\n \"xzf\",\n archivePath,\n \"-C\",\n extractDir,\n ]);\n if (failure) {\n throw new Error(`Failed to extract ${assetName}: ${failure}`);\n }\n}\n\nfunction getWindowsTarCommand(): string {\n const systemRoot = process.env.SystemRoot ?? process.env.WINDIR;\n if (systemRoot) {\n const systemTar = join(systemRoot, \"System32\", \"tar.exe\");\n if (existsSync(systemTar)) {\n return systemTar;\n }\n }\n return \"tar.exe\";\n}\n\nfunction extractZipArchive(\n archivePath: string,\n extractDir: string,\n assetName: string,\n): void {\n const failures: string[] = [];\n\n if (platform() === \"win32\") {\n // Windows ships bsdtar as tar.exe, which supports zip files. Prefer the\n // System32 binary over Git Bash's GNU tar, which does not handle zip archives.\n const tarFailure = runExtractionCommand(getWindowsTarCommand(), [\n \"xf\",\n archivePath,\n \"-C\",\n extractDir,\n ]);\n if (!tarFailure) return;\n failures.push(tarFailure);\n\n const script =\n \"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }\";\n const powershellFailure = runExtractionCommand(\"powershell.exe\", [\n \"-NoLogo\",\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n script,\n archivePath,\n extractDir,\n ]);\n if (!powershellFailure) return;\n failures.push(powershellFailure);\n } else {\n const unzipFailure = runExtractionCommand(\"unzip\", [\n \"-q\",\n archivePath,\n \"-d\",\n extractDir,\n ]);\n if (!unzipFailure) return;\n failures.push(unzipFailure);\n\n const tarFailure = runExtractionCommand(\"tar\", [\n \"xf\",\n archivePath,\n \"-C\",\n extractDir,\n ]);\n if (!tarFailure) return;\n failures.push(tarFailure);\n }\n\n throw 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 const config = TOOLS[tool];\n if (!config) throw new Error(`Unknown tool: ${tool}`);\n\n const plat = platform();\n const architecture = arch();\n\n // Get latest version\n const version = await getLatestVersion(config.repo);\n\n // Get asset name for this platform\n const assetName = config.getAssetName(version, plat, architecture);\n if (!assetName) {\n throw new Error(`Unsupported platform: ${plat}/${architecture}`);\n }\n\n // Create tools directory\n mkdirSync(TOOLS_DIR, { recursive: true });\n\n const downloadUrl = `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${version}/${assetName}`;\n const archivePath = join(TOOLS_DIR, assetName);\n const binaryExt = plat === \"win32\" ? \".exe\" : \"\";\n const binaryPath = join(TOOLS_DIR, config.binaryName + binaryExt);\n\n // Download\n await downloadFile(downloadUrl, archivePath);\n\n // Extract into a unique temp directory. fd and rg downloads can run concurrently\n // during startup, so sharing a fixed directory causes races.\n const extractDir = join(\n TOOLS_DIR,\n `extract_tmp_${config.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n );\n mkdirSync(extractDir, { recursive: true });\n\n try {\n if (assetName.endsWith(\".tar.gz\")) {\n extractTarGzArchive(archivePath, extractDir, assetName);\n } else if (assetName.endsWith(\".zip\")) {\n extractZipArchive(archivePath, extractDir, assetName);\n } else {\n throw new Error(`Unsupported archive format: ${assetName}`);\n }\n\n // Find the binary in extracted files. Some archives contain files directly\n // at root, others nest under a versioned subdirectory.\n const binaryFileName = config.binaryName + binaryExt;\n const extractedDir = join(\n extractDir,\n assetName.replace(/\\.(tar\\.gz|zip)$/, \"\"),\n );\n const extractedBinaryCandidates = [\n join(extractedDir, binaryFileName),\n join(extractDir, binaryFileName),\n ];\n let extractedBinary = extractedBinaryCandidates.find((candidate) =>\n existsSync(candidate),\n );\n\n if (!extractedBinary) {\n extractedBinary =\n findBinaryRecursively(extractDir, binaryFileName) ?? undefined;\n }\n\n if (extractedBinary) {\n renameSync(extractedBinary, binaryPath);\n } else {\n throw new Error(\n `Binary not found in archive: expected ${binaryFileName} under ${extractDir}`,\n );\n }\n\n // Make executable (Unix only)\n if (plat !== \"win32\") {\n chmodSync(binaryPath, 0o755);\n }\n } finally {\n // Cleanup\n rmSync(archivePath, { force: true });\n rmSync(extractDir, { recursive: true, force: true });\n }\n\n return binaryPath;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n fd: \"fd\",\n rg: \"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(\n tool: \"fd\" | \"rg\",\n silent: boolean = false,\n): Promise<string | undefined> {\n const existingPath = getToolPath(tool);\n if (existingPath) {\n return existingPath;\n }\n\n const config = TOOLS[tool];\n if (!config) return undefined;\n\n if (isOfflineModeEnabled()) {\n if (!silent) {\n console.log(\n chalk.yellow(\n `${config.name} not found. Offline mode enabled, skipping download.`,\n ),\n );\n }\n return undefined;\n }\n\n // On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n // Users must install via pkg.\n if (platform() === \"android\") {\n const pkgName = TERMUX_PACKAGES[tool] ?? tool;\n if (!silent) {\n console.log(\n chalk.yellow(\n `${config.name} not found. Install with: pkg install ${pkgName}`,\n ),\n );\n }\n return undefined;\n }\n\n // Tool not found - download it\n if (!silent) {\n console.log(chalk.dim(`${config.name} not found. Downloading...`));\n }\n\n try {\n const path = await downloadTool(tool);\n if (!silent) {\n console.log(chalk.dim(`${config.name} installed to ${path}`));\n }\n return path;\n } catch (e) {\n if (!silent) {\n console.log(\n chalk.yellow(\n `Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`,\n ),\n );\n }\n return undefined;\n }\n}\n"]}