pixelweaver 0.1.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 (392) hide show
  1. package/.env.development +1 -0
  2. package/.github/workflows/ci.yml +22 -0
  3. package/.github/workflows/publish.yml +18 -0
  4. package/.prettierignore +5 -0
  5. package/.prettierrc +16 -0
  6. package/.python-version +1 -0
  7. package/.rlsbl/bases/.github/workflows/ci.yml +21 -0
  8. package/.rlsbl/bases/.github/workflows/publish.yml +18 -0
  9. package/.rlsbl/bases/.rlsbl/changes/unreleased.jsonl +0 -0
  10. package/.rlsbl/bases/.rlsbl/hooks/post-release.sh +8 -0
  11. package/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +5 -0
  12. package/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +8 -0
  13. package/.rlsbl/bases/.rlsbl/lint/go.toml +17 -0
  14. package/.rlsbl/bases/.rlsbl/lint/npm.toml +19 -0
  15. package/.rlsbl/bases/.rlsbl/lint/python.toml +25 -0
  16. package/.rlsbl/bases/CHANGELOG.md +5 -0
  17. package/.rlsbl/bases/LICENSE +21 -0
  18. package/.rlsbl/changes/.validated +1 -0
  19. package/.rlsbl/changes/0.1.0.jsonl +85 -0
  20. package/.rlsbl/changes/0.1.0.md +13 -0
  21. package/.rlsbl/changes/unreleased.jsonl +0 -0
  22. package/.rlsbl/config.json +6 -0
  23. package/.rlsbl/hashes.json +14 -0
  24. package/.rlsbl/hooks/post-release.sh +8 -0
  25. package/.rlsbl/hooks/pre-checks.sh +5 -0
  26. package/.rlsbl/hooks/pre-release.sh +8 -0
  27. package/.rlsbl/lint/go.toml +17 -0
  28. package/.rlsbl/lint/npm.toml +19 -0
  29. package/.rlsbl/lint/python.toml +25 -0
  30. package/.rlsbl/releases/unreleased.toml +0 -0
  31. package/.rlsbl/releases/v0.1.0.toml +3 -0
  32. package/.rlsbl/version +1 -0
  33. package/.selfdoc/hashes/hashes.json +146 -0
  34. package/.strictcli/schema.json +227 -0
  35. package/CHANGELOG.md +17 -0
  36. package/CLAUDE.md +100 -0
  37. package/LICENSE +21 -0
  38. package/README.md +116 -0
  39. package/assets/icon.png +0 -0
  40. package/docs/_README.md +117 -0
  41. package/docs/cli-config.md +35 -0
  42. package/docs/cli-dev.md +21 -0
  43. package/docs/cli-diagnose.md +12 -0
  44. package/docs/cli-index.md +30 -0
  45. package/docs/cli-list.md +18 -0
  46. package/docs/cli-mcp.md +18 -0
  47. package/docs/cli-new.md +26 -0
  48. package/docs/cli-open.md +21 -0
  49. package/docs/cli-serve.md +21 -0
  50. package/docs/cli-stop.md +12 -0
  51. package/docs/gen-index.md +36 -0
  52. package/docs/index.md +13 -0
  53. package/docs/server-src-pixelweaver-__main__.md +12 -0
  54. package/docs/server-src-pixelweaver-autosave.md +12 -0
  55. package/docs/server-src-pixelweaver-bridge.md +12 -0
  56. package/docs/server-src-pixelweaver-cli.md +12 -0
  57. package/docs/server-src-pixelweaver-config.md +12 -0
  58. package/docs/server-src-pixelweaver-connections.md +12 -0
  59. package/docs/server-src-pixelweaver-main.md +12 -0
  60. package/docs/server-src-pixelweaver-mcp_bridge.md +12 -0
  61. package/docs/server-src-pixelweaver-mcp_drawing_tools.md +12 -0
  62. package/docs/server-src-pixelweaver-mcp_export_tools.md +12 -0
  63. package/docs/server-src-pixelweaver-mcp_frame_tools.md +12 -0
  64. package/docs/server-src-pixelweaver-mcp_history_tools.md +12 -0
  65. package/docs/server-src-pixelweaver-mcp_layer_tools.md +12 -0
  66. package/docs/server-src-pixelweaver-mcp_lock.md +12 -0
  67. package/docs/server-src-pixelweaver-mcp_project_tools.md +12 -0
  68. package/docs/server-src-pixelweaver-mcp_read_tools.md +12 -0
  69. package/docs/server-src-pixelweaver-mcp_registry.md +12 -0
  70. package/docs/server-src-pixelweaver-mcp_resources.md +12 -0
  71. package/docs/server-src-pixelweaver-mcp_server.md +12 -0
  72. package/docs/server-src-pixelweaver-protocol.md +12 -0
  73. package/docs/server-src-pixelweaver-state.md +12 -0
  74. package/docs/server-src-pixelweaver-storage.md +12 -0
  75. package/docs/server-src-pixelweaver-websocket.md +12 -0
  76. package/docs/server-src-pixelweaver.md +12 -0
  77. package/e2e/app-launch.test.ts +35 -0
  78. package/e2e/menus.test.ts +26 -0
  79. package/e2e/tools.test.ts +27 -0
  80. package/e2e/undo-redo.test.ts +11 -0
  81. package/eslint.config.js +62 -0
  82. package/index.html +13 -0
  83. package/package.json +48 -0
  84. package/playwright.config.ts +19 -0
  85. package/plugins/builtin/.gitkeep +0 -0
  86. package/plugins/builtin/advanced-fill-tool.ts +146 -0
  87. package/plugins/builtin/circle-tool.ts +186 -0
  88. package/plugins/builtin/diamond-tool.ts +182 -0
  89. package/plugins/builtin/dither-tool.ts +186 -0
  90. package/plugins/builtin/drawing-primitives-plugin.ts +362 -0
  91. package/plugins/builtin/drawing-utils.test.ts +495 -0
  92. package/plugins/builtin/drawing-utils.ts +431 -0
  93. package/plugins/builtin/effects/blur.ts +97 -0
  94. package/plugins/builtin/effects/color-effects.ts +278 -0
  95. package/plugins/builtin/effects/flip.ts +83 -0
  96. package/plugins/builtin/effects/glow.ts +118 -0
  97. package/plugins/builtin/effects/outline.ts +110 -0
  98. package/plugins/builtin/effects/rotate.ts +357 -0
  99. package/plugins/builtin/effects/scale.ts +258 -0
  100. package/plugins/builtin/effects/shadow.ts +111 -0
  101. package/plugins/builtin/effects/sharpen.ts +102 -0
  102. package/plugins/builtin/effects.test.ts +715 -0
  103. package/plugins/builtin/eraser-tool.ts +23 -0
  104. package/plugins/builtin/eyedropper-tool.ts +83 -0
  105. package/plugins/builtin/fill-tool.ts +93 -0
  106. package/plugins/builtin/gradient-tool.ts +204 -0
  107. package/plugins/builtin/gradient.test.ts +142 -0
  108. package/plugins/builtin/importers/aseprite-importer-plugin.ts +174 -0
  109. package/plugins/builtin/importers/aseprite-parser.ts +497 -0
  110. package/plugins/builtin/importers/piskel-importer-plugin.ts +222 -0
  111. package/plugins/builtin/importers/sky-spec-plugin.ts +409 -0
  112. package/plugins/builtin/line-tool.ts +267 -0
  113. package/plugins/builtin/make-stroke-tool.ts +271 -0
  114. package/plugins/builtin/noise-dither.test.ts +151 -0
  115. package/plugins/builtin/noise-tool.ts +131 -0
  116. package/plugins/builtin/pattern-stamp-tool.ts +162 -0
  117. package/plugins/builtin/pencil-tool.ts +25 -0
  118. package/plugins/builtin/rect-tool.ts +179 -0
  119. package/plugins/builtin/selection-tool.ts +388 -0
  120. package/plugins/builtin/selection.test.ts +195 -0
  121. package/plugins/builtin/tool-plugins.test.ts +529 -0
  122. package/public/favicon.svg +7 -0
  123. package/public/sw.js +91 -0
  124. package/pyproject.toml +49 -0
  125. package/scripts/eslint-wave-a-list.sh +24 -0
  126. package/scripts/eslint-wave-a-status.sh +25 -0
  127. package/scripts/fix-index-signature-access.py +127 -0
  128. package/scripts/fix-unchecked-index.py +128 -0
  129. package/scripts/fix-unchecked-vars.py +127 -0
  130. package/scripts/fix-wave-a-bangs.py +36 -0
  131. package/scripts/fix-wave-a-templates.py +108 -0
  132. package/scripts/fix-wave-b-void-expr.py +167 -0
  133. package/scripts/generate-command-params.py +540 -0
  134. package/scripts/migrate-single-frame-to-multi.py +171 -0
  135. package/scripts/smoke-test.sh +77 -0
  136. package/selfdoc.json +10 -0
  137. package/server/README.md +0 -0
  138. package/server/src/pixelweaver/__init__.py +1 -0
  139. package/server/src/pixelweaver/__main__.py +4 -0
  140. package/server/src/pixelweaver/autosave.py +114 -0
  141. package/server/src/pixelweaver/bridge.py +127 -0
  142. package/server/src/pixelweaver/cli.py +199 -0
  143. package/server/src/pixelweaver/config.py +24 -0
  144. package/server/src/pixelweaver/connections.py +54 -0
  145. package/server/src/pixelweaver/main.py +271 -0
  146. package/server/src/pixelweaver/mcp_bridge.py +189 -0
  147. package/server/src/pixelweaver/mcp_drawing_tools.py +178 -0
  148. package/server/src/pixelweaver/mcp_export_tools.py +291 -0
  149. package/server/src/pixelweaver/mcp_frame_tools.py +423 -0
  150. package/server/src/pixelweaver/mcp_history_tools.py +106 -0
  151. package/server/src/pixelweaver/mcp_layer_tools.py +64 -0
  152. package/server/src/pixelweaver/mcp_lock.py +37 -0
  153. package/server/src/pixelweaver/mcp_project_tools.py +302 -0
  154. package/server/src/pixelweaver/mcp_read_tools.py +163 -0
  155. package/server/src/pixelweaver/mcp_registry.py +247 -0
  156. package/server/src/pixelweaver/mcp_resources.py +312 -0
  157. package/server/src/pixelweaver/mcp_server.py +234 -0
  158. package/server/src/pixelweaver/protocol.py +219 -0
  159. package/server/src/pixelweaver/state.py +267 -0
  160. package/server/src/pixelweaver/storage.py +293 -0
  161. package/server/src/pixelweaver/websocket.py +282 -0
  162. package/server/tests/__init__.py +0 -0
  163. package/server/tests/conftest.py +17 -0
  164. package/server/tests/test_api.py +96 -0
  165. package/server/tests/test_bridge.py +161 -0
  166. package/server/tests/test_health.py +9 -0
  167. package/server/tests/test_integration.py +86 -0
  168. package/server/tests/test_mcp_bridge.py +293 -0
  169. package/server/tests/test_mcp_lock.py +34 -0
  170. package/server/tests/test_mcp_registry.py +679 -0
  171. package/server/tests/test_mcp_resources.py +648 -0
  172. package/server/tests/test_protocol.py +125 -0
  173. package/server/tests/test_state.py +87 -0
  174. package/server/tests/test_storage.py +306 -0
  175. package/server/tests/test_websocket.py +275 -0
  176. package/src/App.svelte +107 -0
  177. package/src/app.css +215 -0
  178. package/src/lib/animation/AnimationPreviewPanel.svelte +667 -0
  179. package/src/lib/animation/animation-commands.test.ts +228 -0
  180. package/src/lib/animation/animation-commands.ts +540 -0
  181. package/src/lib/animation/animation-preview-panel-plugin.ts +25 -0
  182. package/src/lib/animation/animation-preview.svelte.ts +151 -0
  183. package/src/lib/animation/clipboard.ts +134 -0
  184. package/src/lib/animation/frame-model.svelte.ts +437 -0
  185. package/src/lib/animation/frame-model.test.ts +314 -0
  186. package/src/lib/animation/frame-selection.svelte.ts +77 -0
  187. package/src/lib/animation/frame-tags.svelte.ts +238 -0
  188. package/src/lib/animation/frame-tags.test.ts +136 -0
  189. package/src/lib/animation/import.test.ts +141 -0
  190. package/src/lib/animation/import.ts +112 -0
  191. package/src/lib/animation/spritesheet-export.test.ts +239 -0
  192. package/src/lib/animation/spritesheet-export.ts +314 -0
  193. package/src/lib/canvas/CanvasViewport.svelte +216 -0
  194. package/src/lib/canvas/canvas-init-plugin.ts +178 -0
  195. package/src/lib/canvas/canvas-renderer.ts +408 -0
  196. package/src/lib/canvas/canvas-state.svelte.ts +232 -0
  197. package/src/lib/canvas/canvas-state.test.ts +139 -0
  198. package/src/lib/canvas/input-handler.ts +221 -0
  199. package/src/lib/canvas/input-plugin.ts +150 -0
  200. package/src/lib/canvas/onion-skin.ts +94 -0
  201. package/src/lib/canvas/pixel-buffer.test.ts +249 -0
  202. package/src/lib/canvas/pixel-buffer.ts +151 -0
  203. package/src/lib/canvas/render-state.svelte.ts +18 -0
  204. package/src/lib/canvas/shape-preview-state.svelte.ts +36 -0
  205. package/src/lib/canvas/tile-mode.test.ts +53 -0
  206. package/src/lib/canvas/tile-mode.ts +92 -0
  207. package/src/lib/canvas/viewport-utils.ts +24 -0
  208. package/src/lib/canvas/zoom-utils.ts +31 -0
  209. package/src/lib/color/.gitkeep +0 -0
  210. package/src/lib/color/color-commands.ts +87 -0
  211. package/src/lib/color/color-state.svelte.ts +98 -0
  212. package/src/lib/color/color-state.test.ts +91 -0
  213. package/src/lib/color/color-utils.test.ts +220 -0
  214. package/src/lib/color/color-utils.ts +243 -0
  215. package/src/lib/color/palette-state.svelte.ts +127 -0
  216. package/src/lib/color/palette-state.test.ts +154 -0
  217. package/src/lib/color/palette.ts +79 -0
  218. package/src/lib/core/bootstrap.ts +66 -0
  219. package/src/lib/core/command-params.generated.ts +1549 -0
  220. package/src/lib/core/command-params.ts +20 -0
  221. package/src/lib/core/command-runner.ts +79 -0
  222. package/src/lib/core/commands.ts +134 -0
  223. package/src/lib/core/dispatcher.test.ts +548 -0
  224. package/src/lib/core/dispatcher.ts +361 -0
  225. package/src/lib/core/index.test.ts +7 -0
  226. package/src/lib/core/notification-state.svelte.ts +119 -0
  227. package/src/lib/core/plugin-api.ts +210 -0
  228. package/src/lib/core/plugin-discovery.test.ts +53 -0
  229. package/src/lib/core/plugin-discovery.ts +65 -0
  230. package/src/lib/core/plugin-loader.test.ts +159 -0
  231. package/src/lib/core/plugin-loader.ts +240 -0
  232. package/src/lib/core/plugin-types.ts +286 -0
  233. package/src/lib/core/registries.svelte.ts +74 -0
  234. package/src/lib/core/tool-options-state.svelte.ts +61 -0
  235. package/src/lib/dock/DockLayout.svelte +375 -0
  236. package/src/lib/dock/TabAddMenu.svelte +90 -0
  237. package/src/lib/dock/dock-persistence.ts +46 -0
  238. package/src/lib/dock/dock-plugin.ts +49 -0
  239. package/src/lib/dock/dock-presets.ts +156 -0
  240. package/src/lib/dock/dock-state.svelte.ts +77 -0
  241. package/src/lib/dock/dock-types.ts +2 -0
  242. package/src/lib/dock/dockview-theme.css +226 -0
  243. package/src/lib/dock/dockview-theme.ts +17 -0
  244. package/src/lib/dock/header-action-renderer.ts +77 -0
  245. package/src/lib/edit/clipboard-state.ts +34 -0
  246. package/src/lib/export/download.ts +201 -0
  247. package/src/lib/export/png-metadata.ts +181 -0
  248. package/src/lib/history/ActionLogPanel.svelte +418 -0
  249. package/src/lib/history/action-log-panel-plugin.ts +24 -0
  250. package/src/lib/history/action-log.svelte.ts +172 -0
  251. package/src/lib/history/action-log.test.ts +168 -0
  252. package/src/lib/history/history-commands.ts +403 -0
  253. package/src/lib/history/macros.svelte.ts +320 -0
  254. package/src/lib/history/macros.test.ts +224 -0
  255. package/src/lib/history/replay-engine.test.ts +241 -0
  256. package/src/lib/history/replay-engine.ts +149 -0
  257. package/src/lib/history/spec-format.test.ts +250 -0
  258. package/src/lib/history/spec-format.ts +210 -0
  259. package/src/lib/iso/SeamCheckerPanel.svelte +251 -0
  260. package/src/lib/iso/iso-math.test.ts +77 -0
  261. package/src/lib/iso/iso-math.ts +77 -0
  262. package/src/lib/iso/seam-checker-panel-plugin.ts +25 -0
  263. package/src/lib/iso/seam-checker.test.ts +126 -0
  264. package/src/lib/iso/seam-checker.ts +93 -0
  265. package/src/lib/iso/tessellation.ts +67 -0
  266. package/src/lib/layers/compositor.test.ts +193 -0
  267. package/src/lib/layers/compositor.ts +175 -0
  268. package/src/lib/layers/layer-commands.test.ts +263 -0
  269. package/src/lib/layers/layer-commands.ts +429 -0
  270. package/src/lib/layers/layer-tree.svelte.ts +516 -0
  271. package/src/lib/layers/layer-tree.test.ts +383 -0
  272. package/src/lib/layers/layer-types.ts +56 -0
  273. package/src/lib/leveleditor/LevelEditorViewport.svelte +1808 -0
  274. package/src/lib/leveleditor/MapPropertiesPanel.svelte +266 -0
  275. package/src/lib/leveleditor/TilePickerPanel.svelte +324 -0
  276. package/src/lib/leveleditor/depth-sort.test.ts +70 -0
  277. package/src/lib/leveleditor/depth-sort.ts +39 -0
  278. package/src/lib/leveleditor/level-editor-commands.ts +353 -0
  279. package/src/lib/leveleditor/level-editor-viewport-plugin.ts +25 -0
  280. package/src/lib/leveleditor/map-properties-panel-plugin.ts +25 -0
  281. package/src/lib/leveleditor/map-state.svelte.ts +372 -0
  282. package/src/lib/leveleditor/map-state.test.ts +243 -0
  283. package/src/lib/leveleditor/tile-picker-panel-plugin.ts +25 -0
  284. package/src/lib/leveleditor/tile-source-registry.svelte.ts +91 -0
  285. package/src/lib/leveleditor/tiled-export.test.ts +144 -0
  286. package/src/lib/leveleditor/tiled-export.ts +374 -0
  287. package/src/lib/pywebview.d.ts +15 -0
  288. package/src/lib/recovery/.gitkeep +0 -0
  289. package/src/lib/recovery/idb-store.ts +118 -0
  290. package/src/lib/recovery/recovery-manager.test.ts +321 -0
  291. package/src/lib/recovery/recovery-manager.ts +115 -0
  292. package/src/lib/recovery/recovery-plugin.ts +55 -0
  293. package/src/lib/recovery/recovery-state.svelte.ts +21 -0
  294. package/src/lib/recovery/sw-plugin.ts +18 -0
  295. package/src/lib/recovery/sw-register.ts +17 -0
  296. package/src/lib/save/directory-format.ts +42 -0
  297. package/src/lib/save/project-snapshot.ts +139 -0
  298. package/src/lib/save/recent-projects.ts +56 -0
  299. package/src/lib/save/save-state.svelte.ts +35 -0
  300. package/src/lib/save/storage.ts +167 -0
  301. package/src/lib/save/zip-format.ts +45 -0
  302. package/src/lib/shortcuts/.gitkeep +0 -0
  303. package/src/lib/shortcuts/ShortcutEditorPanel.svelte +690 -0
  304. package/src/lib/shortcuts/default-bindings.ts +61 -0
  305. package/src/lib/shortcuts/shortcut-editor-panel-plugin.ts +25 -0
  306. package/src/lib/shortcuts/shortcut-init.ts +380 -0
  307. package/src/lib/shortcuts/shortcut-manager.test.ts +466 -0
  308. package/src/lib/shortcuts/shortcut-manager.ts +281 -0
  309. package/src/lib/shortcuts/shortcut-state.svelte.ts +78 -0
  310. package/src/lib/shortcuts/shortcuts-plugin.ts +17 -0
  311. package/src/lib/sync/patch-applicator.ts +300 -0
  312. package/src/lib/sync/patch-types.ts +65 -0
  313. package/src/lib/sync/project-manager.ts +108 -0
  314. package/src/lib/sync/sync-init.ts +152 -0
  315. package/src/lib/sync/sync-plugin.ts +19 -0
  316. package/src/lib/sync/sync-state.svelte.ts +56 -0
  317. package/src/lib/sync/ws-client.test.ts +604 -0
  318. package/src/lib/sync/ws-client.ts +574 -0
  319. package/src/lib/tools/.gitkeep +0 -0
  320. package/src/lib/ui/.gitkeep +0 -0
  321. package/src/lib/ui/AboutDialog.svelte +113 -0
  322. package/src/lib/ui/ColorPicker.svelte +761 -0
  323. package/src/lib/ui/ContextMenu.svelte +216 -0
  324. package/src/lib/ui/ExportDialog.svelte +747 -0
  325. package/src/lib/ui/FrameStrip.svelte +854 -0
  326. package/src/lib/ui/LayerPanel.svelte +810 -0
  327. package/src/lib/ui/MenuBar.svelte +590 -0
  328. package/src/lib/ui/NewProjectDialog.svelte +803 -0
  329. package/src/lib/ui/PluginManagerPanel.svelte +475 -0
  330. package/src/lib/ui/PromptDialog.svelte +252 -0
  331. package/src/lib/ui/RecoveryDialog.svelte +295 -0
  332. package/src/lib/ui/ResizeDialog.svelte +416 -0
  333. package/src/lib/ui/StatusBar.svelte +145 -0
  334. package/src/lib/ui/ToolbarPanel.svelte +488 -0
  335. package/src/lib/ui/animation-menu-commands.ts +194 -0
  336. package/src/lib/ui/command-palette/CommandPalette.svelte +232 -0
  337. package/src/lib/ui/command-palette/command-palette-plugin.ts +30 -0
  338. package/src/lib/ui/command-palette/command-palette-state.svelte.ts +190 -0
  339. package/src/lib/ui/command-palette/command-palette.test.ts +129 -0
  340. package/src/lib/ui/dialog-state.svelte.ts +70 -0
  341. package/src/lib/ui/edit-commands.ts +271 -0
  342. package/src/lib/ui/file-commands.ts +275 -0
  343. package/src/lib/ui/file-open.ts +99 -0
  344. package/src/lib/ui/help-commands.ts +93 -0
  345. package/src/lib/ui/image-commands.ts +181 -0
  346. package/src/lib/ui/layer-menu-commands.ts +420 -0
  347. package/src/lib/ui/menu-builder.ts +224 -0
  348. package/src/lib/ui/notifications/NotificationBanner.svelte +137 -0
  349. package/src/lib/ui/notifications/notification-plugin.ts +29 -0
  350. package/src/lib/ui/notifications/notification-state.svelte.ts +9 -0
  351. package/src/lib/ui/plugin-manager-panel-plugin.ts +26 -0
  352. package/src/lib/ui/plugin-state.svelte.ts +62 -0
  353. package/src/lib/ui/select-commands.ts +75 -0
  354. package/src/lib/ui/theme-plugin.ts +18 -0
  355. package/src/lib/ui/theme.svelte.ts +45 -0
  356. package/src/lib/ui/theme.test.ts +51 -0
  357. package/src/lib/ui/toolbar-config.ts +90 -0
  358. package/src/lib/ui/toolbar-plugin.ts +39 -0
  359. package/src/lib/ui/view-commands.ts +629 -0
  360. package/src/lib/variants/BisectionExportDialog.svelte +500 -0
  361. package/src/lib/variants/VariantPanel.svelte +822 -0
  362. package/src/lib/variants/bisection-export.test.ts +113 -0
  363. package/src/lib/variants/bisection-export.ts +148 -0
  364. package/src/lib/variants/palette-extraction.test.ts +111 -0
  365. package/src/lib/variants/palette-extraction.ts +84 -0
  366. package/src/lib/variants/palette-interpolation.test.ts +113 -0
  367. package/src/lib/variants/palette-interpolation.ts +87 -0
  368. package/src/lib/variants/palette-swap.test.ts +101 -0
  369. package/src/lib/variants/palette-swap.ts +114 -0
  370. package/src/lib/variants/variant-commands.ts +594 -0
  371. package/src/lib/variants/variant-panel-plugin.ts +27 -0
  372. package/src/lib/variants/variant-randomizer.ts +101 -0
  373. package/src/lib/variants/variant-state.svelte.ts +166 -0
  374. package/src/lib/variants/variant-state.test.ts +138 -0
  375. package/src/main.ts +14 -0
  376. package/src/vite-env.d.ts +3 -0
  377. package/svelte.config.js +2 -0
  378. package/todo/.done/audit-design-decisions.md +812 -0
  379. package/todo/.done/audit-implementation-plan.md +1235 -0
  380. package/todo/.done/happy-path-polish.md +177 -0
  381. package/todo/.done/pixelweaver-full-build.md +937 -0
  382. package/todo/.done/server-multi-frame-design.md +405 -0
  383. package/todo/.done/typed-dispatcher-design.md +435 -0
  384. package/todo/.done/unified-toolbar-and-action-system.md +323 -0
  385. package/todo/.obsolete/comprehensive-audit-obsolete-items.md +33 -0
  386. package/todo/.obsolete/tauri-desktop-bundle.md +424 -0
  387. package/todo/comprehensive-audit.md +1085 -0
  388. package/tsconfig.app.json +26 -0
  389. package/tsconfig.json +7 -0
  390. package/tsconfig.node.json +20 -0
  391. package/uv.lock +1167 -0
  392. package/vite.config.ts +32 -0
@@ -0,0 +1,234 @@
1
+ """MCP tool server for PixelWeaver.
2
+
3
+ Exposes PixelWeaver operations as MCP tools so that LLMs (via Claude Code,
4
+ Claude Desktop, etc.) can programmatically create and edit pixel art.
5
+
6
+ Runs as a separate process using stdio transport. Start with:
7
+ pixelweaver mcp [--data-dir ./projects]
8
+
9
+ Architecture
10
+ ------------
11
+ The MCP server does NOT own its own state. Before each tool execution it
12
+ pulls the latest state from the collaboration server (HTTP on port 7779),
13
+ runs the tool logic locally, then pushes the modified state back. The
14
+ collab server broadcasts a full-state patch to all WebSocket clients so
15
+ the frontend sees every MCP mutation in real time.
16
+
17
+ The collaboration server must be running for MCP to work. If it is
18
+ unreachable the MCP server returns a clear error to the LLM.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import logging
25
+ from contextlib import asynccontextmanager
26
+ from typing import Any
27
+
28
+ from mcp.server.fastmcp import FastMCP
29
+ from mcp.types import ImageContent, TextContent
30
+
31
+ from pixelweaver.connections import ConnectionManager
32
+ from pixelweaver.mcp_bridge import (
33
+ CollabServerUnreachableError,
34
+ deserialize_into_state,
35
+ pull_full_state,
36
+ push_full_state,
37
+ serialize_state,
38
+ )
39
+ from pixelweaver.mcp_registry import MCPCommandRegistry
40
+ from pixelweaver.mcp_resources import (
41
+ init_mcp_resources,
42
+ init_mcp_subscriptions,
43
+ notify_resource_subscribers,
44
+ )
45
+ from pixelweaver.state import ServerState
46
+
47
+ logger = logging.getLogger(__name__)
48
+
49
+ # Module-level instances used by the MCP server process.
50
+ # State is populated from the collab server on each tool call -- not from disk.
51
+ _state: ServerState | None = None
52
+ _connections: ConnectionManager | None = None
53
+ _registry: MCPCommandRegistry | None = None
54
+
55
+
56
+ def _ensure_initialized() -> MCPCommandRegistry:
57
+ """Lazily initialize the local state/registry on first tool call.
58
+
59
+ Unlike the old architecture, we do NOT load projects from disk here.
60
+ State is synced from the collab server before each tool execution
61
+ (see _sync_from_collab / _push_to_collab in the handler wrapper).
62
+ """
63
+ global _state, _connections, _registry
64
+ if _registry is not None:
65
+ return _registry
66
+
67
+ _state = ServerState()
68
+ # ConnectionManager is still needed by MCPCommandRegistry's constructor,
69
+ # but its broadcast calls are effectively no-ops (no WS clients connect
70
+ # to the stdio process). State propagation happens via the HTTP push.
71
+ _connections = ConnectionManager()
72
+ _registry = MCPCommandRegistry(_state, _connections)
73
+ return _registry
74
+
75
+
76
+ async def _sync_from_collab() -> None:
77
+ """Pull the latest state from the collab server into _state.
78
+
79
+ Raises CollabServerUnreachableError if the server cannot be reached.
80
+ """
81
+ assert _state is not None
82
+ data = await pull_full_state()
83
+ deserialize_into_state(_state, data)
84
+
85
+
86
+ async def _push_to_collab() -> None:
87
+ """Push the current _state to the collab server (triggers WS broadcast).
88
+
89
+ Raises CollabServerUnreachableError if the server cannot be reached.
90
+ """
91
+ assert _state is not None
92
+ state_dict = serialize_state(_state)
93
+ await push_full_state(state_dict)
94
+
95
+
96
+ # ---------------------------------------------------------------------------
97
+ # FastMCP server
98
+ # ---------------------------------------------------------------------------
99
+
100
+ @asynccontextmanager
101
+ async def mcp_lifespan(server: FastMCP):
102
+ """Initialize state when the MCP server starts."""
103
+ _ensure_initialized()
104
+ yield
105
+
106
+
107
+ mcp = FastMCP(
108
+ "PixelWeaver",
109
+ instructions=(
110
+ "PixelWeaver is a pixel art sprite editor. Use these tools to create "
111
+ "projects, draw shapes, manage layers and frames, and export artwork. "
112
+ "Start by creating a project with create_project, then use drawing "
113
+ "tools to add pixel art. Use get_canvas_thumbnail to see the result. "
114
+ "NOTE: The PixelWeaver collaboration server must be running on port "
115
+ "7779 for these tools to work."
116
+ ),
117
+ lifespan=mcp_lifespan,
118
+ )
119
+
120
+
121
+ def _format_result(result: dict[str, Any]) -> list[TextContent | ImageContent]:
122
+ """Convert a registry result dict into MCP content blocks.
123
+
124
+ Mutation results include a thumbnail as an ImageContent block.
125
+ All results include a TextContent block with the JSON data.
126
+ """
127
+ content: list[TextContent | ImageContent] = []
128
+
129
+ # Extract and remove thumbnail before serializing to text
130
+ thumbnail_b64 = result.pop("thumbnail", None)
131
+
132
+ content.append(TextContent(type="text", text=json.dumps(result, indent=2)))
133
+
134
+ if thumbnail_b64:
135
+ content.append(ImageContent(
136
+ type="image",
137
+ data=thumbnail_b64,
138
+ mimeType="image/png",
139
+ ))
140
+
141
+ return content
142
+
143
+
144
+ # ---------------------------------------------------------------------------
145
+ # Auto-register all tools from the registry
146
+ # ---------------------------------------------------------------------------
147
+ # We register a generic handler for each tool defined in the registry.
148
+ # The tool definitions (names, descriptions, schemas) come from the registry;
149
+ # the actual FastMCP tool functions delegate to registry.execute_tool().
150
+
151
+
152
+ def _make_tool_handler(tool_name: str):
153
+ """Create an async handler closure for a specific tool name.
154
+
155
+ Each handler:
156
+ 1. Pulls latest state from the collab server
157
+ 2. Executes the tool locally via the registry
158
+ 3. Pushes modified state back (for mutating tools)
159
+ If the collab server is unreachable, returns a clear error.
160
+ """
161
+
162
+ async def handler(**kwargs: Any) -> list[TextContent | ImageContent]:
163
+ registry = _ensure_initialized()
164
+ tool_def = registry.get_tool_def(tool_name)
165
+
166
+ # Pull latest state from the collab server
167
+ try:
168
+ await _sync_from_collab()
169
+ except CollabServerUnreachableError as exc:
170
+ return _format_result({
171
+ "success": False,
172
+ "error": (
173
+ f"Collab server unreachable: {exc}. "
174
+ "Make sure the PixelWeaver server is running "
175
+ "(pixelweaver serve --port 7779)."
176
+ ),
177
+ })
178
+
179
+ # Execute the tool locally
180
+ result = await registry.execute_tool(tool_name, kwargs)
181
+
182
+ # Push modified state back if the tool mutates state
183
+ if tool_def and tool_def.mutates and result.get("success", False):
184
+ try:
185
+ await _push_to_collab()
186
+ except CollabServerUnreachableError as exc:
187
+ # Tool succeeded locally but failed to propagate -- warn
188
+ result["warning"] = (
189
+ f"Tool executed but failed to sync to collab server: {exc}"
190
+ )
191
+
192
+ # Notify subscribed MCP clients that resources have changed.
193
+ await notify_resource_subscribers(mcp)
194
+
195
+ return _format_result(result)
196
+
197
+ # FastMCP uses the function name as the tool name
198
+ handler.__name__ = tool_name
199
+ handler.__qualname__ = tool_name
200
+ return handler
201
+
202
+
203
+ def init_mcp_tools() -> None:
204
+ """Register all tools from the registry onto the FastMCP server.
205
+
206
+ Call this explicitly from the server startup path. Importing this module
207
+ no longer has side effects; this keeps tests and tooling from paying the
208
+ cost of initializing the MCP registry unless they actually need it.
209
+ """
210
+ registry = _ensure_initialized()
211
+ for tool_def_dict in registry.get_tool_definitions():
212
+ name = tool_def_dict["name"]
213
+ desc = tool_def_dict["description"]
214
+ handler = _make_tool_handler(name)
215
+ # Use add_tool with the raw handler; FastMCP will expose it
216
+ mcp.add_tool(handler, name=name, description=desc)
217
+
218
+ # Register MCP resources (project state, canvas, frame, palette)
219
+ init_mcp_resources(mcp)
220
+
221
+ # Wire up resource subscription/unsubscription handlers and
222
+ # advertise subscribe=True in the server capabilities.
223
+ init_mcp_subscriptions(mcp)
224
+
225
+
226
+ # ---------------------------------------------------------------------------
227
+ # Entry point
228
+ # ---------------------------------------------------------------------------
229
+
230
+
231
+ def run_mcp_server() -> None:
232
+ """Run the MCP server on stdio transport (blocking)."""
233
+ init_mcp_tools()
234
+ mcp.run(transport="stdio")
@@ -0,0 +1,219 @@
1
+ """Pydantic models for the PixelWeaver WebSocket JSON protocol.
2
+
3
+ Client -> Server message types:
4
+ command -- dispatch a drawing/editing command
5
+ sync_request -- request full state snapshot
6
+ undo -- undo last command
7
+ redo -- redo last undone command
8
+
9
+ Server -> Client message types:
10
+ command_ack -- command accepted
11
+ command_reject -- command rejected with reason
12
+ state_sync -- full state snapshot
13
+ command_broadcast -- a command executed by another client
14
+ error -- generic error
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import Any, Literal
20
+
21
+ from pydantic import BaseModel, Field
22
+
23
+ # ---------------------------------------------------------------------------
24
+ # Client -> Server
25
+ # ---------------------------------------------------------------------------
26
+
27
+
28
+ class CommandPayload(BaseModel):
29
+ """The inner command object sent by the client."""
30
+
31
+ type: str
32
+ plugin: str
33
+ version: str
34
+ params: dict[str, Any] = Field(default_factory=dict)
35
+ id: str
36
+ timestamp: int
37
+
38
+
39
+ class CommandMessage(BaseModel):
40
+ """Client dispatches a command for execution."""
41
+
42
+ type: Literal["command"]
43
+ id: str
44
+ command: CommandPayload
45
+
46
+
47
+ class SyncRequestMessage(BaseModel):
48
+ """Client requests a full state snapshot."""
49
+
50
+ type: Literal["sync_request"]
51
+ id: str
52
+
53
+
54
+ class UndoMessage(BaseModel):
55
+ """Client requests undo of the last command."""
56
+
57
+ type: Literal["undo"]
58
+ id: str
59
+
60
+
61
+ class RedoMessage(BaseModel):
62
+ """Client requests redo of the last undone command."""
63
+
64
+ type: Literal["redo"]
65
+ id: str
66
+
67
+
68
+ # Union discriminator for incoming messages
69
+ ClientMessage = CommandMessage | SyncRequestMessage | UndoMessage | RedoMessage
70
+
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # Server -> Client
74
+ # ---------------------------------------------------------------------------
75
+
76
+
77
+ class CommandAckMessage(BaseModel):
78
+ """Server confirms a command was accepted and executed."""
79
+
80
+ type: Literal["command_ack"] = "command_ack"
81
+ id: str
82
+ command_id: str
83
+ success: Literal[True] = True
84
+
85
+
86
+ class CommandRejectMessage(BaseModel):
87
+ """Server rejects a command with a reason."""
88
+
89
+ type: Literal["command_reject"] = "command_reject"
90
+ id: str
91
+ command_id: str
92
+ error: str
93
+
94
+
95
+ class StateSyncMessage(BaseModel):
96
+ """Full state snapshot sent to the client."""
97
+
98
+ type: Literal["state_sync"] = "state_sync"
99
+ id: str
100
+ state: dict[str, Any]
101
+
102
+
103
+ class CommandBroadcastMessage(BaseModel):
104
+ """Broadcast of a command executed by another client."""
105
+
106
+ type: Literal["command_broadcast"] = "command_broadcast"
107
+ command: dict[str, Any]
108
+ source: str
109
+
110
+
111
+ class ErrorMessage(BaseModel):
112
+ """Generic error message."""
113
+
114
+ type: Literal["error"] = "error"
115
+ id: str
116
+ error: str
117
+
118
+
119
+ ServerMessage = (
120
+ CommandAckMessage
121
+ | CommandRejectMessage
122
+ | StateSyncMessage
123
+ | CommandBroadcastMessage
124
+ | ErrorMessage
125
+ )
126
+
127
+
128
+ # ---------------------------------------------------------------------------
129
+ # Data Patches -- server-to-client state synchronization
130
+ # ---------------------------------------------------------------------------
131
+ # When the server modifies state (via MCP or other operations), it broadcasts
132
+ # patches describing the pre-computed results. The frontend applies these
133
+ # directly without re-executing commands.
134
+
135
+
136
+ class StatePatch(BaseModel):
137
+ """Base for all state patches."""
138
+
139
+ type: str
140
+
141
+
142
+ class PixelPatch(StatePatch):
143
+ """A patch to pixel data on a specific layer/frame."""
144
+
145
+ type: Literal["pixel"] = "pixel"
146
+ layer_id: str
147
+ frame_index: int
148
+ data: list[int] # flat RGBA
149
+ width: int
150
+ height: int
151
+
152
+
153
+ class LayerPatch(StatePatch):
154
+ """A patch to the layer tree structure."""
155
+
156
+ type: Literal["layer"] = "layer"
157
+ action: str # add, remove, update
158
+ layer: dict[str, Any] | None = None
159
+ layer_id: str | None = None
160
+
161
+
162
+ class CanvasPatch(StatePatch):
163
+ """A patch to canvas dimensions."""
164
+
165
+ type: Literal["canvas"] = "canvas"
166
+ width: int
167
+ height: int
168
+
169
+
170
+ class FramePatch(StatePatch):
171
+ """A patch to frame structure (add/remove/reorder/set_duration)."""
172
+
173
+ type: Literal["frame"] = "frame"
174
+ action: str
175
+ frame_index: int | None = None
176
+ frame_data: dict[str, Any] | None = None
177
+ from_index: int | None = None
178
+ to_index: int | None = None
179
+ duration_ms: int | None = None
180
+
181
+
182
+ class FullStatePatch(StatePatch):
183
+ """A full state replacement (initial sync or large MCP operations)."""
184
+
185
+ type: Literal["full_state"] = "full_state"
186
+ snapshot: dict[str, Any]
187
+
188
+
189
+ class PatchMessage(BaseModel):
190
+ """Wrapper message broadcast via WebSocket carrying one or more patches."""
191
+
192
+ type: Literal["state_patch"] = "state_patch"
193
+ project_name: str
194
+ patches: list[dict[str, Any]] # StatePatch dicts
195
+ command_id: str | None = None
196
+
197
+
198
+ # ---------------------------------------------------------------------------
199
+ # Parsing helper
200
+ # ---------------------------------------------------------------------------
201
+
202
+
203
+ def parse_client_message(raw: dict[str, Any]) -> ClientMessage:
204
+ """Parse a raw dict into one of the client message types.
205
+
206
+ Raises ValueError if the message is malformed or has an unknown type.
207
+ """
208
+ msg_type = raw.get("type")
209
+ match msg_type:
210
+ case "command":
211
+ return CommandMessage.model_validate(raw)
212
+ case "sync_request":
213
+ return SyncRequestMessage.model_validate(raw)
214
+ case "undo":
215
+ return UndoMessage.model_validate(raw)
216
+ case "redo":
217
+ return RedoMessage.model_validate(raw)
218
+ case _:
219
+ raise ValueError(f"Unknown message type: {msg_type!r}")