auvrynt 1.0.2 → 1.0.5

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 (550) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +366 -233
  3. package/addons/auvrynt_bridge/auvrynt_bridge.gd +500 -0
  4. package/addons/auvrynt_bridge/plugin.cfg +7 -0
  5. package/bin/auvrynt-launcher.cjs +77 -0
  6. package/dist/artifact-registry.js +1 -0
  7. package/dist/aseprite-tools.js +1 -0
  8. package/dist/auth/connection-registry.js +145 -0
  9. package/dist/auth/connection-status.js +176 -0
  10. package/dist/auth/oauth-provider.js +617 -0
  11. package/dist/auth/room-registry.js +131 -0
  12. package/dist/auth/session-registry.js +531 -0
  13. package/dist/background-lifecycle.js +1 -0
  14. package/dist/blender-client.js +1 -109
  15. package/dist/blender-tools.js +1 -930
  16. package/dist/browser-stability.js +1 -0
  17. package/dist/cli/commands/help-command.js +10 -0
  18. package/dist/cli/commands/init-command.js +190 -0
  19. package/dist/cli/commands/integration-commands.js +88 -0
  20. package/dist/cli/commands/serve-command.js +83 -0
  21. package/dist/cli/commands/setup-command.js +81 -0
  22. package/dist/cli/commands/status-commands.js +166 -0
  23. package/dist/cli/commands/tunnel-command.js +127 -0
  24. package/dist/cli/commands/user-config-commands.js +105 -0
  25. package/dist/cli/dashboard-actions.js +516 -0
  26. package/dist/cli/dashboard-native-actions.js +282 -0
  27. package/dist/cli/foreground-server.js +270 -0
  28. package/dist/cli/instance-control.js +21 -0
  29. package/dist/cli/instance-lock.js +123 -0
  30. package/dist/cli/integration-bootstrap.js +206 -0
  31. package/dist/cli/lifecycle-manager.js +493 -0
  32. package/dist/cli/runtime-support.js +74 -0
  33. package/dist/cli/serena-detected-default.js +27 -0
  34. package/dist/cli/tunnel-health-monitor.js +182 -0
  35. package/dist/cli.js +82 -739
  36. package/dist/config.js +1 -243
  37. package/dist/connection-registry.js +1 -73
  38. package/dist/connection-status.js +1 -139
  39. package/dist/control-retry.js +1 -0
  40. package/dist/crash-recovery.js +1 -0
  41. package/dist/db/client.js +16 -3
  42. package/dist/db/schema.js +30 -0
  43. package/dist/dotnet-tools.js +1 -223
  44. package/dist/git-worktrees.js +1 -134
  45. package/dist/git.js +1 -41
  46. package/dist/godot-animation-tilemap.js +1 -82
  47. package/dist/godot-csharp-build.js +1 -93
  48. package/dist/godot-csharp-project.js +1 -157
  49. package/dist/godot-csharp-runner.js +1 -146
  50. package/dist/godot-csharp-semantic.js +1 -161
  51. package/dist/godot-csharp-validate.js +1 -71
  52. package/dist/godot-dotnet-env.js +1 -169
  53. package/dist/godot-editor-bridge.js +1 -140
  54. package/dist/godot-gdscript.js +1 -824
  55. package/dist/godot-project-settings.js +1 -223
  56. package/dist/godot-runtime-testing.js +1 -249
  57. package/dist/godot-tools.js +1 -183
  58. package/dist/http-rate-limit.js +1 -0
  59. package/dist/http-server-hardening.js +1 -0
  60. package/dist/image-tools.js +1 -157
  61. package/dist/infrastructure/command-reference.js +32 -0
  62. package/dist/infrastructure/config.js +424 -0
  63. package/dist/infrastructure/context7-auth.js +55 -0
  64. package/dist/infrastructure/http-rate-limit.js +42 -0
  65. package/dist/infrastructure/http-server-hardening.js +23 -0
  66. package/dist/infrastructure/integration-queue.js +46 -0
  67. package/dist/infrastructure/logger.js +129 -0
  68. package/dist/infrastructure/mcp-event-store.js +103 -0
  69. package/dist/infrastructure/ngrok-auth-pool.js +208 -0
  70. package/dist/infrastructure/request-context.js +14 -0
  71. package/dist/infrastructure/shutdown.js +37 -0
  72. package/dist/infrastructure/sse-heartbeat.js +47 -0
  73. package/dist/infrastructure/tool-capabilities.js +67 -0
  74. package/dist/infrastructure/tool-result-budget.js +175 -0
  75. package/dist/infrastructure/user-config.js +167 -0
  76. package/dist/integration-discovery.js +1 -98
  77. package/dist/integration-queue.js +1 -0
  78. package/dist/integrations/agent-reach/agent-reach-tools.js +196 -0
  79. package/dist/integrations/agent-reach/native-channels.js +468 -0
  80. package/dist/integrations/aseprite/aseprite-analysis-tools.js +368 -0
  81. package/dist/integrations/aseprite/aseprite-editing-tools.js +647 -0
  82. package/dist/integrations/aseprite/aseprite-live-tools.js +297 -0
  83. package/dist/integrations/aseprite/aseprite-safety-tools.js +594 -0
  84. package/dist/integrations/aseprite/aseprite-specialized-tools.js +289 -0
  85. package/dist/integrations/aseprite/aseprite-tools.js +837 -0
  86. package/dist/integrations/blender/blender-catalog-mesh.js +593 -0
  87. package/dist/integrations/blender/blender-catalog-object.js +906 -0
  88. package/dist/integrations/blender/blender-catalog-scene.js +266 -0
  89. package/dist/integrations/blender/blender-catalog-select.js +581 -0
  90. package/dist/integrations/blender/blender-catalog-stubs.js +7512 -0
  91. package/dist/integrations/blender/blender-catalog.js +165 -0
  92. package/dist/integrations/blender/blender-client.js +105 -0
  93. package/dist/integrations/blender/blender-tools.js +2243 -0
  94. package/dist/integrations/context7/context7-tools.js +106 -0
  95. package/dist/integrations/dotnet/dotnet-tools.js +195 -0
  96. package/dist/integrations/godot/godot-animation-tilemap.js +74 -0
  97. package/dist/integrations/godot/godot-csharp-build.js +91 -0
  98. package/dist/integrations/godot/godot-csharp-project.js +155 -0
  99. package/dist/integrations/godot/godot-csharp-runner.js +118 -0
  100. package/dist/integrations/godot/godot-csharp-semantic.js +146 -0
  101. package/dist/integrations/godot/godot-csharp-validate.js +71 -0
  102. package/dist/integrations/godot/godot-dotnet-env.js +134 -0
  103. package/dist/integrations/godot/godot-editor-bridge.js +328 -0
  104. package/dist/integrations/godot/godot-gdscript.js +536 -0
  105. package/dist/integrations/godot/godot-project-settings.js +178 -0
  106. package/dist/integrations/godot/godot-runtime-testing.js +263 -0
  107. package/dist/integrations/godot/godot-tools.js +193 -0
  108. package/dist/integrations/images/image-tools.js +414 -0
  109. package/dist/integrations/images/png-codec.js +251 -0
  110. package/dist/integrations/images/view-image.js +99 -0
  111. package/dist/integrations/images/window-capture.js +148 -0
  112. package/dist/integrations/integration-discovery.js +185 -0
  113. package/dist/integrations/obsidian/obsidian-tools.js +205 -0
  114. package/dist/integrations/playwright/browser-session.js +516 -0
  115. package/dist/integrations/playwright/browser-stability.js +36 -0
  116. package/dist/integrations/playwright/playwright-runtime.js +105 -0
  117. package/dist/integrations/playwright/web-tools.js +504 -0
  118. package/dist/integrations/roblox/roblox-helpers.js +381 -0
  119. package/dist/integrations/roblox/roblox-studio-client.js +112 -0
  120. package/dist/integrations/roblox/roblox-tools.js +106 -0
  121. package/dist/integrations/serena/serena-manager.js +464 -0
  122. package/dist/integrations/serena/serena-tools.js +411 -0
  123. package/dist/logger.js +1 -76
  124. package/dist/mcp-event-store.js +1 -0
  125. package/dist/oauth-provider.js +1 -377
  126. package/dist/pi-tools.js +1 -81
  127. package/dist/playwright-runtime.js +1 -0
  128. package/dist/process/background-lifecycle.js +189 -0
  129. package/dist/process/control-retry.js +42 -0
  130. package/dist/process/crash-recovery.js +23 -0
  131. package/dist/process/processes.js +456 -0
  132. package/dist/processes.js +1 -218
  133. package/dist/request-context.js +1 -0
  134. package/dist/review-checkpoints.js +1 -142
  135. package/dist/room-registry.js +1 -0
  136. package/dist/roots.js +1 -53
  137. package/dist/search-discovery.js +1 -271
  138. package/dist/serena-manager.js +1 -443
  139. package/dist/serena-tools.js +1 -411
  140. package/dist/server/dashboard-page-styles.js +331 -0
  141. package/dist/server/dashboard-page.js +1509 -0
  142. package/dist/server/dashboard-routes.js +46 -0
  143. package/dist/server/dashboard.js +240 -0
  144. package/dist/server/http-helpers.js +30 -0
  145. package/dist/server/integration-profile-update.js +37 -0
  146. package/dist/server/mcp-policy.js +169 -0
  147. package/dist/server/mcp-server-factory.js +127 -0
  148. package/dist/server/mcp-tool-registrar.js +182 -0
  149. package/dist/server/openai-session-hint.js +14 -0
  150. package/dist/server/tool-groups.js +29 -0
  151. package/dist/server/tool-logging.js +33 -0
  152. package/dist/server/tool-registration-shared.js +59 -0
  153. package/dist/server/tools/aseprite-advanced-tools-registration.js +547 -0
  154. package/dist/server/tools/aseprite-tools-registration.js +213 -0
  155. package/dist/server/tools/blender-tools-registration.js +690 -0
  156. package/dist/server/tools/core-file-tools-registration.js +459 -0
  157. package/dist/server/tools/dotnet-godot-window-tools-registration.js +120 -0
  158. package/dist/server/tools/godot-csharp-tools-registration.js +252 -0
  159. package/dist/server/tools/godot-gdscript-tools-registration.js +189 -0
  160. package/dist/server/tools/process-project-tools-registration.js +155 -0
  161. package/dist/server/tools/roblox-tools-registration.js +514 -0
  162. package/dist/server/tools/web-image-tools-registration.js +326 -0
  163. package/dist/server/tools/workspace-lifecycle-tools-registration.js +130 -0
  164. package/dist/server/ui-assets.js +97 -0
  165. package/dist/server/workspace-analytics.js +92 -0
  166. package/dist/server.js +453 -2713
  167. package/dist/session-registry.js +1 -0
  168. package/dist/shutdown.js +1 -0
  169. package/dist/skills.js +1 -45
  170. package/dist/sse-heartbeat.js +1 -0
  171. package/dist/tool-capabilities.js +1 -0
  172. package/dist/tool-result-budget.js +1 -0
  173. package/dist/tools/pi-tools.js +81 -0
  174. package/dist/tunnels/cloudflare-tunnel.js +217 -0
  175. package/dist/tunnels/cloudflared-service.js +174 -0
  176. package/dist/tunnels/ngrok-tunnel.js +220 -0
  177. package/dist/tunnels/tunnel-manager.js +186 -0
  178. package/dist/tunnels/tunnel-types.js +1 -0
  179. package/dist/tunnels/tunnel-utils.js +62 -0
  180. package/dist/ui/.vite/manifest.json +9 -2487
  181. package/dist/ui/assets/heavy-payload-D42A9ceL.js +1 -0
  182. package/dist/ui/assets/lightweight-diff-Cv4g9Hdr.js +13 -0
  183. package/dist/ui/assets/review-payload-DKIg1cKF.js +1 -0
  184. package/dist/ui/assets/workspace-app-CeK3NhmB.css +1 -0
  185. package/dist/ui/assets/{workspace-app-C5QsE314.js → workspace-app-CkUXnpl4.js} +6 -6
  186. package/dist/ui/brand-theme.js +23 -0
  187. package/dist/ui/workspace-app.html +15 -15
  188. package/dist/user-config.js +1 -57
  189. package/dist/view-image.js +1 -104
  190. package/dist/web-tools.js +1 -200
  191. package/dist/window-capture.js +1 -74
  192. package/dist/workspace/artifact-registry.js +69 -0
  193. package/dist/workspace/git-worktrees.js +141 -0
  194. package/dist/workspace/git.js +42 -0
  195. package/dist/workspace/review-checkpoints.js +142 -0
  196. package/dist/workspace/roots.js +56 -0
  197. package/dist/workspace/search-discovery.js +264 -0
  198. package/dist/workspace/skills.js +45 -0
  199. package/dist/workspace/workspace-store.js +219 -0
  200. package/dist/workspace/workspaces.js +314 -0
  201. package/dist/workspace-store.js +1 -125
  202. package/dist/workspaces.js +1 -226
  203. package/docs/CREATOR_STORE_FREE_ASSETS.md +162 -0
  204. package/docs/DEVELOPMENT_TOOLS.md +136 -93
  205. package/docs/assets/auvrynt-icon.png +0 -0
  206. package/docs/assets/auvrynt-icon.svg +20 -0
  207. package/docs/chatgpt-coding-workflow.md +143 -143
  208. package/docs/configuration.md +156 -124
  209. package/docs/diagnostics.md +79 -0
  210. package/docs/gotchas.md +244 -202
  211. package/docs/multi-agent-and-long-projects.md +91 -0
  212. package/docs/security.md +138 -100
  213. package/docs/setup.md +171 -131
  214. package/docs/source-layout.md +60 -0
  215. package/extensions/auvrynt_bridge/auvrynt_bridge.lua +594 -0
  216. package/extensions/auvrynt_bridge/live_analysis.lua +181 -0
  217. package/extensions/auvrynt_bridge/live_animation.lua +134 -0
  218. package/extensions/auvrynt_bridge/live_commands.lua +24 -0
  219. package/extensions/auvrynt_bridge/live_common.lua +130 -0
  220. package/extensions/auvrynt_bridge/live_document.lua +78 -0
  221. package/extensions/auvrynt_bridge/live_drawing.lua +125 -0
  222. package/extensions/auvrynt_bridge/live_effects.lua +91 -0
  223. package/extensions/auvrynt_bridge/live_palette_template.lua +167 -0
  224. package/extensions/auvrynt_bridge/live_selection.lua +110 -0
  225. package/extensions/auvrynt_bridge/live_structure.lua +129 -0
  226. package/extensions/auvrynt_bridge/package.json +17 -0
  227. package/guides/ASEPRITE_AGENT_GUIDE.md +133 -0
  228. package/guides/BLENDER_AGENT_GUIDE.md +472 -0
  229. package/guides/GODOT_AGENT_GUIDE.md +298 -0
  230. package/guides/PROJECT_ROUTER.md +165 -0
  231. package/guides/ROBLOX_AGENT_GUIDE.md +75 -0
  232. package/guides/SOFTWARE_AGENT_GUIDE.md +104 -0
  233. package/guides/WEB_AGENT_GUIDE.md +287 -0
  234. package/package.json +78 -70
  235. package/scripts/dev-server.mjs +121 -120
  236. package/scripts/live-tunnel-smoke.mjs +538 -0
  237. package/scripts/repo-hygiene.mjs +74 -0
  238. package/scripts/restart-after-crash.mjs +49 -0
  239. package/scripts/run-tests.mjs +166 -0
  240. package/dist/ui/assets/abap-CLvhMVsD.js +0 -1
  241. package/dist/ui/assets/actionscript-3--17pq3dv.js +0 -1
  242. package/dist/ui/assets/ada-C5qYipkI.js +0 -1
  243. package/dist/ui/assets/andromeeda-vGVdxbeo.js +0 -1
  244. package/dist/ui/assets/angular-html-BJqQFYI6.js +0 -1
  245. package/dist/ui/assets/angular-ts-CMJECO0Y.js +0 -1
  246. package/dist/ui/assets/apache-U0d_L8uA.js +0 -1
  247. package/dist/ui/assets/apex-CGTLDQj6.js +0 -1
  248. package/dist/ui/assets/apl-DWeXpBBr.js +0 -1
  249. package/dist/ui/assets/applescript-CCn79oCD.js +0 -1
  250. package/dist/ui/assets/ara-4CJ0cIlV.js +0 -1
  251. package/dist/ui/assets/asciidoc-DE70LPWp.js +0 -1
  252. package/dist/ui/assets/asm-Cmm7eHzH.js +0 -1
  253. package/dist/ui/assets/astro-CLbttLOq.js +0 -1
  254. package/dist/ui/assets/aurora-x-CDeNXAV0.js +0 -1
  255. package/dist/ui/assets/awk-BWXHIvNe.js +0 -1
  256. package/dist/ui/assets/ayu-dark-DluEY0Gj.js +0 -1
  257. package/dist/ui/assets/ayu-light-C3h-C4tm.js +0 -1
  258. package/dist/ui/assets/ayu-mirage-Bqwy1Gya.js +0 -1
  259. package/dist/ui/assets/ballerina-B7ZEbQpA.js +0 -1
  260. package/dist/ui/assets/bat-Bo4NYOV-.js +0 -1
  261. package/dist/ui/assets/beancount-D-usSTwE.js +0 -1
  262. package/dist/ui/assets/berry-DKpUyyne.js +0 -1
  263. package/dist/ui/assets/bibtex-Ci_nEsc7.js +0 -1
  264. package/dist/ui/assets/bicep-CUHmPFLl.js +0 -1
  265. package/dist/ui/assets/bird2-C2hNVINV.js +0 -1
  266. package/dist/ui/assets/blade-BOzY0P2V.js +0 -1
  267. package/dist/ui/assets/bsl-BkkzgIyY.js +0 -1
  268. package/dist/ui/assets/c-BG-8uJvh.js +0 -1
  269. package/dist/ui/assets/c3-CnJL0r0V.js +0 -1
  270. package/dist/ui/assets/cadence-CQ2zXKGN.js +0 -1
  271. package/dist/ui/assets/cairo-DLTphjLi.js +0 -1
  272. package/dist/ui/assets/catppuccin-frappe-3VR1Za6u.js +0 -1
  273. package/dist/ui/assets/catppuccin-latte-DwIHMF0Q.js +0 -1
  274. package/dist/ui/assets/catppuccin-macchiato-DYnBP6_5.js +0 -1
  275. package/dist/ui/assets/catppuccin-mocha-DYhrFGRu.js +0 -1
  276. package/dist/ui/assets/clarity-SemFz856.js +0 -1
  277. package/dist/ui/assets/clojure-DqKBuwfJ.js +0 -1
  278. package/dist/ui/assets/cmake-Bj61d0ZC.js +0 -1
  279. package/dist/ui/assets/cobol-I_17IuGe.js +0 -1
  280. package/dist/ui/assets/codeowners-C8r90Shi.js +0 -1
  281. package/dist/ui/assets/codeql-oeQT6MSM.js +0 -1
  282. package/dist/ui/assets/coffee-C0B8hmok.js +0 -1
  283. package/dist/ui/assets/common-lisp-Cv5bFMCO.js +0 -1
  284. package/dist/ui/assets/coq-BrsZFFmf.js +0 -1
  285. package/dist/ui/assets/cpp-DDJXt6Vs.js +0 -1
  286. package/dist/ui/assets/crystal-Bexqace6.js +0 -1
  287. package/dist/ui/assets/csharp-Ct8U2NOr.js +0 -1
  288. package/dist/ui/assets/css-DSQhw_9R.js +0 -1
  289. package/dist/ui/assets/csv-Dx-8-gkx.js +0 -1
  290. package/dist/ui/assets/cue-CE9AQfxI.js +0 -1
  291. package/dist/ui/assets/cypher-ClKdZ_lG.js +0 -1
  292. package/dist/ui/assets/d-qD-0Kul2.js +0 -1
  293. package/dist/ui/assets/dark-plus-Cs2F2srj.js +0 -1
  294. package/dist/ui/assets/dart-DkHntEIa.js +0 -1
  295. package/dist/ui/assets/dax-BkyTk9wS.js +0 -1
  296. package/dist/ui/assets/desktop-Dlh5hvp9.js +0 -1
  297. package/dist/ui/assets/diff-woXpYk--.js +0 -1
  298. package/dist/ui/assets/docker-IyjqRm3v.js +0 -1
  299. package/dist/ui/assets/dotenv-_5a1GRtc.js +0 -1
  300. package/dist/ui/assets/dracula-BHWKrbxM.js +0 -1
  301. package/dist/ui/assets/dracula-soft-5eyTD99u.js +0 -1
  302. package/dist/ui/assets/dream-maker-DW3nJb8Q.js +0 -1
  303. package/dist/ui/assets/edge-Z8YYkHOr.js +0 -1
  304. package/dist/ui/assets/elixir-CG9uKtAF.js +0 -1
  305. package/dist/ui/assets/elm-GfWG2IfT.js +0 -1
  306. package/dist/ui/assets/emacs-lisp-C9PiwqqW.js +0 -1
  307. package/dist/ui/assets/erb-xe89e73J.js +0 -1
  308. package/dist/ui/assets/erlang-Cphh6RMH.js +0 -1
  309. package/dist/ui/assets/everforest-dark-sB-x3p7T.js +0 -1
  310. package/dist/ui/assets/everforest-light-Df2xbC6M.js +0 -1
  311. package/dist/ui/assets/fennel-DQxkIbk2.js +0 -1
  312. package/dist/ui/assets/fish-BJitypiv.js +0 -1
  313. package/dist/ui/assets/fluent-C03EYrpw.js +0 -1
  314. package/dist/ui/assets/fortran-fixed-form-DEKoE2YW.js +0 -1
  315. package/dist/ui/assets/fortran-free-form-CYNrtFtB.js +0 -1
  316. package/dist/ui/assets/fsharp-D13ZGOAj.js +0 -1
  317. package/dist/ui/assets/gdresource-C0sCabJj.js +0 -1
  318. package/dist/ui/assets/gdscript-Cp2uCuqX.js +0 -1
  319. package/dist/ui/assets/gdshader-CBce3t8t.js +0 -1
  320. package/dist/ui/assets/genie-CV2tkWYe.js +0 -1
  321. package/dist/ui/assets/gherkin-DExj1W_8.js +0 -1
  322. package/dist/ui/assets/git-commit-BSykSTBG.js +0 -1
  323. package/dist/ui/assets/git-rebase-CoTL_uSE.js +0 -1
  324. package/dist/ui/assets/github-dark-C-LZuMrd.js +0 -1
  325. package/dist/ui/assets/github-dark-default-DXG-b-1a.js +0 -1
  326. package/dist/ui/assets/github-dark-dimmed-Bx1FflLF.js +0 -1
  327. package/dist/ui/assets/github-dark-high-contrast-B_tTalzw.js +0 -1
  328. package/dist/ui/assets/github-light-EUqPIrTm.js +0 -1
  329. package/dist/ui/assets/github-light-default-BXViO-2h.js +0 -1
  330. package/dist/ui/assets/github-light-high-contrast-B68TUdTA.js +0 -1
  331. package/dist/ui/assets/gleam-CSRkHgEL.js +0 -1
  332. package/dist/ui/assets/glimmer-js-DAn4UCSu.js +0 -1
  333. package/dist/ui/assets/glimmer-ts-Tjyi6Gh5.js +0 -1
  334. package/dist/ui/assets/glsl-CtTDZoqR.js +0 -1
  335. package/dist/ui/assets/gn-ilITqXS6.js +0 -1
  336. package/dist/ui/assets/gnuplot-7GGW24-e.js +0 -1
  337. package/dist/ui/assets/go-BJwz_mda.js +0 -1
  338. package/dist/ui/assets/graphql-D5gcC_Jk.js +0 -1
  339. package/dist/ui/assets/groovy-CacY0gHj.js +0 -1
  340. package/dist/ui/assets/gruvbox-dark-hard-C820rvS2.js +0 -1
  341. package/dist/ui/assets/gruvbox-dark-medium-BPjhmG05.js +0 -1
  342. package/dist/ui/assets/gruvbox-dark-soft-MrdJrrXF.js +0 -1
  343. package/dist/ui/assets/gruvbox-light-hard-BC_s9l72.js +0 -1
  344. package/dist/ui/assets/gruvbox-light-medium-BAWPOn9u.js +0 -1
  345. package/dist/ui/assets/gruvbox-light-soft-BSMLrYjP.js +0 -1
  346. package/dist/ui/assets/hack-BJXY27dt.js +0 -1
  347. package/dist/ui/assets/haml-BLfFoZf7.js +0 -1
  348. package/dist/ui/assets/handlebars-Dw6CWkvN.js +0 -1
  349. package/dist/ui/assets/haskell-D8IpX4py.js +0 -1
  350. package/dist/ui/assets/haxe-OTjmBuCE.js +0 -1
  351. package/dist/ui/assets/hcl-Dh228itO.js +0 -1
  352. package/dist/ui/assets/heavy-payload-Dcg0-F-S.js +0 -4
  353. package/dist/ui/assets/hjson-CxZEssPk.js +0 -1
  354. package/dist/ui/assets/hlsl-Cvrh5tZx.js +0 -1
  355. package/dist/ui/assets/horizon-CE9ld1lL.js +0 -1
  356. package/dist/ui/assets/horizon-bright-Br1oVSNq.js +0 -1
  357. package/dist/ui/assets/houston-CsvMBhTu.js +0 -1
  358. package/dist/ui/assets/html-CAcAWvoN.js +0 -1
  359. package/dist/ui/assets/html-derivative-8aSW2E_7.js +0 -1
  360. package/dist/ui/assets/http-B2YR_3V1.js +0 -1
  361. package/dist/ui/assets/hurl-BnDJ9Zhf.js +0 -1
  362. package/dist/ui/assets/hxml-B0Qn7Nwc.js +0 -1
  363. package/dist/ui/assets/hy-CZbG8q4J.js +0 -1
  364. package/dist/ui/assets/imba-DsUTQ-LC.js +0 -1
  365. package/dist/ui/assets/ini-B5eOa1yu.js +0 -1
  366. package/dist/ui/assets/java-Doxi4W8u.js +0 -1
  367. package/dist/ui/assets/javascript-BbRdEqti.js +0 -1
  368. package/dist/ui/assets/jinja-CS3Tpv_Q.js +0 -1
  369. package/dist/ui/assets/jison-BkPGYVHR.js +0 -1
  370. package/dist/ui/assets/json-BMt1m3m8.js +0 -1
  371. package/dist/ui/assets/json5-BR5RXkoi.js +0 -1
  372. package/dist/ui/assets/jsonc-CYpm1nAK.js +0 -1
  373. package/dist/ui/assets/jsonl-CmCQp5Yx.js +0 -1
  374. package/dist/ui/assets/jsonnet-CJTPZ8u_.js +0 -1
  375. package/dist/ui/assets/jssm-DXw9l8Rf.js +0 -1
  376. package/dist/ui/assets/jsx-CQKrJXXY.js +0 -1
  377. package/dist/ui/assets/julia-BuN7i2du.js +0 -1
  378. package/dist/ui/assets/just-BswEPFPu.js +0 -1
  379. package/dist/ui/assets/kanagawa-dragon-CXtmUGW6.js +0 -1
  380. package/dist/ui/assets/kanagawa-lotus-BN08jTvb.js +0 -1
  381. package/dist/ui/assets/kanagawa-wave-CTweb8Dz.js +0 -1
  382. package/dist/ui/assets/kdl-CsD5j6eV.js +0 -1
  383. package/dist/ui/assets/kotlin-DhhofPvG.js +0 -1
  384. package/dist/ui/assets/kusto-BUv0MjJC.js +0 -1
  385. package/dist/ui/assets/laserwave-C_8bwKvT.js +0 -1
  386. package/dist/ui/assets/latex-DU2VyFfA.js +0 -1
  387. package/dist/ui/assets/lean-CewbzKMR.js +0 -1
  388. package/dist/ui/assets/less-DVTAwKKz.js +0 -1
  389. package/dist/ui/assets/light-plus-DVQuIRkW.js +0 -1
  390. package/dist/ui/assets/liquid-Cmz1L3dq.js +0 -1
  391. package/dist/ui/assets/llvm-Cm23YOpf.js +0 -1
  392. package/dist/ui/assets/log-BNLmms1o.js +0 -1
  393. package/dist/ui/assets/logo-Cluzi2Zq.js +0 -1
  394. package/dist/ui/assets/lua-CfVXhkaJ.js +0 -1
  395. package/dist/ui/assets/luau-CNKltnaQ.js +0 -1
  396. package/dist/ui/assets/make-Dixweg8N.js +0 -1
  397. package/dist/ui/assets/markdown-BYOwaDjH.js +0 -1
  398. package/dist/ui/assets/marko-C5bfqGiA.js +0 -1
  399. package/dist/ui/assets/material-theme-Bm3Qr25_.js +0 -1
  400. package/dist/ui/assets/material-theme-darker-2IIEA8gg.js +0 -1
  401. package/dist/ui/assets/material-theme-lighter-uhdI0v04.js +0 -1
  402. package/dist/ui/assets/material-theme-ocean-CHQ94UKr.js +0 -1
  403. package/dist/ui/assets/material-theme-palenight-B5W6OYN7.js +0 -1
  404. package/dist/ui/assets/matlab-D7qyCx1q.js +0 -1
  405. package/dist/ui/assets/mdc-CVBXYfyq.js +0 -1
  406. package/dist/ui/assets/mdx-DQZ5AkYe.js +0 -1
  407. package/dist/ui/assets/mermaid-Bk4SNUv9.js +0 -1
  408. package/dist/ui/assets/min-dark-BSWPekZh.js +0 -1
  409. package/dist/ui/assets/min-light-DDpmG2fV.js +0 -1
  410. package/dist/ui/assets/mipsasm-BMqwQI7S.js +0 -1
  411. package/dist/ui/assets/mojo-BgCJLMeH.js +0 -1
  412. package/dist/ui/assets/monokai-CdkpiU2Y.js +0 -1
  413. package/dist/ui/assets/moonbit-CaWjb8XO.js +0 -1
  414. package/dist/ui/assets/move-B1IS1UjX.js +0 -1
  415. package/dist/ui/assets/narrat-_X_XdTYD.js +0 -1
  416. package/dist/ui/assets/nextflow-Bbiyy34d.js +0 -1
  417. package/dist/ui/assets/nextflow-groovy-Dc_ddanL.js +0 -1
  418. package/dist/ui/assets/nginx-CzJafxLa.js +0 -1
  419. package/dist/ui/assets/night-owl-DhmEMT88.js +0 -1
  420. package/dist/ui/assets/night-owl-light-eJ-hLW7d.js +0 -1
  421. package/dist/ui/assets/nim-DABKtHtM.js +0 -1
  422. package/dist/ui/assets/nix-IvuFDN5E.js +0 -1
  423. package/dist/ui/assets/nord-Cb4Vim4T.js +0 -1
  424. package/dist/ui/assets/nushell-DcLAeLz5.js +0 -1
  425. package/dist/ui/assets/objective-c-D1A_Heim.js +0 -1
  426. package/dist/ui/assets/objective-cpp-BsSzOQcm.js +0 -1
  427. package/dist/ui/assets/ocaml-O90oeIOV.js +0 -1
  428. package/dist/ui/assets/odin-B1RWQWA5.js +0 -1
  429. package/dist/ui/assets/one-dark-pro-CLwyXe_n.js +0 -1
  430. package/dist/ui/assets/one-light-D7Lr4KcI.js +0 -1
  431. package/dist/ui/assets/openscad-BUDT5pXO.js +0 -1
  432. package/dist/ui/assets/pascal-4ZHwLPI5.js +0 -1
  433. package/dist/ui/assets/perl-CBh7L3lZ.js +0 -1
  434. package/dist/ui/assets/php-DFbI9q5p.js +0 -1
  435. package/dist/ui/assets/pierre-dark-sU4Zdns8.js +0 -1
  436. package/dist/ui/assets/pierre-dark-soft-HXgun5vs.js +0 -1
  437. package/dist/ui/assets/pierre-light-DWBk51d8.js +0 -1
  438. package/dist/ui/assets/pierre-light-soft-CodEzPxf.js +0 -1
  439. package/dist/ui/assets/pkl-ot-7Btpt.js +0 -1
  440. package/dist/ui/assets/plastic-DQwYfKfQ.js +0 -1
  441. package/dist/ui/assets/plsql-DGHpHOYJ.js +0 -1
  442. package/dist/ui/assets/po-BiJDBrnU.js +0 -1
  443. package/dist/ui/assets/poimandres-DRFjx7u4.js +0 -1
  444. package/dist/ui/assets/polar-C7UOKdEL.js +0 -1
  445. package/dist/ui/assets/postcss-BXeXVLqQ.js +0 -1
  446. package/dist/ui/assets/powerquery-DNMTfnFr.js +0 -1
  447. package/dist/ui/assets/powershell-DshXNtvi.js +0 -1
  448. package/dist/ui/assets/prisma-BsRQq5mF.js +0 -1
  449. package/dist/ui/assets/prolog-iXnhIJG7.js +0 -1
  450. package/dist/ui/assets/proto-DB4EqR-F.js +0 -1
  451. package/dist/ui/assets/pug-C_PxC7gO.js +0 -1
  452. package/dist/ui/assets/puppet-CDv2pdJW.js +0 -1
  453. package/dist/ui/assets/purescript-9MfHhQsQ.js +0 -1
  454. package/dist/ui/assets/python-gzcpVVnB.js +0 -1
  455. package/dist/ui/assets/qml-DtngoFFK.js +0 -1
  456. package/dist/ui/assets/qmldir-DCQb3MpD.js +0 -1
  457. package/dist/ui/assets/qss-Fe1Jh2GI.js +0 -1
  458. package/dist/ui/assets/r-DohkcevW.js +0 -1
  459. package/dist/ui/assets/racket-DcIDlBhZ.js +0 -1
  460. package/dist/ui/assets/raku-B3gFvitq.js +0 -1
  461. package/dist/ui/assets/razor-C4t8J0_E.js +0 -1
  462. package/dist/ui/assets/red-CJ3rzSJv.js +0 -1
  463. package/dist/ui/assets/reg-CRGYupPL.js +0 -1
  464. package/dist/ui/assets/regexp-OGbTbjgY.js +0 -1
  465. package/dist/ui/assets/rel-BtDbiS_P.js +0 -1
  466. package/dist/ui/assets/review-payload-C5Qz60Kq.js +0 -1
  467. package/dist/ui/assets/riscv-Ckw8ddFX.js +0 -1
  468. package/dist/ui/assets/ron-VUp2lXgN.js +0 -1
  469. package/dist/ui/assets/rose-pine-BthvhNj6.js +0 -1
  470. package/dist/ui/assets/rose-pine-dawn-Dg85fqjY.js +0 -1
  471. package/dist/ui/assets/rose-pine-moon-hon4tzzS.js +0 -1
  472. package/dist/ui/assets/rosmsg-CAekHB0j.js +0 -1
  473. package/dist/ui/assets/rst-CfBbbcvC.js +0 -1
  474. package/dist/ui/assets/ruby-Bw0mpxHY.js +0 -1
  475. package/dist/ui/assets/rust-Cfkwpbl8.js +0 -1
  476. package/dist/ui/assets/sas-Cs3s9JOf.js +0 -1
  477. package/dist/ui/assets/sass-DXrisJhu.js +0 -1
  478. package/dist/ui/assets/scala-DKOlJaKm.js +0 -1
  479. package/dist/ui/assets/scheme-DQCgrYNe.js +0 -1
  480. package/dist/ui/assets/scss-sRxNJS54.js +0 -1
  481. package/dist/ui/assets/sdbl-bTVj8UrX.js +0 -1
  482. package/dist/ui/assets/shaderlab-TOUzSsQk.js +0 -1
  483. package/dist/ui/assets/shellscript-B0X6wh0R.js +0 -1
  484. package/dist/ui/assets/shellsession-D2FdOioR.js +0 -1
  485. package/dist/ui/assets/slack-dark-DnToyrRv.js +0 -1
  486. package/dist/ui/assets/slack-ochin-B2OO5cIa.js +0 -1
  487. package/dist/ui/assets/smalltalk-B16xEiuN.js +0 -1
  488. package/dist/ui/assets/snazzy-light-4G7pJPwS.js +0 -1
  489. package/dist/ui/assets/solarized-dark-DV17i1UV.js +0 -1
  490. package/dist/ui/assets/solarized-light-DSh2HLQt.js +0 -1
  491. package/dist/ui/assets/solidity-CKzVLygQ.js +0 -1
  492. package/dist/ui/assets/soy-s6g9OjJ1.js +0 -1
  493. package/dist/ui/assets/sparql-D_iOobhT.js +0 -1
  494. package/dist/ui/assets/splunk-BC2Px7Mm.js +0 -1
  495. package/dist/ui/assets/sql-DVTnh5V-.js +0 -1
  496. package/dist/ui/assets/ssh-config-BgfXC-Er.js +0 -1
  497. package/dist/ui/assets/stata-DFIDiXF9.js +0 -1
  498. package/dist/ui/assets/stylus-B6D30XZt.js +0 -1
  499. package/dist/ui/assets/surrealql-DVfrsLyg.js +0 -1
  500. package/dist/ui/assets/svelte-D5GsxESq.js +0 -1
  501. package/dist/ui/assets/swift-DonLKvLd.js +0 -1
  502. package/dist/ui/assets/synthwave-84-nFMaYfgc.js +0 -1
  503. package/dist/ui/assets/system-verilog-DJ5XKQeo.js +0 -1
  504. package/dist/ui/assets/systemd-BxMlprV5.js +0 -1
  505. package/dist/ui/assets/talonscript-CohzipZa.js +0 -1
  506. package/dist/ui/assets/tasl-DMoTqEGO.js +0 -1
  507. package/dist/ui/assets/tcl-CZd0xW_V.js +0 -1
  508. package/dist/ui/assets/templ-DI-Zyqzd.js +0 -1
  509. package/dist/ui/assets/terraform-DswuEJGm.js +0 -1
  510. package/dist/ui/assets/tex-WHHmlbJI.js +0 -1
  511. package/dist/ui/assets/tokyo-night-oM2G3aXe.js +0 -1
  512. package/dist/ui/assets/toml-CcmNWLt0.js +0 -1
  513. package/dist/ui/assets/ts-tags-Dwpo2RFJ.js +0 -1
  514. package/dist/ui/assets/tsv-sltzmVWM.js +0 -1
  515. package/dist/ui/assets/tsx-ph_idaAW.js +0 -1
  516. package/dist/ui/assets/turtle-ByJddavk.js +0 -1
  517. package/dist/ui/assets/twig-YO17jFjR.js +0 -1
  518. package/dist/ui/assets/typescript-CbAxjhkl.js +0 -1
  519. package/dist/ui/assets/typespec-B88KGewJ.js +0 -1
  520. package/dist/ui/assets/typst-DI99ib-x.js +0 -1
  521. package/dist/ui/assets/useFileDiffInstance-B2DLX3Vg.js +0 -244
  522. package/dist/ui/assets/v-DETTlOr0.js +0 -1
  523. package/dist/ui/assets/vala-zf12oZj6.js +0 -1
  524. package/dist/ui/assets/vb-Djn5o6TS.js +0 -1
  525. package/dist/ui/assets/verilog-CiiDBU1e.js +0 -1
  526. package/dist/ui/assets/vesper-D5bVUKB1.js +0 -1
  527. package/dist/ui/assets/vhdl-BroJfC0k.js +0 -1
  528. package/dist/ui/assets/viml-DvXPmvsu.js +0 -1
  529. package/dist/ui/assets/vitesse-black-fwtXNY1n.js +0 -1
  530. package/dist/ui/assets/vitesse-dark-BZCL-v6S.js +0 -1
  531. package/dist/ui/assets/vitesse-light-VbXTXTou.js +0 -1
  532. package/dist/ui/assets/vue-CraHL-Ps.js +0 -1
  533. package/dist/ui/assets/vue-html-e9tyZNtS.js +0 -1
  534. package/dist/ui/assets/vue-vine-CofmxaUb.js +0 -1
  535. package/dist/ui/assets/vyper-CgoNMtux.js +0 -1
  536. package/dist/ui/assets/wasm-BnjxR4X6.js +0 -1
  537. package/dist/ui/assets/wasm-ByWQv1Qj.js +0 -1
  538. package/dist/ui/assets/wenyan-C8pVoKbM.js +0 -1
  539. package/dist/ui/assets/wgsl-BsKzXJz4.js +0 -1
  540. package/dist/ui/assets/wikitext-ClFFjSW2.js +0 -1
  541. package/dist/ui/assets/wit-DdvCle-K.js +0 -1
  542. package/dist/ui/assets/wolfram-DLL8P-h_.js +0 -1
  543. package/dist/ui/assets/workspace-app-8--oTbCd.css +0 -1
  544. package/dist/ui/assets/xml-BWSdPZHm.js +0 -1
  545. package/dist/ui/assets/xsl-BtCt7Hx8.js +0 -1
  546. package/dist/ui/assets/yaml-BZiTw7cQ.js +0 -1
  547. package/dist/ui/assets/zenscript-BnlCZFoB.js +0 -1
  548. package/dist/ui/assets/zig-CMLA9XwU.js +0 -1
  549. package/docs/assets/auvrynt-logo-light.png +0 -0
  550. package/docs/assets/auvrynt-screenshot.png +0 -0
@@ -0,0 +1,647 @@
1
+ import { execFile } from "node:child_process";
2
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { assertInteger, assertPositiveInteger, errorResponse, getAsepritePalettePreset, luaString, openSpriteScript, parseColor, resolveAsepriteExecutable, runLua, textResponse, workspacePath, } from "./aseprite-tools.js";
6
+ import { asepriteFileSafety, assertAsepriteExpectedVersion, } from "./aseprite-safety-tools.js";
7
+ const execFileAsync = promisify(execFile);
8
+ const MAX_MASK_POINTS = 262_144;
9
+ const MAX_STROKE_POINTS = 16_384;
10
+ async function prepareMutation(config, registry, workspaceId, filePath, guard, label) {
11
+ await assertAsepriteExpectedVersion(registry, workspaceId, filePath, guard.expectedVersion);
12
+ if (guard.checkpoint !== false) {
13
+ const response = await asepriteFileSafety(config, registry, {
14
+ workspaceId,
15
+ filePath,
16
+ action: "checkpoint",
17
+ expectedVersion: guard.expectedVersion,
18
+ label,
19
+ });
20
+ if (response.isError) {
21
+ const message = response.content[0]?.type === "text" ? response.content[0].text : "Checkpoint failed.";
22
+ throw new Error(message);
23
+ }
24
+ }
25
+ }
26
+ function luaColor(value) {
27
+ const color = parseColor(value);
28
+ return `{r=${color.r},g=${color.g},b=${color.b},a=${color.a}}`;
29
+ }
30
+ function normalizeRegion(region) {
31
+ return {
32
+ x: assertInteger(region.x, "region.x"),
33
+ y: assertInteger(region.y, "region.y"),
34
+ width: assertPositiveInteger(region.width, "region.width"),
35
+ height: assertPositiveInteger(region.height, "region.height"),
36
+ };
37
+ }
38
+ export async function asepriteImportSpriteSheet(config, registry, input) {
39
+ try {
40
+ const source = workspacePath(registry, input.workspaceId, input.imagePath);
41
+ const output = workspacePath(registry, input.workspaceId, input.outputPath);
42
+ await mkdir(dirname(output.absolute), { recursive: true });
43
+ const frameWidth = assertPositiveInteger(input.frameWidth, "frameWidth");
44
+ const frameHeight = assertPositiveInteger(input.frameHeight, "frameHeight");
45
+ const marginX = Math.max(0, assertInteger(input.marginX ?? 0, "marginX"));
46
+ const marginY = Math.max(0, assertInteger(input.marginY ?? 0, "marginY"));
47
+ const spacingX = Math.max(0, assertInteger(input.spacingX ?? 0, "spacingX"));
48
+ const spacingY = Math.max(0, assertInteger(input.spacingY ?? 0, "spacingY"));
49
+ const durationMs = Math.max(1, Math.min(60_000, assertInteger(input.durationMs ?? 100, "durationMs")));
50
+ const result = await runLua(config, registry, input.workspaceId, `
51
+ local source=app.open(${luaString(source.absolute)})
52
+ if source==nil then fail("Unable to open sprite sheet") end
53
+ local rendered=Image(source.spec) rendered:clear() rendered:drawSprite(source,1,Point(0,0))
54
+ local frame_width=${frameWidth} local frame_height=${frameHeight}
55
+ local margin_x=${marginX} local margin_y=${marginY} local spacing_x=${spacingX} local spacing_y=${spacingY}
56
+ local columns=${input.columns ?? "math.floor((source.width-margin_x+spacing_x)/(frame_width+spacing_x))"}
57
+ local rows=${input.rows ?? "math.floor((source.height-margin_y+spacing_y)/(frame_height+spacing_y))"}
58
+ if columns<1 or rows<1 then fail("Frame grid does not fit the source image") end
59
+ local count=${input.frameCount ?? "columns*rows"}
60
+ if count<1 or count>columns*rows then fail("frameCount is outside the available grid") end
61
+ local sprite=Sprite(frame_width,frame_height,source.colorMode)
62
+ if #source.palettes>0 then sprite:setPalette(source.palettes[1]) end
63
+ sprite.transparentColor=source.transparentColor
64
+ local layer=sprite.layers[1] layer.name=${luaString(input.layerName?.trim() || "Imported")}
65
+ for index=0,count-1 do
66
+ local column local row
67
+ if ${luaString(input.order ?? "row_major")}=="column_major" then column=math.floor(index/rows) row=index%rows
68
+ else row=math.floor(index/columns) column=index%columns end
69
+ local sx=margin_x+column*(frame_width+spacing_x)
70
+ local sy=margin_y+row*(frame_height+spacing_y)
71
+ if sx+frame_width>source.width or sy+frame_height>source.height then fail("Frame rectangle exceeds source image") end
72
+ local image=Image(sprite.spec) image:clear() image:drawImage(rendered,Point(-sx,-sy))
73
+ local frame_number=index+1
74
+ if frame_number==1 then
75
+ layer:cel(1).image=image
76
+ else
77
+ sprite:newEmptyFrame(frame_number)
78
+ sprite:newCel(layer,frame_number,image,Point(0,0))
79
+ end
80
+ sprite.frames[frame_number].duration=${durationMs / 1000}
81
+ end
82
+ ${input.tagName ? `local tag=sprite:newTag(1,count) tag.name=${luaString(input.tagName)}` : ""}
83
+ if not sprite:saveAs(${luaString(output.absolute)}) then fail("Unable to save imported sprite") end
84
+ emit_result({source=${luaString(source.relative)},output=${luaString(output.relative)},frames=count,columns=columns,rows=rows,summary=sprite_summary(sprite)})
85
+ sprite:close() source:close()
86
+ `);
87
+ return textResponse(result);
88
+ }
89
+ catch (error) {
90
+ return errorResponse(error instanceof Error ? error.message : String(error));
91
+ }
92
+ }
93
+ export async function asepriteEditRegion(config, registry, input) {
94
+ try {
95
+ await prepareMutation(config, registry, input.workspaceId, input.targetPath, input, "edit-region");
96
+ const target = workspacePath(registry, input.workspaceId, input.targetPath);
97
+ const source = workspacePath(registry, input.workspaceId, input.sourcePath ?? input.targetPath);
98
+ const region = normalizeRegion(input.sourceRegion);
99
+ const sourceFrame = assertPositiveInteger(input.sourceFrame ?? 1, "sourceFrame");
100
+ const targetFrame = assertPositiveInteger(input.targetFrame ?? 1, "targetFrame");
101
+ const targetX = assertInteger(input.targetX, "targetX");
102
+ const targetY = assertInteger(input.targetY, "targetY");
103
+ const scale = Math.min(16, assertPositiveInteger(input.scale ?? 1, "scale"));
104
+ const rotation = input.rotation ?? 0;
105
+ const result = await runLua(config, registry, input.workspaceId, `
106
+ local target=app.open(${luaString(target.absolute)})
107
+ if target==nil then fail("Unable to open target sprite") end
108
+ local source=${source.absolute === target.absolute ? "target" : `app.open(${luaString(source.absolute)})`}
109
+ if source==nil then fail("Unable to open source sprite") end
110
+ if ${sourceFrame}>#source.frames or ${targetFrame}>#target.frames then fail("Frame is outside a sprite") end
111
+ local source_image=Image(source.spec) source_image:clear()
112
+ ${input.sourceLayer ? `
113
+ local source_layer=find_layer(source.layers,${luaString(input.sourceLayer)})
114
+ if source_layer==nil or source_layer.isGroup then fail("Source layer was not found") end
115
+ local source_cel=source_layer:cel(${sourceFrame})
116
+ if source_cel~=nil then source_image:drawImage(source_cel.image,source_cel.position) end
117
+ ` : `source_image:drawSprite(source,${sourceFrame},Point(0,0))`}
118
+ if ${region.x}<0 or ${region.y}<0 or ${region.x + region.width}>source.width or ${region.y + region.height}>source.height then fail("Source region is outside the sprite") end
119
+ local rotation=${rotation}
120
+ local scaled_width=${region.width * scale} local scaled_height=${region.height * scale}
121
+ local output_width=(rotation==90 or rotation==270) and scaled_height or scaled_width
122
+ local output_height=(rotation==90 or rotation==270) and scaled_width or scaled_height
123
+ local target_layer=${input.targetLayer ? `find_layer(target.layers,${luaString(input.targetLayer)})` : "target.layers[1]"}
124
+ if target_layer==nil and ${input.createTargetLayer === true ? "true" : "false"} then target_layer=target:newLayer() target_layer.name=${luaString(input.targetLayer ?? "Region")} end
125
+ if target_layer==nil or target_layer.isGroup then fail("Target layer was not found") end
126
+ local target_cel=ensure_canvas_cel(target,target_layer,${targetFrame})
127
+ local target_image=target_cel.image
128
+ local function transformed_source(dx,dy)
129
+ local rx=dx local ry=dy
130
+ if rotation==90 then rx=dy ry=scaled_height-1-dx
131
+ elseif rotation==180 then rx=scaled_width-1-dx ry=scaled_height-1-dy
132
+ elseif rotation==270 then rx=scaled_width-1-dy ry=dx end
133
+ if ${input.flipX === true ? "true" : "false"} then rx=scaled_width-1-rx end
134
+ if ${input.flipY === true ? "true" : "false"} then ry=scaled_height-1-ry end
135
+ local sx=${region.x}+math.floor(rx/${scale})
136
+ local sy=${region.y}+math.floor(ry/${scale})
137
+ return pixel_rgba(source,source_image:getPixel(sx,sy))
138
+ end
139
+ local blend=${luaString(input.blend ?? "replace")}
140
+ local changed=0
141
+ for dy=0,output_height-1 do for dx=0,output_width-1 do
142
+ local tx=${targetX}+dx local ty=${targetY}+dy
143
+ if tx>=0 and ty>=0 and tx<target.width and ty<target.height then
144
+ local src=transformed_source(dx,dy)
145
+ local dst=pixel_rgba(target,target_image:getPixel(tx,ty))
146
+ local write=nil
147
+ if blend=="replace" then write=src
148
+ elseif blend=="behind" then if dst.a==0 then write=src end
149
+ elseif src.a>0 then
150
+ if src.a==255 then write=src
151
+ else
152
+ local sa=src.a/255 local da=dst.a/255 local oa=sa+da*(1-sa)
153
+ if oa>0 then write={r=math.floor((src.r*sa+dst.r*da*(1-sa))/oa+0.5),g=math.floor((src.g*sa+dst.g*da*(1-sa))/oa+0.5),b=math.floor((src.b*sa+dst.b*da*(1-sa))/oa+0.5),a=math.floor(oa*255+0.5)} end
154
+ end
155
+ end
156
+ if write~=nil then target_image:drawPixel(tx,ty,color_pixel(target,write)) changed=changed+1 end
157
+ end
158
+ end end
159
+ if not target:saveAs(${luaString(target.absolute)}) then fail("Unable to save target sprite") end
160
+ emit_result({target=${luaString(target.relative)},source=${luaString(source.relative)},changed_pixels=changed,output_bounds={x=${targetX},y=${targetY},width=output_width,height=output_height},layer=target_layer.name,frame=${targetFrame}})
161
+ if source~=target then source:close() end target:close()
162
+ `);
163
+ return textResponse(result);
164
+ }
165
+ catch (error) {
166
+ return errorResponse(error instanceof Error ? error.message : String(error));
167
+ }
168
+ }
169
+ export async function asepriteDrawStroke(config, registry, input) {
170
+ try {
171
+ if (!Array.isArray(input.points) || input.points.length < 1 || input.points.length > MAX_STROKE_POINTS) {
172
+ throw new Error(`points must contain 1-${MAX_STROKE_POINTS} entries.`);
173
+ }
174
+ await prepareMutation(config, registry, input.workspaceId, input.filePath, input, "draw-stroke");
175
+ const file = workspacePath(registry, input.workspaceId, input.filePath);
176
+ const frame = assertPositiveInteger(input.frame ?? 1, "frame");
177
+ const points = input.points.map((point) => ({
178
+ x: assertInteger(point.x, "point.x"),
179
+ y: assertInteger(point.y, "point.y"),
180
+ pressure: point.pressure === undefined ? undefined : Math.max(0, Math.min(1, point.pressure)),
181
+ }));
182
+ const size = Math.min(256, assertPositiveInteger(input.brush?.size ?? 1, "brush.size"));
183
+ const angle = assertInteger(input.brush?.angle ?? 0, "brush.angle");
184
+ const opacity = Math.max(0, Math.min(255, assertInteger(input.opacity ?? 255, "opacity")));
185
+ const pointLua = `{${points.map((point) => `Point(${point.x},${point.y})`).join(",")}}`;
186
+ const color = input.color ? luaColor(input.color) : "{r=0,g=0,b=0,a=255}";
187
+ const brushType = input.brush?.type ?? "circle";
188
+ const brushTypeLua = brushType === "square" ? "BrushType.SQUARE" : brushType === "line" ? "BrushType.LINE" : "BrushType.CIRCLE";
189
+ const customBrush = input.brush?.imagePath ? workspacePath(registry, input.workspaceId, input.brush.imagePath) : undefined;
190
+ const inkLua = input.ink === "alpha" ? "Ink.ALPHA_COMPOSITING"
191
+ : input.ink === "copy" ? "Ink.COPY_COLOR"
192
+ : input.ink === "lock_alpha" ? "Ink.LOCK_ALPHA"
193
+ : input.ink === "shading" ? "Ink.SHADING"
194
+ : "Ink.SIMPLE";
195
+ const result = await runLua(config, registry, input.workspaceId, `
196
+ ${openSpriteScript(file.absolute)}
197
+ if ${frame}>#sprite.frames then fail("Frame is outside the sprite") end
198
+ local layer=${input.layer ? `find_layer(sprite.layers,${luaString(input.layer)})` : "sprite.layers[1]"}
199
+ if layer==nil or layer.isGroup then fail("Drawable layer was not found") end
200
+ app.activeLayer=layer app.activeFrame=sprite.frames[${frame}]
201
+ ensure_canvas_cel(sprite,layer,${frame})
202
+ local brush=nil
203
+ ${customBrush ? `
204
+ local brush_sprite=app.open(${luaString(customBrush.absolute)})
205
+ if brush_sprite==nil then fail("Unable to open custom brush image") end
206
+ local brush_image=Image(brush_sprite.spec) brush_image:clear() brush_image:drawSprite(brush_sprite,1,Point(0,0))
207
+ brush=Brush{type=BrushType.IMAGE,image=brush_image}
208
+ app.activeSprite=sprite app.activeLayer=layer app.activeFrame=sprite.frames[${frame}]
209
+ ` : `brush=Brush{type=${brushTypeLua},size=${size},angle=${angle}}`}
210
+ local prefs=app.preferences
211
+ local doc_prefs=prefs.document(sprite)
212
+ prefs.symmetry_mode.enabled=${input.symmetry ? "true" : "false"}
213
+ doc_prefs.symmetry.mode=${input.symmetry ? ((input.symmetry.horizontalX !== undefined ? 1 : 0) + (input.symmetry.verticalY !== undefined ? 2 : 0)) : 0}
214
+ doc_prefs.symmetry.x_axis=${input.symmetry?.horizontalX ?? "math.floor(sprite.width/2)"}
215
+ doc_prefs.symmetry.y_axis=${input.symmetry?.verticalY ?? "math.floor(sprite.height/2)"}
216
+ doc_prefs.tiled.mode=${(input.tiled?.horizontal ? 1 : 0) + (input.tiled?.vertical ? 2 : 0)}
217
+ local color=Color(${color})
218
+ app.useTool{
219
+ tool=${luaString(input.tool ?? "pencil")},
220
+ freehandAlgorithm=${input.pixelPerfect === true ? 1 : 0},
221
+ brush=brush,
222
+ color=color,
223
+ points=${pointLua},
224
+ ink=${inkLua},
225
+ opacity=${opacity},
226
+ }
227
+ if not sprite:saveAs(${luaString(file.absolute)}) then fail("Unable to save sprite") end
228
+ emit_result({file=${luaString(file.relative)},layer=layer.name,frame=${frame},points=${points.length},tool=${luaString(input.tool ?? "pencil")},brush={type=${luaString(brushType)},size=${size},angle=${angle}},pixel_perfect=${input.pixelPerfect === true ? "true" : "false"}})
229
+ ${customBrush ? "brush_sprite:close()" : ""} sprite:close()
230
+ `);
231
+ return textResponse(result);
232
+ }
233
+ catch (error) {
234
+ return errorResponse(error instanceof Error ? error.message : String(error));
235
+ }
236
+ }
237
+ export async function asepriteComposeLayers(config, registry, input) {
238
+ try {
239
+ await prepareMutation(config, registry, input.workspaceId, input.filePath, input, `layers-${input.action}`);
240
+ const file = workspacePath(registry, input.workspaceId, input.filePath);
241
+ const blendMap = {
242
+ normal: "BlendMode.NORMAL", multiply: "BlendMode.MULTIPLY", screen: "BlendMode.SCREEN",
243
+ overlay: "BlendMode.OVERLAY", darken: "BlendMode.DARKEN", lighten: "BlendMode.LIGHTEN",
244
+ addition: "BlendMode.ADDITION", subtract: "BlendMode.SUBTRACT", divide: "BlendMode.DIVIDE",
245
+ };
246
+ const result = await runLua(config, registry, input.workspaceId, `
247
+ ${openSpriteScript(file.absolute)}
248
+ local action=${luaString(input.action)}
249
+ local layer=${input.layer ? `find_layer(sprite.layers,${luaString(input.layer)})` : "nil"}
250
+ if action=="create_group" then
251
+ local group=sprite:newGroup() group.name=${luaString(input.name?.trim() || "Group")}
252
+ elseif layer==nil then fail("Layer was not found")
253
+ elseif action=="duplicate" then
254
+ app.activeLayer=layer app.command.DuplicateLayer()
255
+ if ${input.name ? "true" : "false"} then app.activeLayer.name=${luaString(input.name ?? "Layer Copy")} end
256
+ elseif action=="merge_down" then app.activeLayer=layer app.command.MergeDownLayer()
257
+ elseif action=="flatten_all" then sprite:flatten()
258
+ elseif action=="flatten_visible" then
259
+ local hidden={}
260
+ local function visit(items) for _,candidate in ipairs(items) do if not candidate.isVisible then table.insert(hidden,candidate) end if candidate.isGroup then visit(candidate.layers) end end end
261
+ visit(sprite.layers)
262
+ sprite:flatten()
263
+ elseif action=="to_background" then app.activeLayer=layer app.command.BackgroundFromLayer()
264
+ elseif action=="from_background" then app.activeLayer=layer app.command.LayerFromBackground()
265
+ elseif action=="reorder" then layer.stackIndex=${input.stackIndex ?? 1}
266
+ elseif action=="move_to_group" then
267
+ local group=find_layer(sprite.layers,${luaString(input.group ?? "")})
268
+ if group==nil or not group.isGroup then fail("Target group was not found") end
269
+ layer.parent=group
270
+ elseif action=="set_locked" then layer.isEditable=${input.locked === false ? "true" : "false"}
271
+ elseif action=="set_blend_mode" then layer.blendMode=${blendMap[input.blendMode ?? "normal"]}
272
+ elseif action=="solo" then
273
+ local function visit(items) for _,candidate in ipairs(items) do candidate.isVisible=(candidate==layer or candidate.isGroup) if candidate.isGroup then visit(candidate.layers) end end end
274
+ visit(sprite.layers) layer.isVisible=true
275
+ else fail("Unsupported layer composition action") end
276
+ if not sprite:saveAs(${luaString(file.absolute)}) then fail("Unable to save sprite") end
277
+ emit_result(sprite_summary(sprite)) sprite:close()
278
+ `);
279
+ return textResponse(result);
280
+ }
281
+ catch (error) {
282
+ return errorResponse(error instanceof Error ? error.message : String(error));
283
+ }
284
+ }
285
+ async function readMask(registry, workspaceId, path) {
286
+ const file = workspacePath(registry, workspaceId, path);
287
+ const parsed = JSON.parse(await readFile(file.absolute, "utf8"));
288
+ if (parsed.format !== "auvrynt-aseprite-mask-v1" || !Array.isArray(parsed.points))
289
+ throw new Error("Invalid Auvrynt Aseprite mask file.");
290
+ if (parsed.points.length > MAX_MASK_POINTS)
291
+ throw new Error("Mask contains too many points.");
292
+ return parsed;
293
+ }
294
+ export async function asepriteManageMask(config, registry, input) {
295
+ try {
296
+ const file = workspacePath(registry, input.workspaceId, input.filePath);
297
+ const maskFile = workspacePath(registry, input.workspaceId, input.maskPath);
298
+ const frame = assertPositiveInteger(input.frame ?? 1, "frame");
299
+ if (input.action === "inspect")
300
+ return textResponse(await readMask(registry, input.workspaceId, input.maskPath));
301
+ let existing;
302
+ if (["invert", "move", "apply_clear", "apply_fill"].includes(input.action))
303
+ existing = await readMask(registry, input.workspaceId, input.maskPath);
304
+ if (input.action === "move") {
305
+ const dx = assertInteger(input.dx ?? 0, "dx");
306
+ const dy = assertInteger(input.dy ?? 0, "dy");
307
+ if (input.movePixels) {
308
+ await prepareMutation(config, registry, input.workspaceId, input.filePath, input, "move-mask-pixels");
309
+ const pointsLua = `{${existing.points.map((point) => `{x=${point.x},y=${point.y}}`).join(",")}}`;
310
+ await runLua(config, registry, input.workspaceId, `
311
+ ${openSpriteScript(file.absolute)}
312
+ local layer=${input.layer ? `find_layer(sprite.layers,${luaString(input.layer)})` : "sprite.layers[1]"}
313
+ if layer==nil or layer.isGroup then fail("Drawable layer was not found") end
314
+ local cel=ensure_canvas_cel(sprite,layer,${frame}) local image=cel.image
315
+ local points=${pointsLua} local captured={}
316
+ for _,point in ipairs(points) do if point.x>=0 and point.y>=0 and point.x<sprite.width and point.y<sprite.height then table.insert(captured,{x=point.x,y=point.y,p=image:getPixel(point.x,point.y)}) image:drawPixel(point.x,point.y,sprite.colorMode==ColorMode.INDEXED and sprite.transparentColor or 0) end end
317
+ for _,point in ipairs(captured) do local x=point.x+${dx} local y=point.y+${dy} if x>=0 and y>=0 and x<sprite.width and y<sprite.height then image:drawPixel(x,y,point.p) end end
318
+ if not sprite:saveAs(${luaString(file.absolute)}) then fail("Unable to save sprite") end emit_result({moved=#captured}) sprite:close()
319
+ `);
320
+ }
321
+ const moved = { ...existing, points: existing.points.map((point) => ({ x: point.x + dx, y: point.y + dy })) };
322
+ await mkdir(dirname(maskFile.absolute), { recursive: true });
323
+ await writeFile(maskFile.absolute, JSON.stringify(moved, null, 2), "utf8");
324
+ return textResponse({ mask: maskFile.relative, points: moved.points.length, dx, dy, movedPixels: input.movePixels === true });
325
+ }
326
+ if (input.action === "apply_clear" || input.action === "apply_fill") {
327
+ await prepareMutation(config, registry, input.workspaceId, input.filePath, input, `mask-${input.action}`);
328
+ const pointsLua = `{${existing.points.map((point) => `{x=${point.x},y=${point.y}}`).join(",")}}`;
329
+ const fill = input.action === "apply_fill" ? luaColor(input.fillColor ?? "#000000FF") : undefined;
330
+ const result = await runLua(config, registry, input.workspaceId, `
331
+ ${openSpriteScript(file.absolute)}
332
+ local layer=${input.layer ? `find_layer(sprite.layers,${luaString(input.layer)})` : "sprite.layers[1]"}
333
+ if layer==nil or layer.isGroup then fail("Drawable layer was not found") end
334
+ local image=ensure_canvas_cel(sprite,layer,${frame}).image local points=${pointsLua}
335
+ local pixel=${fill ? `color_pixel(sprite,${fill})` : "sprite.colorMode==ColorMode.INDEXED and sprite.transparentColor or 0"}
336
+ local changed=0
337
+ for _,point in ipairs(points) do if point.x>=0 and point.y>=0 and point.x<sprite.width and point.y<sprite.height then image:drawPixel(point.x,point.y,pixel) changed=changed+1 end end
338
+ if not sprite:saveAs(${luaString(file.absolute)}) then fail("Unable to save sprite") end emit_result({changed_pixels=changed}) sprite:close()
339
+ `);
340
+ return textResponse(result);
341
+ }
342
+ const rect = input.rect ? normalizeRegion(input.rect) : undefined;
343
+ const targetColor = input.color ? parseColor(input.color) : undefined;
344
+ const tolerance = Math.max(0, Math.min(255, assertInteger(input.tolerance ?? 0, "tolerance")));
345
+ const currentPoints = existing?.points ?? [];
346
+ const pointsLua = `{${currentPoints.map((point) => `{x=${point.x},y=${point.y}}`).join(",")}}`;
347
+ const result = await runLua(config, registry, input.workspaceId, `
348
+ ${openSpriteScript(file.absolute)}
349
+ local points={}
350
+ local selected={}
351
+ local function add(x,y) local key=x..","..y if not selected[key] then selected[key]=true table.insert(points,{x=x,y=y}) end end
352
+ if ${luaString(input.action)}=="create_rect" then
353
+ local x=${rect?.x ?? 0} local y=${rect?.y ?? 0} local width=${rect?.width ?? 0} local height=${rect?.height ?? 0}
354
+ if x<0 or y<0 or x+width>sprite.width or y+height>sprite.height then fail("Mask rectangle is outside the sprite") end
355
+ for py=y,y+height-1 do for px=x,x+width-1 do add(px,py) end end
356
+ elseif ${luaString(input.action)}=="invert" then
357
+ local old=${pointsLua} for _,point in ipairs(old) do selected[point.x..","..point.y]=true end
358
+ local inverted={} for y=0,sprite.height-1 do for x=0,sprite.width-1 do if not selected[x..","..y] then table.insert(inverted,{x=x,y=y}) end end end points=inverted
359
+ else
360
+ local layer=${input.layer ? `find_layer(sprite.layers,${luaString(input.layer)})` : "sprite.layers[1]"}
361
+ if layer==nil or layer.isGroup then fail("Drawable layer was not found") end
362
+ for y=0,sprite.height-1 do for x=0,sprite.width-1 do
363
+ local c=canvas_pixel(sprite,layer,${frame},x,y)
364
+ if ${luaString(input.action)}=="create_opaque" then if c.a>0 then add(x,y) end
365
+ else
366
+ local target=${targetColor ? `{r=${targetColor.r},g=${targetColor.g},b=${targetColor.b},a=${targetColor.a}}` : "{r=0,g=0,b=0,a=0}"}
367
+ if math.abs(c.r-target.r)<=${tolerance} and math.abs(c.g-target.g)<=${tolerance} and math.abs(c.b-target.b)<=${tolerance} and math.abs(c.a-target.a)<=${tolerance} then add(x,y) end
368
+ end
369
+ end end
370
+ end
371
+ if #points>${MAX_MASK_POINTS} then fail("Generated mask exceeds ${MAX_MASK_POINTS} points") end
372
+ emit_result({spriteWidth=sprite.width,spriteHeight=sprite.height,points=points}) sprite:close()
373
+ `);
374
+ const stored = { format: "auvrynt-aseprite-mask-v1", ...result };
375
+ await mkdir(dirname(maskFile.absolute), { recursive: true });
376
+ await writeFile(maskFile.absolute, JSON.stringify(stored, null, 2), "utf8");
377
+ return textResponse({ mask: maskFile.relative, points: stored.points.length, spriteWidth: stored.spriteWidth, spriteHeight: stored.spriteHeight });
378
+ }
379
+ catch (error) {
380
+ return errorResponse(error instanceof Error ? error.message : String(error));
381
+ }
382
+ }
383
+ export async function asepriteManageColor(config, registry, input) {
384
+ try {
385
+ const file = workspacePath(registry, input.workspaceId, input.filePath);
386
+ if (input.action === "inspect") {
387
+ const result = await runLua(config, registry, input.workspaceId, `
388
+ ${openSpriteScript(file.absolute)}
389
+ local colors={}
390
+ if #sprite.palettes>0 then local palette=sprite.palettes[1] for index=0,#palette-1 do local c=palette:getColor(index) table.insert(colors,{index=index,color=rgba_hex({r=c.red,g=c.green,b=c.blue,a=(index==sprite.transparentColor and 0 or c.alpha)})}) end end
391
+ emit_result({file=${luaString(file.relative)},color_mode=color_mode_name(sprite.colorMode),color_profile=sprite.colorSpace and sprite.colorSpace.name or nil,transparent_index=sprite.transparentColor,palette=colors}) sprite:close()
392
+ `);
393
+ return textResponse(result);
394
+ }
395
+ await prepareMutation(config, registry, input.workspaceId, input.filePath, input, `color-${input.action}`);
396
+ if (input.action === "quantize") {
397
+ const executable = await resolveAsepriteExecutable(config);
398
+ const temporary = `${file.absolute}.auvrynt-quantized.aseprite`;
399
+ const args = ["-b", file.absolute];
400
+ if (input.palettePath)
401
+ args.push("--palette", workspacePath(registry, input.workspaceId, input.palettePath).absolute);
402
+ args.push("--color-mode", "indexed", "--dithering-algorithm", input.dithering ?? "none");
403
+ if (input.ditheringMatrix)
404
+ args.push("--dithering-matrix", input.ditheringMatrix);
405
+ args.push("--save-as", temporary);
406
+ await execFileAsync(executable, args, { timeout: 120_000, windowsHide: true });
407
+ if (input.maxColors && !input.palettePath) {
408
+ await runLua(config, registry, input.workspaceId, `
409
+ ${openSpriteScript(temporary)}
410
+ app.command.ColorQuantization{ui=false,withAlpha=true,maxColors=${Math.max(2, Math.min(256, assertInteger(input.maxColors, "maxColors")))}}
411
+ if not sprite:saveAs(${luaString(temporary)}) then fail("Unable to save quantized sprite") end emit_result({ok=true}) sprite:close()
412
+ `);
413
+ }
414
+ await rename(temporary, file.absolute);
415
+ return textResponse({ file: file.relative, quantized: true, version: await assertAsepriteExpectedVersion(registry, input.workspaceId, input.filePath) });
416
+ }
417
+ const palette = input.palettePath ? workspacePath(registry, input.workspaceId, input.palettePath) : undefined;
418
+ const profile = input.profilePath ? workspacePath(registry, input.workspaceId, input.profilePath) : undefined;
419
+ const from = input.fromColor ? parseColor(input.fromColor) : undefined;
420
+ const to = input.toColor ? parseColor(input.toColor) : undefined;
421
+ const tolerance = Math.max(0, Math.min(255, assertInteger(input.tolerance ?? 0, "tolerance")));
422
+ const presetColors = input.preset ? getAsepritePalettePreset(input.preset) : undefined;
423
+ const presetLua = presetColors ? `{${presetColors.map((color) => {
424
+ const parsed = parseColor(color);
425
+ return `{r=${parsed.r},g=${parsed.g},b=${parsed.b},a=${parsed.a}}`;
426
+ }).join(",")}}` : "nil";
427
+ const result = await runLua(config, registry, input.workspaceId, `
428
+ ${openSpriteScript(file.absolute)}
429
+ local action=${luaString(input.action)}
430
+ if action=="apply_palette_preset" then
431
+ if ${presetLua}==nil then fail("preset is required") end
432
+ local colors=${presetLua} local new_palette=Palette(#colors)
433
+ for index,color in ipairs(colors) do new_palette:setColor(index-1,Color(color)) end
434
+ sprite:setPalette(new_palette)
435
+ elseif action=="set_palette_entry" then
436
+ local index=${input.index ?? -1} if index<0 or #sprite.palettes==0 or index>=#sprite.palettes[1] then fail("Palette index is outside the palette") end
437
+ sprite.palettes[1]:setColor(index,Color(${input.color ? luaColor(input.color) : "{r=0,g=0,b=0,a=255}"}))
438
+ elseif action=="set_transparent_index" then
439
+ if sprite.colorMode~=ColorMode.INDEXED then fail("Transparent index is only valid for indexed sprites") end
440
+ local index=${input.index ?? -1} if index<0 or index>=#sprite.palettes[1] then fail("Transparent index is outside the palette") end sprite.transparentColor=index
441
+ elseif action=="remap_color" then
442
+ local from=${from ? `{r=${from.r},g=${from.g},b=${from.b},a=${from.a}}` : "nil"} local replacement=${to ? `{r=${to.r},g=${to.g},b=${to.b},a=${to.a}}` : "nil"}
443
+ if from==nil or replacement==nil then fail("fromColor and toColor are required") end
444
+ local changed=0
445
+ for _,cel in ipairs(sprite.cels) do for pixel in cel.image:pixels() do local c=pixel_rgba(sprite,pixel()) if math.abs(c.r-from.r)<=${tolerance} and math.abs(c.g-from.g)<=${tolerance} and math.abs(c.b-from.b)<=${tolerance} and math.abs(c.a-from.a)<=${tolerance} then pixel(color_pixel(sprite,replacement)) changed=changed+1 end end end
446
+ elseif action=="assign_profile" or action=="convert_profile" then
447
+ local cs=${profile ? `ColorSpace{fromFile=${luaString(profile.absolute)}}` : "ColorSpace{sRGB=true}"}
448
+ if action=="assign_profile" then sprite:assignColorSpace(cs) else sprite:convertColorSpace(cs) end
449
+ elseif action=="remove_profile" then sprite:assignColorSpace(ColorSpace())
450
+ elseif action=="load_palette" then sprite:setPalette(Palette{fromFile=${luaString(palette?.absolute ?? "")}})
451
+ elseif action=="save_palette" then app.command.SavePalette{ui=false,filename=${luaString(palette?.absolute ?? "")},columns=16}
452
+ else fail("Unsupported color action") end
453
+ if action~="save_palette" and not sprite:saveAs(${luaString(file.absolute)}) then fail("Unable to save sprite") end
454
+ emit_result({file=${luaString(file.relative)},action=action,color_profile=sprite.colorSpace and sprite.colorSpace.name or nil,transparent_index=sprite.transparentColor,palette_size=#sprite.palettes>0 and #sprite.palettes[1] or 0}) sprite:close()
455
+ `);
456
+ return textResponse(result);
457
+ }
458
+ catch (error) {
459
+ return errorResponse(error instanceof Error ? error.message : String(error));
460
+ }
461
+ }
462
+ export async function asepriteManageDocument(config, registry, input) {
463
+ try {
464
+ await prepareMutation(config, registry, input.workspaceId, input.filePath, input, `document-${input.action}`);
465
+ const file = workspacePath(registry, input.workspaceId, input.filePath);
466
+ const properties = input.properties ?? {};
467
+ const propertiesLua = `{${Object.entries(properties).map(([key, value]) => {
468
+ const encoded = value === null ? "nil" : typeof value === "string" ? luaString(value) : String(value);
469
+ return `[${luaString(key)}]=${encoded}`;
470
+ }).join(",")}}`;
471
+ const result = await runLua(config, registry, input.workspaceId, `
472
+ ${openSpriteScript(file.absolute)}
473
+ local action=${luaString(input.action)}
474
+ if action=="resize_sprite" then sprite:resize(${input.width ?? 0},${input.height ?? 0})
475
+ elseif action=="resize_canvas" then
476
+ local old_width=sprite.width local old_height=sprite.height sprite.width=${input.width ?? 0} sprite.height=${input.height ?? 0}
477
+ for _,cel in ipairs(sprite.cels) do cel.position=Point(cel.position.x+${input.offsetX ?? 0},cel.position.y+${input.offsetY ?? 0}) end
478
+ elseif action=="crop" then sprite:crop{x=${input.region?.x ?? 0},y=${input.region?.y ?? 0},width=${input.region?.width ?? 0},height=${input.region?.height ?? 0}}
479
+ elseif action=="trim" then
480
+ local bounds=nil
481
+ for frame=1,#sprite.frames do local render=Image(sprite.spec) render:clear() render:drawSprite(sprite,frame,Point(0,0)) for y=0,sprite.height-1 do for x=0,sprite.width-1 do if pixel_rgba(sprite,render:getPixel(x,y)).a>0 then if bounds==nil then bounds={x=x,y=y,right=x,bottom=y} else if x<bounds.x then bounds.x=x end if y<bounds.y then bounds.y=y end if x>bounds.right then bounds.right=x end if y>bounds.bottom then bounds.bottom=y end end end end end end
482
+ if bounds~=nil then sprite:crop{x=bounds.x,y=bounds.y,width=bounds.right-bounds.x+1,height=bounds.bottom-bounds.y+1} end
483
+ elseif action=="set_grid" then sprite.gridBounds=Rectangle(${input.grid?.x ?? 0},${input.grid?.y ?? 0},${input.grid?.width ?? 16},${input.grid?.height ?? 16})
484
+ elseif action=="set_pixel_ratio" then sprite.pixelRatio=Size(${input.ratioWidth ?? 1},${input.ratioHeight ?? 1})
485
+ elseif action=="set_userdata" then
486
+ local target=sprite local target_type=${luaString(input.targetType ?? "sprite")}
487
+ if target_type=="layer" then target=find_layer(sprite.layers,${luaString(input.targetName ?? "")})
488
+ elseif target_type=="cel" then local layer=find_layer(sprite.layers,${luaString(input.targetName ?? "")}) target=layer and layer:cel(${input.frame ?? 1}) or nil
489
+ elseif target_type=="tag" then for _,item in ipairs(sprite.tags) do if item.name==${luaString(input.targetName ?? "")} then target=item break end end
490
+ elseif target_type=="slice" then for _,item in ipairs(sprite.slices) do if item.name==${luaString(input.targetName ?? "")} then target=item break end end end
491
+ if target==nil then fail("User-data target was not found") end
492
+ target.data=${luaString(input.data ?? "")} target.properties=${propertiesLua}
493
+ elseif action=="create_slice" then
494
+ local region=Rectangle(${input.region?.x ?? 0},${input.region?.y ?? 0},${input.region?.width ?? 1},${input.region?.height ?? 1})
495
+ local slice=sprite:newSlice(region) slice.name=${luaString(input.sliceName ?? "Slice")}
496
+ ${input.center ? `slice.center=Rectangle(${input.center.x},${input.center.y},${input.center.width},${input.center.height})` : ""}
497
+ ${input.pivot ? `slice.pivot=Point(${input.pivot.x},${input.pivot.y})` : ""}
498
+ elseif action=="update_slice" then
499
+ local slice=nil for _,item in ipairs(sprite.slices) do if item.name==${luaString(input.sliceName ?? "")} then slice=item break end end if slice==nil then fail("Slice was not found") end
500
+ ${input.region ? `slice.bounds=Rectangle(${input.region.x},${input.region.y},${input.region.width},${input.region.height})` : ""}
501
+ ${input.center ? `slice.center=Rectangle(${input.center.x},${input.center.y},${input.center.width},${input.center.height})` : ""}
502
+ ${input.pivot ? `slice.pivot=Point(${input.pivot.x},${input.pivot.y})` : ""}
503
+ elseif action=="delete_slice" then
504
+ local slice=nil for _,item in ipairs(sprite.slices) do if item.name==${luaString(input.sliceName ?? "")} then slice=item break end end if slice==nil then fail("Slice was not found") end sprite:deleteSlice(slice)
505
+ else fail("Unsupported document action") end
506
+ if not sprite:saveAs(${luaString(file.absolute)}) then fail("Unable to save sprite") end emit_result(sprite_summary(sprite)) sprite:close()
507
+ `);
508
+ return textResponse(result);
509
+ }
510
+ catch (error) {
511
+ return errorResponse(error instanceof Error ? error.message : String(error));
512
+ }
513
+ }
514
+ export async function asepriteManageCels(config, registry, input) {
515
+ try {
516
+ const file = workspacePath(registry, input.workspaceId, input.filePath);
517
+ const frame = assertPositiveInteger(input.frame, "frame");
518
+ if (input.action !== "inspect")
519
+ await prepareMutation(config, registry, input.workspaceId, input.filePath, input, `cel-${input.action}`);
520
+ const result = await runLua(config, registry, input.workspaceId, `
521
+ ${openSpriteScript(file.absolute)}
522
+ local layer=find_layer(sprite.layers,${luaString(input.layer)}) if layer==nil or layer.isGroup then fail("Layer was not found") end
523
+ if ${frame}>#sprite.frames then fail("Frame is outside the sprite") end
524
+ local cel=layer:cel(${frame}) local action=${luaString(input.action)}
525
+ if action=="inspect" then
526
+ emit_result({exists=cel~=nil,cel=cel and {layer=layer.name,frame=${frame},x=cel.position.x,y=cel.position.y,width=cel.image.width,height=cel.image.height,opacity=cel.opacity,z_index=cel.zIndex,image_id=tostring(cel.image.id or cel.image),data=cel.data} or nil}) sprite:close() return
527
+ elseif action=="create" then if cel==nil then cel=sprite:newCel(layer,${frame},Image(sprite.spec),Point(${input.x ?? 0},${input.y ?? 0})) end
528
+ elseif cel==nil then fail("Cel was not found")
529
+ elseif action=="delete" then sprite:deleteCel(cel)
530
+ elseif action=="move" then cel.position=Point(${input.x ?? 0},${input.y ?? 0})
531
+ elseif action=="set_opacity" then cel.opacity=${input.opacity ?? 255}
532
+ elseif action=="set_z_index" then cel.zIndex=${input.zIndex ?? 0}
533
+ elseif action=="duplicate" or action=="link" then
534
+ local target_layer=find_layer(sprite.layers,${luaString(input.targetLayer ?? input.layer)}) if target_layer==nil or target_layer.isGroup then fail("Target layer was not found") end
535
+ local target_frame=${input.targetFrame ?? frame} if target_frame>#sprite.frames then fail("Target frame is outside the sprite") end
536
+ local existing=target_layer:cel(target_frame) if existing~=nil then sprite:deleteCel(existing) end
537
+ local image=action=="link" and cel.image or Image(cel.image)
538
+ local target=sprite:newCel(target_layer,target_frame,image,Point(cel.position.x,cel.position.y)) target.opacity=cel.opacity target.zIndex=cel.zIndex
539
+ elseif action=="unlink" then cel.image=Image(cel.image)
540
+ else fail("Unsupported cel action") end
541
+ if not sprite:saveAs(${luaString(file.absolute)}) then fail("Unable to save sprite") end emit_result(sprite_summary(sprite)) sprite:close()
542
+ `);
543
+ return textResponse(result);
544
+ }
545
+ catch (error) {
546
+ return errorResponse(error instanceof Error ? error.message : String(error));
547
+ }
548
+ }
549
+ export async function asepriteManageAnimation(config, registry, input) {
550
+ try {
551
+ await prepareMutation(config, registry, input.workspaceId, input.filePath, input, `animation-${input.action}`);
552
+ const file = workspacePath(registry, input.workspaceId, input.filePath);
553
+ const durationLua = `{${(input.durations ?? []).map((item) => `{frame=${assertPositiveInteger(item.frame, "duration.frame")},duration=${Math.max(1, Math.min(60000, assertInteger(item.durationMs, "duration.durationMs"))) / 1000}}`).join(",")}}`;
554
+ const directionLua = input.direction === "reverse" ? "AniDir.REVERSE" : input.direction === "ping_pong" ? "AniDir.PING_PONG" : "AniDir.FORWARD";
555
+ const result = await runLua(config, registry, input.workspaceId, `
556
+ ${openSpriteScript(file.absolute)}
557
+ local action=${luaString(input.action)}
558
+ if action=="set_durations" then for _,item in ipairs(${durationLua}) do if item.frame>#sprite.frames then fail("Duration frame is outside the sprite") end sprite.frames[item.frame].duration=item.duration end
559
+ elseif action=="update_tag" then
560
+ local tag=nil for _,item in ipairs(sprite.tags) do if item.name==${luaString(input.tagName ?? "")} then tag=item break end end if tag==nil then fail("Tag was not found") end
561
+ ${input.newName ? `tag.name=${luaString(input.newName)}` : ""} tag.aniDir=${directionLua} ${input.repeats !== undefined ? `tag.repeats=${Math.max(0, assertInteger(input.repeats, "repeats"))}` : ""}
562
+ ${input.from !== undefined && input.to !== undefined ? `tag.fromFrame=sprite.frames[${input.from}] tag.toFrame=sprite.frames[${input.to}]` : ""}
563
+ elseif action=="tween_cel_position" then
564
+ local layer=find_layer(sprite.layers,${luaString(input.layer ?? "")})
565
+ if layer==nil or layer.isGroup then fail("A drawable layer is required") end
566
+ local first=${input.from ?? 1} local last=${input.to ?? 1}
567
+ if first<1 or last>#sprite.frames or first>=last then fail("A valid tween frame range is required") end
568
+ local first_cel=layer:cel(first)
569
+ if first_cel==nil then fail("The first tween frame has no cel on the selected layer") end
570
+ local start_x=first_cel.position.x local start_y=first_cel.position.y
571
+ local end_x=${input.targetX ?? 0} local end_y=${input.targetY ?? 0}
572
+ local easing=${luaString(input.easing ?? "smoothstep")}
573
+ local function ease(t)
574
+ if easing=="ease_in" then return t*t end
575
+ if easing=="ease_out" then return 1-(1-t)*(1-t) end
576
+ if easing=="smoothstep" then return t*t*(3-2*t) end
577
+ return t
578
+ end
579
+ for frame_number=first,last do
580
+ local cel=layer:cel(frame_number)
581
+ if cel~=nil then local t=ease((frame_number-first)/(last-first)) cel.position=Point(math.floor(start_x+(end_x-start_x)*t+0.5),math.floor(start_y+(end_y-start_y)*t+0.5)) end
582
+ end
583
+ else
584
+ local frame=${input.frame ?? input.from ?? 1} local target=${input.targetFrame ?? input.to ?? 1}
585
+ if frame<1 or frame>#sprite.frames or target<1 or target>#sprite.frames then fail("Frame is outside the sprite") end
586
+ if action=="copy_frame" or action=="move_frame" then
587
+ local source_frame=sprite.frames[frame]
588
+ local duration=source_frame.duration
589
+ local stored={}
590
+ local function capture_layers(items)
591
+ for _,candidate in ipairs(items) do
592
+ if candidate.isGroup then capture_layers(candidate.layers)
593
+ else
594
+ local cel=candidate:cel(frame)
595
+ if cel~=nil then table.insert(stored,{layer=candidate,image=Image(cel.image),position=Point(cel.position.x,cel.position.y),opacity=cel.opacity,z_index=cel.zIndex,data=cel.data}) end
596
+ end
597
+ end
598
+ end
599
+ capture_layers(sprite.layers)
600
+ local insertion=target
601
+ if action=="move_frame" and target>frame then insertion=target+1 end
602
+ if insertion<1 or insertion>#sprite.frames+1 then fail("Target frame is outside the insertion range") end
603
+ local inserted=sprite:newEmptyFrame(insertion)
604
+ local inserted_number=inserted.frameNumber
605
+ inserted.duration=duration
606
+ for _,item in ipairs(stored) do local cel=sprite:newCel(item.layer,inserted_number,item.image,item.position) cel.opacity=item.opacity cel.zIndex=item.z_index cel.data=item.data end
607
+ if action=="move_frame" then
608
+ local original=target<=frame and frame+1 or frame
609
+ sprite:deleteFrame(original)
610
+ end
611
+ elseif action=="reverse_range" then app.range.frames={} for index=${input.from ?? 1},${input.to ?? 1} do table.insert(app.range.frames,sprite.frames[index]) end app.command.ReverseFrames()
612
+ else fail("Unsupported animation action") end
613
+ end
614
+ if not sprite:saveAs(${luaString(file.absolute)}) then fail("Unable to save sprite") end emit_result(sprite_summary(sprite)) sprite:close()
615
+ `);
616
+ return textResponse(result);
617
+ }
618
+ catch (error) {
619
+ return errorResponse(error instanceof Error ? error.message : String(error));
620
+ }
621
+ }
622
+ export async function asepriteRunSafeCommand(config, registry, input) {
623
+ try {
624
+ await prepareMutation(config, registry, input.workspaceId, input.filePath, input, `safe-command-${input.command}`);
625
+ const file = workspacePath(registry, input.workspaceId, input.filePath);
626
+ const result = await runLua(config, registry, input.workspaceId, `
627
+ ${openSpriteScript(file.absolute)}
628
+ local command=${luaString(input.command)}
629
+ local layer=${input.layer ? `find_layer(sprite.layers,${luaString(input.layer)})` : "sprite.layers[1]"}
630
+ if command=="flatten" then sprite:flatten()
631
+ elseif command=="clear_cel" then if layer==nil or layer.isGroup then fail("Layer was not found") end local cel=layer:cel(${input.frame ?? 1}) if cel~=nil then cel.image:clear() end
632
+ elseif command=="merge_down" then if layer==nil then fail("Layer was not found") end app.activeLayer=layer app.command.MergeDownLayer()
633
+ elseif command=="background_from_layer" then if layer==nil then fail("Layer was not found") end app.activeLayer=layer app.command.BackgroundFromLayer()
634
+ elseif command=="layer_from_background" then if layer==nil then fail("Layer was not found") end app.activeLayer=layer app.command.LayerFromBackground()
635
+ elseif command=="reverse_frames" then app.range.frames={} for index=${input.from ?? 1},${input.to ?? "#sprite.frames"} do table.insert(app.range.frames,sprite.frames[index]) end app.command.ReverseFrames()
636
+ elseif command=="copy_merged_to_layer" then
637
+ local output=sprite:newLayer() output.name=${luaString(input.outputLayerName ?? "Merged Copy")}
638
+ for frame=1,#sprite.frames do local render=Image(sprite.spec) render:clear() render:drawSprite(sprite,frame,Point(0,0)) sprite:newCel(output,frame,render,Point(0,0)) end
639
+ else fail("Command is not in the safe allowlist") end
640
+ if not sprite:saveAs(${luaString(file.absolute)}) then fail("Unable to save sprite") end emit_result({command=command,summary=sprite_summary(sprite)}) sprite:close()
641
+ `);
642
+ return textResponse(result);
643
+ }
644
+ catch (error) {
645
+ return errorResponse(error instanceof Error ? error.message : String(error));
646
+ }
647
+ }