onbuzz 3.3.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 (506) hide show
  1. package/LICENSE +267 -0
  2. package/README.md +425 -0
  3. package/bin/cli.js +556 -0
  4. package/bin/loxia-terminal-v2.js +162 -0
  5. package/bin/loxia-terminal.js +90 -0
  6. package/bin/start-with-terminal.js +200 -0
  7. package/node_modules/@isaacs/balanced-match/LICENSE.md +23 -0
  8. package/node_modules/@isaacs/balanced-match/README.md +60 -0
  9. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.d.ts +9 -0
  10. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.d.ts.map +1 -0
  11. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.js +59 -0
  12. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.js.map +1 -0
  13. package/node_modules/@isaacs/balanced-match/dist/commonjs/package.json +3 -0
  14. package/node_modules/@isaacs/balanced-match/dist/esm/index.d.ts +9 -0
  15. package/node_modules/@isaacs/balanced-match/dist/esm/index.d.ts.map +1 -0
  16. package/node_modules/@isaacs/balanced-match/dist/esm/index.js +54 -0
  17. package/node_modules/@isaacs/balanced-match/dist/esm/index.js.map +1 -0
  18. package/node_modules/@isaacs/balanced-match/dist/esm/package.json +3 -0
  19. package/node_modules/@isaacs/balanced-match/package.json +79 -0
  20. package/node_modules/@isaacs/brace-expansion/LICENSE +23 -0
  21. package/node_modules/@isaacs/brace-expansion/README.md +97 -0
  22. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.d.ts +6 -0
  23. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.d.ts.map +1 -0
  24. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js +199 -0
  25. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +1 -0
  26. package/node_modules/@isaacs/brace-expansion/dist/commonjs/package.json +3 -0
  27. package/node_modules/@isaacs/brace-expansion/dist/esm/index.d.ts +6 -0
  28. package/node_modules/@isaacs/brace-expansion/dist/esm/index.d.ts.map +1 -0
  29. package/node_modules/@isaacs/brace-expansion/dist/esm/index.js +195 -0
  30. package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +1 -0
  31. package/node_modules/@isaacs/brace-expansion/dist/esm/package.json +3 -0
  32. package/node_modules/@isaacs/brace-expansion/package.json +60 -0
  33. package/node_modules/glob/LICENSE.md +63 -0
  34. package/node_modules/glob/README.md +1177 -0
  35. package/node_modules/glob/dist/commonjs/glob.d.ts +388 -0
  36. package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -0
  37. package/node_modules/glob/dist/commonjs/glob.js +247 -0
  38. package/node_modules/glob/dist/commonjs/glob.js.map +1 -0
  39. package/node_modules/glob/dist/commonjs/has-magic.d.ts +14 -0
  40. package/node_modules/glob/dist/commonjs/has-magic.d.ts.map +1 -0
  41. package/node_modules/glob/dist/commonjs/has-magic.js +27 -0
  42. package/node_modules/glob/dist/commonjs/has-magic.js.map +1 -0
  43. package/node_modules/glob/dist/commonjs/ignore.d.ts +24 -0
  44. package/node_modules/glob/dist/commonjs/ignore.d.ts.map +1 -0
  45. package/node_modules/glob/dist/commonjs/ignore.js +119 -0
  46. package/node_modules/glob/dist/commonjs/ignore.js.map +1 -0
  47. package/node_modules/glob/dist/commonjs/index.d.ts +97 -0
  48. package/node_modules/glob/dist/commonjs/index.d.ts.map +1 -0
  49. package/node_modules/glob/dist/commonjs/index.js +68 -0
  50. package/node_modules/glob/dist/commonjs/index.js.map +1 -0
  51. package/node_modules/glob/dist/commonjs/index.min.js +4 -0
  52. package/node_modules/glob/dist/commonjs/index.min.js.map +7 -0
  53. package/node_modules/glob/dist/commonjs/package.json +3 -0
  54. package/node_modules/glob/dist/commonjs/pattern.d.ts +76 -0
  55. package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -0
  56. package/node_modules/glob/dist/commonjs/pattern.js +219 -0
  57. package/node_modules/glob/dist/commonjs/pattern.js.map +1 -0
  58. package/node_modules/glob/dist/commonjs/processor.d.ts +59 -0
  59. package/node_modules/glob/dist/commonjs/processor.d.ts.map +1 -0
  60. package/node_modules/glob/dist/commonjs/processor.js +301 -0
  61. package/node_modules/glob/dist/commonjs/processor.js.map +1 -0
  62. package/node_modules/glob/dist/commonjs/walker.d.ts +97 -0
  63. package/node_modules/glob/dist/commonjs/walker.d.ts.map +1 -0
  64. package/node_modules/glob/dist/commonjs/walker.js +387 -0
  65. package/node_modules/glob/dist/commonjs/walker.js.map +1 -0
  66. package/node_modules/glob/dist/esm/glob.d.ts +388 -0
  67. package/node_modules/glob/dist/esm/glob.d.ts.map +1 -0
  68. package/node_modules/glob/dist/esm/glob.js +243 -0
  69. package/node_modules/glob/dist/esm/glob.js.map +1 -0
  70. package/node_modules/glob/dist/esm/has-magic.d.ts +14 -0
  71. package/node_modules/glob/dist/esm/has-magic.d.ts.map +1 -0
  72. package/node_modules/glob/dist/esm/has-magic.js +23 -0
  73. package/node_modules/glob/dist/esm/has-magic.js.map +1 -0
  74. package/node_modules/glob/dist/esm/ignore.d.ts +24 -0
  75. package/node_modules/glob/dist/esm/ignore.d.ts.map +1 -0
  76. package/node_modules/glob/dist/esm/ignore.js +115 -0
  77. package/node_modules/glob/dist/esm/ignore.js.map +1 -0
  78. package/node_modules/glob/dist/esm/index.d.ts +97 -0
  79. package/node_modules/glob/dist/esm/index.d.ts.map +1 -0
  80. package/node_modules/glob/dist/esm/index.js +55 -0
  81. package/node_modules/glob/dist/esm/index.js.map +1 -0
  82. package/node_modules/glob/dist/esm/index.min.js +4 -0
  83. package/node_modules/glob/dist/esm/index.min.js.map +7 -0
  84. package/node_modules/glob/dist/esm/package.json +3 -0
  85. package/node_modules/glob/dist/esm/pattern.d.ts +76 -0
  86. package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -0
  87. package/node_modules/glob/dist/esm/pattern.js +215 -0
  88. package/node_modules/glob/dist/esm/pattern.js.map +1 -0
  89. package/node_modules/glob/dist/esm/processor.d.ts +59 -0
  90. package/node_modules/glob/dist/esm/processor.d.ts.map +1 -0
  91. package/node_modules/glob/dist/esm/processor.js +294 -0
  92. package/node_modules/glob/dist/esm/processor.js.map +1 -0
  93. package/node_modules/glob/dist/esm/walker.d.ts +97 -0
  94. package/node_modules/glob/dist/esm/walker.d.ts.map +1 -0
  95. package/node_modules/glob/dist/esm/walker.js +381 -0
  96. package/node_modules/glob/dist/esm/walker.js.map +1 -0
  97. package/node_modules/glob/node_modules/minimatch/LICENSE.md +55 -0
  98. package/node_modules/glob/node_modules/minimatch/README.md +453 -0
  99. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +2 -0
  100. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -0
  101. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js +14 -0
  102. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -0
  103. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +20 -0
  104. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -0
  105. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +591 -0
  106. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -0
  107. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts +8 -0
  108. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -0
  109. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +152 -0
  110. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -0
  111. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +15 -0
  112. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -0
  113. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +30 -0
  114. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -0
  115. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +94 -0
  116. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -0
  117. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +1029 -0
  118. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -0
  119. package/node_modules/glob/node_modules/minimatch/dist/commonjs/package.json +3 -0
  120. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +22 -0
  121. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -0
  122. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +38 -0
  123. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -0
  124. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +2 -0
  125. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -0
  126. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js +10 -0
  127. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -0
  128. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +20 -0
  129. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -0
  130. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +587 -0
  131. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -0
  132. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts +8 -0
  133. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -0
  134. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +148 -0
  135. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -0
  136. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +15 -0
  137. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -0
  138. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +26 -0
  139. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -0
  140. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +94 -0
  141. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -0
  142. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +1016 -0
  143. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -0
  144. package/node_modules/glob/node_modules/minimatch/dist/esm/package.json +3 -0
  145. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +22 -0
  146. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -0
  147. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +34 -0
  148. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -0
  149. package/node_modules/glob/node_modules/minimatch/package.json +67 -0
  150. package/node_modules/glob/package.json +101 -0
  151. package/node_modules/minipass/LICENSE +15 -0
  152. package/node_modules/minipass/README.md +825 -0
  153. package/node_modules/minipass/dist/commonjs/index.d.ts +549 -0
  154. package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -0
  155. package/node_modules/minipass/dist/commonjs/index.js +1028 -0
  156. package/node_modules/minipass/dist/commonjs/index.js.map +1 -0
  157. package/node_modules/minipass/dist/commonjs/package.json +3 -0
  158. package/node_modules/minipass/dist/esm/index.d.ts +549 -0
  159. package/node_modules/minipass/dist/esm/index.d.ts.map +1 -0
  160. package/node_modules/minipass/dist/esm/index.js +1018 -0
  161. package/node_modules/minipass/dist/esm/index.js.map +1 -0
  162. package/node_modules/minipass/dist/esm/package.json +3 -0
  163. package/node_modules/minipass/package.json +82 -0
  164. package/node_modules/package-json-from-dist/LICENSE.md +63 -0
  165. package/node_modules/package-json-from-dist/README.md +110 -0
  166. package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts +89 -0
  167. package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts.map +1 -0
  168. package/node_modules/package-json-from-dist/dist/commonjs/index.js +134 -0
  169. package/node_modules/package-json-from-dist/dist/commonjs/index.js.map +1 -0
  170. package/node_modules/package-json-from-dist/dist/commonjs/package.json +3 -0
  171. package/node_modules/package-json-from-dist/dist/esm/index.d.ts +89 -0
  172. package/node_modules/package-json-from-dist/dist/esm/index.d.ts.map +1 -0
  173. package/node_modules/package-json-from-dist/dist/esm/index.js +129 -0
  174. package/node_modules/package-json-from-dist/dist/esm/index.js.map +1 -0
  175. package/node_modules/package-json-from-dist/dist/esm/package.json +3 -0
  176. package/node_modules/package-json-from-dist/package.json +68 -0
  177. package/node_modules/path-scurry/LICENSE.md +55 -0
  178. package/node_modules/path-scurry/README.md +636 -0
  179. package/node_modules/path-scurry/dist/commonjs/index.d.ts +1115 -0
  180. package/node_modules/path-scurry/dist/commonjs/index.d.ts.map +1 -0
  181. package/node_modules/path-scurry/dist/commonjs/index.js +2018 -0
  182. package/node_modules/path-scurry/dist/commonjs/index.js.map +1 -0
  183. package/node_modules/path-scurry/dist/commonjs/package.json +3 -0
  184. package/node_modules/path-scurry/dist/esm/index.d.ts +1115 -0
  185. package/node_modules/path-scurry/dist/esm/index.d.ts.map +1 -0
  186. package/node_modules/path-scurry/dist/esm/index.js +1983 -0
  187. package/node_modules/path-scurry/dist/esm/index.js.map +1 -0
  188. package/node_modules/path-scurry/dist/esm/package.json +3 -0
  189. package/node_modules/path-scurry/node_modules/lru-cache/LICENSE.md +55 -0
  190. package/node_modules/path-scurry/node_modules/lru-cache/README.md +383 -0
  191. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +1323 -0
  192. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -0
  193. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +1589 -0
  194. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -0
  195. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +2 -0
  196. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +7 -0
  197. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/package.json +3 -0
  198. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +1323 -0
  199. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -0
  200. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +1585 -0
  201. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -0
  202. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +2 -0
  203. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +7 -0
  204. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/package.json +3 -0
  205. package/node_modules/path-scurry/node_modules/lru-cache/package.json +101 -0
  206. package/node_modules/path-scurry/package.json +88 -0
  207. package/node_modules/rimraf/LICENSE.md +55 -0
  208. package/node_modules/rimraf/README.md +226 -0
  209. package/node_modules/rimraf/dist/commonjs/default-tmp.d.ts +3 -0
  210. package/node_modules/rimraf/dist/commonjs/default-tmp.d.ts.map +1 -0
  211. package/node_modules/rimraf/dist/commonjs/default-tmp.js +58 -0
  212. package/node_modules/rimraf/dist/commonjs/default-tmp.js.map +1 -0
  213. package/node_modules/rimraf/dist/commonjs/error.d.ts +6 -0
  214. package/node_modules/rimraf/dist/commonjs/error.d.ts.map +1 -0
  215. package/node_modules/rimraf/dist/commonjs/error.js +10 -0
  216. package/node_modules/rimraf/dist/commonjs/error.js.map +1 -0
  217. package/node_modules/rimraf/dist/commonjs/fix-eperm.d.ts +3 -0
  218. package/node_modules/rimraf/dist/commonjs/fix-eperm.d.ts.map +1 -0
  219. package/node_modules/rimraf/dist/commonjs/fix-eperm.js +38 -0
  220. package/node_modules/rimraf/dist/commonjs/fix-eperm.js.map +1 -0
  221. package/node_modules/rimraf/dist/commonjs/fs.d.ts +15 -0
  222. package/node_modules/rimraf/dist/commonjs/fs.d.ts.map +1 -0
  223. package/node_modules/rimraf/dist/commonjs/fs.js +33 -0
  224. package/node_modules/rimraf/dist/commonjs/fs.js.map +1 -0
  225. package/node_modules/rimraf/dist/commonjs/ignore-enoent.d.ts +3 -0
  226. package/node_modules/rimraf/dist/commonjs/ignore-enoent.d.ts.map +1 -0
  227. package/node_modules/rimraf/dist/commonjs/ignore-enoent.js +24 -0
  228. package/node_modules/rimraf/dist/commonjs/ignore-enoent.js.map +1 -0
  229. package/node_modules/rimraf/dist/commonjs/index.d.ts +50 -0
  230. package/node_modules/rimraf/dist/commonjs/index.d.ts.map +1 -0
  231. package/node_modules/rimraf/dist/commonjs/index.js +78 -0
  232. package/node_modules/rimraf/dist/commonjs/index.js.map +1 -0
  233. package/node_modules/rimraf/dist/commonjs/opt-arg.d.ts +34 -0
  234. package/node_modules/rimraf/dist/commonjs/opt-arg.d.ts.map +1 -0
  235. package/node_modules/rimraf/dist/commonjs/opt-arg.js +53 -0
  236. package/node_modules/rimraf/dist/commonjs/opt-arg.js.map +1 -0
  237. package/node_modules/rimraf/dist/commonjs/package.json +3 -0
  238. package/node_modules/rimraf/dist/commonjs/path-arg.d.ts +4 -0
  239. package/node_modules/rimraf/dist/commonjs/path-arg.d.ts.map +1 -0
  240. package/node_modules/rimraf/dist/commonjs/path-arg.js +48 -0
  241. package/node_modules/rimraf/dist/commonjs/path-arg.js.map +1 -0
  242. package/node_modules/rimraf/dist/commonjs/readdir-or-error.d.ts +3 -0
  243. package/node_modules/rimraf/dist/commonjs/readdir-or-error.d.ts.map +1 -0
  244. package/node_modules/rimraf/dist/commonjs/readdir-or-error.js +19 -0
  245. package/node_modules/rimraf/dist/commonjs/readdir-or-error.js.map +1 -0
  246. package/node_modules/rimraf/dist/commonjs/retry-busy.d.ts +8 -0
  247. package/node_modules/rimraf/dist/commonjs/retry-busy.d.ts.map +1 -0
  248. package/node_modules/rimraf/dist/commonjs/retry-busy.js +65 -0
  249. package/node_modules/rimraf/dist/commonjs/retry-busy.js.map +1 -0
  250. package/node_modules/rimraf/dist/commonjs/rimraf-manual.d.ts +3 -0
  251. package/node_modules/rimraf/dist/commonjs/rimraf-manual.d.ts.map +1 -0
  252. package/node_modules/rimraf/dist/commonjs/rimraf-manual.js +8 -0
  253. package/node_modules/rimraf/dist/commonjs/rimraf-manual.js.map +1 -0
  254. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.d.ts +4 -0
  255. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.d.ts.map +1 -0
  256. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.js +138 -0
  257. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.js.map +1 -0
  258. package/node_modules/rimraf/dist/commonjs/rimraf-native.d.ts +4 -0
  259. package/node_modules/rimraf/dist/commonjs/rimraf-native.d.ts.map +1 -0
  260. package/node_modules/rimraf/dist/commonjs/rimraf-native.js +24 -0
  261. package/node_modules/rimraf/dist/commonjs/rimraf-native.js.map +1 -0
  262. package/node_modules/rimraf/dist/commonjs/rimraf-posix.d.ts +4 -0
  263. package/node_modules/rimraf/dist/commonjs/rimraf-posix.d.ts.map +1 -0
  264. package/node_modules/rimraf/dist/commonjs/rimraf-posix.js +103 -0
  265. package/node_modules/rimraf/dist/commonjs/rimraf-posix.js.map +1 -0
  266. package/node_modules/rimraf/dist/commonjs/rimraf-windows.d.ts +4 -0
  267. package/node_modules/rimraf/dist/commonjs/rimraf-windows.d.ts.map +1 -0
  268. package/node_modules/rimraf/dist/commonjs/rimraf-windows.js +159 -0
  269. package/node_modules/rimraf/dist/commonjs/rimraf-windows.js.map +1 -0
  270. package/node_modules/rimraf/dist/commonjs/use-native.d.ts +4 -0
  271. package/node_modules/rimraf/dist/commonjs/use-native.d.ts.map +1 -0
  272. package/node_modules/rimraf/dist/commonjs/use-native.js +18 -0
  273. package/node_modules/rimraf/dist/commonjs/use-native.js.map +1 -0
  274. package/node_modules/rimraf/dist/esm/bin.d.mts +3 -0
  275. package/node_modules/rimraf/dist/esm/bin.d.mts.map +1 -0
  276. package/node_modules/rimraf/dist/esm/bin.mjs +250 -0
  277. package/node_modules/rimraf/dist/esm/bin.mjs.map +1 -0
  278. package/node_modules/rimraf/dist/esm/default-tmp.d.ts +3 -0
  279. package/node_modules/rimraf/dist/esm/default-tmp.d.ts.map +1 -0
  280. package/node_modules/rimraf/dist/esm/default-tmp.js +55 -0
  281. package/node_modules/rimraf/dist/esm/default-tmp.js.map +1 -0
  282. package/node_modules/rimraf/dist/esm/error.d.ts +6 -0
  283. package/node_modules/rimraf/dist/esm/error.d.ts.map +1 -0
  284. package/node_modules/rimraf/dist/esm/error.js +5 -0
  285. package/node_modules/rimraf/dist/esm/error.js.map +1 -0
  286. package/node_modules/rimraf/dist/esm/fix-eperm.d.ts +3 -0
  287. package/node_modules/rimraf/dist/esm/fix-eperm.d.ts.map +1 -0
  288. package/node_modules/rimraf/dist/esm/fix-eperm.js +33 -0
  289. package/node_modules/rimraf/dist/esm/fix-eperm.js.map +1 -0
  290. package/node_modules/rimraf/dist/esm/fs.d.ts +15 -0
  291. package/node_modules/rimraf/dist/esm/fs.d.ts.map +1 -0
  292. package/node_modules/rimraf/dist/esm/fs.js +18 -0
  293. package/node_modules/rimraf/dist/esm/fs.js.map +1 -0
  294. package/node_modules/rimraf/dist/esm/ignore-enoent.d.ts +3 -0
  295. package/node_modules/rimraf/dist/esm/ignore-enoent.d.ts.map +1 -0
  296. package/node_modules/rimraf/dist/esm/ignore-enoent.js +19 -0
  297. package/node_modules/rimraf/dist/esm/ignore-enoent.js.map +1 -0
  298. package/node_modules/rimraf/dist/esm/index.d.ts +50 -0
  299. package/node_modules/rimraf/dist/esm/index.d.ts.map +1 -0
  300. package/node_modules/rimraf/dist/esm/index.js +70 -0
  301. package/node_modules/rimraf/dist/esm/index.js.map +1 -0
  302. package/node_modules/rimraf/dist/esm/opt-arg.d.ts +34 -0
  303. package/node_modules/rimraf/dist/esm/opt-arg.d.ts.map +1 -0
  304. package/node_modules/rimraf/dist/esm/opt-arg.js +46 -0
  305. package/node_modules/rimraf/dist/esm/opt-arg.js.map +1 -0
  306. package/node_modules/rimraf/dist/esm/package.json +3 -0
  307. package/node_modules/rimraf/dist/esm/path-arg.d.ts +4 -0
  308. package/node_modules/rimraf/dist/esm/path-arg.d.ts.map +1 -0
  309. package/node_modules/rimraf/dist/esm/path-arg.js +46 -0
  310. package/node_modules/rimraf/dist/esm/path-arg.js.map +1 -0
  311. package/node_modules/rimraf/dist/esm/readdir-or-error.d.ts +3 -0
  312. package/node_modules/rimraf/dist/esm/readdir-or-error.d.ts.map +1 -0
  313. package/node_modules/rimraf/dist/esm/readdir-or-error.js +14 -0
  314. package/node_modules/rimraf/dist/esm/readdir-or-error.js.map +1 -0
  315. package/node_modules/rimraf/dist/esm/retry-busy.d.ts +8 -0
  316. package/node_modules/rimraf/dist/esm/retry-busy.d.ts.map +1 -0
  317. package/node_modules/rimraf/dist/esm/retry-busy.js +60 -0
  318. package/node_modules/rimraf/dist/esm/retry-busy.js.map +1 -0
  319. package/node_modules/rimraf/dist/esm/rimraf-manual.d.ts +3 -0
  320. package/node_modules/rimraf/dist/esm/rimraf-manual.d.ts.map +1 -0
  321. package/node_modules/rimraf/dist/esm/rimraf-manual.js +5 -0
  322. package/node_modules/rimraf/dist/esm/rimraf-manual.js.map +1 -0
  323. package/node_modules/rimraf/dist/esm/rimraf-move-remove.d.ts +4 -0
  324. package/node_modules/rimraf/dist/esm/rimraf-move-remove.d.ts.map +1 -0
  325. package/node_modules/rimraf/dist/esm/rimraf-move-remove.js +133 -0
  326. package/node_modules/rimraf/dist/esm/rimraf-move-remove.js.map +1 -0
  327. package/node_modules/rimraf/dist/esm/rimraf-native.d.ts +4 -0
  328. package/node_modules/rimraf/dist/esm/rimraf-native.d.ts.map +1 -0
  329. package/node_modules/rimraf/dist/esm/rimraf-native.js +19 -0
  330. package/node_modules/rimraf/dist/esm/rimraf-native.js.map +1 -0
  331. package/node_modules/rimraf/dist/esm/rimraf-posix.d.ts +4 -0
  332. package/node_modules/rimraf/dist/esm/rimraf-posix.d.ts.map +1 -0
  333. package/node_modules/rimraf/dist/esm/rimraf-posix.js +98 -0
  334. package/node_modules/rimraf/dist/esm/rimraf-posix.js.map +1 -0
  335. package/node_modules/rimraf/dist/esm/rimraf-windows.d.ts +4 -0
  336. package/node_modules/rimraf/dist/esm/rimraf-windows.d.ts.map +1 -0
  337. package/node_modules/rimraf/dist/esm/rimraf-windows.js +154 -0
  338. package/node_modules/rimraf/dist/esm/rimraf-windows.js.map +1 -0
  339. package/node_modules/rimraf/dist/esm/use-native.d.ts +4 -0
  340. package/node_modules/rimraf/dist/esm/use-native.d.ts.map +1 -0
  341. package/node_modules/rimraf/dist/esm/use-native.js +15 -0
  342. package/node_modules/rimraf/dist/esm/use-native.js.map +1 -0
  343. package/node_modules/rimraf/package.json +92 -0
  344. package/package.json +152 -0
  345. package/scripts/install-scanners.js +258 -0
  346. package/scripts/watchdog.js +147 -0
  347. package/src/analyzers/CSSAnalyzer.js +297 -0
  348. package/src/analyzers/ConfigValidator.js +690 -0
  349. package/src/analyzers/ESLintAnalyzer.js +320 -0
  350. package/src/analyzers/JavaScriptAnalyzer.js +261 -0
  351. package/src/analyzers/PrettierFormatter.js +247 -0
  352. package/src/analyzers/PythonAnalyzer.js +283 -0
  353. package/src/analyzers/SecurityAnalyzer.js +729 -0
  354. package/src/analyzers/SparrowAnalyzer.js +341 -0
  355. package/src/analyzers/TypeScriptAnalyzer.js +247 -0
  356. package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
  357. package/src/analyzers/codeCloneDetector/detector.js +250 -0
  358. package/src/analyzers/codeCloneDetector/index.js +192 -0
  359. package/src/analyzers/codeCloneDetector/parser.js +199 -0
  360. package/src/analyzers/codeCloneDetector/reporter.js +148 -0
  361. package/src/analyzers/codeCloneDetector/scanner.js +88 -0
  362. package/src/core/agentPool.js +1957 -0
  363. package/src/core/agentScheduler.js +3212 -0
  364. package/src/core/contextManager.js +709 -0
  365. package/src/core/flowExecutor.js +928 -0
  366. package/src/core/messageProcessor.js +808 -0
  367. package/src/core/orchestrator.js +584 -0
  368. package/src/core/stateManager.js +1500 -0
  369. package/src/index.js +972 -0
  370. package/src/interfaces/cli.js +553 -0
  371. package/src/interfaces/terminal/__tests__/smoke/advancedFeatures.test.js +208 -0
  372. package/src/interfaces/terminal/__tests__/smoke/agentControl.test.js +236 -0
  373. package/src/interfaces/terminal/__tests__/smoke/agents.test.js +138 -0
  374. package/src/interfaces/terminal/__tests__/smoke/components.test.js +137 -0
  375. package/src/interfaces/terminal/__tests__/smoke/connection.test.js +350 -0
  376. package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -0
  377. package/src/interfaces/terminal/__tests__/smoke/imports.test.js +332 -0
  378. package/src/interfaces/terminal/__tests__/smoke/messages.test.js +256 -0
  379. package/src/interfaces/terminal/__tests__/smoke/tools.test.js +388 -0
  380. package/src/interfaces/terminal/api/apiClient.js +299 -0
  381. package/src/interfaces/terminal/api/messageRouter.js +262 -0
  382. package/src/interfaces/terminal/api/session.js +266 -0
  383. package/src/interfaces/terminal/api/websocket.js +497 -0
  384. package/src/interfaces/terminal/components/AgentCreator.js +705 -0
  385. package/src/interfaces/terminal/components/AgentEditor.js +678 -0
  386. package/src/interfaces/terminal/components/AgentSwitcher.js +330 -0
  387. package/src/interfaces/terminal/components/ErrorBoundary.js +92 -0
  388. package/src/interfaces/terminal/components/ErrorPanel.js +264 -0
  389. package/src/interfaces/terminal/components/Header.js +28 -0
  390. package/src/interfaces/terminal/components/HelpPanel.js +231 -0
  391. package/src/interfaces/terminal/components/InputBox.js +118 -0
  392. package/src/interfaces/terminal/components/Layout.js +603 -0
  393. package/src/interfaces/terminal/components/LoadingSpinner.js +71 -0
  394. package/src/interfaces/terminal/components/MessageList.js +281 -0
  395. package/src/interfaces/terminal/components/MultilineTextInput.js +251 -0
  396. package/src/interfaces/terminal/components/SearchPanel.js +265 -0
  397. package/src/interfaces/terminal/components/SettingsPanel.js +415 -0
  398. package/src/interfaces/terminal/components/StatusBar.js +65 -0
  399. package/src/interfaces/terminal/components/TextInput.js +127 -0
  400. package/src/interfaces/terminal/config/agentEditorConstants.js +227 -0
  401. package/src/interfaces/terminal/config/constants.js +393 -0
  402. package/src/interfaces/terminal/index.js +168 -0
  403. package/src/interfaces/terminal/state/useAgentControl.js +496 -0
  404. package/src/interfaces/terminal/state/useAgents.js +537 -0
  405. package/src/interfaces/terminal/state/useConnection.js +444 -0
  406. package/src/interfaces/terminal/state/useMessages.js +630 -0
  407. package/src/interfaces/terminal/state/useTools.js +554 -0
  408. package/src/interfaces/terminal/utils/debugLogger.js +44 -0
  409. package/src/interfaces/terminal/utils/settingsStorage.js +232 -0
  410. package/src/interfaces/terminal/utils/theme.js +85 -0
  411. package/src/interfaces/webServer.js +5457 -0
  412. package/src/modules/fileExplorer/controller.js +413 -0
  413. package/src/modules/fileExplorer/index.js +37 -0
  414. package/src/modules/fileExplorer/middleware.js +92 -0
  415. package/src/modules/fileExplorer/routes.js +158 -0
  416. package/src/modules/fileExplorer/types.js +44 -0
  417. package/src/services/agentActivityService.js +399 -0
  418. package/src/services/aiService.js +2618 -0
  419. package/src/services/apiKeyManager.js +334 -0
  420. package/src/services/benchmarkService.js +196 -0
  421. package/src/services/budgetService.js +565 -0
  422. package/src/services/contextInjectionService.js +268 -0
  423. package/src/services/conversationCompactionService.js +1103 -0
  424. package/src/services/credentialVault.js +685 -0
  425. package/src/services/errorHandler.js +810 -0
  426. package/src/services/fileAttachmentService.js +547 -0
  427. package/src/services/flowContextService.js +189 -0
  428. package/src/services/memoryService.js +521 -0
  429. package/src/services/modelRouterService.js +365 -0
  430. package/src/services/modelsService.js +323 -0
  431. package/src/services/ollamaService.js +452 -0
  432. package/src/services/portRegistry.js +336 -0
  433. package/src/services/portTracker.js +223 -0
  434. package/src/services/projectDetector.js +404 -0
  435. package/src/services/promptService.js +372 -0
  436. package/src/services/qualityInspector.js +796 -0
  437. package/src/services/scheduleService.js +725 -0
  438. package/src/services/serviceRegistry.js +386 -0
  439. package/src/services/skillsService.js +486 -0
  440. package/src/services/telegramService.js +920 -0
  441. package/src/services/tokenCountingService.js +316 -0
  442. package/src/services/visualEditorBridge.js +1033 -0
  443. package/src/services/visualEditorServer.js +1727 -0
  444. package/src/services/whatsappService.js +663 -0
  445. package/src/tools/__tests__/webTool.e2e.test.js +569 -0
  446. package/src/tools/__tests__/webTool.unit.test.js +195 -0
  447. package/src/tools/agentCommunicationTool.js +1343 -0
  448. package/src/tools/agentDelayTool.js +498 -0
  449. package/src/tools/asyncToolManager.js +604 -0
  450. package/src/tools/baseTool.js +887 -0
  451. package/src/tools/browserTool.js +897 -0
  452. package/src/tools/cloneDetectionTool.js +581 -0
  453. package/src/tools/codeMapTool.js +857 -0
  454. package/src/tools/dependencyResolverTool.js +1212 -0
  455. package/src/tools/docxTool.js +623 -0
  456. package/src/tools/excelTool.js +636 -0
  457. package/src/tools/fileContentReplaceTool.js +840 -0
  458. package/src/tools/fileTreeTool.js +833 -0
  459. package/src/tools/filesystemTool.js +1217 -0
  460. package/src/tools/helpTool.js +198 -0
  461. package/src/tools/imageTool.js +1034 -0
  462. package/src/tools/importAnalyzerTool.js +1056 -0
  463. package/src/tools/jobDoneTool.js +388 -0
  464. package/src/tools/memoryTool.js +554 -0
  465. package/src/tools/pdfTool.js +627 -0
  466. package/src/tools/seekTool.js +883 -0
  467. package/src/tools/skillsTool.js +276 -0
  468. package/src/tools/staticAnalysisTool.js +2146 -0
  469. package/src/tools/taskManagerTool.js +2836 -0
  470. package/src/tools/terminalTool.js +2486 -0
  471. package/src/tools/userPromptTool.js +474 -0
  472. package/src/tools/videoTool.js +1139 -0
  473. package/src/tools/visionTool.js +507 -0
  474. package/src/tools/visualEditorTool.js +1175 -0
  475. package/src/tools/webTool.js +3114 -0
  476. package/src/tools/whatsappTool.js +457 -0
  477. package/src/types/agent.js +519 -0
  478. package/src/types/contextReference.js +972 -0
  479. package/src/types/conversation.js +730 -0
  480. package/src/types/toolCommand.js +747 -0
  481. package/src/utilities/attachmentValidator.js +288 -0
  482. package/src/utilities/browserStealth.js +630 -0
  483. package/src/utilities/configManager.js +618 -0
  484. package/src/utilities/constants.js +870 -0
  485. package/src/utilities/directoryAccessManager.js +566 -0
  486. package/src/utilities/fileProcessor.js +307 -0
  487. package/src/utilities/humanBehavior.js +453 -0
  488. package/src/utilities/jsonRepair.js +242 -0
  489. package/src/utilities/logger.js +436 -0
  490. package/src/utilities/platformUtils.js +255 -0
  491. package/src/utilities/platformUtils.test.js +98 -0
  492. package/src/utilities/stealthConstants.js +377 -0
  493. package/src/utilities/structuredFileValidator.js +699 -0
  494. package/src/utilities/tagParser.js +878 -0
  495. package/src/utilities/toolConstants.js +415 -0
  496. package/src/utilities/userDataDir.js +300 -0
  497. package/web-ui/build/brands/autopilot/favicon.svg +1 -0
  498. package/web-ui/build/brands/autopilot/logo.webp +0 -0
  499. package/web-ui/build/brands/onbuzz/favicon.svg +1 -0
  500. package/web-ui/build/brands/onbuzz/logo-text.webp +0 -0
  501. package/web-ui/build/brands/onbuzz/logo.webp +0 -0
  502. package/web-ui/build/index.html +15 -0
  503. package/web-ui/build/logo.png +0 -0
  504. package/web-ui/build/logo2.png +0 -0
  505. package/web-ui/build/static/index-SmQFfvBs.js +746 -0
  506. package/web-ui/build/static/index-V2ySwjHp.css +1 -0
@@ -0,0 +1,1034 @@
1
+ /**
2
+ * @file tools/imageTool.js
3
+ * @description Tool for generating images using AI models (resolved dynamically from backend)
4
+ */
5
+
6
+ import path from 'path';
7
+ import os from 'os';
8
+ import { promises as fs } from 'fs';
9
+ import { BaseTool } from './baseTool.js';
10
+
11
+ /**
12
+ * Configuration constants for image generation
13
+ */
14
+ const IMAGE_CONFIG = {
15
+ DEFAULT_MODEL: null, // Resolved dynamically from modelsService via aiService
16
+ DEFAULT_SIZE: '1024x1024',
17
+ DEFAULT_QUALITY: 'standard',
18
+ // Flux 1.1 Pro size constraints
19
+ SIZE_MIN: 256,
20
+ SIZE_MAX: 1440,
21
+ SIZE_INCREMENT: 32, // Must be multiples of 32
22
+ // Common presets for convenience (all valid per constraints above)
23
+ SIZE_PRESETS: {
24
+ 'square': '1024x1024',
25
+ 'square_hd': '1440x1440',
26
+ 'portrait': '1024x1440',
27
+ 'landscape': '1440x1024',
28
+ 'portrait_4_3': '768x1024',
29
+ 'landscape_4_3': '1024x768',
30
+ 'small': '512x512'
31
+ },
32
+ VALID_FORMATS: ['png', 'jpg', 'jpeg', 'webp'],
33
+ MAX_CONCURRENT: 3,
34
+ QUEUE_LIMIT: 10,
35
+ TEMP_CLEANUP_MS: 3600000, // 1 hour
36
+ MAX_PROMPT_LENGTH: 4000,
37
+ DOWNLOAD_TIMEOUT: 60000 // 60 seconds
38
+ };
39
+
40
+ /**
41
+ * ImageTool - Generate images using AI models
42
+ * Supports queueing, async processing, and both temp/project directory storage
43
+ */
44
+ export class ImageTool extends BaseTool {
45
+ constructor(config = {}, logger = null) {
46
+ super(config, logger);
47
+
48
+ // Override tool ID
49
+ this.id = 'image-gen';
50
+
51
+ // Job queue and tracking
52
+ this.queue = [];
53
+ this.currentJob = null;
54
+ this.completedJobs = new Map();
55
+ this.isProcessing = false;
56
+
57
+ // AIService will be injected later
58
+ this.aiService = null;
59
+
60
+ // AgentPool will be injected later (for saving to conversation history)
61
+ this.agentPool = null;
62
+
63
+ // Temp directory for images
64
+ this.tempDir = path.join(os.tmpdir(), 'loxia-images');
65
+
66
+ // Cleanup timers
67
+ this.cleanupTimers = new Map();
68
+ }
69
+
70
+ /**
71
+ * Set AI service for image generation
72
+ * @param {AIService} aiService - AI service instance
73
+ */
74
+ setAIService(aiService) {
75
+ this.aiService = aiService;
76
+ this.logger?.info('AI Service set for ImageTool');
77
+ }
78
+
79
+ /**
80
+ * Set Agent Pool for saving results to conversation history
81
+ * @param {AgentPool} agentPool - AgentPool instance
82
+ */
83
+ setAgentPool(agentPool) {
84
+ this.agentPool = agentPool;
85
+ this.logger?.info('AgentPool set for ImageTool');
86
+ }
87
+
88
+ /**
89
+ * Get tool description for agent system prompt
90
+ * @returns {string} Formatted tool description
91
+ */
92
+ getDescription() {
93
+ return `Tool: Image Generator - Generate and edit images using AI models
94
+
95
+ **Purpose:** Generate images from text descriptions, edit existing images, and create images with transparency. Images are saved to files and displayed in chat.
96
+
97
+ **CRITICAL: Automatic Execution**
98
+ - ANY \`\`\`json block with "toolId": "image-gen" will be EXECUTED IMMEDIATELY
99
+ - Just output the command when you want to generate or edit an image
100
+ - If generation fails, output a NEW command with corrections
101
+
102
+ **USAGE — Generate:**
103
+ \`\`\`json
104
+ {
105
+ "toolId": "image-gen",
106
+ "parameters": {
107
+ "prompt": "Detailed description of the image",
108
+ "outputPath": "images/filename.png",
109
+ "size": "1024x1024"
110
+ }
111
+ }
112
+ \`\`\`
113
+
114
+ **USAGE — Generate with Transparency:**
115
+ \`\`\`json
116
+ {
117
+ "toolId": "image-gen",
118
+ "parameters": {
119
+ "prompt": "A cute fox mascot on a transparent background",
120
+ "model": "gpt-image-1.5",
121
+ "transparency": true,
122
+ "outputPath": "images/fox-mascot.png"
123
+ }
124
+ }
125
+ \`\`\`
126
+
127
+ **USAGE — Edit Existing Image:**
128
+ \`\`\`json
129
+ {
130
+ "toolId": "image-gen",
131
+ "parameters": {
132
+ "prompt": "Remove the background and make it transparent",
133
+ "sourceImage": "images/photo.png",
134
+ "model": "gpt-image-1.5",
135
+ "outputPath": "images/photo-nobg.png"
136
+ }
137
+ }
138
+ \`\`\`
139
+
140
+ **Parameters:**
141
+ - **prompt** (required): Description of image to generate OR editing instruction
142
+ - **outputPath** (optional): Path to save image (permanent). Omit for temp file.
143
+ - **size** (optional): Custom WIDTHxHEIGHT or preset name (default: 1024x1024)
144
+ - For Flux models: 256-1440px, multiples of 32 (e.g., 1024x768, 1280x960)
145
+ - For gpt-image-1.5: 1024x1024, 1024x1536, 1536x1024, or "auto"
146
+ - Presets: square (1024x1024), square_hd (1440x1440), portrait (1024x1440), landscape (1440x1024), portrait_4_3 (768x1024), landscape_4_3 (1024x768), small (512x512)
147
+ - **quality** (optional): "standard" or "hd" (default: standard)
148
+ - **model** (optional): Use "gpt-image-1.5" for transparency and editing capabilities (default: auto)
149
+ - **sourceImage** (optional): Path to source image for editing (enables edit mode)
150
+ - **mask** (optional): Path to mask image for editing (white=edit area, black=keep area)
151
+ - **transparency** (optional): true to generate image with transparent background (auto-selects gpt-image-1.5)
152
+
153
+ **Notes:**
154
+ - Images take 15-30 seconds to generate
155
+ - Be descriptive in prompts for better results
156
+ - Use **gpt-image-1.5** when you need: transparent backgrounds, image editing, or image-to-image transformations
157
+ - Max ${IMAGE_CONFIG.QUEUE_LIMIT} images in queue`;
158
+ }
159
+
160
+ /**
161
+ * Parse image generation parameters
162
+ * @param {string|Object} content - Raw content or parsed object
163
+ * @returns {Object} Parsed parameters
164
+ */
165
+ parseParameters(content) {
166
+ // Handle JSON format
167
+ if (typeof content === 'object' && content !== null) {
168
+ return this._parseJSONParams(content);
169
+ }
170
+
171
+ // Handle string format
172
+ if (typeof content === 'string') {
173
+ const trimmed = content.trim();
174
+
175
+ // Try to parse as JSON first
176
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
177
+ try {
178
+ const parsed = JSON.parse(trimmed);
179
+ return this._parseJSONParams(parsed);
180
+ } catch (err) {
181
+ // Not valid JSON, fall through to XML parsing
182
+ }
183
+ }
184
+
185
+ // Parse as XML
186
+ return this._parseXMLParams(content);
187
+ }
188
+
189
+ throw new Error('Invalid parameter format');
190
+ }
191
+
192
+ /**
193
+ * Parse JSON parameters
194
+ * @private
195
+ */
196
+ _parseJSONParams(obj) {
197
+ // Handle parameters wrapper (when called via toolId/parameters structure)
198
+ if (obj.parameters) {
199
+ obj = obj.parameters;
200
+ }
201
+
202
+ // Check for batch mode
203
+ if (obj.batch && Array.isArray(obj.batch)) {
204
+ return {
205
+ batch: true,
206
+ images: obj.batch.map(img => this._parseImageParams(img))
207
+ };
208
+ }
209
+
210
+ return {
211
+ batch: false,
212
+ images: [this._parseImageParams(obj)]
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Parse XML parameters
218
+ * @private
219
+ */
220
+ _parseXMLParams(content) {
221
+ const params = { batch: false, images: [] };
222
+
223
+ // Check for batch mode
224
+ const batchMatch = /<batch>([\s\S]*?)<\/batch>/i.exec(content);
225
+
226
+ if (batchMatch) {
227
+ params.batch = true;
228
+ const batchContent = batchMatch[1];
229
+
230
+ // Extract individual <image> blocks
231
+ const imageRegex = /<image>([\s\S]*?)<\/image>/gi;
232
+ let match;
233
+
234
+ while ((match = imageRegex.exec(batchContent)) !== null) {
235
+ params.images.push(this._parseXMLImage(match[1]));
236
+ }
237
+ } else {
238
+ // Single image mode
239
+ params.images.push(this._parseXMLImage(content));
240
+ }
241
+
242
+ if (params.images.length === 0) {
243
+ throw new Error('No valid image parameters found');
244
+ }
245
+
246
+ return params;
247
+ }
248
+
249
+ /**
250
+ * Parse single image parameters from object
251
+ * @private
252
+ */
253
+ _parseImageParams(obj) {
254
+ const outputPath = obj.outputPath || obj['output-path'] || null;
255
+
256
+ return {
257
+ prompt: obj.prompt || '',
258
+ outputPath: outputPath,
259
+ saveToProject: outputPath !== null, // If path specified, save to project
260
+ model: obj.model || IMAGE_CONFIG.DEFAULT_MODEL,
261
+ size: obj.size || IMAGE_CONFIG.DEFAULT_SIZE,
262
+ quality: obj.quality || IMAGE_CONFIG.DEFAULT_QUALITY,
263
+ sourceImage: obj.sourceImage || null, // Edit mode: path to source image
264
+ mask: obj.mask || null, // Edit mode: path to mask image
265
+ transparency: obj.transparency || false // Generate with transparent background
266
+ };
267
+ }
268
+
269
+ /**
270
+ * Parse single image parameters from XML string
271
+ * @private
272
+ */
273
+ _parseXMLImage(xmlContent) {
274
+ const extractTag = (tag) => {
275
+ const regex = new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`, 'i');
276
+ const match = regex.exec(xmlContent);
277
+ return match ? match[1].trim() : null;
278
+ };
279
+
280
+ const outputPath = extractTag('output-path') || null;
281
+
282
+ return {
283
+ prompt: extractTag('prompt') || '',
284
+ outputPath: outputPath,
285
+ saveToProject: outputPath !== null, // If path specified, save to project
286
+ model: extractTag('model') || IMAGE_CONFIG.DEFAULT_MODEL,
287
+ size: extractTag('size') || IMAGE_CONFIG.DEFAULT_SIZE,
288
+ quality: extractTag('quality') || IMAGE_CONFIG.DEFAULT_QUALITY,
289
+ sourceImage: extractTag('source-image') || null,
290
+ mask: extractTag('mask') || null,
291
+ transparency: extractTag('transparency') === 'true'
292
+ };
293
+ }
294
+
295
+ /**
296
+ * Execute image generation
297
+ * @param {Object|string} params - Parsed parameters object OR raw XML/JSON string
298
+ * @param {Object} context - Execution context
299
+ * @returns {Promise<Object>} Execution result
300
+ */
301
+ async execute(params, context = {}) {
302
+ try {
303
+ const { agentId, projectDir, directoryAccess, sessionId } = context;
304
+
305
+ // Auto-detect and parse inputs (from TagParser or direct call)
306
+ // parseParameters() normalizes input to { batch: bool, images: [...] } format
307
+ if (typeof params === 'string') {
308
+ this.logger?.info('ImageTool: Auto-parsing string parameters');
309
+ params = this.parseParameters(params);
310
+ } else if (typeof params === 'object' && params !== null && !params.images) {
311
+ // Object params without 'images' array - needs parsing to normalize structure
312
+ this.logger?.info('ImageTool: Normalizing object parameters');
313
+ params = this.parseParameters(params);
314
+ }
315
+
316
+ // Validate parameters
317
+ this._validateParameters(params);
318
+
319
+ // Queue images
320
+ const jobIds = [];
321
+
322
+ for (const imageParams of params.images) {
323
+ // Create job
324
+ const jobId = this._generateJobId();
325
+
326
+ const job = {
327
+ jobId,
328
+ agentId,
329
+ sessionId,
330
+ prompt: imageParams.prompt,
331
+ outputPath: imageParams.outputPath,
332
+ saveToProject: imageParams.saveToProject,
333
+ model: imageParams.model,
334
+ size: imageParams.size,
335
+ quality: imageParams.quality,
336
+ projectDir: projectDir || process.cwd(),
337
+ directoryAccess,
338
+ status: 'queued',
339
+ createdAt: new Date().toISOString()
340
+ };
341
+
342
+ // Check queue limit
343
+ if (this.queue.length >= IMAGE_CONFIG.QUEUE_LIMIT) {
344
+ return {
345
+ success: false,
346
+ error: `Queue limit reached (${IMAGE_CONFIG.QUEUE_LIMIT} images). Please wait for current jobs to complete.`,
347
+ queueLength: this.queue.length
348
+ };
349
+ }
350
+
351
+ this.queue.push(job);
352
+ jobIds.push(jobId);
353
+
354
+ this.logger?.info(`Image generation job queued: ${jobId}`, {
355
+ prompt: imageParams.prompt.substring(0, 50) + '...',
356
+ queuePosition: this.queue.length
357
+ });
358
+ }
359
+
360
+ // Start processing if not already running
361
+ if (!this.isProcessing) {
362
+ this._processQueue().catch(err => {
363
+ this.logger?.error('Queue processing error:', err);
364
+ });
365
+ }
366
+
367
+ // Return immediate response
368
+ return {
369
+ success: true,
370
+ jobIds,
371
+ queueLength: this.queue.length,
372
+ message: params.batch
373
+ ? `${jobIds.length} images queued for generation`
374
+ : 'Image queued for generation',
375
+ estimatedWaitTime: this._estimateWaitTime()
376
+ };
377
+
378
+ } catch (error) {
379
+ this.logger?.error('Image generation error:', error);
380
+ return {
381
+ success: false,
382
+ error: error.message
383
+ };
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Validate parameters
389
+ * @private
390
+ */
391
+ _validateParameters(params) {
392
+ if (!params.images || params.images.length === 0) {
393
+ throw new Error('No images specified');
394
+ }
395
+
396
+ for (const img of params.images) {
397
+ if (!img.prompt || img.prompt.trim().length === 0) {
398
+ throw new Error('Image prompt is required');
399
+ }
400
+
401
+ if (img.prompt.length > IMAGE_CONFIG.MAX_PROMPT_LENGTH) {
402
+ throw new Error(`Prompt too long (max ${IMAGE_CONFIG.MAX_PROMPT_LENGTH} characters)`);
403
+ }
404
+
405
+ if (img.size) {
406
+ // Check for preset names first
407
+ if (IMAGE_CONFIG.SIZE_PRESETS[img.size]) {
408
+ img.size = IMAGE_CONFIG.SIZE_PRESETS[img.size];
409
+ }
410
+
411
+ // Validate WxH format
412
+ const sizeMatch = img.size.match(/^(\d+)x(\d+)$/);
413
+ if (!sizeMatch) {
414
+ throw new Error(`Invalid size format: ${img.size}. Use WIDTHxHEIGHT (e.g., 1024x768) or preset: ${Object.keys(IMAGE_CONFIG.SIZE_PRESETS).join(', ')}`);
415
+ }
416
+
417
+ const width = parseInt(sizeMatch[1], 10);
418
+ const height = parseInt(sizeMatch[2], 10);
419
+
420
+ // Validate constraints
421
+ const { SIZE_MIN, SIZE_MAX, SIZE_INCREMENT } = IMAGE_CONFIG;
422
+
423
+ if (width < SIZE_MIN || width > SIZE_MAX) {
424
+ throw new Error(`Width ${width} out of range. Must be ${SIZE_MIN}-${SIZE_MAX}px.`);
425
+ }
426
+ if (height < SIZE_MIN || height > SIZE_MAX) {
427
+ throw new Error(`Height ${height} out of range. Must be ${SIZE_MIN}-${SIZE_MAX}px.`);
428
+ }
429
+ if (width % SIZE_INCREMENT !== 0) {
430
+ throw new Error(`Width ${width} must be a multiple of ${SIZE_INCREMENT}.`);
431
+ }
432
+ if (height % SIZE_INCREMENT !== 0) {
433
+ throw new Error(`Height ${height} must be a multiple of ${SIZE_INCREMENT}.`);
434
+ }
435
+ }
436
+
437
+ if (img.outputPath) {
438
+ const ext = path.extname(img.outputPath).toLowerCase().replace('.', '');
439
+ if (ext && !IMAGE_CONFIG.VALID_FORMATS.includes(ext)) {
440
+ throw new Error(`Invalid format: ${ext}. Valid formats: ${IMAGE_CONFIG.VALID_FORMATS.join(', ')}`);
441
+ }
442
+ }
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Process the image generation queue
448
+ * @private
449
+ */
450
+ async _processQueue() {
451
+ if (this.isProcessing) {
452
+ return;
453
+ }
454
+
455
+ this.isProcessing = true;
456
+
457
+ while (this.queue.length > 0) {
458
+ const job = this.queue.shift();
459
+ this.currentJob = job;
460
+
461
+ this.logger?.info(`Processing image generation job: ${job.jobId}`);
462
+
463
+ try {
464
+ job.status = 'processing';
465
+
466
+ // Generate the image
467
+ const result = await this._generateImage(job);
468
+
469
+ job.status = 'completed';
470
+ job.result = result;
471
+ job.completedAt = new Date().toISOString();
472
+
473
+ // Store completed job
474
+ this.completedJobs.set(job.jobId, job);
475
+
476
+ // Broadcast result via WebSocket
477
+ if (global.loxiaWebServer && job.sessionId) {
478
+ // Determine which URL to use: local saved file or temporary AI URL
479
+ let imageUrl;
480
+ let isTemporary = false;
481
+
482
+ if (result.savedToDisk && result.resolvedOutputPath) {
483
+ // Image was saved successfully - use our server endpoint
484
+ imageUrl = this._convertToWebUrl(result.resolvedOutputPath, job.sessionId);
485
+ } else if (result.temporaryUrl) {
486
+ // Download failed - use temporary AI-generated URL (expires in ~1 hour)
487
+ imageUrl = result.temporaryUrl;
488
+ isTemporary = true;
489
+ }
490
+
491
+ global.loxiaWebServer.broadcastToSession(job.sessionId, {
492
+ type: 'imageGenerated',
493
+ agentId: job.agentId,
494
+ jobId: job.jobId,
495
+ imageUrl,
496
+ localPath: result.resolvedOutputPath,
497
+ prompt: job.prompt,
498
+ success: true,
499
+ isTemporary, // Indicates if URL will expire
500
+ savedToDisk: result.savedToDisk,
501
+ downloadError: result.downloadError, // Include error if save failed
502
+ timestamp: job.completedAt
503
+ });
504
+
505
+ this.logger?.info('Image generation broadcast sent', {
506
+ jobId: job.jobId,
507
+ imageUrl,
508
+ localPath: result.resolvedOutputPath,
509
+ savedToDisk: result.savedToDisk,
510
+ isTemporary
511
+ });
512
+
513
+ // Save image result to conversation history for persistence
514
+ if (this.agentPool && job.agentId) {
515
+ try {
516
+ const agent = await this.agentPool.getAgent(job.agentId);
517
+ if (agent) {
518
+ // Build message content with warnings if applicable
519
+ let content = `Image generated: ${job.prompt}`;
520
+
521
+ if (isTemporary) {
522
+ content += '\n\n⚠️ **Warning:** Image is using a temporary URL (expires in ~1 hour). Failed to save to disk.';
523
+ if (result.downloadError) {
524
+ content += `\n**Error:** ${result.downloadError}`;
525
+ }
526
+ }
527
+
528
+ // Create image message for conversation history
529
+ const imageMessage = {
530
+ id: `img-result-${job.jobId}`,
531
+ role: 'assistant',
532
+ content,
533
+ timestamp: job.completedAt,
534
+ imageUrl, // CRITICAL: Include imageUrl so it persists across page refreshes
535
+ type: 'image-result',
536
+ toolId: 'image-gen',
537
+ status: 'completed',
538
+ isTemporary: isTemporary || false,
539
+ savedToDisk: result.savedToDisk !== false
540
+ };
541
+
542
+ // Add to full conversation
543
+ agent.conversations.full.messages.push(imageMessage);
544
+ agent.conversations.full.lastUpdated = job.completedAt;
545
+
546
+ // Add to current model conversation if exists
547
+ if (agent.currentModel && agent.conversations[agent.currentModel]) {
548
+ agent.conversations[agent.currentModel].messages.push(imageMessage);
549
+ agent.conversations[agent.currentModel].lastUpdated = job.completedAt;
550
+ }
551
+
552
+ // Update agent activity
553
+ agent.lastActivity = job.completedAt;
554
+
555
+ // Persist agent state to save conversation history
556
+ await this.agentPool.persistAgentState(job.agentId);
557
+
558
+ this.logger?.info('Image result saved to conversation history', {
559
+ agentId: job.agentId,
560
+ jobId: job.jobId,
561
+ messageId: imageMessage.id
562
+ });
563
+
564
+ // Queue tool result so agent "sees" the completion and can continue
565
+ await this.agentPool.addToolResult(job.agentId, {
566
+ toolId: 'image-gen',
567
+ status: 'completed',
568
+ result: {
569
+ jobId: job.jobId,
570
+ prompt: job.prompt,
571
+ imageUrl,
572
+ localPath: result.resolvedOutputPath,
573
+ savedToDisk: result.savedToDisk,
574
+ isTemporary
575
+ },
576
+ timestamp: job.completedAt
577
+ });
578
+
579
+ this.logger?.info('Image result queued for agent processing', {
580
+ agentId: job.agentId,
581
+ jobId: job.jobId
582
+ });
583
+ }
584
+ } catch (error) {
585
+ this.logger?.error('Failed to save image result to conversation history', {
586
+ error: error.message,
587
+ agentId: job.agentId,
588
+ jobId: job.jobId
589
+ });
590
+ }
591
+ }
592
+ }
593
+
594
+ this.logger?.info(`Image generation completed: ${job.jobId}`, {
595
+ outputPath: result.resolvedOutputPath
596
+ });
597
+
598
+ } catch (error) {
599
+ this.logger?.error(`Image generation failed: ${job.jobId}`, error);
600
+
601
+ job.status = 'failed';
602
+ job.error = error.message;
603
+ job.completedAt = new Date().toISOString();
604
+
605
+ this.completedJobs.set(job.jobId, job);
606
+
607
+ // Broadcast error to specific session
608
+ if (global.loxiaWebServer && job.sessionId) {
609
+ global.loxiaWebServer.broadcastToSession(job.sessionId, {
610
+ type: 'imageGenerated',
611
+ jobId: job.jobId,
612
+ agentId: job.agentId,
613
+ prompt: job.prompt,
614
+ success: false,
615
+ error: error.message,
616
+ timestamp: job.completedAt
617
+ });
618
+
619
+ this.logger?.info('Image generation error broadcast sent', {
620
+ jobId: job.jobId,
621
+ sessionId: job.sessionId,
622
+ error: error.message
623
+ });
624
+ }
625
+
626
+ // Save error message to conversation history
627
+ if (this.agentPool && job.agentId) {
628
+ try {
629
+ const agent = await this.agentPool.getAgent(job.agentId);
630
+ if (agent) {
631
+ // Create error message for conversation history
632
+ const errorMessage = {
633
+ id: `img-error-${job.jobId}`,
634
+ role: 'system',
635
+ content: `❌ Image generation failed: ${error.message}\n\n**Prompt:** ${job.prompt}`,
636
+ timestamp: job.completedAt,
637
+ type: 'error',
638
+ toolId: 'image-gen',
639
+ status: 'failed',
640
+ jobId: job.jobId
641
+ };
642
+
643
+ // Add to full conversation
644
+ agent.conversations.full.messages.push(errorMessage);
645
+ agent.conversations.full.lastUpdated = job.completedAt;
646
+
647
+ // Add to current model conversation if exists
648
+ if (agent.currentModel && agent.conversations[agent.currentModel]) {
649
+ agent.conversations[agent.currentModel].messages.push(errorMessage);
650
+ agent.conversations[agent.currentModel].lastUpdated = job.completedAt;
651
+ }
652
+
653
+ // Update agent activity
654
+ agent.lastActivity = job.completedAt;
655
+
656
+ // Persist agent state to save conversation history
657
+ await this.agentPool.persistAgentState(job.agentId);
658
+
659
+ this.logger?.info('Image error saved to conversation history', {
660
+ agentId: job.agentId,
661
+ jobId: job.jobId,
662
+ error: error.message
663
+ });
664
+
665
+ // Queue tool result so agent "sees" the failure and can handle it
666
+ await this.agentPool.addToolResult(job.agentId, {
667
+ toolId: 'image-gen',
668
+ status: 'failed',
669
+ error: error.message,
670
+ result: {
671
+ jobId: job.jobId,
672
+ prompt: job.prompt
673
+ },
674
+ timestamp: job.completedAt
675
+ });
676
+
677
+ this.logger?.info('Image error queued for agent processing', {
678
+ agentId: job.agentId,
679
+ jobId: job.jobId
680
+ });
681
+ }
682
+ } catch (historyError) {
683
+ this.logger?.error('Failed to save image error to conversation history', {
684
+ error: historyError.message,
685
+ agentId: job.agentId,
686
+ jobId: job.jobId
687
+ });
688
+ }
689
+ }
690
+ }
691
+ }
692
+
693
+ this.isProcessing = false;
694
+ this.currentJob = null;
695
+ }
696
+
697
+ /**
698
+ * Generate a single image
699
+ * @private
700
+ */
701
+ async _generateImage(job) {
702
+ // Check if AI service is available
703
+ if (!this.aiService) {
704
+ throw new Error('AI service not available. Image generation requires AI service.');
705
+ }
706
+
707
+ // Auto-select gpt-image-1.5 for transparency or editing
708
+ if (job.transparency && !job.model) {
709
+ job.model = 'gpt-image-1.5';
710
+ }
711
+ if (job.sourceImage && !job.model) {
712
+ job.model = 'gpt-image-1.5';
713
+ }
714
+
715
+ // Resolve output path
716
+ const resolvedOutputPath = await this._resolveOutputPath(job);
717
+
718
+ // Ensure directory exists
719
+ const outputDir = path.dirname(resolvedOutputPath);
720
+ await fs.mkdir(outputDir, { recursive: true });
721
+
722
+ let aiResult;
723
+
724
+ // Edit mode: source image provided
725
+ if (job.sourceImage) {
726
+ this.logger?.info(`Editing image with ${job.model}`, {
727
+ sourceImage: job.sourceImage,
728
+ hasMask: !!job.mask
729
+ });
730
+
731
+ // Resolve source image path relative to project directory
732
+ const projectDir = job.projectDir || process.cwd();
733
+ const resolvedSourcePath = path.isAbsolute(job.sourceImage)
734
+ ? job.sourceImage
735
+ : path.resolve(projectDir, job.sourceImage);
736
+
737
+ // Read source image and convert to base64
738
+ const imageBuffer = await fs.readFile(resolvedSourcePath);
739
+ const imageBase64 = imageBuffer.toString('base64');
740
+
741
+ // Read mask if provided
742
+ let maskBase64 = null;
743
+ if (job.mask) {
744
+ const resolvedMaskPath = path.isAbsolute(job.mask)
745
+ ? job.mask
746
+ : path.resolve(projectDir, job.mask);
747
+ const maskBuffer = await fs.readFile(resolvedMaskPath);
748
+ maskBase64 = maskBuffer.toString('base64');
749
+ }
750
+
751
+ aiResult = await this.aiService.editImage(job.prompt, imageBase64, {
752
+ model: job.model,
753
+ maskBase64,
754
+ sessionId: job.sessionId
755
+ });
756
+ } else {
757
+ // Standard generation mode
758
+ this.logger?.info(`Generating image with ${job.model}`, {
759
+ size: job.size,
760
+ quality: job.quality,
761
+ transparency: job.transparency
762
+ });
763
+
764
+ const options = {
765
+ model: job.model,
766
+ size: job.size,
767
+ quality: job.quality,
768
+ responseFormat: 'url', // Prefer URL, but Flux/GPT-Image returns b64_json
769
+ sessionId: job.sessionId // CRITICAL: Pass sessionId for API key retrieval
770
+ };
771
+
772
+ aiResult = await this.aiService.generateImage(job.prompt, options);
773
+ }
774
+
775
+ // AIService returns: { url, b64_json, model, requestId, revisedPrompt }
776
+ // Flux API returns b64_json (base64 encoded image)
777
+ const imageUrl = aiResult?.url || aiResult?.imageUrl;
778
+ const b64Json = aiResult?.b64_json;
779
+
780
+ if (!imageUrl && !b64Json) {
781
+ throw new Error('No image data received from AI service (no URL or base64)');
782
+ }
783
+
784
+ // Try to save image to disk
785
+ let savedToDisk = false;
786
+ let downloadError = null;
787
+ let displayUrl = imageUrl; // URL for web display
788
+
789
+ try {
790
+ if (b64Json) {
791
+ // Flux response: Save base64 directly to disk
792
+ this.logger?.info('Saving base64 image directly to disk (Flux response)');
793
+ const imageBuffer = Buffer.from(b64Json, 'base64');
794
+ await fs.writeFile(resolvedOutputPath, imageBuffer);
795
+ savedToDisk = true;
796
+
797
+ // For web display, we'll use our local server endpoint (set below)
798
+ displayUrl = null; // Will be converted to web URL later
799
+ } else if (imageUrl) {
800
+ // URL response: Download from URL
801
+ this.logger?.info(`Downloading image from URL: ${imageUrl.substring(0, 50)}...`);
802
+ await this._downloadImage(imageUrl, resolvedOutputPath);
803
+ savedToDisk = true;
804
+ displayUrl = imageUrl;
805
+ }
806
+
807
+ // Schedule cleanup if temp file
808
+ if (savedToDisk && !job.saveToProject) {
809
+ this._scheduleCleanup(resolvedOutputPath, job.jobId);
810
+ }
811
+ } catch (error) {
812
+ // Save failed, but we might still have a temporary URL
813
+ downloadError = error.message;
814
+ this.logger?.warn(`Failed to save image to disk: ${error.message}`);
815
+
816
+ if (!imageUrl) {
817
+ // No URL fallback for Flux - this is a real failure
818
+ throw new Error(`Failed to save image: ${error.message}`);
819
+ }
820
+ }
821
+
822
+ return {
823
+ jobId: job.jobId,
824
+ prompt: job.prompt,
825
+ outputPath: job.outputPath,
826
+ resolvedOutputPath: savedToDisk ? resolvedOutputPath : null,
827
+ temporaryUrl: displayUrl, // AI-generated URL (valid for ~1 hour) or null for Flux
828
+ savedToDisk,
829
+ downloadError,
830
+ success: true, // Image was generated successfully
831
+ model: aiResult.model || job.model,
832
+ size: job.size,
833
+ usage: aiResult.usage,
834
+ isBase64Response: !!b64Json // Flag to indicate Flux response
835
+ };
836
+ }
837
+
838
+ /**
839
+ * Resolve output path (temp or project directory)
840
+ * @private
841
+ */
842
+ async _resolveOutputPath(job) {
843
+ if (job.saveToProject) {
844
+ // Save to project directory
845
+ const projectDir = job.projectDir || process.cwd();
846
+
847
+ let outputPath = job.outputPath;
848
+ if (!outputPath) {
849
+ // Auto-generate filename
850
+ const timestamp = Date.now();
851
+ outputPath = `images/generated-${timestamp}.png`;
852
+ }
853
+
854
+ const resolvedPath = path.isAbsolute(outputPath)
855
+ ? path.normalize(outputPath)
856
+ : path.normalize(path.join(projectDir, outputPath));
857
+
858
+ // Security: Check for path traversal
859
+ if (!resolvedPath.startsWith(path.normalize(projectDir))) {
860
+ throw new Error('Path traversal detected');
861
+ }
862
+
863
+ // Check directory access if provided
864
+ if (job.directoryAccess) {
865
+ // Simple check - file must be within allowed directories
866
+ // Full implementation would use DirectoryAccessManager
867
+ const relativePath = path.relative(projectDir, resolvedPath);
868
+ if (relativePath.startsWith('..')) {
869
+ throw new Error('Access denied: path outside project directory');
870
+ }
871
+ }
872
+
873
+ return resolvedPath;
874
+ } else {
875
+ // Save to temp directory
876
+ await fs.mkdir(this.tempDir, { recursive: true });
877
+
878
+ let filename = job.outputPath
879
+ ? path.basename(job.outputPath)
880
+ : `generated-${job.jobId}.png`;
881
+
882
+ return path.join(this.tempDir, filename);
883
+ }
884
+ }
885
+
886
+ /**
887
+ * Download image from URL
888
+ * @private
889
+ */
890
+ async _downloadImage(imageUrl, outputPath) {
891
+ try {
892
+ const response = await fetch(imageUrl, {
893
+ signal: AbortSignal.timeout(IMAGE_CONFIG.DOWNLOAD_TIMEOUT)
894
+ });
895
+
896
+ if (!response.ok) {
897
+ throw new Error(`Failed to download image: HTTP ${response.status}`);
898
+ }
899
+
900
+ const buffer = Buffer.from(await response.arrayBuffer());
901
+ await fs.writeFile(outputPath, buffer);
902
+
903
+ this.logger?.info(`Image saved to: ${outputPath}`);
904
+
905
+ } catch (error) {
906
+ if (error.name === 'TimeoutError') {
907
+ throw new Error('Image download timeout');
908
+ } else if (error.name === 'TypeError') {
909
+ throw new Error(`Network error: ${error.message}`);
910
+ } else {
911
+ throw new Error(`Download failed: ${error.message}`);
912
+ }
913
+ }
914
+ }
915
+
916
+ /**
917
+ * Schedule cleanup of temp file
918
+ * @private
919
+ */
920
+ _scheduleCleanup(filePath, jobId) {
921
+ const timer = setTimeout(async () => {
922
+ try {
923
+ await fs.unlink(filePath);
924
+ this.logger?.debug(`Cleaned up temp image: ${filePath}`);
925
+ this.cleanupTimers.delete(jobId);
926
+ } catch (error) {
927
+ // File might already be deleted, ignore
928
+ }
929
+ }, IMAGE_CONFIG.TEMP_CLEANUP_MS);
930
+
931
+ this.cleanupTimers.set(jobId, timer);
932
+ }
933
+
934
+ /**
935
+ * Convert local file path to web-accessible URL
936
+ * @private
937
+ */
938
+ _convertToWebUrl(localPath, sessionId) {
939
+ // Extract just the filename from the path
940
+ const filename = path.basename(localPath);
941
+
942
+ // Construct web URL using the image serving endpoint
943
+ // Assumes web server runs on port 8080 (can be made configurable)
944
+ const port = global.loxiaWebServer?.port || 8080;
945
+ let host = global.loxiaWebServer?.host || 'localhost';
946
+
947
+ // Convert 0.0.0.0 (server binding address) to localhost (browser-accessible)
948
+ // Browsers cannot connect to 0.0.0.0, even though servers can bind to it
949
+ if (host === '0.0.0.0') {
950
+ host = 'localhost';
951
+ }
952
+
953
+ return `http://${host}:${port}/api/images/${sessionId}/${filename}`;
954
+ }
955
+
956
+ /**
957
+ * Estimate wait time based on queue
958
+ * @private
959
+ */
960
+ _estimateWaitTime() {
961
+ const avgGenerationTime = 30; // seconds
962
+ const queuePosition = this.queue.length;
963
+
964
+ if (queuePosition === 0) {
965
+ return '~30 seconds';
966
+ }
967
+
968
+ const estimatedSeconds = queuePosition * avgGenerationTime;
969
+ const minutes = Math.floor(estimatedSeconds / 60);
970
+ const seconds = estimatedSeconds % 60;
971
+
972
+ if (minutes > 0) {
973
+ return `~${minutes}m ${seconds}s`;
974
+ }
975
+ return `~${seconds}s`;
976
+ }
977
+
978
+ /**
979
+ * Generate unique job ID
980
+ * @private
981
+ */
982
+ _generateJobId() {
983
+ return `img-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
984
+ }
985
+
986
+ /**
987
+ * Get job status
988
+ * @param {string} jobId - Job ID
989
+ * @returns {Object} Job status
990
+ */
991
+ getJobStatus(jobId) {
992
+ // Check completed jobs
993
+ if (this.completedJobs.has(jobId)) {
994
+ return this.completedJobs.get(jobId);
995
+ }
996
+
997
+ // Check current job
998
+ if (this.currentJob && this.currentJob.jobId === jobId) {
999
+ return this.currentJob;
1000
+ }
1001
+
1002
+ // Check queue
1003
+ const queuedJob = this.queue.find(job => job.jobId === jobId);
1004
+ if (queuedJob) {
1005
+ return queuedJob;
1006
+ }
1007
+
1008
+ return {
1009
+ jobId,
1010
+ status: 'not_found'
1011
+ };
1012
+ }
1013
+
1014
+ /**
1015
+ * Cleanup on shutdown
1016
+ */
1017
+ async cleanup() {
1018
+ this.logger?.info('Shutting down ImageTool');
1019
+
1020
+ // Clear all cleanup timers
1021
+ for (const timer of this.cleanupTimers.values()) {
1022
+ clearTimeout(timer);
1023
+ }
1024
+ this.cleanupTimers.clear();
1025
+
1026
+ // Mark queued jobs as cancelled
1027
+ for (const job of this.queue) {
1028
+ job.status = 'cancelled';
1029
+ }
1030
+ this.queue = [];
1031
+ }
1032
+ }
1033
+
1034
+ export default ImageTool;