@vaclav-synacek/pi-coding-agent-termux 0.45.7

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 (478) hide show
  1. package/CHANGELOG.md +1961 -0
  2. package/README.md +1392 -0
  3. package/dist/cli/args.d.ts +42 -0
  4. package/dist/cli/args.d.ts.map +1 -0
  5. package/dist/cli/args.js +248 -0
  6. package/dist/cli/args.js.map +1 -0
  7. package/dist/cli/file-processor.d.ts +15 -0
  8. package/dist/cli/file-processor.d.ts.map +1 -0
  9. package/dist/cli/file-processor.js +79 -0
  10. package/dist/cli/file-processor.js.map +1 -0
  11. package/dist/cli/list-models.d.ts +9 -0
  12. package/dist/cli/list-models.d.ts.map +1 -0
  13. package/dist/cli/list-models.js +92 -0
  14. package/dist/cli/list-models.js.map +1 -0
  15. package/dist/cli/session-picker.d.ts +9 -0
  16. package/dist/cli/session-picker.d.ts.map +1 -0
  17. package/dist/cli/session-picker.js +32 -0
  18. package/dist/cli/session-picker.js.map +1 -0
  19. package/dist/cli.d.ts +3 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +10 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/config.d.ts +61 -0
  24. package/dist/config.d.ts.map +1 -0
  25. package/dist/config.js +141 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/core/agent-session.d.ts +523 -0
  28. package/dist/core/agent-session.d.ts.map +1 -0
  29. package/dist/core/agent-session.js +1795 -0
  30. package/dist/core/agent-session.js.map +1 -0
  31. package/dist/core/auth-storage.d.ts +112 -0
  32. package/dist/core/auth-storage.d.ts.map +1 -0
  33. package/dist/core/auth-storage.js +297 -0
  34. package/dist/core/auth-storage.js.map +1 -0
  35. package/dist/core/bash-executor.d.ts +47 -0
  36. package/dist/core/bash-executor.d.ts.map +1 -0
  37. package/dist/core/bash-executor.js +211 -0
  38. package/dist/core/bash-executor.js.map +1 -0
  39. package/dist/core/compaction/branch-summarization.d.ts +84 -0
  40. package/dist/core/compaction/branch-summarization.d.ts.map +1 -0
  41. package/dist/core/compaction/branch-summarization.js +235 -0
  42. package/dist/core/compaction/branch-summarization.js.map +1 -0
  43. package/dist/core/compaction/compaction.d.ts +110 -0
  44. package/dist/core/compaction/compaction.d.ts.map +1 -0
  45. package/dist/core/compaction/compaction.js +559 -0
  46. package/dist/core/compaction/compaction.js.map +1 -0
  47. package/dist/core/compaction/index.d.ts +7 -0
  48. package/dist/core/compaction/index.d.ts.map +1 -0
  49. package/dist/core/compaction/index.js +7 -0
  50. package/dist/core/compaction/index.js.map +1 -0
  51. package/dist/core/compaction/utils.d.ts +35 -0
  52. package/dist/core/compaction/utils.d.ts.map +1 -0
  53. package/dist/core/compaction/utils.js +138 -0
  54. package/dist/core/compaction/utils.js.map +1 -0
  55. package/dist/core/event-bus.d.ts +9 -0
  56. package/dist/core/event-bus.d.ts.map +1 -0
  57. package/dist/core/event-bus.js +25 -0
  58. package/dist/core/event-bus.js.map +1 -0
  59. package/dist/core/exec.d.ts +29 -0
  60. package/dist/core/exec.d.ts.map +1 -0
  61. package/dist/core/exec.js +71 -0
  62. package/dist/core/exec.js.map +1 -0
  63. package/dist/core/export-html/index.d.ts +17 -0
  64. package/dist/core/export-html/index.d.ts.map +1 -0
  65. package/dist/core/export-html/index.js +193 -0
  66. package/dist/core/export-html/index.js.map +1 -0
  67. package/dist/core/export-html/template.css +910 -0
  68. package/dist/core/export-html/template.html +54 -0
  69. package/dist/core/export-html/template.js +1329 -0
  70. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  71. package/dist/core/export-html/vendor/marked.min.js +6 -0
  72. package/dist/core/extensions/index.d.ts +10 -0
  73. package/dist/core/extensions/index.d.ts.map +1 -0
  74. package/dist/core/extensions/index.js +9 -0
  75. package/dist/core/extensions/index.js.map +1 -0
  76. package/dist/core/extensions/loader.d.ts +25 -0
  77. package/dist/core/extensions/loader.d.ts.map +1 -0
  78. package/dist/core/extensions/loader.js +383 -0
  79. package/dist/core/extensions/loader.js.map +1 -0
  80. package/dist/core/extensions/runner.d.ts +89 -0
  81. package/dist/core/extensions/runner.d.ts.map +1 -0
  82. package/dist/core/extensions/runner.js +406 -0
  83. package/dist/core/extensions/runner.js.map +1 -0
  84. package/dist/core/extensions/types.d.ts +654 -0
  85. package/dist/core/extensions/types.d.ts.map +1 -0
  86. package/dist/core/extensions/types.js +32 -0
  87. package/dist/core/extensions/types.js.map +1 -0
  88. package/dist/core/extensions/wrapper.d.ts +27 -0
  89. package/dist/core/extensions/wrapper.d.ts.map +1 -0
  90. package/dist/core/extensions/wrapper.js +102 -0
  91. package/dist/core/extensions/wrapper.js.map +1 -0
  92. package/dist/core/footer-data-provider.d.ts +25 -0
  93. package/dist/core/footer-data-provider.d.ts.map +1 -0
  94. package/dist/core/footer-data-provider.js +121 -0
  95. package/dist/core/footer-data-provider.js.map +1 -0
  96. package/dist/core/index.d.ts +9 -0
  97. package/dist/core/index.d.ts.map +1 -0
  98. package/dist/core/index.js +9 -0
  99. package/dist/core/index.js.map +1 -0
  100. package/dist/core/keybindings.d.ts +59 -0
  101. package/dist/core/keybindings.d.ts.map +1 -0
  102. package/dist/core/keybindings.js +151 -0
  103. package/dist/core/keybindings.js.map +1 -0
  104. package/dist/core/messages.d.ts +77 -0
  105. package/dist/core/messages.d.ts.map +1 -0
  106. package/dist/core/messages.js +123 -0
  107. package/dist/core/messages.js.map +1 -0
  108. package/dist/core/model-registry.d.ts +57 -0
  109. package/dist/core/model-registry.d.ts.map +1 -0
  110. package/dist/core/model-registry.js +314 -0
  111. package/dist/core/model-registry.js.map +1 -0
  112. package/dist/core/model-resolver.d.ts +76 -0
  113. package/dist/core/model-resolver.d.ts.map +1 -0
  114. package/dist/core/model-resolver.js +308 -0
  115. package/dist/core/model-resolver.js.map +1 -0
  116. package/dist/core/prompt-templates.d.ts +40 -0
  117. package/dist/core/prompt-templates.d.ts.map +1 -0
  118. package/dist/core/prompt-templates.js +197 -0
  119. package/dist/core/prompt-templates.js.map +1 -0
  120. package/dist/core/sdk.d.ts +181 -0
  121. package/dist/core/sdk.d.ts.map +1 -0
  122. package/dist/core/sdk.js +466 -0
  123. package/dist/core/sdk.js.map +1 -0
  124. package/dist/core/session-manager.d.ts +313 -0
  125. package/dist/core/session-manager.d.ts.map +1 -0
  126. package/dist/core/session-manager.js +996 -0
  127. package/dist/core/session-manager.js.map +1 -0
  128. package/dist/core/settings-manager.d.ts +138 -0
  129. package/dist/core/settings-manager.d.ts.map +1 -0
  130. package/dist/core/settings-manager.js +327 -0
  131. package/dist/core/settings-manager.js.map +1 -0
  132. package/dist/core/skills.d.ts +50 -0
  133. package/dist/core/skills.d.ts.map +1 -0
  134. package/dist/core/skills.js +338 -0
  135. package/dist/core/skills.js.map +1 -0
  136. package/dist/core/system-prompt.d.ts +48 -0
  137. package/dist/core/system-prompt.d.ts.map +1 -0
  138. package/dist/core/system-prompt.js +224 -0
  139. package/dist/core/system-prompt.js.map +1 -0
  140. package/dist/core/timings.d.ts +7 -0
  141. package/dist/core/timings.d.ts.map +1 -0
  142. package/dist/core/timings.js +25 -0
  143. package/dist/core/timings.js.map +1 -0
  144. package/dist/core/tools/bash.d.ts +42 -0
  145. package/dist/core/tools/bash.d.ts.map +1 -0
  146. package/dist/core/tools/bash.js +223 -0
  147. package/dist/core/tools/bash.js.map +1 -0
  148. package/dist/core/tools/edit-diff.d.ts +33 -0
  149. package/dist/core/tools/edit-diff.d.ts.map +1 -0
  150. package/dist/core/tools/edit-diff.js +171 -0
  151. package/dist/core/tools/edit-diff.js.map +1 -0
  152. package/dist/core/tools/edit.d.ts +37 -0
  153. package/dist/core/tools/edit.d.ts.map +1 -0
  154. package/dist/core/tools/edit.js +143 -0
  155. package/dist/core/tools/edit.js.map +1 -0
  156. package/dist/core/tools/find.d.ts +37 -0
  157. package/dist/core/tools/find.d.ts.map +1 -0
  158. package/dist/core/tools/find.js +206 -0
  159. package/dist/core/tools/find.js.map +1 -0
  160. package/dist/core/tools/grep.d.ts +43 -0
  161. package/dist/core/tools/grep.d.ts.map +1 -0
  162. package/dist/core/tools/grep.js +239 -0
  163. package/dist/core/tools/grep.js.map +1 -0
  164. package/dist/core/tools/index.d.ts +70 -0
  165. package/dist/core/tools/index.d.ts.map +1 -0
  166. package/dist/core/tools/index.js +56 -0
  167. package/dist/core/tools/index.js.map +1 -0
  168. package/dist/core/tools/ls.d.ts +38 -0
  169. package/dist/core/tools/ls.d.ts.map +1 -0
  170. package/dist/core/tools/ls.js +118 -0
  171. package/dist/core/tools/ls.js.map +1 -0
  172. package/dist/core/tools/path-utils.d.ts +8 -0
  173. package/dist/core/tools/path-utils.d.ts.map +1 -0
  174. package/dist/core/tools/path-utils.js +53 -0
  175. package/dist/core/tools/path-utils.js.map +1 -0
  176. package/dist/core/tools/read.d.ts +37 -0
  177. package/dist/core/tools/read.d.ts.map +1 -0
  178. package/dist/core/tools/read.js +165 -0
  179. package/dist/core/tools/read.js.map +1 -0
  180. package/dist/core/tools/truncate.d.ts +70 -0
  181. package/dist/core/tools/truncate.d.ts.map +1 -0
  182. package/dist/core/tools/truncate.js +205 -0
  183. package/dist/core/tools/truncate.js.map +1 -0
  184. package/dist/core/tools/write.d.ts +27 -0
  185. package/dist/core/tools/write.d.ts.map +1 -0
  186. package/dist/core/tools/write.js +78 -0
  187. package/dist/core/tools/write.js.map +1 -0
  188. package/dist/index.d.ts +19 -0
  189. package/dist/index.d.ts.map +1 -0
  190. package/dist/index.js +35 -0
  191. package/dist/index.js.map +1 -0
  192. package/dist/main.d.ts +8 -0
  193. package/dist/main.d.ts.map +1 -0
  194. package/dist/main.js +354 -0
  195. package/dist/main.js.map +1 -0
  196. package/dist/migrations.d.ts +33 -0
  197. package/dist/migrations.d.ts.map +1 -0
  198. package/dist/migrations.js +261 -0
  199. package/dist/migrations.js.map +1 -0
  200. package/dist/modes/index.d.ts +9 -0
  201. package/dist/modes/index.d.ts.map +1 -0
  202. package/dist/modes/index.js +8 -0
  203. package/dist/modes/index.js.map +1 -0
  204. package/dist/modes/interactive/components/armin.d.ts +34 -0
  205. package/dist/modes/interactive/components/armin.d.ts.map +1 -0
  206. package/dist/modes/interactive/components/armin.js +333 -0
  207. package/dist/modes/interactive/components/armin.js.map +1 -0
  208. package/dist/modes/interactive/components/assistant-message.d.ts +15 -0
  209. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -0
  210. package/dist/modes/interactive/components/assistant-message.js +89 -0
  211. package/dist/modes/interactive/components/assistant-message.js.map +1 -0
  212. package/dist/modes/interactive/components/bash-execution.d.ts +35 -0
  213. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -0
  214. package/dist/modes/interactive/components/bash-execution.js +161 -0
  215. package/dist/modes/interactive/components/bash-execution.js.map +1 -0
  216. package/dist/modes/interactive/components/bordered-loader.d.ts +12 -0
  217. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -0
  218. package/dist/modes/interactive/components/bordered-loader.js +30 -0
  219. package/dist/modes/interactive/components/bordered-loader.js.map +1 -0
  220. package/dist/modes/interactive/components/branch-summary-message.d.ts +15 -0
  221. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -0
  222. package/dist/modes/interactive/components/branch-summary-message.js +39 -0
  223. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -0
  224. package/dist/modes/interactive/components/compaction-summary-message.d.ts +15 -0
  225. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -0
  226. package/dist/modes/interactive/components/compaction-summary-message.js +40 -0
  227. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -0
  228. package/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
  229. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -0
  230. package/dist/modes/interactive/components/countdown-timer.js +33 -0
  231. package/dist/modes/interactive/components/countdown-timer.js.map +1 -0
  232. package/dist/modes/interactive/components/custom-editor.d.ts +21 -0
  233. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -0
  234. package/dist/modes/interactive/components/custom-editor.js +69 -0
  235. package/dist/modes/interactive/components/custom-editor.js.map +1 -0
  236. package/dist/modes/interactive/components/custom-message.d.ts +19 -0
  237. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -0
  238. package/dist/modes/interactive/components/custom-message.js +84 -0
  239. package/dist/modes/interactive/components/custom-message.js.map +1 -0
  240. package/dist/modes/interactive/components/diff.d.ts +12 -0
  241. package/dist/modes/interactive/components/diff.d.ts.map +1 -0
  242. package/dist/modes/interactive/components/diff.js +133 -0
  243. package/dist/modes/interactive/components/diff.js.map +1 -0
  244. package/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
  245. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -0
  246. package/dist/modes/interactive/components/dynamic-border.js +21 -0
  247. package/dist/modes/interactive/components/dynamic-border.js.map +1 -0
  248. package/dist/modes/interactive/components/extension-editor.d.ts +15 -0
  249. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -0
  250. package/dist/modes/interactive/components/extension-editor.js +96 -0
  251. package/dist/modes/interactive/components/extension-editor.js.map +1 -0
  252. package/dist/modes/interactive/components/extension-input.d.ts +20 -0
  253. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -0
  254. package/dist/modes/interactive/components/extension-input.js +51 -0
  255. package/dist/modes/interactive/components/extension-input.js.map +1 -0
  256. package/dist/modes/interactive/components/extension-selector.d.ts +24 -0
  257. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -0
  258. package/dist/modes/interactive/components/extension-selector.js +73 -0
  259. package/dist/modes/interactive/components/extension-selector.js.map +1 -0
  260. package/dist/modes/interactive/components/footer.d.ts +26 -0
  261. package/dist/modes/interactive/components/footer.d.ts.map +1 -0
  262. package/dist/modes/interactive/components/footer.js +207 -0
  263. package/dist/modes/interactive/components/footer.js.map +1 -0
  264. package/dist/modes/interactive/components/index.d.ts +29 -0
  265. package/dist/modes/interactive/components/index.d.ts.map +1 -0
  266. package/dist/modes/interactive/components/index.js +30 -0
  267. package/dist/modes/interactive/components/index.js.map +1 -0
  268. package/dist/modes/interactive/components/login-dialog.d.ts +39 -0
  269. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -0
  270. package/dist/modes/interactive/components/login-dialog.js +135 -0
  271. package/dist/modes/interactive/components/login-dialog.js.map +1 -0
  272. package/dist/modes/interactive/components/model-selector.d.ts +35 -0
  273. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -0
  274. package/dist/modes/interactive/components/model-selector.js +211 -0
  275. package/dist/modes/interactive/components/model-selector.js.map +1 -0
  276. package/dist/modes/interactive/components/oauth-selector.d.ts +19 -0
  277. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -0
  278. package/dist/modes/interactive/components/oauth-selector.js +98 -0
  279. package/dist/modes/interactive/components/oauth-selector.js.map +1 -0
  280. package/dist/modes/interactive/components/scoped-models-selector.d.ts +46 -0
  281. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -0
  282. package/dist/modes/interactive/components/scoped-models-selector.js +258 -0
  283. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -0
  284. package/dist/modes/interactive/components/session-selector.d.ts +44 -0
  285. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -0
  286. package/dist/modes/interactive/components/session-selector.js +311 -0
  287. package/dist/modes/interactive/components/session-selector.js.map +1 -0
  288. package/dist/modes/interactive/components/settings-selector.d.ts +43 -0
  289. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -0
  290. package/dist/modes/interactive/components/settings-selector.js +219 -0
  291. package/dist/modes/interactive/components/settings-selector.js.map +1 -0
  292. package/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
  293. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -0
  294. package/dist/modes/interactive/components/show-images-selector.js +35 -0
  295. package/dist/modes/interactive/components/show-images-selector.js.map +1 -0
  296. package/dist/modes/interactive/components/theme-selector.d.ts +11 -0
  297. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -0
  298. package/dist/modes/interactive/components/theme-selector.js +46 -0
  299. package/dist/modes/interactive/components/theme-selector.js.map +1 -0
  300. package/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
  301. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -0
  302. package/dist/modes/interactive/components/thinking-selector.js +47 -0
  303. package/dist/modes/interactive/components/thinking-selector.js.map +1 -0
  304. package/dist/modes/interactive/components/tool-execution.d.ts +70 -0
  305. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -0
  306. package/dist/modes/interactive/components/tool-execution.js +606 -0
  307. package/dist/modes/interactive/components/tool-execution.js.map +1 -0
  308. package/dist/modes/interactive/components/tree-selector.d.ts +52 -0
  309. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -0
  310. package/dist/modes/interactive/components/tree-selector.js +745 -0
  311. package/dist/modes/interactive/components/tree-selector.js.map +1 -0
  312. package/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
  313. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -0
  314. package/dist/modes/interactive/components/user-message-selector.js +113 -0
  315. package/dist/modes/interactive/components/user-message-selector.js.map +1 -0
  316. package/dist/modes/interactive/components/user-message.d.ts +8 -0
  317. package/dist/modes/interactive/components/user-message.d.ts.map +1 -0
  318. package/dist/modes/interactive/components/user-message.js +16 -0
  319. package/dist/modes/interactive/components/user-message.js.map +1 -0
  320. package/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
  321. package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -0
  322. package/dist/modes/interactive/components/visual-truncate.js +33 -0
  323. package/dist/modes/interactive/components/visual-truncate.js.map +1 -0
  324. package/dist/modes/interactive/interactive-mode.d.ts +261 -0
  325. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -0
  326. package/dist/modes/interactive/interactive-mode.js +3194 -0
  327. package/dist/modes/interactive/interactive-mode.js.map +1 -0
  328. package/dist/modes/interactive/theme/dark.json +85 -0
  329. package/dist/modes/interactive/theme/light.json +84 -0
  330. package/dist/modes/interactive/theme/theme-schema.json +308 -0
  331. package/dist/modes/interactive/theme/theme.d.ts +71 -0
  332. package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  333. package/dist/modes/interactive/theme/theme.js +893 -0
  334. package/dist/modes/interactive/theme/theme.js.map +1 -0
  335. package/dist/modes/print-mode.d.ts +28 -0
  336. package/dist/modes/print-mode.d.ts.map +1 -0
  337. package/dist/modes/print-mode.js +140 -0
  338. package/dist/modes/print-mode.js.map +1 -0
  339. package/dist/modes/rpc/rpc-client.d.ts +209 -0
  340. package/dist/modes/rpc/rpc-client.d.ts.map +1 -0
  341. package/dist/modes/rpc/rpc-client.js +392 -0
  342. package/dist/modes/rpc/rpc-client.js.map +1 -0
  343. package/dist/modes/rpc/rpc-mode.d.ts +20 -0
  344. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
  345. package/dist/modes/rpc/rpc-mode.js +486 -0
  346. package/dist/modes/rpc/rpc-mode.js.map +1 -0
  347. package/dist/modes/rpc/rpc-types.d.ts +372 -0
  348. package/dist/modes/rpc/rpc-types.d.ts.map +1 -0
  349. package/dist/modes/rpc/rpc-types.js +8 -0
  350. package/dist/modes/rpc/rpc-types.js.map +1 -0
  351. package/dist/utils/changelog.d.ts +21 -0
  352. package/dist/utils/changelog.d.ts.map +1 -0
  353. package/dist/utils/changelog.js +87 -0
  354. package/dist/utils/changelog.js.map +1 -0
  355. package/dist/utils/clipboard-image.d.ts +11 -0
  356. package/dist/utils/clipboard-image.d.ts.map +1 -0
  357. package/dist/utils/clipboard-image.js +129 -0
  358. package/dist/utils/clipboard-image.js.map +1 -0
  359. package/dist/utils/clipboard.d.ts +2 -0
  360. package/dist/utils/clipboard.d.ts.map +1 -0
  361. package/dist/utils/clipboard.js +73 -0
  362. package/dist/utils/clipboard.js.map +1 -0
  363. package/dist/utils/image-convert.d.ts +9 -0
  364. package/dist/utils/image-convert.d.ts.map +1 -0
  365. package/dist/utils/image-convert.js +31 -0
  366. package/dist/utils/image-convert.js.map +1 -0
  367. package/dist/utils/image-resize.d.ts +36 -0
  368. package/dist/utils/image-resize.d.ts.map +1 -0
  369. package/dist/utils/image-resize.js +188 -0
  370. package/dist/utils/image-resize.js.map +1 -0
  371. package/dist/utils/mime.d.ts +2 -0
  372. package/dist/utils/mime.d.ts.map +1 -0
  373. package/dist/utils/mime.js +26 -0
  374. package/dist/utils/mime.js.map +1 -0
  375. package/dist/utils/shell.d.ts +26 -0
  376. package/dist/utils/shell.d.ts.map +1 -0
  377. package/dist/utils/shell.js +151 -0
  378. package/dist/utils/shell.js.map +1 -0
  379. package/dist/utils/tools-manager.d.ts +3 -0
  380. package/dist/utils/tools-manager.d.ts.map +1 -0
  381. package/dist/utils/tools-manager.js +187 -0
  382. package/dist/utils/tools-manager.js.map +1 -0
  383. package/dist/utils/vips.d.ts +11 -0
  384. package/dist/utils/vips.d.ts.map +1 -0
  385. package/dist/utils/vips.js +35 -0
  386. package/dist/utils/vips.js.map +1 -0
  387. package/docs/compaction.md +388 -0
  388. package/docs/extensions.md +1524 -0
  389. package/docs/rpc.md +1046 -0
  390. package/docs/sdk.md +1024 -0
  391. package/docs/session.md +255 -0
  392. package/docs/skills.md +317 -0
  393. package/docs/theme.md +617 -0
  394. package/docs/tree.md +201 -0
  395. package/docs/tui.md +797 -0
  396. package/examples/README.md +24 -0
  397. package/examples/extensions/README.md +168 -0
  398. package/examples/extensions/auto-commit-on-exit.ts +49 -0
  399. package/examples/extensions/chalk-logger.ts +26 -0
  400. package/examples/extensions/claude-rules.ts +86 -0
  401. package/examples/extensions/confirm-destructive.ts +59 -0
  402. package/examples/extensions/custom-compaction.ts +114 -0
  403. package/examples/extensions/custom-footer.ts +64 -0
  404. package/examples/extensions/custom-header.ts +72 -0
  405. package/examples/extensions/dirty-repo-guard.ts +56 -0
  406. package/examples/extensions/doom-overlay/README.md +46 -0
  407. package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  408. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  409. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  410. package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  411. package/examples/extensions/doom-overlay/doom-component.ts +132 -0
  412. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  413. package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  414. package/examples/extensions/doom-overlay/index.ts +74 -0
  415. package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  416. package/examples/extensions/file-trigger.ts +41 -0
  417. package/examples/extensions/git-checkpoint.ts +53 -0
  418. package/examples/extensions/handoff.ts +150 -0
  419. package/examples/extensions/hello.ts +25 -0
  420. package/examples/extensions/interactive-shell.ts +196 -0
  421. package/examples/extensions/mac-system-theme.ts +47 -0
  422. package/examples/extensions/modal-editor.ts +85 -0
  423. package/examples/extensions/model-status.ts +31 -0
  424. package/examples/extensions/notify.ts +25 -0
  425. package/examples/extensions/overlay-qa-tests.ts +881 -0
  426. package/examples/extensions/overlay-test.ts +145 -0
  427. package/examples/extensions/permission-gate.ts +34 -0
  428. package/examples/extensions/pirate.ts +47 -0
  429. package/examples/extensions/plan-mode/README.md +65 -0
  430. package/examples/extensions/plan-mode/index.ts +340 -0
  431. package/examples/extensions/plan-mode/utils.ts +168 -0
  432. package/examples/extensions/preset.ts +398 -0
  433. package/examples/extensions/protected-paths.ts +30 -0
  434. package/examples/extensions/qna.ts +119 -0
  435. package/examples/extensions/question.ts +277 -0
  436. package/examples/extensions/questionnaire.ts +427 -0
  437. package/examples/extensions/rainbow-editor.ts +95 -0
  438. package/examples/extensions/sandbox/index.ts +318 -0
  439. package/examples/extensions/sandbox/package-lock.json +92 -0
  440. package/examples/extensions/sandbox/package.json +19 -0
  441. package/examples/extensions/send-user-message.ts +97 -0
  442. package/examples/extensions/shutdown-command.ts +63 -0
  443. package/examples/extensions/snake.ts +343 -0
  444. package/examples/extensions/ssh.ts +220 -0
  445. package/examples/extensions/status-line.ts +40 -0
  446. package/examples/extensions/subagent/README.md +172 -0
  447. package/examples/extensions/subagent/agents/planner.md +37 -0
  448. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  449. package/examples/extensions/subagent/agents/scout.md +50 -0
  450. package/examples/extensions/subagent/agents/worker.md +24 -0
  451. package/examples/extensions/subagent/agents.ts +156 -0
  452. package/examples/extensions/subagent/index.ts +963 -0
  453. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  454. package/examples/extensions/subagent/prompts/implement.md +10 -0
  455. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  456. package/examples/extensions/summarize.ts +195 -0
  457. package/examples/extensions/timed-confirm.ts +70 -0
  458. package/examples/extensions/todo.ts +299 -0
  459. package/examples/extensions/tool-override.ts +143 -0
  460. package/examples/extensions/tools.ts +146 -0
  461. package/examples/extensions/truncated-tool.ts +192 -0
  462. package/examples/extensions/with-deps/index.ts +36 -0
  463. package/examples/extensions/with-deps/package-lock.json +31 -0
  464. package/examples/extensions/with-deps/package.json +22 -0
  465. package/examples/sdk/01-minimal.ts +22 -0
  466. package/examples/sdk/02-custom-model.ts +49 -0
  467. package/examples/sdk/03-custom-prompt.ts +44 -0
  468. package/examples/sdk/04-skills.ts +47 -0
  469. package/examples/sdk/05-tools.ts +56 -0
  470. package/examples/sdk/06-extensions.ts +79 -0
  471. package/examples/sdk/07-context-files.ts +36 -0
  472. package/examples/sdk/08-prompt-templates.ts +42 -0
  473. package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
  474. package/examples/sdk/10-settings.ts +38 -0
  475. package/examples/sdk/11-sessions.ts +48 -0
  476. package/examples/sdk/12-full-control.ts +72 -0
  477. package/examples/sdk/README.md +150 -0
  478. package/package.json +88 -0
@@ -0,0 +1,129 @@
1
+ import { spawnSync } from "child_process";
2
+ // Optional import - @mariozechner/clipboard may not be available on all platforms (e.g., Termux)
3
+ let Clipboard = null;
4
+ try {
5
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
6
+ Clipboard = require("@mariozechner/clipboard");
7
+ }
8
+ catch {
9
+ // Clipboard not available - will fall back to platform-specific commands
10
+ }
11
+ const PREFERRED_IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
12
+ const DEFAULT_LIST_TIMEOUT_MS = 1000;
13
+ const DEFAULT_READ_TIMEOUT_MS = 3000;
14
+ const DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
15
+ export function isWaylandSession(env = process.env) {
16
+ return Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === "wayland";
17
+ }
18
+ function baseMimeType(mimeType) {
19
+ return mimeType.split(";")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();
20
+ }
21
+ export function extensionForImageMimeType(mimeType) {
22
+ switch (baseMimeType(mimeType)) {
23
+ case "image/png":
24
+ return "png";
25
+ case "image/jpeg":
26
+ return "jpg";
27
+ case "image/webp":
28
+ return "webp";
29
+ case "image/gif":
30
+ return "gif";
31
+ default:
32
+ return null;
33
+ }
34
+ }
35
+ function selectPreferredImageMimeType(mimeTypes) {
36
+ const normalized = mimeTypes
37
+ .map((t) => t.trim())
38
+ .filter(Boolean)
39
+ .map((t) => ({ raw: t, base: baseMimeType(t) }));
40
+ for (const preferred of PREFERRED_IMAGE_MIME_TYPES) {
41
+ const match = normalized.find((t) => t.base === preferred);
42
+ if (match) {
43
+ return match.raw;
44
+ }
45
+ }
46
+ const anyImage = normalized.find((t) => t.base.startsWith("image/"));
47
+ return anyImage?.raw ?? null;
48
+ }
49
+ function runCommand(command, args, options) {
50
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;
51
+ const maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;
52
+ const result = spawnSync(command, args, {
53
+ timeout: timeoutMs,
54
+ maxBuffer: maxBufferBytes,
55
+ });
56
+ if (result.error) {
57
+ return { ok: false, stdout: Buffer.alloc(0) };
58
+ }
59
+ if (result.status !== 0) {
60
+ return { ok: false, stdout: Buffer.alloc(0) };
61
+ }
62
+ const stdout = Buffer.isBuffer(result.stdout)
63
+ ? result.stdout
64
+ : Buffer.from(result.stdout ?? "", typeof result.stdout === "string" ? "utf-8" : undefined);
65
+ return { ok: true, stdout };
66
+ }
67
+ function readClipboardImageViaWlPaste() {
68
+ const list = runCommand("wl-paste", ["--list-types"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });
69
+ if (!list.ok) {
70
+ return null;
71
+ }
72
+ const types = list.stdout
73
+ .toString("utf-8")
74
+ .split(/\r?\n/)
75
+ .map((t) => t.trim())
76
+ .filter(Boolean);
77
+ const selectedType = selectPreferredImageMimeType(types);
78
+ if (!selectedType) {
79
+ return null;
80
+ }
81
+ const data = runCommand("wl-paste", ["--type", selectedType, "--no-newline"]);
82
+ if (!data.ok || data.stdout.length === 0) {
83
+ return null;
84
+ }
85
+ return { bytes: data.stdout, mimeType: baseMimeType(selectedType) };
86
+ }
87
+ function readClipboardImageViaXclip() {
88
+ const targets = runCommand("xclip", ["-selection", "clipboard", "-t", "TARGETS", "-o"], {
89
+ timeoutMs: DEFAULT_LIST_TIMEOUT_MS,
90
+ });
91
+ let candidateTypes = [];
92
+ if (targets.ok) {
93
+ candidateTypes = targets.stdout
94
+ .toString("utf-8")
95
+ .split(/\r?\n/)
96
+ .map((t) => t.trim())
97
+ .filter(Boolean);
98
+ }
99
+ const preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;
100
+ const tryTypes = preferred ? [preferred, ...PREFERRED_IMAGE_MIME_TYPES] : [...PREFERRED_IMAGE_MIME_TYPES];
101
+ for (const mimeType of tryTypes) {
102
+ const data = runCommand("xclip", ["-selection", "clipboard", "-t", mimeType, "-o"]);
103
+ if (data.ok && data.stdout.length > 0) {
104
+ return { bytes: data.stdout, mimeType: baseMimeType(mimeType) };
105
+ }
106
+ }
107
+ return null;
108
+ }
109
+ export async function readClipboardImage(options) {
110
+ const env = options?.env ?? process.env;
111
+ const platform = options?.platform ?? process.platform;
112
+ // Termux: termux-clipboard-get only handles text, not images
113
+ if (env.TERMUX_VERSION) {
114
+ return null;
115
+ }
116
+ if (platform === "linux" && isWaylandSession(env)) {
117
+ return readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();
118
+ }
119
+ if (!Clipboard || !Clipboard.hasImage()) {
120
+ return null;
121
+ }
122
+ const imageData = await Clipboard.getImageBinary();
123
+ if (!imageData || imageData.length === 0) {
124
+ return null;
125
+ }
126
+ const bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);
127
+ return { bytes, mimeType: "image/png" };
128
+ }
129
+ //# sourceMappingURL=clipboard-image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard-image.js","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,iGAAiG;AACjG,IAAI,SAAS,GAAQ,IAAI,CAAC;AAC1B,IAAI,CAAC;IACJ,iEAAiE;IACjE,SAAS,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;AAChD,CAAC;AAAC,MAAM,CAAC;IACR,yEAAyE;AAC1E,CAAC;AAOD,MAAM,0BAA0B,GAAG,CAAC,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,CAAU,CAAC;AAEnG,MAAM,uBAAuB,GAAG,IAAI,CAAC;AACrC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AACrC,MAAM,wBAAwB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD,MAAM,UAAU,gBAAgB,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG,EAAW;IAC/E,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,GAAG,CAAC,gBAAgB,KAAK,SAAS,CAAC;AAAA,CAC1E;AAED,SAAS,YAAY,CAAC,QAAgB,EAAU;IAC/C,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;AAAA,CAC9E;AAED,MAAM,UAAU,yBAAyB,CAAC,QAAgB,EAAiB;IAC1E,QAAQ,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChC,KAAK,WAAW;YACf,OAAO,KAAK,CAAC;QACd,KAAK,YAAY;YAChB,OAAO,KAAK,CAAC;QACd,KAAK,YAAY;YAChB,OAAO,MAAM,CAAC;QACf,KAAK,WAAW;YACf,OAAO,KAAK,CAAC;QACd;YACC,OAAO,IAAI,CAAC;IACd,CAAC;AAAA,CACD;AAED,SAAS,4BAA4B,CAAC,SAAmB,EAAiB;IACzE,MAAM,UAAU,GAAG,SAAS;SAC1B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAElD,KAAK,MAAM,SAAS,IAAI,0BAA0B,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;QAC3D,IAAI,KAAK,EAAE,CAAC;YACX,OAAO,KAAK,CAAC,GAAG,CAAC;QAClB,CAAC;IACF,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrE,OAAO,QAAQ,EAAE,GAAG,IAAI,IAAI,CAAC;AAAA,CAC7B;AAED,SAAS,UAAU,CAClB,OAAe,EACf,IAAc,EACd,OAAyD,EACvB;IAClC,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,uBAAuB,CAAC;IAChE,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,wBAAwB,CAAC;IAE3E,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE;QACvC,OAAO,EAAE,SAAS;QAClB,SAAS,EAAE,cAAc;KACzB,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/C,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/C,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;QAC5C,CAAC,CAAC,MAAM,CAAC,MAAM;QACf,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAE7F,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAAA,CAC5B;AAED,SAAS,4BAA4B,GAA0B;IAC9D,MAAM,IAAI,GAAG,UAAU,CAAC,UAAU,EAAE,CAAC,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,uBAAuB,EAAE,CAAC,CAAC;IAC9F,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM;SACvB,QAAQ,CAAC,OAAO,CAAC;SACjB,KAAK,CAAC,OAAO,CAAC;SACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;IAElB,MAAM,YAAY,GAAG,4BAA4B,CAAC,KAAK,CAAC,CAAC;IACzD,IAAI,CAAC,YAAY,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC,CAAC;IAC9E,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC;IACb,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,YAAY,CAAC,YAAY,CAAC,EAAE,CAAC;AAAA,CACpE;AAED,SAAS,0BAA0B,GAA0B;IAC5D,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE;QACvF,SAAS,EAAE,uBAAuB;KAClC,CAAC,CAAC;IAEH,IAAI,cAAc,GAAa,EAAE,CAAC;IAClC,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QAChB,cAAc,GAAG,OAAO,CAAC,MAAM;aAC7B,QAAQ,CAAC,OAAO,CAAC;aACjB,KAAK,CAAC,OAAO,CAAC;aACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;IAED,MAAM,SAAS,GAAG,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,4BAA4B,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAClG,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,GAAG,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,0BAA0B,CAAC,CAAC;IAE1G,KAAK,MAAM,QAAQ,IAAI,QAAQ,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;QACpF,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjE,CAAC;IACF,CAAC;IAED,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAGxC,EAAkC;IAClC,MAAM,GAAG,GAAG,OAAO,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACxC,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;IAEvD,6DAA6D;IAC7D,IAAI,GAAG,CAAC,cAAc,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,QAAQ,KAAK,OAAO,IAAI,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;QACnD,OAAO,4BAA4B,EAAE,IAAI,0BAA0B,EAAE,CAAC;IACvE,CAAC;IAED,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,CAAC;QACzC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,cAAc,EAAE,CAAC;IACnD,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,KAAK,GAAG,SAAS,YAAY,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACvF,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AAAA,CACxC","sourcesContent":["import { spawnSync } from \"child_process\";\n\n// Optional import - @mariozechner/clipboard may not be available on all platforms (e.g., Termux)\nlet Clipboard: any = null;\ntry {\n\t// eslint-disable-next-line @typescript-eslint/no-require-imports\n\tClipboard = require(\"@mariozechner/clipboard\");\n} catch {\n\t// Clipboard not available - will fall back to platform-specific commands\n}\n\nexport type ClipboardImage = {\n\tbytes: Uint8Array;\n\tmimeType: string;\n};\n\nconst PREFERRED_IMAGE_MIME_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"] as const;\n\nconst DEFAULT_LIST_TIMEOUT_MS = 1000;\nconst DEFAULT_READ_TIMEOUT_MS = 3000;\nconst DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;\n\nexport function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === \"wayland\";\n}\n\nfunction baseMimeType(mimeType: string): string {\n\treturn mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n}\n\nexport function extensionForImageMimeType(mimeType: string): string | null {\n\tswitch (baseMimeType(mimeType)) {\n\t\tcase \"image/png\":\n\t\t\treturn \"png\";\n\t\tcase \"image/jpeg\":\n\t\t\treturn \"jpg\";\n\t\tcase \"image/webp\":\n\t\t\treturn \"webp\";\n\t\tcase \"image/gif\":\n\t\t\treturn \"gif\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction selectPreferredImageMimeType(mimeTypes: string[]): string | null {\n\tconst normalized = mimeTypes\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean)\n\t\t.map((t) => ({ raw: t, base: baseMimeType(t) }));\n\n\tfor (const preferred of PREFERRED_IMAGE_MIME_TYPES) {\n\t\tconst match = normalized.find((t) => t.base === preferred);\n\t\tif (match) {\n\t\t\treturn match.raw;\n\t\t}\n\t}\n\n\tconst anyImage = normalized.find((t) => t.base.startsWith(\"image/\"));\n\treturn anyImage?.raw ?? null;\n}\n\nfunction runCommand(\n\tcommand: string,\n\targs: string[],\n\toptions?: { timeoutMs?: number; maxBufferBytes?: number },\n): { stdout: Buffer; ok: boolean } {\n\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;\n\tconst maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;\n\n\tconst result = spawnSync(command, args, {\n\t\ttimeout: timeoutMs,\n\t\tmaxBuffer: maxBufferBytes,\n\t});\n\n\tif (result.error) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tif (result.status !== 0) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tconst stdout = Buffer.isBuffer(result.stdout)\n\t\t? result.stdout\n\t\t: Buffer.from(result.stdout ?? \"\", typeof result.stdout === \"string\" ? \"utf-8\" : undefined);\n\n\treturn { ok: true, stdout };\n}\n\nfunction readClipboardImageViaWlPaste(): ClipboardImage | null {\n\tconst list = runCommand(\"wl-paste\", [\"--list-types\"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\tif (!list.ok) {\n\t\treturn null;\n\t}\n\n\tconst types = list.stdout\n\t\t.toString(\"utf-8\")\n\t\t.split(/\\r?\\n/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\n\tconst selectedType = selectPreferredImageMimeType(types);\n\tif (!selectedType) {\n\t\treturn null;\n\t}\n\n\tconst data = runCommand(\"wl-paste\", [\"--type\", selectedType, \"--no-newline\"]);\n\tif (!data.ok || data.stdout.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn { bytes: data.stdout, mimeType: baseMimeType(selectedType) };\n}\n\nfunction readClipboardImageViaXclip(): ClipboardImage | null {\n\tconst targets = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"], {\n\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t});\n\n\tlet candidateTypes: string[] = [];\n\tif (targets.ok) {\n\t\tcandidateTypes = targets.stdout\n\t\t\t.toString(\"utf-8\")\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\tconst preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;\n\tconst tryTypes = preferred ? [preferred, ...PREFERRED_IMAGE_MIME_TYPES] : [...PREFERRED_IMAGE_MIME_TYPES];\n\n\tfor (const mimeType of tryTypes) {\n\t\tconst data = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", mimeType, \"-o\"]);\n\t\tif (data.ok && data.stdout.length > 0) {\n\t\t\treturn { bytes: data.stdout, mimeType: baseMimeType(mimeType) };\n\t\t}\n\t}\n\n\treturn null;\n}\n\nexport async function readClipboardImage(options?: {\n\tenv?: NodeJS.ProcessEnv;\n\tplatform?: NodeJS.Platform;\n}): Promise<ClipboardImage | null> {\n\tconst env = options?.env ?? process.env;\n\tconst platform = options?.platform ?? process.platform;\n\n\t// Termux: termux-clipboard-get only handles text, not images\n\tif (env.TERMUX_VERSION) {\n\t\treturn null;\n\t}\n\n\tif (platform === \"linux\" && isWaylandSession(env)) {\n\t\treturn readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();\n\t}\n\n\tif (!Clipboard || !Clipboard.hasImage()) {\n\t\treturn null;\n\t}\n\n\tconst imageData = await Clipboard.getImageBinary();\n\tif (!imageData || imageData.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);\n\treturn { bytes, mimeType: \"image/png\" };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export declare function copyToClipboard(text: string): void;
2
+ //# sourceMappingURL=clipboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"AAIA,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CA8DlD","sourcesContent":["import { execSync, spawn } from \"child_process\";\nimport { platform } from \"os\";\nimport { isWaylandSession } from \"./clipboard-image.js\";\n\nexport function copyToClipboard(text: string): void {\n\tconst p = platform();\n\tconst options = { input: text, timeout: 5000 };\n\n\ttry {\n\t\tif (p === \"darwin\") {\n\t\t\texecSync(\"pbcopy\", options);\n\t\t} else if (p === \"win32\") {\n\t\t\texecSync(\"clip\", options);\n\t\t} else {\n\t\t\t// Linux - try Termux, Wayland, or X11 clipboard tools\n\t\t\t// Try termux-clipboard-set first if TERMUX_VERSION is set\n\t\t\tif (process.env.TERMUX_VERSION) {\n\t\t\t\ttry {\n\t\t\t\t\texecSync(\"termux-clipboard-set\", options);\n\t\t\t\t\treturn;\n\t\t\t\t} catch {\n\t\t\t\t\tthrow new Error(\"Clipboard requires Termux:API (install with: pkg install termux-api)\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst isWayland = isWaylandSession();\n\t\t\tif (isWayland) {\n\t\t\t\ttry {\n\t\t\t\t\t// Verify wl-copy exists (spawn errors are async and won't be caught)\n\t\t\t\t\texecSync(\"which wl-copy\", { stdio: \"ignore\" });\n\t\t\t\t\t// wl-copy with execSync hangs due to fork behavior; use spawn instead\n\t\t\t\t\tconst proc = spawn(\"wl-copy\", [], { stdio: [\"pipe\", \"ignore\", \"ignore\"] });\n\t\t\t\t\tproc.stdin.on(\"error\", () => {\n\t\t\t\t\t\t// Ignore EPIPE errors if wl-copy exits early\n\t\t\t\t\t});\n\t\t\t\t\tproc.stdin.write(text);\n\t\t\t\t\tproc.stdin.end();\n\t\t\t\t\tproc.unref();\n\t\t\t\t} catch {\n\t\t\t\t\t// Fall back to xclip/xsel (works on XWayland)\n\t\t\t\t\ttry {\n\t\t\t\t\t\texecSync(\"xclip -selection clipboard\", options);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\texecSync(\"xsel --clipboard --input\", options);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\texecSync(\"xclip -selection clipboard\", options);\n\t\t\t\t} catch {\n\t\t\t\t\texecSync(\"xsel --clipboard --input\", options);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\t// Re-throw Termux error as-is\n\t\tif (msg.includes(\"Termux:API\")) {\n\t\t\tthrow error;\n\t\t}\n\t\tif (p === \"linux\") {\n\t\t\tconst tools = isWaylandSession() ? \"wl-copy, xclip, or xsel\" : \"xclip or xsel\";\n\t\t\tthrow new Error(`Failed to copy to clipboard. Install ${tools}: ${msg}`);\n\t\t}\n\t\tthrow new Error(`Failed to copy to clipboard: ${msg}`);\n\t}\n}\n"]}
@@ -0,0 +1,73 @@
1
+ import { execSync, spawn } from "child_process";
2
+ import { platform } from "os";
3
+ import { isWaylandSession } from "./clipboard-image.js";
4
+ export function copyToClipboard(text) {
5
+ const p = platform();
6
+ const options = { input: text, timeout: 5000 };
7
+ try {
8
+ if (p === "darwin") {
9
+ execSync("pbcopy", options);
10
+ }
11
+ else if (p === "win32") {
12
+ execSync("clip", options);
13
+ }
14
+ else {
15
+ // Linux - try Termux, Wayland, or X11 clipboard tools
16
+ // Try termux-clipboard-set first if TERMUX_VERSION is set
17
+ if (process.env.TERMUX_VERSION) {
18
+ try {
19
+ execSync("termux-clipboard-set", options);
20
+ return;
21
+ }
22
+ catch {
23
+ throw new Error("Clipboard requires Termux:API (install with: pkg install termux-api)");
24
+ }
25
+ }
26
+ const isWayland = isWaylandSession();
27
+ if (isWayland) {
28
+ try {
29
+ // Verify wl-copy exists (spawn errors are async and won't be caught)
30
+ execSync("which wl-copy", { stdio: "ignore" });
31
+ // wl-copy with execSync hangs due to fork behavior; use spawn instead
32
+ const proc = spawn("wl-copy", [], { stdio: ["pipe", "ignore", "ignore"] });
33
+ proc.stdin.on("error", () => {
34
+ // Ignore EPIPE errors if wl-copy exits early
35
+ });
36
+ proc.stdin.write(text);
37
+ proc.stdin.end();
38
+ proc.unref();
39
+ }
40
+ catch {
41
+ // Fall back to xclip/xsel (works on XWayland)
42
+ try {
43
+ execSync("xclip -selection clipboard", options);
44
+ }
45
+ catch {
46
+ execSync("xsel --clipboard --input", options);
47
+ }
48
+ }
49
+ }
50
+ else {
51
+ try {
52
+ execSync("xclip -selection clipboard", options);
53
+ }
54
+ catch {
55
+ execSync("xsel --clipboard --input", options);
56
+ }
57
+ }
58
+ }
59
+ }
60
+ catch (error) {
61
+ const msg = error instanceof Error ? error.message : String(error);
62
+ // Re-throw Termux error as-is
63
+ if (msg.includes("Termux:API")) {
64
+ throw error;
65
+ }
66
+ if (p === "linux") {
67
+ const tools = isWaylandSession() ? "wl-copy, xclip, or xsel" : "xclip or xsel";
68
+ throw new Error(`Failed to copy to clipboard. Install ${tools}: ${msg}`);
69
+ }
70
+ throw new Error(`Failed to copy to clipboard: ${msg}`);
71
+ }
72
+ }
73
+ //# sourceMappingURL=clipboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync, spawn } from \"child_process\";\nimport { platform } from \"os\";\nimport { isWaylandSession } from \"./clipboard-image.js\";\n\nexport function copyToClipboard(text: string): void {\n\tconst p = platform();\n\tconst options = { input: text, timeout: 5000 };\n\n\ttry {\n\t\tif (p === \"darwin\") {\n\t\t\texecSync(\"pbcopy\", options);\n\t\t} else if (p === \"win32\") {\n\t\t\texecSync(\"clip\", options);\n\t\t} else {\n\t\t\t// Linux - try Termux, Wayland, or X11 clipboard tools\n\t\t\t// Try termux-clipboard-set first if TERMUX_VERSION is set\n\t\t\tif (process.env.TERMUX_VERSION) {\n\t\t\t\ttry {\n\t\t\t\t\texecSync(\"termux-clipboard-set\", options);\n\t\t\t\t\treturn;\n\t\t\t\t} catch {\n\t\t\t\t\tthrow new Error(\"Clipboard requires Termux:API (install with: pkg install termux-api)\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst isWayland = isWaylandSession();\n\t\t\tif (isWayland) {\n\t\t\t\ttry {\n\t\t\t\t\t// Verify wl-copy exists (spawn errors are async and won't be caught)\n\t\t\t\t\texecSync(\"which wl-copy\", { stdio: \"ignore\" });\n\t\t\t\t\t// wl-copy with execSync hangs due to fork behavior; use spawn instead\n\t\t\t\t\tconst proc = spawn(\"wl-copy\", [], { stdio: [\"pipe\", \"ignore\", \"ignore\"] });\n\t\t\t\t\tproc.stdin.on(\"error\", () => {\n\t\t\t\t\t\t// Ignore EPIPE errors if wl-copy exits early\n\t\t\t\t\t});\n\t\t\t\t\tproc.stdin.write(text);\n\t\t\t\t\tproc.stdin.end();\n\t\t\t\t\tproc.unref();\n\t\t\t\t} catch {\n\t\t\t\t\t// Fall back to xclip/xsel (works on XWayland)\n\t\t\t\t\ttry {\n\t\t\t\t\t\texecSync(\"xclip -selection clipboard\", options);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\texecSync(\"xsel --clipboard --input\", options);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\texecSync(\"xclip -selection clipboard\", options);\n\t\t\t\t} catch {\n\t\t\t\t\texecSync(\"xsel --clipboard --input\", options);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\t// Re-throw Termux error as-is\n\t\tif (msg.includes(\"Termux:API\")) {\n\t\t\tthrow error;\n\t\t}\n\t\tif (p === \"linux\") {\n\t\t\tconst tools = isWaylandSession() ? \"wl-copy, xclip, or xsel\" : \"xclip or xsel\";\n\t\t\tthrow new Error(`Failed to copy to clipboard. Install ${tools}: ${msg}`);\n\t\t}\n\t\tthrow new Error(`Failed to copy to clipboard: ${msg}`);\n\t}\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Convert image to PNG format for terminal display.
3
+ * Kitty graphics protocol requires PNG format (f=100).
4
+ */
5
+ export declare function convertToPng(base64Data: string, mimeType: string): Promise<{
6
+ data: string;
7
+ mimeType: string;
8
+ } | null>;
9
+ //# sourceMappingURL=image-convert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-convert.d.ts","sourceRoot":"","sources":["../../src/utils/image-convert.ts"],"names":[],"mappings":"AAEA;;;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,CAyBpD","sourcesContent":["import { getVips } from \"./vips.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 vips = await getVips();\n\tif (!vips) {\n\t\t// wasm-vips not available\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst buffer = Buffer.from(base64Data, \"base64\");\n\t\tconst img = vips.Image.newFromBuffer(buffer);\n\t\tconst pngBuffer = img.writeToBuffer(\".png\");\n\t\timg.delete();\n\t\treturn {\n\t\t\tdata: Buffer.from(pngBuffer).toString(\"base64\"),\n\t\t\tmimeType: \"image/png\",\n\t\t};\n\t} catch {\n\t\t// Conversion failed\n\t\treturn null;\n\t}\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import { getVips } from "./vips.js";
2
+ /**
3
+ * Convert image to PNG format for terminal display.
4
+ * Kitty graphics protocol requires PNG format (f=100).
5
+ */
6
+ export async function convertToPng(base64Data, mimeType) {
7
+ // Already PNG, no conversion needed
8
+ if (mimeType === "image/png") {
9
+ return { data: base64Data, mimeType };
10
+ }
11
+ const vips = await getVips();
12
+ if (!vips) {
13
+ // wasm-vips not available
14
+ return null;
15
+ }
16
+ try {
17
+ const buffer = Buffer.from(base64Data, "base64");
18
+ const img = vips.Image.newFromBuffer(buffer);
19
+ const pngBuffer = img.writeToBuffer(".png");
20
+ img.delete();
21
+ return {
22
+ data: Buffer.from(pngBuffer).toString("base64"),
23
+ mimeType: "image/png",
24
+ };
25
+ }
26
+ catch {
27
+ // Conversion failed
28
+ return null;
29
+ }
30
+ }
31
+ //# sourceMappingURL=image-convert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-convert.js","sourceRoot":"","sources":["../../src/utils/image-convert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,UAAkB,EAClB,QAAgB,EACqC;IACrD,oCAAoC;IACpC,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvC,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,OAAO,EAAE,CAAC;IAC7B,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,0BAA0B;QAC1B,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC7C,MAAM,SAAS,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC5C,GAAG,CAAC,MAAM,EAAE,CAAC;QACb,OAAO;YACN,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAC/C,QAAQ,EAAE,WAAW;SACrB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACR,oBAAoB;QACpB,OAAO,IAAI,CAAC;IACb,CAAC;AAAA,CACD","sourcesContent":["import { getVips } from \"./vips.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 vips = await getVips();\n\tif (!vips) {\n\t\t// wasm-vips not available\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst buffer = Buffer.from(base64Data, \"base64\");\n\t\tconst img = vips.Image.newFromBuffer(buffer);\n\t\tconst pngBuffer = img.writeToBuffer(\".png\");\n\t\timg.delete();\n\t\treturn {\n\t\t\tdata: Buffer.from(pngBuffer).toString(\"base64\"),\n\t\t\tmimeType: \"image/png\",\n\t\t};\n\t} catch {\n\t\t// Conversion failed\n\t\treturn null;\n\t}\n}\n"]}
@@ -0,0 +1,36 @@
1
+ import type { ImageContent } from "@mariozechner/pi-ai";
2
+ export interface ImageResizeOptions {
3
+ maxWidth?: number;
4
+ maxHeight?: number;
5
+ maxBytes?: number;
6
+ jpegQuality?: number;
7
+ }
8
+ export interface ResizedImage {
9
+ data: string;
10
+ mimeType: string;
11
+ originalWidth: number;
12
+ originalHeight: number;
13
+ width: number;
14
+ height: number;
15
+ wasResized: boolean;
16
+ }
17
+ /**
18
+ * Resize an image to fit within the specified max dimensions and file size.
19
+ * Returns the original image if it already fits within the limits.
20
+ *
21
+ * Uses wasm-vips for image processing. If wasm-vips is not available (e.g., in some
22
+ * environments), returns the original image unchanged.
23
+ *
24
+ * Strategy for staying under maxBytes:
25
+ * 1. First resize to maxWidth/maxHeight
26
+ * 2. Try both PNG and JPEG formats, pick the smaller one
27
+ * 3. If still too large, try JPEG with decreasing quality
28
+ * 4. If still too large, progressively reduce dimensions
29
+ */
30
+ export declare function resizeImage(img: ImageContent, options?: ImageResizeOptions): Promise<ResizedImage>;
31
+ /**
32
+ * Format a dimension note for resized images.
33
+ * This helps the model understand the coordinate mapping.
34
+ */
35
+ export declare function formatDimensionNote(result: ResizedImage): string | undefined;
36
+ //# sourceMappingURL=image-resize.d.ts.map
@@ -0,0 +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,qBAAqB,CAAC;AAGxD,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;AAoBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CA8KxG;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAO5E","sourcesContent":["import type { ImageContent } from \"@mariozechner/pi-ai\";\nimport { getVips } from \"./vips.js\";\n\nexport interface ImageResizeOptions {\n\tmaxWidth?: number; // Default: 2000\n\tmaxHeight?: number; // Default: 2000\n\tmaxBytes?: number; // Default: 4.5MB (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 - 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\n/** Helper to pick the smaller of two buffers */\nfunction pickSmaller(\n\ta: { buffer: Uint8Array; mimeType: string },\n\tb: { buffer: Uint8Array; mimeType: string },\n): { buffer: Uint8Array; mimeType: string } {\n\treturn a.buffer.length <= b.buffer.length ? a : b;\n}\n\n/**\n * Resize an image to fit within the specified max dimensions and file size.\n * Returns the original image if it already fits within the limits.\n *\n * Uses wasm-vips for image processing. If wasm-vips is not available (e.g., in some\n * environments), returns the original image unchanged.\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\n */\nexport async function resizeImage(img: ImageContent, options?: ImageResizeOptions): Promise<ResizedImage> {\n\tconst opts = { ...DEFAULT_OPTIONS, ...options };\n\tconst buffer = Buffer.from(img.data, \"base64\");\n\n\tconst vipsOrNull = await getVips();\n\tif (!vipsOrNull) {\n\t\t// wasm-vips not available - return original image\n\t\t// We can't get dimensions without vips, so return 0s\n\t\treturn {\n\t\t\tdata: img.data,\n\t\t\tmimeType: img.mimeType,\n\t\t\toriginalWidth: 0,\n\t\t\toriginalHeight: 0,\n\t\t\twidth: 0,\n\t\t\theight: 0,\n\t\t\twasResized: false,\n\t\t};\n\t}\n\t// Capture non-null reference for use in nested functions\n\tconst vips = vipsOrNull;\n\n\t// Load image to get metadata\n\tlet sourceImg: InstanceType<typeof vips.Image>;\n\ttry {\n\t\tsourceImg = vips.Image.newFromBuffer(buffer);\n\t} catch {\n\t\t// Failed to load image\n\t\treturn {\n\t\t\tdata: img.data,\n\t\t\tmimeType: img.mimeType,\n\t\t\toriginalWidth: 0,\n\t\t\toriginalHeight: 0,\n\t\t\twidth: 0,\n\t\t\theight: 0,\n\t\t\twasResized: false,\n\t\t};\n\t}\n\n\tconst originalWidth = sourceImg.width;\n\tconst originalHeight = sourceImg.height;\n\n\t// Check if already within all limits (dimensions AND size)\n\tconst originalSize = buffer.length;\n\tif (originalWidth <= opts.maxWidth && originalHeight <= opts.maxHeight && originalSize <= opts.maxBytes) {\n\t\tsourceImg.delete();\n\t\tconst format = img.mimeType?.split(\"/\")[1] ?? \"png\";\n\t\treturn {\n\t\t\tdata: img.data,\n\t\t\tmimeType: img.mimeType ?? `image/${format}`,\n\t\t\toriginalWidth,\n\t\t\toriginalHeight,\n\t\t\twidth: originalWidth,\n\t\t\theight: originalHeight,\n\t\t\twasResized: false,\n\t\t};\n\t}\n\n\t// Calculate initial dimensions respecting max limits\n\tlet targetWidth = originalWidth;\n\tlet targetHeight = originalHeight;\n\n\tif (targetWidth > opts.maxWidth) {\n\t\ttargetHeight = Math.round((targetHeight * opts.maxWidth) / targetWidth);\n\t\ttargetWidth = opts.maxWidth;\n\t}\n\tif (targetHeight > opts.maxHeight) {\n\t\ttargetWidth = Math.round((targetWidth * opts.maxHeight) / targetHeight);\n\t\ttargetHeight = opts.maxHeight;\n\t}\n\n\t// Helper to resize and encode in both formats, returning the smaller one\n\tfunction tryBothFormats(\n\t\twidth: number,\n\t\theight: number,\n\t\tjpegQuality: number,\n\t): { buffer: Uint8Array; mimeType: string } {\n\t\t// Load image fresh and resize using scale factor\n\t\t// (Using newFromBuffer + resize instead of thumbnailBuffer to avoid lazy re-read issues)\n\t\tconst img = vips.Image.newFromBuffer(buffer);\n\t\tconst scale = Math.min(width / img.width, height / img.height);\n\t\tconst resized = scale < 1 ? img.resize(scale) : img;\n\n\t\tconst pngBuffer = resized.writeToBuffer(\".png\");\n\t\tconst jpegBuffer = resized.writeToBuffer(\".jpg\", { Q: jpegQuality });\n\n\t\tif (resized !== img) {\n\t\t\tresized.delete();\n\t\t}\n\t\timg.delete();\n\n\t\treturn pickSmaller({ buffer: pngBuffer, mimeType: \"image/png\" }, { buffer: jpegBuffer, mimeType: \"image/jpeg\" });\n\t}\n\n\t// Clean up the source image\n\tsourceImg.delete();\n\n\t// Try to produce an image under maxBytes\n\tconst qualitySteps = [85, 70, 55, 40];\n\tconst scaleSteps = [1.0, 0.75, 0.5, 0.35, 0.25];\n\n\tlet best: { buffer: Uint8Array; mimeType: string };\n\tlet finalWidth = targetWidth;\n\tlet finalHeight = targetHeight;\n\n\t// First attempt: resize to target dimensions, try both formats\n\tbest = tryBothFormats(targetWidth, targetHeight, opts.jpegQuality);\n\n\tif (best.buffer.length <= opts.maxBytes) {\n\t\treturn {\n\t\t\tdata: Buffer.from(best.buffer).toString(\"base64\"),\n\t\t\tmimeType: best.mimeType,\n\t\t\toriginalWidth,\n\t\t\toriginalHeight,\n\t\t\twidth: finalWidth,\n\t\t\theight: finalHeight,\n\t\t\twasResized: true,\n\t\t};\n\t}\n\n\t// Still too large - try JPEG with decreasing quality (and compare to PNG each time)\n\tfor (const quality of qualitySteps) {\n\t\tbest = tryBothFormats(targetWidth, targetHeight, quality);\n\n\t\tif (best.buffer.length <= opts.maxBytes) {\n\t\t\treturn {\n\t\t\t\tdata: Buffer.from(best.buffer).toString(\"base64\"),\n\t\t\t\tmimeType: best.mimeType,\n\t\t\t\toriginalWidth,\n\t\t\t\toriginalHeight,\n\t\t\t\twidth: finalWidth,\n\t\t\t\theight: finalHeight,\n\t\t\t\twasResized: true,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Still too large - reduce dimensions progressively\n\tfor (const scale of scaleSteps) {\n\t\tfinalWidth = Math.round(targetWidth * scale);\n\t\tfinalHeight = Math.round(targetHeight * scale);\n\n\t\t// Skip if dimensions are too small\n\t\tif (finalWidth < 100 || finalHeight < 100) {\n\t\t\tbreak;\n\t\t}\n\n\t\tfor (const quality of qualitySteps) {\n\t\t\tbest = tryBothFormats(finalWidth, finalHeight, quality);\n\n\t\t\tif (best.buffer.length <= opts.maxBytes) {\n\t\t\t\treturn {\n\t\t\t\t\tdata: Buffer.from(best.buffer).toString(\"base64\"),\n\t\t\t\t\tmimeType: best.mimeType,\n\t\t\t\t\toriginalWidth,\n\t\t\t\t\toriginalHeight,\n\t\t\t\t\twidth: finalWidth,\n\t\t\t\t\theight: finalHeight,\n\t\t\t\t\twasResized: true,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\n\t// Last resort: return smallest version we produced even if over limit\n\t// (the API will reject it, but at least we tried everything)\n\treturn {\n\t\tdata: Buffer.from(best.buffer).toString(\"base64\"),\n\t\tmimeType: best.mimeType,\n\t\toriginalWidth,\n\t\toriginalHeight,\n\t\twidth: finalWidth,\n\t\theight: finalHeight,\n\t\twasResized: true,\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"]}
@@ -0,0 +1,188 @@
1
+ import { getVips } from "./vips.js";
2
+ // 4.5MB - provides headroom below Anthropic's 5MB limit
3
+ const DEFAULT_MAX_BYTES = 4.5 * 1024 * 1024;
4
+ const DEFAULT_OPTIONS = {
5
+ maxWidth: 2000,
6
+ maxHeight: 2000,
7
+ maxBytes: DEFAULT_MAX_BYTES,
8
+ jpegQuality: 80,
9
+ };
10
+ /** Helper to pick the smaller of two buffers */
11
+ function pickSmaller(a, b) {
12
+ return a.buffer.length <= b.buffer.length ? a : b;
13
+ }
14
+ /**
15
+ * Resize an image to fit within the specified max dimensions and file size.
16
+ * Returns the original image if it already fits within the limits.
17
+ *
18
+ * Uses wasm-vips for image processing. If wasm-vips is not available (e.g., in some
19
+ * environments), returns the original image unchanged.
20
+ *
21
+ * Strategy for staying under maxBytes:
22
+ * 1. First resize to maxWidth/maxHeight
23
+ * 2. Try both PNG and JPEG formats, pick the smaller one
24
+ * 3. If still too large, try JPEG with decreasing quality
25
+ * 4. If still too large, progressively reduce dimensions
26
+ */
27
+ export async function resizeImage(img, options) {
28
+ const opts = { ...DEFAULT_OPTIONS, ...options };
29
+ const buffer = Buffer.from(img.data, "base64");
30
+ const vipsOrNull = await getVips();
31
+ if (!vipsOrNull) {
32
+ // wasm-vips not available - return original image
33
+ // We can't get dimensions without vips, so return 0s
34
+ return {
35
+ data: img.data,
36
+ mimeType: img.mimeType,
37
+ originalWidth: 0,
38
+ originalHeight: 0,
39
+ width: 0,
40
+ height: 0,
41
+ wasResized: false,
42
+ };
43
+ }
44
+ // Capture non-null reference for use in nested functions
45
+ const vips = vipsOrNull;
46
+ // Load image to get metadata
47
+ let sourceImg;
48
+ try {
49
+ sourceImg = vips.Image.newFromBuffer(buffer);
50
+ }
51
+ catch {
52
+ // Failed to load image
53
+ return {
54
+ data: img.data,
55
+ mimeType: img.mimeType,
56
+ originalWidth: 0,
57
+ originalHeight: 0,
58
+ width: 0,
59
+ height: 0,
60
+ wasResized: false,
61
+ };
62
+ }
63
+ const originalWidth = sourceImg.width;
64
+ const originalHeight = sourceImg.height;
65
+ // Check if already within all limits (dimensions AND size)
66
+ const originalSize = buffer.length;
67
+ if (originalWidth <= opts.maxWidth && originalHeight <= opts.maxHeight && originalSize <= opts.maxBytes) {
68
+ sourceImg.delete();
69
+ const format = img.mimeType?.split("/")[1] ?? "png";
70
+ return {
71
+ data: img.data,
72
+ mimeType: img.mimeType ?? `image/${format}`,
73
+ originalWidth,
74
+ originalHeight,
75
+ width: originalWidth,
76
+ height: originalHeight,
77
+ wasResized: false,
78
+ };
79
+ }
80
+ // Calculate initial dimensions respecting max limits
81
+ let targetWidth = originalWidth;
82
+ let targetHeight = originalHeight;
83
+ if (targetWidth > opts.maxWidth) {
84
+ targetHeight = Math.round((targetHeight * opts.maxWidth) / targetWidth);
85
+ targetWidth = opts.maxWidth;
86
+ }
87
+ if (targetHeight > opts.maxHeight) {
88
+ targetWidth = Math.round((targetWidth * opts.maxHeight) / targetHeight);
89
+ targetHeight = opts.maxHeight;
90
+ }
91
+ // Helper to resize and encode in both formats, returning the smaller one
92
+ function tryBothFormats(width, height, jpegQuality) {
93
+ // Load image fresh and resize using scale factor
94
+ // (Using newFromBuffer + resize instead of thumbnailBuffer to avoid lazy re-read issues)
95
+ const img = vips.Image.newFromBuffer(buffer);
96
+ const scale = Math.min(width / img.width, height / img.height);
97
+ const resized = scale < 1 ? img.resize(scale) : img;
98
+ const pngBuffer = resized.writeToBuffer(".png");
99
+ const jpegBuffer = resized.writeToBuffer(".jpg", { Q: jpegQuality });
100
+ if (resized !== img) {
101
+ resized.delete();
102
+ }
103
+ img.delete();
104
+ return pickSmaller({ buffer: pngBuffer, mimeType: "image/png" }, { buffer: jpegBuffer, mimeType: "image/jpeg" });
105
+ }
106
+ // Clean up the source image
107
+ sourceImg.delete();
108
+ // Try to produce an image under maxBytes
109
+ const qualitySteps = [85, 70, 55, 40];
110
+ const scaleSteps = [1.0, 0.75, 0.5, 0.35, 0.25];
111
+ let best;
112
+ let finalWidth = targetWidth;
113
+ let finalHeight = targetHeight;
114
+ // First attempt: resize to target dimensions, try both formats
115
+ best = tryBothFormats(targetWidth, targetHeight, opts.jpegQuality);
116
+ if (best.buffer.length <= opts.maxBytes) {
117
+ return {
118
+ data: Buffer.from(best.buffer).toString("base64"),
119
+ mimeType: best.mimeType,
120
+ originalWidth,
121
+ originalHeight,
122
+ width: finalWidth,
123
+ height: finalHeight,
124
+ wasResized: true,
125
+ };
126
+ }
127
+ // Still too large - try JPEG with decreasing quality (and compare to PNG each time)
128
+ for (const quality of qualitySteps) {
129
+ best = tryBothFormats(targetWidth, targetHeight, quality);
130
+ if (best.buffer.length <= opts.maxBytes) {
131
+ return {
132
+ data: Buffer.from(best.buffer).toString("base64"),
133
+ mimeType: best.mimeType,
134
+ originalWidth,
135
+ originalHeight,
136
+ width: finalWidth,
137
+ height: finalHeight,
138
+ wasResized: true,
139
+ };
140
+ }
141
+ }
142
+ // Still too large - reduce dimensions progressively
143
+ for (const scale of scaleSteps) {
144
+ finalWidth = Math.round(targetWidth * scale);
145
+ finalHeight = Math.round(targetHeight * scale);
146
+ // Skip if dimensions are too small
147
+ if (finalWidth < 100 || finalHeight < 100) {
148
+ break;
149
+ }
150
+ for (const quality of qualitySteps) {
151
+ best = tryBothFormats(finalWidth, finalHeight, quality);
152
+ if (best.buffer.length <= opts.maxBytes) {
153
+ return {
154
+ data: Buffer.from(best.buffer).toString("base64"),
155
+ mimeType: best.mimeType,
156
+ originalWidth,
157
+ originalHeight,
158
+ width: finalWidth,
159
+ height: finalHeight,
160
+ wasResized: true,
161
+ };
162
+ }
163
+ }
164
+ }
165
+ // Last resort: return smallest version we produced even if over limit
166
+ // (the API will reject it, but at least we tried everything)
167
+ return {
168
+ data: Buffer.from(best.buffer).toString("base64"),
169
+ mimeType: best.mimeType,
170
+ originalWidth,
171
+ originalHeight,
172
+ width: finalWidth,
173
+ height: finalHeight,
174
+ wasResized: true,
175
+ };
176
+ }
177
+ /**
178
+ * Format a dimension note for resized images.
179
+ * This helps the model understand the coordinate mapping.
180
+ */
181
+ export function formatDimensionNote(result) {
182
+ if (!result.wasResized) {
183
+ return undefined;
184
+ }
185
+ const scale = result.originalWidth / result.width;
186
+ return `[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.]`;
187
+ }
188
+ //# sourceMappingURL=image-resize.js.map
@@ -0,0 +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 \"@mariozechner/pi-ai\";\nimport { getVips } from \"./vips.js\";\n\nexport interface ImageResizeOptions {\n\tmaxWidth?: number; // Default: 2000\n\tmaxHeight?: number; // Default: 2000\n\tmaxBytes?: number; // Default: 4.5MB (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 - 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\n/** Helper to pick the smaller of two buffers */\nfunction pickSmaller(\n\ta: { buffer: Uint8Array; mimeType: string },\n\tb: { buffer: Uint8Array; mimeType: string },\n): { buffer: Uint8Array; mimeType: string } {\n\treturn a.buffer.length <= b.buffer.length ? a : b;\n}\n\n/**\n * Resize an image to fit within the specified max dimensions and file size.\n * Returns the original image if it already fits within the limits.\n *\n * Uses wasm-vips for image processing. If wasm-vips is not available (e.g., in some\n * environments), returns the original image unchanged.\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\n */\nexport async function resizeImage(img: ImageContent, options?: ImageResizeOptions): Promise<ResizedImage> {\n\tconst opts = { ...DEFAULT_OPTIONS, ...options };\n\tconst buffer = Buffer.from(img.data, \"base64\");\n\n\tconst vipsOrNull = await getVips();\n\tif (!vipsOrNull) {\n\t\t// wasm-vips not available - return original image\n\t\t// We can't get dimensions without vips, so return 0s\n\t\treturn {\n\t\t\tdata: img.data,\n\t\t\tmimeType: img.mimeType,\n\t\t\toriginalWidth: 0,\n\t\t\toriginalHeight: 0,\n\t\t\twidth: 0,\n\t\t\theight: 0,\n\t\t\twasResized: false,\n\t\t};\n\t}\n\t// Capture non-null reference for use in nested functions\n\tconst vips = vipsOrNull;\n\n\t// Load image to get metadata\n\tlet sourceImg: InstanceType<typeof vips.Image>;\n\ttry {\n\t\tsourceImg = vips.Image.newFromBuffer(buffer);\n\t} catch {\n\t\t// Failed to load image\n\t\treturn {\n\t\t\tdata: img.data,\n\t\t\tmimeType: img.mimeType,\n\t\t\toriginalWidth: 0,\n\t\t\toriginalHeight: 0,\n\t\t\twidth: 0,\n\t\t\theight: 0,\n\t\t\twasResized: false,\n\t\t};\n\t}\n\n\tconst originalWidth = sourceImg.width;\n\tconst originalHeight = sourceImg.height;\n\n\t// Check if already within all limits (dimensions AND size)\n\tconst originalSize = buffer.length;\n\tif (originalWidth <= opts.maxWidth && originalHeight <= opts.maxHeight && originalSize <= opts.maxBytes) {\n\t\tsourceImg.delete();\n\t\tconst format = img.mimeType?.split(\"/\")[1] ?? \"png\";\n\t\treturn {\n\t\t\tdata: img.data,\n\t\t\tmimeType: img.mimeType ?? `image/${format}`,\n\t\t\toriginalWidth,\n\t\t\toriginalHeight,\n\t\t\twidth: originalWidth,\n\t\t\theight: originalHeight,\n\t\t\twasResized: false,\n\t\t};\n\t}\n\n\t// Calculate initial dimensions respecting max limits\n\tlet targetWidth = originalWidth;\n\tlet targetHeight = originalHeight;\n\n\tif (targetWidth > opts.maxWidth) {\n\t\ttargetHeight = Math.round((targetHeight * opts.maxWidth) / targetWidth);\n\t\ttargetWidth = opts.maxWidth;\n\t}\n\tif (targetHeight > opts.maxHeight) {\n\t\ttargetWidth = Math.round((targetWidth * opts.maxHeight) / targetHeight);\n\t\ttargetHeight = opts.maxHeight;\n\t}\n\n\t// Helper to resize and encode in both formats, returning the smaller one\n\tfunction tryBothFormats(\n\t\twidth: number,\n\t\theight: number,\n\t\tjpegQuality: number,\n\t): { buffer: Uint8Array; mimeType: string } {\n\t\t// Load image fresh and resize using scale factor\n\t\t// (Using newFromBuffer + resize instead of thumbnailBuffer to avoid lazy re-read issues)\n\t\tconst img = vips.Image.newFromBuffer(buffer);\n\t\tconst scale = Math.min(width / img.width, height / img.height);\n\t\tconst resized = scale < 1 ? img.resize(scale) : img;\n\n\t\tconst pngBuffer = resized.writeToBuffer(\".png\");\n\t\tconst jpegBuffer = resized.writeToBuffer(\".jpg\", { Q: jpegQuality });\n\n\t\tif (resized !== img) {\n\t\t\tresized.delete();\n\t\t}\n\t\timg.delete();\n\n\t\treturn pickSmaller({ buffer: pngBuffer, mimeType: \"image/png\" }, { buffer: jpegBuffer, mimeType: \"image/jpeg\" });\n\t}\n\n\t// Clean up the source image\n\tsourceImg.delete();\n\n\t// Try to produce an image under maxBytes\n\tconst qualitySteps = [85, 70, 55, 40];\n\tconst scaleSteps = [1.0, 0.75, 0.5, 0.35, 0.25];\n\n\tlet best: { buffer: Uint8Array; mimeType: string };\n\tlet finalWidth = targetWidth;\n\tlet finalHeight = targetHeight;\n\n\t// First attempt: resize to target dimensions, try both formats\n\tbest = tryBothFormats(targetWidth, targetHeight, opts.jpegQuality);\n\n\tif (best.buffer.length <= opts.maxBytes) {\n\t\treturn {\n\t\t\tdata: Buffer.from(best.buffer).toString(\"base64\"),\n\t\t\tmimeType: best.mimeType,\n\t\t\toriginalWidth,\n\t\t\toriginalHeight,\n\t\t\twidth: finalWidth,\n\t\t\theight: finalHeight,\n\t\t\twasResized: true,\n\t\t};\n\t}\n\n\t// Still too large - try JPEG with decreasing quality (and compare to PNG each time)\n\tfor (const quality of qualitySteps) {\n\t\tbest = tryBothFormats(targetWidth, targetHeight, quality);\n\n\t\tif (best.buffer.length <= opts.maxBytes) {\n\t\t\treturn {\n\t\t\t\tdata: Buffer.from(best.buffer).toString(\"base64\"),\n\t\t\t\tmimeType: best.mimeType,\n\t\t\t\toriginalWidth,\n\t\t\t\toriginalHeight,\n\t\t\t\twidth: finalWidth,\n\t\t\t\theight: finalHeight,\n\t\t\t\twasResized: true,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Still too large - reduce dimensions progressively\n\tfor (const scale of scaleSteps) {\n\t\tfinalWidth = Math.round(targetWidth * scale);\n\t\tfinalHeight = Math.round(targetHeight * scale);\n\n\t\t// Skip if dimensions are too small\n\t\tif (finalWidth < 100 || finalHeight < 100) {\n\t\t\tbreak;\n\t\t}\n\n\t\tfor (const quality of qualitySteps) {\n\t\t\tbest = tryBothFormats(finalWidth, finalHeight, quality);\n\n\t\t\tif (best.buffer.length <= opts.maxBytes) {\n\t\t\t\treturn {\n\t\t\t\t\tdata: Buffer.from(best.buffer).toString(\"base64\"),\n\t\t\t\t\tmimeType: best.mimeType,\n\t\t\t\t\toriginalWidth,\n\t\t\t\t\toriginalHeight,\n\t\t\t\t\twidth: finalWidth,\n\t\t\t\t\theight: finalHeight,\n\t\t\t\t\twasResized: true,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\n\t// Last resort: return smallest version we produced even if over limit\n\t// (the API will reject it, but at least we tried everything)\n\treturn {\n\t\tdata: Buffer.from(best.buffer).toString(\"base64\"),\n\t\tmimeType: best.mimeType,\n\t\toriginalWidth,\n\t\toriginalHeight,\n\t\twidth: finalWidth,\n\t\theight: finalHeight,\n\t\twasResized: true,\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"]}
@@ -0,0 +1,2 @@
1
+ export declare function detectSupportedImageMimeTypeFromFile(filePath: string): Promise<string | null>;
2
+ //# sourceMappingURL=mime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mime.d.ts","sourceRoot":"","sources":["../../src/utils/mime.ts"],"names":[],"mappings":"AAOA,wBAAsB,oCAAoC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsBnG","sourcesContent":["import { open } from \"node:fs/promises\";\nimport { fileTypeFromBuffer } from \"file-type\";\n\nconst IMAGE_MIME_TYPES = new Set([\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"]);\n\nconst FILE_TYPE_SNIFF_BYTES = 4100;\n\nexport async function detectSupportedImageMimeTypeFromFile(filePath: string): Promise<string | null> {\n\tconst fileHandle = await open(filePath, \"r\");\n\ttry {\n\t\tconst buffer = Buffer.alloc(FILE_TYPE_SNIFF_BYTES);\n\t\tconst { bytesRead } = await fileHandle.read(buffer, 0, FILE_TYPE_SNIFF_BYTES, 0);\n\t\tif (bytesRead === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst fileType = await fileTypeFromBuffer(buffer.subarray(0, bytesRead));\n\t\tif (!fileType) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (!IMAGE_MIME_TYPES.has(fileType.mime)) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn fileType.mime;\n\t} finally {\n\t\tawait fileHandle.close();\n\t}\n}\n"]}
@@ -0,0 +1,26 @@
1
+ import { open } from "node:fs/promises";
2
+ import { fileTypeFromBuffer } from "file-type";
3
+ const IMAGE_MIME_TYPES = new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
4
+ const FILE_TYPE_SNIFF_BYTES = 4100;
5
+ export async function detectSupportedImageMimeTypeFromFile(filePath) {
6
+ const fileHandle = await open(filePath, "r");
7
+ try {
8
+ const buffer = Buffer.alloc(FILE_TYPE_SNIFF_BYTES);
9
+ const { bytesRead } = await fileHandle.read(buffer, 0, FILE_TYPE_SNIFF_BYTES, 0);
10
+ if (bytesRead === 0) {
11
+ return null;
12
+ }
13
+ const fileType = await fileTypeFromBuffer(buffer.subarray(0, bytesRead));
14
+ if (!fileType) {
15
+ return null;
16
+ }
17
+ if (!IMAGE_MIME_TYPES.has(fileType.mime)) {
18
+ return null;
19
+ }
20
+ return fileType.mime;
21
+ }
22
+ finally {
23
+ await fileHandle.close();
24
+ }
25
+ }
26
+ //# sourceMappingURL=mime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mime.js","sourceRoot":"","sources":["../../src/utils/mime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;AAEzF,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAEnC,MAAM,CAAC,KAAK,UAAU,oCAAoC,CAAC,QAAgB,EAA0B;IACpG,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC7C,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACnD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,qBAAqB,EAAE,CAAC,CAAC,CAAC;QACjF,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC;QACb,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;QACzE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACf,OAAO,IAAI,CAAC;QACb,CAAC;QAED,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,OAAO,IAAI,CAAC;QACb,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAC;IACtB,CAAC;YAAS,CAAC;QACV,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;AAAA,CACD","sourcesContent":["import { open } from \"node:fs/promises\";\nimport { fileTypeFromBuffer } from \"file-type\";\n\nconst IMAGE_MIME_TYPES = new Set([\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"]);\n\nconst FILE_TYPE_SNIFF_BYTES = 4100;\n\nexport async function detectSupportedImageMimeTypeFromFile(filePath: string): Promise<string | null> {\n\tconst fileHandle = await open(filePath, \"r\");\n\ttry {\n\t\tconst buffer = Buffer.alloc(FILE_TYPE_SNIFF_BYTES);\n\t\tconst { bytesRead } = await fileHandle.read(buffer, 0, FILE_TYPE_SNIFF_BYTES, 0);\n\t\tif (bytesRead === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst fileType = await fileTypeFromBuffer(buffer.subarray(0, bytesRead));\n\t\tif (!fileType) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (!IMAGE_MIME_TYPES.has(fileType.mime)) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn fileType.mime;\n\t} finally {\n\t\tawait fileHandle.close();\n\t}\n}\n"]}