@pixel-normal-edit/mcp 2.1.0 → 2.2.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 (132) hide show
  1. package/clear-firebase.js +37 -0
  2. package/domains/art-tree/README.md +134 -0
  3. package/domains/art-tree/REORGANIZATION_PLAN.md +168 -0
  4. package/domains/art-tree/REORGANIZATION_SUMMARY.md +260 -0
  5. package/domains/art-tree/index.js +58 -58
  6. package/domains/art-tree/lessons/3d/box-properties.js +38 -0
  7. package/domains/art-tree/lessons/3d/cone-properties.js +31 -0
  8. package/domains/art-tree/lessons/3d/cylinder-properties.js +29 -0
  9. package/domains/art-tree/lessons/3d/sphere-properties.js +25 -0
  10. package/domains/art-tree/lessons/advanced/angles.js +30 -0
  11. package/domains/art-tree/lessons/advanced/distances.js +26 -0
  12. package/domains/art-tree/lessons/advanced/shape-ratios.js +25 -0
  13. package/domains/art-tree/lessons/advanced/shape-relationships.js +28 -0
  14. package/domains/art-tree/lessons/advanced/symmetry-and-axis.js +26 -0
  15. package/domains/art-tree/{analysis_lessons.js → lessons/analysis/object-analysis.js} +5 -16
  16. package/domains/art-tree/lessons/basic/circles.js +26 -0
  17. package/domains/art-tree/lessons/basic/composition.js +34 -0
  18. package/domains/art-tree/lessons/basic/curves.js +31 -0
  19. package/domains/art-tree/lessons/basic/ellipses.js +26 -0
  20. package/domains/art-tree/lessons/basic/freehand.js +19 -0
  21. package/domains/art-tree/lessons/basic/index.js +36 -0
  22. package/domains/art-tree/lessons/basic/lines.js +38 -0
  23. package/domains/art-tree/lessons/basic/parallel-and-intersecting.js +22 -0
  24. package/domains/art-tree/lessons/basic/polygons.js +25 -0
  25. package/domains/art-tree/lessons/basic/proportions-and-angles.js +20 -0
  26. package/domains/art-tree/lessons/basic/spiral.js +21 -0
  27. package/domains/art-tree/lessons/basic/squares.js +28 -0
  28. package/domains/art-tree/lessons/basic/strokes.js +23 -0
  29. package/domains/art-tree/lessons/basic/triangles.js +30 -0
  30. package/domains/art-tree/lessons/cross-sections/bottle-sections.js +26 -0
  31. package/domains/art-tree/lessons/cross-sections/glass-sections.js +24 -0
  32. package/domains/art-tree/lessons/cross-sections/head-sections.js +17 -0
  33. package/domains/art-tree/lessons/curves/curve-properties.js +50 -0
  34. package/domains/art-tree/lessons/curves/curve-topology.js +45 -0
  35. package/domains/art-tree/lessons/curves/curve-types.js +44 -0
  36. package/domains/art-tree/lessons/ellipses/coaxial-ellipses.js +29 -0
  37. package/domains/art-tree/lessons/ellipses/ellipse-anatomy.js +26 -0
  38. package/domains/art-tree/lessons/ellipses/ellipse-orientations.js +28 -0
  39. package/domains/art-tree/lessons/ellipses/ellipse-proportions.js +27 -0
  40. package/domains/art-tree/lessons/hidden/hidden-box.js +20 -0
  41. package/domains/art-tree/lessons/hidden/hidden-cup.js +19 -0
  42. package/domains/art-tree/lessons/hidden/hidden-head.js +18 -0
  43. package/domains/art-tree/lessons/index.js +42 -0
  44. package/domains/art-tree/{layer_lessons.js → lessons/layers/layer-step.js} +5 -16
  45. package/domains/art-tree/lessons/lighting/contact-shadow.js +33 -0
  46. package/domains/art-tree/lessons/lighting/light-direction.js +30 -0
  47. package/domains/art-tree/lessons/lighting/light-zones.js +30 -0
  48. package/domains/art-tree/lessons/materials/material-glass.js +21 -0
  49. package/domains/art-tree/lessons/materials/material-shiny.js +26 -0
  50. package/domains/art-tree/lessons/materials/material-texture.js +26 -0
  51. package/domains/art-tree/lessons/perspective/1point-perspective.js +30 -0
  52. package/domains/art-tree/lessons/perspective/2point-perspective.js +30 -0
  53. package/domains/art-tree/lessons/perspective/3point-perspective.js +45 -0
  54. package/domains/art-tree/lessons/perspective/foreshortening.js +38 -0
  55. package/domains/art-tree/lessons/sky/sun-shapes.js +25 -0
  56. package/domains/art-tree/lessons/sky/sunset-colors.js +27 -0
  57. package/domains/art-tree/lessons/sky/sunset-sky.js +32 -0
  58. package/domains/art-tree/{structure_lessons.js → lessons/structure/axis-orientation.js} +3 -53
  59. package/domains/art-tree/lessons/structure/complex-structure.js +21 -0
  60. package/domains/art-tree/lessons/structure/x-yzaxes.js +28 -0
  61. package/domains/art-tree/lessons/surfaces/chair-analysis.js +17 -0
  62. package/domains/art-tree/lessons/surfaces/cup-analysis.js +18 -0
  63. package/domains/art-tree/lessons/surfaces/edge-types.js +40 -0
  64. package/domains/art-tree/lessons/surfaces/surface-types.js +42 -0
  65. package/domains/art-tree/lessons/transforms/bend.js +27 -0
  66. package/domains/art-tree/{transform_lessons_2.js → lessons/transforms/combine.js} +3 -54
  67. package/domains/art-tree/lessons/transforms/cut.js +25 -0
  68. package/domains/art-tree/lessons/transforms/hollow.js +24 -0
  69. package/domains/art-tree/lessons/transforms/rotate.js +16 -0
  70. package/domains/art-tree/lessons/transforms/squash.js +31 -0
  71. package/domains/art-tree/lessons/transforms/stretch.js +31 -0
  72. package/domains/art-tree/lessons/transforms/taper-swell.js +33 -0
  73. package/domains/art-tree/{vocab_lessons.js → lessons/vocab/vocab-house.js} +5 -16
  74. package/domains/art-tree/lib/tool-register.js +104 -0
  75. package/domains/art-tree/lib/validators.js +78 -0
  76. package/domains/art-tree/shapes/grid.js +37 -0
  77. package/domains/art-tree/shapes/polyline.js +26 -0
  78. package/domains/art-tree/{3d_tools.js → tools/3d.js} +2 -2
  79. package/domains/art-tree/{advanced_tools.js → tools/advanced.js} +2 -2
  80. package/domains/art-tree/{analysis_tools.js → tools/analysis.js} +2 -2
  81. package/domains/art-tree/{tools.js → tools/basic.js} +3 -3
  82. package/domains/art-tree/{cross_section_tools.js → tools/cross-sections.js} +2 -2
  83. package/domains/art-tree/{curve_tools.js → tools/curves.js} +2 -2
  84. package/domains/art-tree/{ellipse_tools.js → tools/ellipses.js} +2 -2
  85. package/domains/art-tree/{hidden_tools.js → tools/hidden.js} +2 -2
  86. package/domains/art-tree/tools/index.js +53 -0
  87. package/domains/art-tree/{layer_tools.js → tools/layers.js} +2 -2
  88. package/domains/art-tree/{light_tools.js → tools/lighting.js} +2 -2
  89. package/domains/art-tree/{material_tools.js → tools/materials.js} +2 -2
  90. package/domains/art-tree/{perspective_tools.js → tools/perspective.js} +2 -2
  91. package/domains/art-tree/{sky_tools.js → tools/sky.js} +2 -2
  92. package/domains/art-tree/{structure_tools.js → tools/structure.js} +2 -2
  93. package/domains/art-tree/{surface_tools.js → tools/surfaces.js} +2 -2
  94. package/domains/art-tree/{transform_tools.js → tools/transforms.js} +3 -3
  95. package/domains/art-tree/{vocab_tools.js → tools/vocab.js} +2 -2
  96. package/package.json +1 -1
  97. package/rules/prerequisites.js +30 -30
  98. package/rules/rules.js +1 -1
  99. package/rules/workflow.js +28 -20
  100. package/tools/canvas-tools.js +2 -2
  101. package/tools/drawing-tools.js +23 -12
  102. package/tools/workspace-tools.js +5 -4
  103. package/domains/art-tree/3d_lessons.js +0 -114
  104. package/domains/art-tree/advanced_lessons.js +0 -124
  105. package/domains/art-tree/cross_section_lessons.js +0 -65
  106. package/domains/art-tree/curve_lessons.js +0 -138
  107. package/domains/art-tree/ellipse_lessons.js +0 -105
  108. package/domains/art-tree/hidden_lessons.js +0 -55
  109. package/domains/art-tree/lessons.js +0 -305
  110. package/domains/art-tree/light_lessons.js +0 -92
  111. package/domains/art-tree/material_lessons.js +0 -71
  112. package/domains/art-tree/perspective_lessons.js +0 -138
  113. package/domains/art-tree/sky_lessons_1.js +0 -82
  114. package/domains/art-tree/surface_lessons.js +0 -112
  115. package/domains/art-tree/transform_lessons_1.js +0 -108
  116. /package/domains/art-tree/{3d_shapes.js → shapes/3d.js} +0 -0
  117. /package/domains/art-tree/{advanced_shapes.js → shapes/advanced.js} +0 -0
  118. /package/domains/art-tree/{analysis_shapes.js → shapes/analysis.js} +0 -0
  119. /package/domains/art-tree/{shapes.js → shapes/basic.js} +0 -0
  120. /package/domains/art-tree/{cross_section_shapes.js → shapes/cross-sections.js} +0 -0
  121. /package/domains/art-tree/{curve_shapes.js → shapes/curves.js} +0 -0
  122. /package/domains/art-tree/{ellipse_shapes.js → shapes/ellipses.js} +0 -0
  123. /package/domains/art-tree/{hidden_shapes.js → shapes/hidden.js} +0 -0
  124. /package/domains/art-tree/{layer_shapes.js → shapes/layers.js} +0 -0
  125. /package/domains/art-tree/{light_shapes.js → shapes/lighting.js} +0 -0
  126. /package/domains/art-tree/{material_shapes.js → shapes/materials.js} +0 -0
  127. /package/domains/art-tree/{perspective_shapes.js → shapes/perspective.js} +0 -0
  128. /package/domains/art-tree/{sky_shapes_1.js → shapes/sky.js} +0 -0
  129. /package/domains/art-tree/{structure_shapes.js → shapes/structure.js} +0 -0
  130. /package/domains/art-tree/{surface_shapes.js → shapes/surfaces.js} +0 -0
  131. /package/domains/art-tree/{transform_shapes.js → shapes/transforms.js} +0 -0
  132. /package/domains/art-tree/{vocab_shapes.js → shapes/vocab.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  const { z } = require('zod');
2
- const { registerTool, sendCommand } = require('../../core/server');
3
- const materialLessons = require('./material_lessons');
2
+ const { registerTool, sendCommand } = require('../../../core/server');
3
+ const materialLessons = require('../lessons/material');
4
4
 
5
5
  /**
6
6
  * Register all material & surface rendering tools on the MCP server.
@@ -1,6 +1,6 @@
1
1
  const { z } = require('zod');
2
- const { registerTool, sendCommand } = require('../../core/server');
3
- const perspLessons = require('./perspective_lessons');
2
+ const { registerTool, sendCommand } = require('../../../core/server');
3
+ const perspLessons = require('../lessons/perspective');
4
4
 
5
5
  const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
6
6
 
@@ -1,6 +1,6 @@
1
1
  const { z } = require('zod');
2
- const { registerTool, sendCommand } = require('../../core/server');
3
- const skyLessons1 = require('./sky_lessons_1');
2
+ const { registerTool, sendCommand } = require('../../../core/server');
3
+ const skyLessons1 = require('../lessons/sky_1');
4
4
 
5
5
  /**
6
6
  * Register all sky and sun tools on the MCP server.
@@ -1,6 +1,6 @@
1
1
  const { z } = require('zod');
2
- const { registerTool, sendCommand } = require('../../core/server');
3
- const structureLessons = require('./structure_lessons');
2
+ const { registerTool, sendCommand } = require('../../../core/server');
3
+ const structureLessons = require('../lessons/structure');
4
4
 
5
5
  const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
6
6
 
@@ -1,6 +1,6 @@
1
1
  const { z } = require('zod');
2
- const { registerTool, sendCommand } = require('../../core/server');
3
- const surfaceLessons = require('./surface_lessons');
2
+ const { registerTool, sendCommand } = require('../../../core/server');
3
+ const surfaceLessons = require('../lessons/surface');
4
4
 
5
5
  const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
6
6
 
@@ -1,7 +1,7 @@
1
1
  const { z } = require('zod');
2
- const { registerTool, sendCommand } = require('../../core/server');
3
- const lessons1 = require('./transform_lessons_1');
4
- const lessons2 = require('./transform_lessons_2');
2
+ const { registerTool, sendCommand } = require('../../../core/server');
3
+ const lessons1 = require('../lessons/transform_1');
4
+ const lessons2 = require('../lessons/transform_2');
5
5
 
6
6
  const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g. #ff0000');
7
7
 
@@ -1,6 +1,6 @@
1
1
  const { z } = require('zod');
2
- const { registerTool, sendCommand } = require('../../core/server');
3
- const vocabLessons = require('./vocab_lessons');
2
+ const { registerTool, sendCommand } = require('../../../core/server');
3
+ const vocabLessons = require('../lessons/vocab');
4
4
 
5
5
  /**
6
6
  * Register all visual vocabulary tools on the MCP server.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixel-normal-edit/mcp",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "MCP server for Pixel Normal Edit canvas - enables AI agents to draw pixel art",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -20,7 +20,7 @@ const { registerTool } = require('../core/server');
20
20
  */
21
21
  function register(server) {
22
22
  registerTool(server, 'workflow_start',
23
- `🚀 REQUIRED: Call this tool FIRST before drawing anything.
23
+ `REQUIRED: Call this tool FIRST before drawing anything.
24
24
  This tool initializes a drawing workflow with sequential steps for a specific mode.
25
25
  Modes: CREATE_IMAGE, EDIT_IMAGE, CREATE_ANIMATION`,
26
26
  { mode: z.enum([MODES.CREATE_IMAGE, MODES.EDIT_IMAGE, MODES.CREATE_ANIMATION]).describe('Workflow mode to start') },
@@ -28,7 +28,7 @@ Modes: CREATE_IMAGE, EDIT_IMAGE, CREATE_ANIMATION`,
28
28
  const session = require('../core/command-bus').SESSION;
29
29
  const state = workflow.start(session, p.mode);
30
30
  return {
31
- content: [{ type: 'text', text: `🚀 **Workflow started in mode: ${p.mode}!**\n\n${workflow.getProgressReport(session)}\n\n---\nStart with step: **${state.steps[0].label}** → ${state.steps[0].description}` }]
31
+ content: [{ type: 'text', text: `**Workflow started in mode: ${p.mode}!**\n\n${workflow.getProgressReport(session)}\n\n---\nStart with step: **${state.steps[0].label}** → ${state.steps[0].description}` }]
32
32
  };
33
33
  });
34
34
 
@@ -40,24 +40,24 @@ Modes: CREATE_IMAGE, EDIT_IMAGE, CREATE_ANIMATION`,
40
40
  const report = workflow.getProgressReport(session);
41
41
  const current = workflow.getCurrentStep(session);
42
42
  if (!current) {
43
- return { content: [{ type: 'text', text: '⚠️ Workflow has not started. Call **workflow_start** first.' }] };
43
+ return { content: [{ type: 'text', text: ' Workflow has not started. Call **workflow_start** first.' }] };
44
44
  }
45
45
  return {
46
46
  content: [{
47
47
  type: 'text',
48
- text: `${report}\n\n👉 **Pending:** ${current.label} — ${current.description}`
48
+ text: `${report}\n\n **Pending:** ${current.label} — ${current.description}`
49
49
  }]
50
50
  };
51
51
  });
52
52
 
53
53
  registerTool(server, 'workflow_advance',
54
- 'Mark current step as complete and move to the next step. Only call AFTER successfully finishing the current step.',
54
+ ' Mark current step as complete and move to the next step. Only call AFTER successfully finishing the current step.',
55
55
  {},
56
56
  () => {
57
57
  const session = require('../core/command-bus').SESSION;
58
58
  const result = workflow.advance(session);
59
59
  if (!result.success) {
60
- return { isError: true, content: [{ type: 'text', text: `❌ ${result.message}` }] };
60
+ return { isError: true, content: [{ type: 'text', text: ` ${result.message}` }] };
61
61
  }
62
62
  if (!result.nextStep) {
63
63
  return { content: [{ type: 'text', text: `🎉 ${result.message}` }] };
@@ -66,7 +66,7 @@ Modes: CREATE_IMAGE, EDIT_IMAGE, CREATE_ANIMATION`,
66
66
  });
67
67
 
68
68
  registerTool(server, 'workflow_validate',
69
- `🔍 Check if the agent is allowed to perform a certain action.
69
+ ` Check if the agent is allowed to perform a certain action.
70
70
  Call this tool BEFORE performing operations to ensure correct workflow order.`,
71
71
  { stepId: z.string().describe('ID of the step to validate (e.g., "INITIALIZE_CANVAS")') },
72
72
  (p) => {
@@ -75,7 +75,7 @@ Modes: CREATE_IMAGE, EDIT_IMAGE, CREATE_ANIMATION`,
75
75
  if (!result.allowed) {
76
76
  return { isError: true, content: [{ type: 'text', text: result.message }] };
77
77
  }
78
- return { content: [{ type: 'text', text: result.message || `✅ You can perform "${p.stepId}". Please proceed!` }] };
78
+ return { content: [{ type: 'text', text: result.message || ` You can perform "${p.stepId}". Please proceed!` }] };
79
79
  });
80
80
 
81
81
  registerTool(server, 'workflow_set_layer',
@@ -85,9 +85,9 @@ Modes: CREATE_IMAGE, EDIT_IMAGE, CREATE_ANIMATION`,
85
85
  const session = require('../core/command-bus').SESSION;
86
86
  const success = workflow.setLayerAndFrame(session, p.layer, undefined);
87
87
  if (!success) {
88
- return { isError: true, content: [{ type: 'text', text: 'Workflow not started' }] };
88
+ return { isError: true, content: [{ type: 'text', text: ' Workflow not started' }] };
89
89
  }
90
- return { content: [{ type: 'text', text: `✅ Active layer set to ${p.layer}` }] };
90
+ return { content: [{ type: 'text', text: ` Active layer set to ${p.layer}` }] };
91
91
  });
92
92
 
93
93
  registerTool(server, 'workflow_set_frame',
@@ -97,9 +97,9 @@ Modes: CREATE_IMAGE, EDIT_IMAGE, CREATE_ANIMATION`,
97
97
  const session = require('../core/command-bus').SESSION;
98
98
  const success = workflow.setLayerAndFrame(session, undefined, p.frame);
99
99
  if (!success) {
100
- return { isError: true, content: [{ type: 'text', text: 'Workflow not started' }] };
100
+ return { isError: true, content: [{ type: 'text', text: ' Workflow not started' }] };
101
101
  }
102
- return { content: [{ type: 'text', text: `✅ Active frame set to ${p.frame}` }] };
102
+ return { content: [{ type: 'text', text: ` Active frame set to ${p.frame}` }] };
103
103
  });
104
104
 
105
105
  registerTool(server, 'workflow_set_phase',
@@ -109,14 +109,14 @@ Modes: CREATE_IMAGE, EDIT_IMAGE, CREATE_ANIMATION`,
109
109
  const session = require('../core/command-bus').SESSION;
110
110
  const success = workflow.setPhase(session, p.phase);
111
111
  if (!success) {
112
- return { isError: true, content: [{ type: 'text', text: 'Workflow not started' }] };
112
+ return { isError: true, content: [{ type: 'text', text: ' Workflow not started' }] };
113
113
  }
114
- return { content: [{ type: 'text', text: `✅ Active phase set to ${p.phase}` }] };
114
+ return { content: [{ type: 'text', text: ` Active phase set to ${p.phase}` }] };
115
115
  });
116
116
 
117
117
  registerTool(server, 'workflow_setup_animation',
118
118
  'Declare animation metadata for tracking (fps, total_frames, loop).',
119
- {
119
+ {
120
120
  fps: z.number().int().optional().describe('Frames per second'),
121
121
  total_frames: z.number().int().optional().describe('Total frames in the animation'),
122
122
  loop: z.boolean().optional().describe('Whether the animation loops')
@@ -125,14 +125,14 @@ Modes: CREATE_IMAGE, EDIT_IMAGE, CREATE_ANIMATION`,
125
125
  const session = require('../core/command-bus').SESSION;
126
126
  const success = workflow.setAnimationMeta(session, p.fps, p.total_frames, p.loop);
127
127
  if (!success) {
128
- return { isError: true, content: [{ type: 'text', text: 'Workflow not started' }] };
128
+ return { isError: true, content: [{ type: 'text', text: ' Workflow not started' }] };
129
129
  }
130
- return { content: [{ type: 'text', text: `✅ Animation meta updated: FPS=${p.fps}, Frames=${p.total_frames}, Loop=${p.loop}` }] };
130
+ return { content: [{ type: 'text', text: ` Animation meta updated: FPS=${p.fps}, Frames=${p.total_frames}, Loop=${p.loop}` }] };
131
131
  });
132
132
 
133
133
  registerTool(server, 'workflow_create_input_request',
134
134
  'Pause the workflow and ask the user for specific input via the UI (e.g. CANVAS_SIZE, APPROVE_PLAN, SELECT_EDIT_REGION).',
135
- {
135
+ {
136
136
  type: z.enum(['CANVAS_SIZE', 'APPROVE_PLAN', 'REVIEW_RESULT', 'SELECT_EDIT_REGION', 'ANIMATION_SETUP']).describe('The type of request'),
137
137
  fields: z.record(z.any()).optional().describe('Context or fields for the request')
138
138
  },
@@ -140,15 +140,15 @@ Modes: CREATE_IMAGE, EDIT_IMAGE, CREATE_ANIMATION`,
140
140
  const { sendCommand, SESSION } = require('../core/command-bus');
141
141
  const reqId = workflow.createInputRequest(SESSION, p.type, p.fields || {});
142
142
  if (!reqId) {
143
- return { isError: true, content: [{ type: 'text', text: 'Workflow not started' }] };
143
+ return { isError: true, content: [{ type: 'text', text: ' Workflow not started' }] };
144
144
  }
145
-
145
+
146
146
  const res = await sendCommand({ action: 'showUserInputRequest', type: p.type, fields: p.fields || {}, reqId }, 35000);
147
-
147
+
148
148
  if (!res.isError) {
149
149
  workflow.completeInputRequest(SESSION, reqId, res.content[0].text);
150
150
  }
151
-
151
+
152
152
  return res;
153
153
  });
154
154
 
@@ -159,18 +159,18 @@ Modes: CREATE_IMAGE, EDIT_IMAGE, CREATE_ANIMATION`,
159
159
  const session = require('../core/command-bus').SESSION;
160
160
  const success = workflow.setApprovalRequired(session, p.required);
161
161
  if (!success) {
162
- return { isError: true, content: [{ type: 'text', text: 'Workflow not started' }] };
162
+ return { isError: true, content: [{ type: 'text', text: ' Workflow not started' }] };
163
163
  }
164
- return { content: [{ type: 'text', text: p.required ? 'Workflow paused, waiting for user approval.' : 'Workflow resumed.' }] };
164
+ return { content: [{ type: 'text', text: p.required ? ' Workflow paused, waiting for user approval.' : ' Workflow resumed.' }] };
165
165
  });
166
166
 
167
167
  registerTool(server, 'workflow_reset',
168
- '🔄 Reset workflow to initial state. Clears all progress.',
168
+ ' Reset workflow to initial state. Clears all progress.',
169
169
  {},
170
170
  () => {
171
171
  const session = require('../core/command-bus').SESSION;
172
172
  workflow.reset(session);
173
- return { content: [{ type: 'text', text: '🔄 Workflow reset. Please start from the beginning.' }] };
173
+ return { content: [{ type: 'text', text: ' Workflow reset. Please start from the beginning.' }] };
174
174
  });
175
175
 
176
176
  registerTool(server, 'validate_canvas_name',
@@ -181,9 +181,9 @@ Modes: CREATE_IMAGE, EDIT_IMAGE, CREATE_ANIMATION`,
181
181
  (p) => {
182
182
  const result = validateCanvasName(p.name);
183
183
  if (!result.valid) {
184
- return { isError: true, content: [{ type: 'text', text: `❌ ${result.message}` }] };
184
+ return { isError: true, content: [{ type: 'text', text: ` ${result.message}` }] };
185
185
  }
186
- return { content: [{ type: 'text', text: `✅ Name "${p.name}" is valid!` }] };
186
+ return { content: [{ type: 'text', text: ` Name "${p.name}" is valid!` }] };
187
187
  });
188
188
 
189
189
  registerTool(server, 'validate_canvas_size',
@@ -193,9 +193,9 @@ Modes: CREATE_IMAGE, EDIT_IMAGE, CREATE_ANIMATION`,
193
193
  (p) => {
194
194
  const result = validateCanvasSize(p.width, p.height);
195
195
  if (!result.valid) {
196
- return { isError: true, content: [{ type: 'text', text: `❌ ${result.message}` }] };
196
+ return { isError: true, content: [{ type: 'text', text: ` ${result.message}` }] };
197
197
  }
198
- return { content: [{ type: 'text', text: `✅ Size ${p.width}x${p.height} is valid!` }] };
198
+ return { content: [{ type: 'text', text: ` Size ${p.width}x${p.height} is valid!` }] };
199
199
  });
200
200
  }
201
201
 
package/rules/rules.js CHANGED
@@ -84,7 +84,7 @@ const DRAWING_STEPS = [
84
84
  ];
85
85
 
86
86
  const DRAWING_CONSTRAINTS = {
87
- maxCanvasSize: { width: 1024, height: 1024 },
87
+ maxCanvasSize: { width: 256, height: 256 },
88
88
  minCanvasSize: { width: 8, height: 8 },
89
89
  allowedColors: null,
90
90
  maxStampNameLength: 32,
package/rules/workflow.js CHANGED
@@ -66,7 +66,7 @@ function start(sessionId, mode = MODES.CREATE_IMAGE) {
66
66
  history: [],
67
67
  startedAt: Date.now(),
68
68
  };
69
-
69
+
70
70
  sessions.set(sessionId, state);
71
71
  saveState();
72
72
  return getState(sessionId);
@@ -185,7 +185,7 @@ function createInputRequest(sessionId, type, fields = {}) {
185
185
  if (!state) return false;
186
186
 
187
187
  state.user_input_request = {
188
- id: `req_${Date.now()}_${Math.floor(Math.random()*1000)}`,
188
+ id: `req_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
189
189
  type,
190
190
  fields,
191
191
  status: 'WAITING_FOR_USER',
@@ -210,7 +210,15 @@ function completeInputRequest(sessionId, reqId, response) {
210
210
  state.user_input_request.status = 'COMPLETED';
211
211
  state.user_input_request.response = response;
212
212
  state.step_status = STATUSES.IN_PROGRESS; // Unpause workflow
213
-
213
+
214
+ // Auto-set flags based on completed request type
215
+ if (state.user_input_request.type === 'CANVAS_SIZE') {
216
+ state.step_completion_flags.canvas_initialized = true;
217
+ }
218
+ if (state.user_input_request.type === 'APPROVE_PLAN') {
219
+ state.step_completion_flags.plan_created = true;
220
+ }
221
+
214
222
  state.history.push({ event: 'input_request_completed', reqId, time: Date.now() });
215
223
  saveState();
216
224
  return true;
@@ -222,18 +230,18 @@ function completeInputRequest(sessionId, reqId, response) {
222
230
  function setLayerAndFrame(sessionId, layer, frame = null) {
223
231
  const state = sessions.get(sessionId);
224
232
  if (!state) return false;
225
-
233
+
226
234
  if (layer !== undefined && layer !== null) {
227
235
  state.current_layer = layer;
228
236
  }
229
237
  if (frame !== undefined && frame !== null) {
230
238
  state.current_frame = frame;
231
239
  }
232
- state.history.push({
233
- event: 'layer_frame_change',
234
- layer: state.current_layer,
235
- frame: state.current_frame,
236
- time: Date.now()
240
+ state.history.push({
241
+ event: 'layer_frame_change',
242
+ layer: state.current_layer,
243
+ frame: state.current_frame,
244
+ time: Date.now()
237
245
  });
238
246
  saveState();
239
247
  return true;
@@ -245,12 +253,12 @@ function setLayerAndFrame(sessionId, layer, frame = null) {
245
253
  function setPhase(sessionId, phase) {
246
254
  const state = sessions.get(sessionId);
247
255
  if (!state) return false;
248
-
256
+
249
257
  state.current_phase = phase;
250
- state.history.push({
251
- event: 'phase_change',
252
- phase: phase,
253
- time: Date.now()
258
+ state.history.push({
259
+ event: 'phase_change',
260
+ phase: phase,
261
+ time: Date.now()
254
262
  });
255
263
  saveState();
256
264
  return true;
@@ -262,11 +270,11 @@ function setPhase(sessionId, phase) {
262
270
  function setAnimationMeta(sessionId, fps, total_frames, loop) {
263
271
  const state = sessions.get(sessionId);
264
272
  if (!state) return false;
265
-
273
+
266
274
  if (fps !== undefined) state.animation_meta.fps = fps;
267
275
  if (total_frames !== undefined) state.animation_meta.total_frames = total_frames;
268
276
  if (loop !== undefined) state.animation_meta.loop = loop;
269
-
277
+
270
278
  saveState();
271
279
  return true;
272
280
  }
@@ -292,7 +300,7 @@ function advance(sessionId) {
292
300
  if (!state) {
293
301
  return { success: false, message: 'Workflow has not started' };
294
302
  }
295
-
303
+
296
304
  if (state.user_approval_required) {
297
305
  return { success: false, message: 'Cannot advance: Waiting for user approval.' };
298
306
  }
@@ -308,7 +316,7 @@ function advance(sessionId) {
308
316
 
309
317
  // Pre-condition validation block
310
318
  if (current.id === 'INITIALIZE_CANVAS' && !state.step_completion_flags.canvas_initialized) {
311
- return { success: false, message: 'Cannot advance: Canvas has not been initialized. Create a canvas first.' };
319
+ return { success: false, message: 'Cannot advance: Canvas has not been initialized. You MUST use workflow_create_input_request with type CANVAS_SIZE first.' };
312
320
  }
313
321
  if (current.id === 'PLAN_DRAWING' && !state.step_completion_flags.plan_created) {
314
322
  return { success: false, message: 'Cannot advance: Drawing plan has not been finalized.' };
@@ -374,10 +382,10 @@ function getProgressReport(sessionId) {
374
382
  state.user_input_request && state.user_input_request.status === 'WAITING_FOR_USER' ? `⚠️ PENDING REQUEST: ${state.user_input_request.type}` : '',
375
383
  `\n**Steps:**`
376
384
  ];
377
-
385
+
378
386
  state.steps.forEach((step, i) => {
379
387
  const icon = step.status === STATUSES.COMPLETED ? '✅' :
380
- step.status === STATUSES.IN_PROGRESS ? '🔄' : '⬜';
388
+ step.status === STATUSES.IN_PROGRESS ? '🔄' : '⬜';
381
389
  const marker = i === state.currentStepIndex ? ' ← **IN PROGRESS**' : '';
382
390
  lines.push(`${icon} **${step.label}**${marker}`);
383
391
  if (step.status === STATUSES.IN_PROGRESS || i === state.currentStepIndex) {
@@ -16,8 +16,8 @@ function register(server) {
16
16
  registerTool(server, 'canvas_resize',
17
17
  'Resize canvas to new dimensions. mode=clear resets pixels, extend keeps them.',
18
18
  {
19
- width: z.number().int().min(1).max(1024).describe('New width in pixels'),
20
- height: z.number().int().min(1).max(1024).describe('New height in pixels'),
19
+ width: z.number().int().min(1).max(256).describe('New width in pixels (max 256)'),
20
+ height: z.number().int().min(1).max(256).describe('New height in pixels (max 256)'),
21
21
  mode: z.enum(['clear', 'extend', 'fit']).default('clear').describe('How to handle existing content'),
22
22
  dx: z.number().int().default(0).describe('X offset for content when extending'),
23
23
  dy: z.number().int().default(0).describe('Y offset for content when extending')
@@ -14,54 +14,65 @@ const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g.
14
14
  function register(server) {
15
15
  registerTool(server, 'draw_pixel',
16
16
  'Set a single pixel to a color',
17
- { x: z.number().int().describe('X coordinate'), y: z.number().int().describe('Y coordinate'), color: hexColor },
17
+ { x: z.number().int().max(1024).describe('X coordinate'), y: z.number().int().max(1024).describe('Y coordinate'), color: hexColor },
18
18
  (p) => ({ action: 'drawPixel', ...p }));
19
19
 
20
20
  registerTool(server, 'draw_erase',
21
21
  'Erase a single pixel (make transparent)',
22
- { x: z.number().int(), y: z.number().int() },
22
+ { x: z.number().int().max(1024), y: z.number().int().max(1024) },
23
23
  (p) => ({ action: 'erasePixel', ...p }));
24
24
 
25
+ registerTool(server, 'draw_pixels_bulk',
26
+ 'Set multiple pixels at once (much faster than calling draw_pixel repeatedly)',
27
+ {
28
+ pixels: z.array(z.object({
29
+ x: z.number().int().max(1024),
30
+ y: z.number().int().max(1024),
31
+ color: hexColor.optional()
32
+ })).max(5000).describe('Array of pixels {x, y, color}. Omit color to erase.')
33
+ },
34
+ (p) => ({ action: 'drawPixelsBulk', pixels: p.pixels }));
35
+
25
36
  registerTool(server, 'draw_line',
26
37
  'Draw a straight line using Bresenham algorithm',
27
- { x0: z.number().int(), y0: z.number().int(), x1: z.number().int(), y1: z.number().int(), color: hexColor },
38
+ { x0: z.number().int().max(1024), y0: z.number().int().max(1024), x1: z.number().int().max(1024), y1: z.number().int().max(1024), color: hexColor },
28
39
  (p) => ({ action: 'drawLine', ...p }));
29
40
 
30
41
  registerTool(server, 'draw_rect',
31
42
  'Draw a rectangle (outline or filled)',
32
43
  {
33
- x: z.number().int().describe('Top-left X'), y: z.number().int().describe('Top-left Y'),
34
- w: z.number().int().min(1).describe('Width'), h: z.number().int().min(1).describe('Height'),
44
+ x: z.number().int().max(1024).describe('Top-left X'), y: z.number().int().max(1024).describe('Top-left Y'),
45
+ w: z.number().int().min(1).max(256).describe('Width (max 256)'), h: z.number().int().min(1).max(256).describe('Height (max 256)'),
35
46
  color: hexColor, filled: z.boolean().default(false).describe('Fill interior?')
36
47
  },
37
48
  (p) => ({ action: 'drawRect', ...p }));
38
49
 
39
50
  registerTool(server, 'draw_circle',
40
51
  'Draw a circle (outline or filled)',
41
- { cx: z.number().int().describe('Center X'), cy: z.number().int().describe('Center Y'),
42
- r: z.number().int().min(1).describe('Radius'), color: hexColor, filled: z.boolean().default(false) },
52
+ { cx: z.number().int().max(1024).describe('Center X'), cy: z.number().int().max(1024).describe('Center Y'),
53
+ r: z.number().int().min(1).max(128).describe('Radius (max 128)'), color: hexColor, filled: z.boolean().default(false) },
43
54
  (p) => ({ action: 'drawCircle', ...p }));
44
55
 
45
56
  registerTool(server, 'draw_ellipse',
46
57
  'Draw an ellipse (outline or filled)',
47
- { cx: z.number().int(), cy: z.number().int(), rx: z.number().int().min(1).describe('Horizontal radius'),
48
- ry: z.number().int().min(1).describe('Vertical radius'), color: hexColor, filled: z.boolean().default(false) },
58
+ { cx: z.number().int().max(1024), cy: z.number().int().max(1024), rx: z.number().int().min(1).max(128).describe('Horizontal radius'),
59
+ ry: z.number().int().min(1).max(128).describe('Vertical radius'), color: hexColor, filled: z.boolean().default(false) },
49
60
  (p) => ({ action: 'drawEllipse', ...p }));
50
61
 
51
62
  registerTool(server, 'draw_polygon',
52
63
  'Draw a polygon from a list of points (outline or filled)',
53
- { points: z.array(z.object({ x: z.number().int(), y: z.number().int() })).min(3).describe('Array of {x,y} vertices'),
64
+ { points: z.array(z.object({ x: z.number().int().max(1024), y: z.number().int().max(1024) })).min(3).describe('Array of {x,y} vertices'),
54
65
  color: hexColor, filled: z.boolean().default(false) },
55
66
  (p) => ({ action: 'drawPolygon', ...p }));
56
67
 
57
68
  registerTool(server, 'draw_fill',
58
69
  'Flood-fill starting from (x,y) with a color',
59
- { x: z.number().int(), y: z.number().int(), color: hexColor },
70
+ { x: z.number().int().max(1024), y: z.number().int().max(1024), color: hexColor },
60
71
  (p) => ({ action: 'fill', ...p }));
61
72
 
62
73
  registerTool(server, 'draw_gradient_rect',
63
74
  'Draw a rectangle filled with a smooth gradient',
64
- { x: z.number().int(), y: z.number().int(), w: z.number().int().min(1), h: z.number().int().min(1),
75
+ { x: z.number().int().max(1024), y: z.number().int().max(1024), w: z.number().int().min(1).max(256).describe('Width (max 256)'), h: z.number().int().min(1).max(256).describe('Height (max 256)'),
65
76
  colorFrom: hexColor.describe('Start color'), colorTo: hexColor.describe('End color'),
66
77
  direction: z.enum(['h', 'v']).default('h').describe('h=horizontal, v=vertical') },
67
78
  (p) => ({ action: 'drawGradientRect', ...p }));
@@ -19,21 +19,22 @@ function register(server) {
19
19
  {},
20
20
  () => ({ action: 'getActiveTabId' }));
21
21
 
22
- server.tool('workspace_create_tab',
22
+ registerTool(server, 'workspace_create_tab',
23
23
  'Create a new blank canvas tab',
24
24
  {
25
25
  name: z.string().optional().describe('Tab name'),
26
- width: z.number().int().optional().describe('Canvas width'),
27
- height: z.number().int().optional().describe('Canvas height')
26
+ width: z.number().int().min(1).max(256).optional().describe('Canvas width (max 256)'),
27
+ height: z.number().int().min(1).max(256).optional().describe('Canvas height (max 256)')
28
28
  },
29
29
  async (p) => {
30
30
  if (p.width === undefined || p.height === undefined) {
31
31
  return {
32
+ action: 'noop',
32
33
  isError: true,
33
34
  content: [{ type: 'text', text: 'Please provide the width and height dimensions (resize) when creating a new canvas.' }]
34
35
  };
35
36
  }
36
- return sendCommand({ action: 'createTab', ...p });
37
+ return { action: 'createTab', ...p };
37
38
  }
38
39
  );
39
40
 
@@ -1,114 +0,0 @@
1
- const shapes3d = require('./3d_shapes');
2
- const advShapes = require('./advanced_shapes');
3
-
4
- /**
5
- * Lesson: Box Properties (Width, Height, Depth, Planes, Edges)
6
- */
7
- function lessonBoxProperties(canvasSize = 32, color = '#2196f3') {
8
- const commands = [];
9
- const cx = Math.floor(canvasSize / 2);
10
- const cy = Math.floor(canvasSize / 2);
11
- const w = Math.floor(canvasSize * 0.4);
12
- const h = Math.floor(canvasSize * 0.4);
13
- const d = Math.floor(canvasSize * 0.3);
14
-
15
- // Draw wireframe box
16
- commands.push(...shapes3d.drawWireframeBox(cx, cy, w, h, d, color));
17
-
18
- // Highlight dimensions
19
- const fX = cx - Math.floor(w/2);
20
- const fY = cy - Math.floor(h/2);
21
-
22
- // Width
23
- commands.push(...advShapes.drawDistance(fX, fY + h + 2, fX + w, fY + h + 2, '#4caf50'));
24
- // Height
25
- commands.push(...advShapes.drawDistance(fX - 2, fY, fX - 2, fY + h, '#ff9800'));
26
-
27
- // Depth (approximate along the oblique edge)
28
- const dx = Math.floor(d * 0.7);
29
- const dy = Math.floor(d * 0.5);
30
- commands.push(...advShapes.drawDistance(fX + w + 2, fY + h, fX + w + 2 + dx, fY + h - dy, '#e91e63'));
31
-
32
- return commands;
33
- }
34
-
35
- /**
36
- * Lesson: Sphere Properties (Center, Radius, Cross contours, Axis)
37
- */
38
- function lessonSphereProperties(canvasSize = 32, color = '#9c27b0') {
39
- const commands = [];
40
- const cx = Math.floor(canvasSize / 2);
41
- const cy = Math.floor(canvasSize / 2);
42
- const r = Math.floor(canvasSize * 0.35);
43
-
44
- // Draw sphere with volume lines
45
- commands.push(...shapes3d.drawSphereVolume(cx, cy, r, color));
46
-
47
- // Highlight radius
48
- commands.push(...advShapes.drawDistance(cx, cy, cx + r, cy, '#ff9800'));
49
-
50
- return commands;
51
- }
52
-
53
- /**
54
- * Lesson: Cylinder Properties (Central axis, Bases, Perspective change)
55
- */
56
- function lessonCylinderProperties(canvasSize = 32, color = '#00bcd4') {
57
- const commands = [];
58
- const cx = Math.floor(canvasSize / 2);
59
- const cy = Math.floor(canvasSize / 2);
60
- const r = Math.floor(canvasSize * 0.3);
61
- const h = Math.floor(canvasSize * 0.6);
62
-
63
- // Draw cylinder volume
64
- commands.push(...shapes3d.drawCylinderVolume(cx, cy, r, h, color));
65
-
66
- // Highlight Height
67
- commands.push(...advShapes.drawDistance(cx + r + 4, cy - Math.floor(h/2), cx + r + 4, cy + Math.floor(h/2), '#4caf50'));
68
-
69
- // Highlight Radius (top base)
70
- commands.push(...advShapes.drawDistance(cx, cy - Math.floor(h/2), cx + r, cy - Math.floor(h/2), '#ff9800'));
71
-
72
- return commands;
73
- }
74
-
75
- /**
76
- * Lesson: Cone Properties (Apex, Base, Axis)
77
- */
78
- function lessonConeProperties(canvasSize = 32, color = '#ff5722') {
79
- const commands = [];
80
- const cx = Math.floor(canvasSize / 2);
81
- const cy = Math.floor(canvasSize / 2);
82
- const r = Math.floor(canvasSize * 0.35);
83
- const h = Math.floor(canvasSize * 0.6);
84
-
85
- // Draw cone volume
86
- commands.push(...shapes3d.drawConeVolume(cx, cy, r, h, color));
87
-
88
- // Highlight Height
89
- const apexY = cy - Math.floor(h/2);
90
- const baseY = cy + Math.floor(h/2);
91
- commands.push(...advShapes.drawDistance(cx + r + 4, apexY, cx + r + 4, baseY, '#4caf50'));
92
-
93
- // Highlight Radius (base)
94
- commands.push(...advShapes.drawDistance(cx, baseY, cx + r, baseY, '#ff9800'));
95
-
96
- return commands;
97
- }
98
-
99
- function get3DLessonCatalog() {
100
- return [
101
- { id: '3d_box', name: '3D Box Properties', description: 'Width, height, depth, planes, inner structure', fn: lessonBoxProperties },
102
- { id: '3d_sphere', name: '3D Sphere Properties', description: 'Center, radius, axis, cross contours', fn: lessonSphereProperties },
103
- { id: '3d_cylinder', name: '3D Cylinder Properties', description: 'Axis, bases, height, perspective changes', fn: lessonCylinderProperties },
104
- { id: '3d_cone', name: '3D Cone Properties', description: 'Apex, base, axis, radius', fn: lessonConeProperties },
105
- ];
106
- }
107
-
108
- module.exports = {
109
- lessonBoxProperties,
110
- lessonSphereProperties,
111
- lessonCylinderProperties,
112
- lessonConeProperties,
113
- get3DLessonCatalog
114
- };