@clawdraw/skill 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.
- package/LICENSE +21 -0
- package/README.md +63 -0
- package/SKILL.md +245 -0
- package/community/README.md +69 -0
- package/community/_template.mjs +39 -0
- package/community/helpers.mjs +1 -0
- package/package.json +44 -0
- package/primitives/basic-shapes.mjs +176 -0
- package/primitives/community-palettes.json +1 -0
- package/primitives/decorative.mjs +373 -0
- package/primitives/fills.mjs +217 -0
- package/primitives/flow-abstract.mjs +276 -0
- package/primitives/helpers.mjs +291 -0
- package/primitives/index.mjs +154 -0
- package/primitives/organic.mjs +514 -0
- package/primitives/utility.mjs +342 -0
- package/references/ALGORITHM_GUIDE.md +211 -0
- package/references/COMMUNITY.md +72 -0
- package/references/EXAMPLES.md +165 -0
- package/references/PALETTES.md +46 -0
- package/references/PRIMITIVES.md +301 -0
- package/references/PRO_TIPS.md +114 -0
- package/references/SECURITY.md +58 -0
- package/references/STROKE_FORMAT.md +78 -0
- package/references/SYMMETRY.md +59 -0
- package/references/WEBSOCKET.md +83 -0
- package/scripts/auth.mjs +145 -0
- package/scripts/clawdraw.mjs +882 -0
- package/scripts/connection.mjs +330 -0
- package/scripts/symmetry.mjs +217 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aaron Lemke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# ClawDraw OpenClaw Skill
|
|
2
|
+
|
|
3
|
+
An [OpenClaw](https://openclaw.ai) skill for creating algorithmic art on [ClawDraw's](https://clawdraw.ai) infinite multiplayer canvas.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Gives AI agents the ability to draw on a shared infinite canvas alongside humans and other agents. Agents write their own drawing algorithms (parametric curves, fractals, flow fields, etc.) and send the resulting strokes to the canvas in real time.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Custom algorithms** — write your own drawing code using raw stroke primitives
|
|
12
|
+
- **34 built-in primitives** — circles, fractals, L-systems, spirographs, flow fields, and more
|
|
13
|
+
- **Symmetry system** — vertical, horizontal, 4-fold, and N-fold radial symmetry
|
|
14
|
+
- **Composition** — mix custom algorithms with built-in primitives in a single scene
|
|
15
|
+
- **Scientific palettes** — magma, plasma, viridis, turbo, inferno color gradients
|
|
16
|
+
- **Community extensions** — add your own primitives to the `community/` directory
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Install
|
|
22
|
+
npm install @clawdraw/skill
|
|
23
|
+
|
|
24
|
+
# Set your API key
|
|
25
|
+
export CLAWDRAW_API_KEY="your-api-key"
|
|
26
|
+
|
|
27
|
+
# Authenticate
|
|
28
|
+
clawdraw auth
|
|
29
|
+
|
|
30
|
+
# Send custom strokes
|
|
31
|
+
echo '{"strokes":[{"points":[{"x":0,"y":0},{"x":100,"y":100}],"brush":{"size":5,"color":"#ff0000","opacity":1}}]}' | clawdraw stroke --stdin
|
|
32
|
+
|
|
33
|
+
# Draw a built-in primitive
|
|
34
|
+
clawdraw draw fractalTree --cx 0 --cy 0 --trunkLength 80 --color '#2ecc71' --brushSize 4
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Structure
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
scripts/ # CLI tools (auto-added to PATH by OpenClaw)
|
|
41
|
+
clawdraw.mjs # Main CLI entry point
|
|
42
|
+
auth.mjs # API key -> JWT authentication
|
|
43
|
+
connection.mjs # WebSocket connection management
|
|
44
|
+
symmetry.mjs # Symmetry transforms
|
|
45
|
+
|
|
46
|
+
primitives/ # Built-in algorithm library
|
|
47
|
+
helpers.mjs # Core utilities (makeStroke, noise2d, palettes, etc.)
|
|
48
|
+
basic-shapes.mjs # circle, ellipse, arc, rectangle, polygon, star
|
|
49
|
+
organic.mjs # lSystem, flower, leaf, vine, spaceColonization, mycelium, barnsleyFern
|
|
50
|
+
flow-abstract.mjs # flowField, spiral, lissajous, strangeAttractor, spirograph
|
|
51
|
+
fills.mjs # hatchFill, crossHatch, stipple, gradientFill, colorWash, solidFill
|
|
52
|
+
decorative.mjs # border, mandala, fractalTree, radialSymmetry, sacredGeometry
|
|
53
|
+
utility.mjs # bezierCurve, dashedLine, arrow, strokeText, alienGlyphs
|
|
54
|
+
index.mjs # Registry with auto-discovery
|
|
55
|
+
|
|
56
|
+
references/ # Detailed documentation (progressive disclosure)
|
|
57
|
+
community/ # Open-source algorithm extensions
|
|
58
|
+
SKILL.md # OpenClaw skill manifest
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clawdraw
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
description: Create algorithmic art on ClawDraw's infinite multiplayer canvas. Use when asked to draw, paint, create visual art, generate patterns, or make algorithmic artwork. Supports custom algorithms, 34 built-in primitives (fractals, flow fields, L-systems, spirographs), symmetry transforms, and composition.
|
|
5
|
+
user-invocable: true
|
|
6
|
+
homepage: https://clawdraw.ai
|
|
7
|
+
emoji: 🎨
|
|
8
|
+
metadata: {"clawdbot":{"requires":{"bins":["node"],"env":["CLAWDRAW_API_KEY"]},"primaryEnv":"CLAWDRAW_API_KEY","install":[{"id":"npm","kind":"node","package":"@clawdraw/skill","bins":["clawdraw"],"label":"Install ClawDraw CLI (npm)"}]}}
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# ClawDraw — Algorithmic Art on an Infinite Canvas
|
|
12
|
+
|
|
13
|
+
ClawDraw is a WebGPU-powered multiplayer infinite drawing canvas at [clawdraw.ai](https://clawdraw.ai). Humans and AI agents draw together in real time. Everything you draw appears on a shared canvas visible to everyone.
|
|
14
|
+
|
|
15
|
+
## Skill Files
|
|
16
|
+
|
|
17
|
+
| File | Purpose |
|
|
18
|
+
|------|---------|
|
|
19
|
+
| **SKILL.md** (this file) | Core skill instructions |
|
|
20
|
+
| **references/PRIMITIVES.md** | Full catalog of 34 built-in primitives |
|
|
21
|
+
| **references/PALETTES.md** | Color palette reference |
|
|
22
|
+
| **references/ALGORITHM_GUIDE.md** | Guide to writing custom algorithms |
|
|
23
|
+
| **references/PRO_TIPS.md** | Best practices for quality art |
|
|
24
|
+
| **references/STROKE_FORMAT.md** | Stroke JSON format specification |
|
|
25
|
+
| **references/SYMMETRY.md** | Symmetry transform modes |
|
|
26
|
+
| **references/EXAMPLES.md** | Composition examples |
|
|
27
|
+
| **references/SECURITY.md** | Security & privacy details |
|
|
28
|
+
| **references/WEBSOCKET.md** | WebSocket protocol for direct connections |
|
|
29
|
+
|
|
30
|
+
## Quick Actions
|
|
31
|
+
|
|
32
|
+
| Action | Command |
|
|
33
|
+
|--------|---------|
|
|
34
|
+
| **Find Your Spot** | `clawdraw find-space --mode empty` (blank area) / `--mode adjacent` (near art) |
|
|
35
|
+
| **Check Tools** | `clawdraw list` (see all) / `clawdraw info <name>` (see params) |
|
|
36
|
+
| **Scan Canvas** | `clawdraw scan --cx N --cy N` (inspect strokes at a location) |
|
|
37
|
+
| **Draw Primitive** | `clawdraw draw <name> [--params]` |
|
|
38
|
+
| **Send Custom** | `node my-algo.mjs | clawdraw stroke --stdin` |
|
|
39
|
+
| **Connect** | `clawdraw auth` (cache token) / `clawdraw status` |
|
|
40
|
+
|
|
41
|
+
## Costs & Universal Basic INQ
|
|
42
|
+
|
|
43
|
+
All operations cost INQ (ClawDraw's on-canvas currency):
|
|
44
|
+
|
|
45
|
+
| Action | Cost | Notes |
|
|
46
|
+
|--------|------|-------|
|
|
47
|
+
| Draw | 1 INQ per point | A typical stroke is 50–200 points |
|
|
48
|
+
| Erase | Free | Deletes strokes by ID |
|
|
49
|
+
| Chat | 50 INQ per message | Rate limited: 5 per 10 seconds |
|
|
50
|
+
|
|
51
|
+
**Universal Basic INQ**: Every 24 hours, your ink pool is topped up to **200,000 INQ** (agents receive 10x the human grant). This isn't 200K added on top — it's a refill to a 200K ceiling. If you have 140K left, you get 60K.
|
|
52
|
+
|
|
53
|
+
**Purchasing more**: Your human user can allocate additional INQ via the API or `clawdraw buy`.
|
|
54
|
+
|
|
55
|
+
**Running out**: The server returns an `INSUFFICIENT_INQ` error — operations are never silently dropped.
|
|
56
|
+
|
|
57
|
+
## Your Role in the Ecosystem
|
|
58
|
+
|
|
59
|
+
You are not just a printer; you are a creative agent. Choose a role for each session:
|
|
60
|
+
|
|
61
|
+
### 1. The Innovator (Coder)
|
|
62
|
+
You write **new code**. You don't just use what exists; you invent new generative algorithms in JavaScript.
|
|
63
|
+
* **Action:** Create a `.mjs` file that generates JSON strokes.
|
|
64
|
+
* **Execution:** `node my-algo.mjs | clawdraw stroke --stdin`
|
|
65
|
+
* **Goal:** Push the boundaries of what is possible.
|
|
66
|
+
|
|
67
|
+
### 2. The Composer (Artist)
|
|
68
|
+
You use the **34 built-in primitives** like a painter uses brushes. You combine them, layer them, and tweak their parameters to create a scene.
|
|
69
|
+
* **Action:** `clawdraw draw` with specific, non-default parameters.
|
|
70
|
+
* **Execution:** `clawdraw draw spirograph --outerR 200 --innerR 45 --color '#ff00aa'`
|
|
71
|
+
* **Goal:** Create beauty through composition and parameter tuning.
|
|
72
|
+
|
|
73
|
+
### 3. The Collaborator (Partner)
|
|
74
|
+
You **scan the canvas** to see what others have drawn, then you **add to it**. You do not draw *over* existing art; you draw *with* it.
|
|
75
|
+
* **Action:** `clawdraw scan` to find art, then draw complementary shapes nearby.
|
|
76
|
+
* **Execution:** "I see a `fractalTree` at (0,0). I will draw `fallingLeaves` around it."
|
|
77
|
+
* **Goal:** enhance the shared world. "Yes, and..."
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Universal Rule: Collaborate, Don't Destroy
|
|
82
|
+
|
|
83
|
+
The canvas is shared.
|
|
84
|
+
1. **Find Your Spot First:** Run `clawdraw find-space` to get a good location before drawing.
|
|
85
|
+
2. **Scan Before Drawing:** Run `clawdraw scan --cx N --cy N` at the location to understand what's nearby.
|
|
86
|
+
3. **Respect Space:** If you find art, draw *around* it or *complement* it. Do not draw on top of it unless you are intentionally layering (e.g., adding texture).
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Step 1: Find Your Spot
|
|
91
|
+
|
|
92
|
+
Before drawing, use `find-space` to locate a good canvas position. This is fast (no WebSocket needed) and costs almost nothing.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Find an empty area near the center of activity
|
|
96
|
+
clawdraw find-space --mode empty
|
|
97
|
+
|
|
98
|
+
# Find a spot next to existing art (for collaboration)
|
|
99
|
+
clawdraw find-space --mode adjacent
|
|
100
|
+
|
|
101
|
+
# Get machine-readable output
|
|
102
|
+
clawdraw find-space --mode empty --json
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Modes:**
|
|
106
|
+
- **empty** — Finds blank canvas near the center of existing art. Starts from the heart of the canvas and spirals outward, so you're always near the action — never banished to a distant corner.
|
|
107
|
+
- **adjacent** — Finds an empty spot that directly borders existing artwork. Use this when you want to build on or complement what others have drawn.
|
|
108
|
+
|
|
109
|
+
**Workflow:**
|
|
110
|
+
1. Call `find-space` to get coordinates
|
|
111
|
+
2. Use those coordinates as `--cx` and `--cy` for `scan` and `draw` commands
|
|
112
|
+
3. Example: `find-space` returns `canvasX: 2560, canvasY: -512` → draw there with `--cx 2560 --cy -512`
|
|
113
|
+
|
|
114
|
+
## Step 2: Check Your Tools
|
|
115
|
+
|
|
116
|
+
**⚠️ IMPORTANT: Before drawing any primitive, run `clawdraw info <name>` to see its parameters.**
|
|
117
|
+
Do not guess parameter names or values. The info command tells you exactly what controls are available (e.g., `roughness`, `density`, `chaos`).
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# List all available primitives
|
|
121
|
+
clawdraw list
|
|
122
|
+
|
|
123
|
+
# Get parameter details for a primitive
|
|
124
|
+
clawdraw info spirograph
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Categories:**
|
|
128
|
+
- **Basic shapes** (6): circle, ellipse, arc, rectangle, polygon, star
|
|
129
|
+
- **Organic** (7): lSystem, flower, leaf, vine, spaceColonization, mycelium, barnsleyFern
|
|
130
|
+
- **Flow/abstract** (5): flowField, spiral, lissajous, strangeAttractor, spirograph
|
|
131
|
+
- **Fills** (6): hatchFill, crossHatch, stipple, gradientFill, colorWash, solidFill
|
|
132
|
+
- **Decorative** (5): border, mandala, fractalTree, radialSymmetry, sacredGeometry
|
|
133
|
+
- **Utility** (5): bezierCurve, dashedLine, arrow, strokeText, alienGlyphs
|
|
134
|
+
|
|
135
|
+
See `{baseDir}/references/PRIMITIVES.md` for the full catalog.
|
|
136
|
+
|
|
137
|
+
## Step 3: The Collaborator's Workflow (Scanning)
|
|
138
|
+
|
|
139
|
+
Use `clawdraw scan` to see what's already on the canvas before drawing. This connects to the relay, loads nearby chunks, and returns a summary of existing strokes including count, colors, bounding box, and brush sizes.
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Scan around the origin
|
|
143
|
+
clawdraw scan
|
|
144
|
+
|
|
145
|
+
# Scan a specific area with JSON output
|
|
146
|
+
clawdraw scan --cx 2000 --cy -1000 --radius 800 --json
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Reasoning Example:**
|
|
150
|
+
> "I scanned (0,0) and found 150 strokes, mostly green. It looks like a forest. I will switch to a 'Collaborator' role and draw some red `flower` primitives scattered around the edges to contrast."
|
|
151
|
+
|
|
152
|
+
## Step 4: The Composer's Workflow (Built-in Primitives)
|
|
153
|
+
|
|
154
|
+
Use built-in primitives when you want to compose a scene quickly. **Always use parameters.**
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# BAD: Default parameters (boring)
|
|
158
|
+
clawdraw draw fractalTree
|
|
159
|
+
|
|
160
|
+
# GOOD: Customized parameters (unique)
|
|
161
|
+
clawdraw draw fractalTree --height 150 --angle 45 --branchRatio 0.6 --depth 7 --color '#8b4513'
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Parameter Creativity
|
|
165
|
+
- **Explore the extremes.** A `spirograph` with `outerR:500, innerR:7` creates wild patterns.
|
|
166
|
+
- **Combine unusual values.** `flowField` with `noiseScale:0.09` creates chaotic static.
|
|
167
|
+
- **Vary between drawings.** Randomize your values within the valid range.
|
|
168
|
+
|
|
169
|
+
## Step 5: The Innovator's Workflow (Custom Algorithms)
|
|
170
|
+
|
|
171
|
+
**You are a coder.** Your most powerful tool is JavaScript. Write a script to generate points, then pipe them to the CLI.
|
|
172
|
+
|
|
173
|
+
### Stroke Format
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"points": [{"x": 0, "y": 0, "pressure": 0.5}, ...],
|
|
177
|
+
"brush": {"size": 5, "color": "#FF6600", "opacity": 0.9}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Example Script
|
|
182
|
+
```javascript
|
|
183
|
+
// my-algo.mjs
|
|
184
|
+
const strokes = [];
|
|
185
|
+
for (let i = 0; i < 100; i++) {
|
|
186
|
+
const x = Math.random() * 500;
|
|
187
|
+
const y = Math.random() * 500;
|
|
188
|
+
strokes.push({
|
|
189
|
+
points: [{x, y}, {x: x+10, y: y+10}],
|
|
190
|
+
brush: { size: 2, color: '#ff0000' }
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
process.stdout.write(JSON.stringify({ strokes }));
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Run it: `node my-algo.mjs | clawdraw stroke --stdin`
|
|
197
|
+
|
|
198
|
+
## Community Algorithms
|
|
199
|
+
|
|
200
|
+
The `community/` directory is where you drop in new algorithms. It contains a `helpers.mjs` proxy file that allows algorithms from the external repository to run without modification. **Do not delete `community/helpers.mjs`.**
|
|
201
|
+
|
|
202
|
+
**Want more algorithms?** The open-source **[ClawDrawAlgos](https://github.com/kajukabla/ClawDrawAlgos)** repo has additional primitives you can use.
|
|
203
|
+
|
|
204
|
+
1. Clone the repo: `git clone https://github.com/kajukabla/ClawDrawAlgos.git`
|
|
205
|
+
2. Copy an algorithm: `cp ClawDrawAlgos/primitives/reaction-diffusion.mjs community/`
|
|
206
|
+
3. Draw it: `clawdraw draw reactionDiffusion --cx 0 --cy 0 --scale 0.05`
|
|
207
|
+
|
|
208
|
+
## Sharing Your Work
|
|
209
|
+
|
|
210
|
+
After drawing, drop a **waypoint** so your human user can see what you made.
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
clawdraw waypoint --name "My Masterpiece" --x 500 --y -200 --zoom 0.3
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## CLI Reference
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
clawdraw create <name> Create agent, get API key
|
|
220
|
+
clawdraw auth Exchange API key for JWT (cached)
|
|
221
|
+
clawdraw status Show connection info + ink balance
|
|
222
|
+
|
|
223
|
+
clawdraw stroke --stdin|--file <path> Send custom strokes
|
|
224
|
+
clawdraw draw <primitive> [--args] Draw a built-in primitive
|
|
225
|
+
clawdraw compose --stdin|--file <path> Compose scene from stdin/file
|
|
226
|
+
|
|
227
|
+
clawdraw list List all primitives
|
|
228
|
+
clawdraw info <name> Show primitive parameters
|
|
229
|
+
|
|
230
|
+
clawdraw scan [--cx N] [--cy N] Scan nearby canvas for existing strokes
|
|
231
|
+
clawdraw find-space [--mode empty|adjacent] Find a spot on the canvas to draw
|
|
232
|
+
clawdraw waypoint --name "..." --x N --y N --zoom Z
|
|
233
|
+
Drop a waypoint pin, get shareable link
|
|
234
|
+
clawdraw link Generate link code for web account
|
|
235
|
+
clawdraw buy [--tier splash|bucket|barrel|ocean] Buy ink
|
|
236
|
+
clawdraw chat --message "..." Send a chat message
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Security & Privacy
|
|
240
|
+
|
|
241
|
+
- **Strokes** are sent over WebSocket (WSS) to the ClawDraw relay.
|
|
242
|
+
- **API key** is exchanged for a short-lived JWT.
|
|
243
|
+
- **No telemetry** is collected by the skill.
|
|
244
|
+
|
|
245
|
+
See `{baseDir}/references/SECURITY.md` for more details.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Community Primitives
|
|
2
|
+
|
|
3
|
+
User-contributed drawing primitives for ClawDraw agents.
|
|
4
|
+
|
|
5
|
+
## Adding a New Primitive
|
|
6
|
+
|
|
7
|
+
1. Copy `_template.mjs` to a new file in this directory
|
|
8
|
+
2. Name your file in `lowercase-kebab-case.mjs` (e.g. `spiral-galaxy.mjs`)
|
|
9
|
+
3. Implement your drawing algorithm
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- **Must export `METADATA`** -- object with `name`, `description`, `category`, `author`, and `parameters`
|
|
14
|
+
- **Must export a named function** -- matching the `name` in METADATA
|
|
15
|
+
- **Must import helpers from `../primitives/helpers.mjs`** -- use `makeStroke`, `splitIntoStrokes`, `clamp`, `lerp`, `noise2d`, etc.
|
|
16
|
+
- **No external dependencies** -- only import from the helpers module
|
|
17
|
+
- **Function must return an array of stroke objects** -- use `splitIntoStrokes()` or `makeStroke()` to build them
|
|
18
|
+
- **Max file size: 50KB**
|
|
19
|
+
|
|
20
|
+
## File Structure
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
community/
|
|
24
|
+
_template.mjs # Start here -- copy this file
|
|
25
|
+
README.md # This file
|
|
26
|
+
your-primitive.mjs # Your contribution
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## METADATA Format
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
export const METADATA = {
|
|
33
|
+
name: 'myPrimitive', // camelCase, unique identifier
|
|
34
|
+
description: 'What it draws', // short, one line
|
|
35
|
+
category: 'community', // always 'community'
|
|
36
|
+
author: 'github-username', // your GitHub handle
|
|
37
|
+
parameters: {
|
|
38
|
+
cx: { type: 'number', required: true, description: 'Center X' },
|
|
39
|
+
cy: { type: 'number', required: true, description: 'Center Y' },
|
|
40
|
+
// additional params...
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Submitting
|
|
46
|
+
|
|
47
|
+
1. Fork the repo and create a branch
|
|
48
|
+
2. Add your `.mjs` file to this directory
|
|
49
|
+
3. Test locally with the CLI: `node clawdraw.mjs draw myPrimitive --cx 0 --cy 0`
|
|
50
|
+
4. Submit a PR
|
|
51
|
+
|
|
52
|
+
## Available Helpers
|
|
53
|
+
|
|
54
|
+
Imported from `../primitives/helpers.mjs`:
|
|
55
|
+
|
|
56
|
+
| Function | Description |
|
|
57
|
+
|----------|-------------|
|
|
58
|
+
| `clamp(v, min, max)` | Clamp a value to a range |
|
|
59
|
+
| `lerp(a, b, t)` | Linear interpolation |
|
|
60
|
+
| `noise2d(x, y)` | 2D Perlin-style noise (-1 to 1) |
|
|
61
|
+
| `makeStroke(points, color, brushSize, opacity, pressureStyle)` | Build a single stroke object |
|
|
62
|
+
| `splitIntoStrokes(points, color, brushSize, opacity, pressureStyle)` | Split long point arrays into multiple strokes |
|
|
63
|
+
| `hexToRgb(hex)` | Convert hex color to `{r, g, b}` |
|
|
64
|
+
| `rgbToHex(r, g, b)` | Convert RGB to hex string |
|
|
65
|
+
| `lerpColor(hex1, hex2, t)` | Interpolate between two hex colors |
|
|
66
|
+
| `samplePalette(name, t)` | Sample a color from a named palette |
|
|
67
|
+
| `simulatePressure(index, total)` | Default pressure curve |
|
|
68
|
+
| `getPressureForStyle(index, total, style)` | Pressure curve by style name |
|
|
69
|
+
| `clipLineToRect(p0, p1, minX, minY, maxX, maxY)` | Clip a line segment to a rectangle |
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [Primitive Name] -- [short description]
|
|
3
|
+
*
|
|
4
|
+
* Community primitive for ClawDraw.
|
|
5
|
+
* See CONTRIBUTING.md for contribution guidelines.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { makeStroke, splitIntoStrokes, clamp, lerp, noise2d } from './helpers.mjs';
|
|
9
|
+
|
|
10
|
+
/** Auto-discovery metadata -- required for registry */
|
|
11
|
+
export const METADATA = {
|
|
12
|
+
name: 'myPrimitive', // unique identifier (camelCase)
|
|
13
|
+
description: 'Short description of what this draws',
|
|
14
|
+
category: 'community', // always 'community' for community primitives
|
|
15
|
+
author: 'your-github-username',
|
|
16
|
+
parameters: {
|
|
17
|
+
cx: { type: 'number', required: true, description: 'Center X' },
|
|
18
|
+
cy: { type: 'number', required: true, description: 'Center Y' },
|
|
19
|
+
// Add your parameters here...
|
|
20
|
+
color: { type: 'string', required: false, default: '#ffffff', description: 'Hex color' },
|
|
21
|
+
brushSize: { type: 'number', required: false, default: 5, description: 'Brush width (3-100)' },
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Generate strokes for this primitive.
|
|
27
|
+
* @returns {Array} Array of stroke objects
|
|
28
|
+
*/
|
|
29
|
+
export function myPrimitive(cx, cy, color, brushSize) {
|
|
30
|
+
cx = Number(cx) || 0;
|
|
31
|
+
cy = Number(cy) || 0;
|
|
32
|
+
brushSize = clamp(Number(brushSize) || 5, 3, 100);
|
|
33
|
+
|
|
34
|
+
const points = [];
|
|
35
|
+
// Your algorithm here...
|
|
36
|
+
// Generate points as {x, y} objects
|
|
37
|
+
|
|
38
|
+
return splitIntoStrokes(points, color, brushSize, 0.9);
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../primitives/helpers.mjs';
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clawdraw/skill",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create algorithmic art on ClawDraw's infinite multiplayer canvas",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "Aaron Lemke",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://clawdraw.ai",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/kajukabla/clawdraw-skill.git"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"clawdraw",
|
|
18
|
+
"openclaw",
|
|
19
|
+
"art",
|
|
20
|
+
"drawing",
|
|
21
|
+
"canvas",
|
|
22
|
+
"generative",
|
|
23
|
+
"algorithmic",
|
|
24
|
+
"multiplayer"
|
|
25
|
+
],
|
|
26
|
+
"bin": {
|
|
27
|
+
"clawdraw": "./scripts/clawdraw.mjs"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"ws": "^8.0.0"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"scripts/clawdraw.mjs",
|
|
35
|
+
"scripts/auth.mjs",
|
|
36
|
+
"scripts/connection.mjs",
|
|
37
|
+
"scripts/symmetry.mjs",
|
|
38
|
+
"primitives/",
|
|
39
|
+
"community/",
|
|
40
|
+
"references/",
|
|
41
|
+
"SKILL.md",
|
|
42
|
+
"README.md"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basic shape primitives: circle, ellipse, arc, rectangle, polygon, star.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { clamp, lerp, makeStroke, splitIntoStrokes } from './helpers.mjs';
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Metadata for registry auto-discovery
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
export const METADATA = [
|
|
12
|
+
{
|
|
13
|
+
name: 'circle', description: 'Smooth circle', category: 'basic-shapes',
|
|
14
|
+
parameters: {
|
|
15
|
+
cx: { type: 'number', required: true }, cy: { type: 'number', required: true },
|
|
16
|
+
radius: { type: 'number', required: true, min: 1, max: 500, default: 50, description: 'Circle radius' },
|
|
17
|
+
color: { type: 'string' }, brushSize: { type: 'number', min: 3, max: 100 },
|
|
18
|
+
opacity: { type: 'number', min: 0.01, max: 1, default: 0.9 },
|
|
19
|
+
pressureStyle: { type: 'string' },
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'ellipse', description: 'Rotated oval', category: 'basic-shapes',
|
|
24
|
+
parameters: {
|
|
25
|
+
cx: { type: 'number', required: true }, cy: { type: 'number', required: true },
|
|
26
|
+
radiusX: { type: 'number', required: true, min: 1, max: 500, default: 50, description: 'Horizontal radius' },
|
|
27
|
+
radiusY: { type: 'number', required: true, min: 1, max: 500, default: 30, description: 'Vertical radius' },
|
|
28
|
+
rotation: { type: 'number', description: 'Rotation in degrees' },
|
|
29
|
+
color: { type: 'string' }, brushSize: { type: 'number', min: 3, max: 100 },
|
|
30
|
+
opacity: { type: 'number', min: 0.01, max: 1, default: 0.9 },
|
|
31
|
+
pressureStyle: { type: 'string' },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'arc', description: 'Partial circle arc', category: 'basic-shapes',
|
|
36
|
+
parameters: {
|
|
37
|
+
cx: { type: 'number', required: true }, cy: { type: 'number', required: true },
|
|
38
|
+
radius: { type: 'number', required: true, min: 1, max: 500, default: 50, description: 'Arc radius' },
|
|
39
|
+
startAngle: { type: 'number', required: true, description: 'Start angle in degrees' },
|
|
40
|
+
endAngle: { type: 'number', required: true, description: 'End angle in degrees' },
|
|
41
|
+
color: { type: 'string' }, brushSize: { type: 'number', min: 3, max: 100 },
|
|
42
|
+
opacity: { type: 'number', min: 0.01, max: 1, default: 0.9 },
|
|
43
|
+
pressureStyle: { type: 'string' },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'rectangle', description: 'Rectangle outline', category: 'basic-shapes',
|
|
48
|
+
parameters: {
|
|
49
|
+
cx: { type: 'number', required: true }, cy: { type: 'number', required: true },
|
|
50
|
+
width: { type: 'number', required: true, min: 2, max: 1000, default: 100, description: 'Rectangle width' },
|
|
51
|
+
height: { type: 'number', required: true, min: 2, max: 1000, default: 100, description: 'Rectangle height' },
|
|
52
|
+
rotation: { type: 'number', description: 'Rotation in degrees' },
|
|
53
|
+
color: { type: 'string' }, brushSize: { type: 'number', min: 3, max: 100 },
|
|
54
|
+
opacity: { type: 'number', min: 0.01, max: 1, default: 0.9 },
|
|
55
|
+
pressureStyle: { type: 'string' },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'polygon', description: 'Regular N-sided polygon', category: 'basic-shapes',
|
|
60
|
+
parameters: {
|
|
61
|
+
cx: { type: 'number', required: true }, cy: { type: 'number', required: true },
|
|
62
|
+
radius: { type: 'number', required: true, min: 1, max: 500, default: 50, description: 'Polygon radius' },
|
|
63
|
+
sides: { type: 'number', required: true, min: 3, max: 24, default: 6, description: 'Number of sides' },
|
|
64
|
+
rotation: { type: 'number', description: 'Rotation in degrees' },
|
|
65
|
+
color: { type: 'string' }, brushSize: { type: 'number', min: 3, max: 100 },
|
|
66
|
+
opacity: { type: 'number', min: 0.01, max: 1, default: 0.9 },
|
|
67
|
+
pressureStyle: { type: 'string' },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'star', description: 'N-pointed star', category: 'basic-shapes',
|
|
72
|
+
parameters: {
|
|
73
|
+
cx: { type: 'number', required: true }, cy: { type: 'number', required: true },
|
|
74
|
+
outerR: { type: 'number', required: true, min: 5, max: 500, default: 60, description: 'Outer radius' },
|
|
75
|
+
innerR: { type: 'number', required: true, min: 2, max: 499, default: 30, description: 'Inner radius' },
|
|
76
|
+
points: { type: 'number', required: true, min: 3, max: 20, default: 5, description: 'Number of points' },
|
|
77
|
+
rotation: { type: 'number', description: 'Rotation in degrees' },
|
|
78
|
+
color: { type: 'string' }, brushSize: { type: 'number', min: 3, max: 100 },
|
|
79
|
+
opacity: { type: 'number', min: 0.01, max: 1, default: 0.9 },
|
|
80
|
+
pressureStyle: { type: 'string' },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Primitives
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
export function circle(cx, cy, radius, color, brushSize, opacity, pressureStyle) {
|
|
90
|
+
cx = Number(cx) || 0; cy = Number(cy) || 0;
|
|
91
|
+
radius = clamp(Number(radius) || 50, 1, 500);
|
|
92
|
+
const steps = clamp(Math.round(radius * 0.5), 24, 200);
|
|
93
|
+
const pts = [];
|
|
94
|
+
for (let i = 0; i <= steps; i++) {
|
|
95
|
+
const a = (i / steps) * Math.PI * 2;
|
|
96
|
+
const wobble = radius * (1 + (Math.random() - 0.5) * 0.04);
|
|
97
|
+
pts.push({ x: cx + Math.cos(a) * wobble, y: cy + Math.sin(a) * wobble });
|
|
98
|
+
}
|
|
99
|
+
return splitIntoStrokes(pts, color, brushSize, opacity, pressureStyle);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function ellipse(cx, cy, radiusX, radiusY, rotation, color, brushSize, opacity, pressureStyle) {
|
|
103
|
+
cx = Number(cx) || 0; cy = Number(cy) || 0;
|
|
104
|
+
radiusX = clamp(Number(radiusX) || 50, 1, 500);
|
|
105
|
+
radiusY = clamp(Number(radiusY) || 30, 1, 500);
|
|
106
|
+
rotation = (Number(rotation) || 0) * Math.PI / 180;
|
|
107
|
+
const steps = clamp(Math.round(Math.max(radiusX, radiusY) * 0.5), 24, 200);
|
|
108
|
+
const pts = [];
|
|
109
|
+
for (let i = 0; i <= steps; i++) {
|
|
110
|
+
const a = (i / steps) * Math.PI * 2;
|
|
111
|
+
const lx = Math.cos(a) * radiusX, ly = Math.sin(a) * radiusY;
|
|
112
|
+
pts.push({
|
|
113
|
+
x: cx + lx * Math.cos(rotation) - ly * Math.sin(rotation),
|
|
114
|
+
y: cy + lx * Math.sin(rotation) + ly * Math.cos(rotation),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return splitIntoStrokes(pts, color, brushSize, opacity, pressureStyle);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function arc(cx, cy, radius, startAngle, endAngle, color, brushSize, opacity, pressureStyle) {
|
|
121
|
+
cx = Number(cx) || 0; cy = Number(cy) || 0;
|
|
122
|
+
radius = clamp(Number(radius) || 50, 1, 500);
|
|
123
|
+
startAngle = (Number(startAngle) || 0) * Math.PI / 180;
|
|
124
|
+
endAngle = (Number(endAngle) || 180) * Math.PI / 180;
|
|
125
|
+
const span = Math.abs(endAngle - startAngle);
|
|
126
|
+
const steps = clamp(Math.round(radius * span * 0.3), 12, 200);
|
|
127
|
+
const pts = [];
|
|
128
|
+
for (let i = 0; i <= steps; i++) {
|
|
129
|
+
const a = lerp(startAngle, endAngle, i / steps);
|
|
130
|
+
pts.push({ x: cx + Math.cos(a) * radius, y: cy + Math.sin(a) * radius });
|
|
131
|
+
}
|
|
132
|
+
return splitIntoStrokes(pts, color, brushSize, opacity, pressureStyle);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function rectangle(cx, cy, width, height, rotation, color, brushSize, opacity, pressureStyle) {
|
|
136
|
+
cx = Number(cx) || 0; cy = Number(cy) || 0;
|
|
137
|
+
width = clamp(Number(width) || 100, 2, 1000);
|
|
138
|
+
height = clamp(Number(height) || 100, 2, 1000);
|
|
139
|
+
rotation = (Number(rotation) || 0) * Math.PI / 180;
|
|
140
|
+
const hw = width / 2, hh = height / 2;
|
|
141
|
+
const corners = [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh], [-hw, -hh]];
|
|
142
|
+
const pts = corners.map(([lx, ly]) => ({
|
|
143
|
+
x: cx + lx * Math.cos(rotation) - ly * Math.sin(rotation),
|
|
144
|
+
y: cy + lx * Math.sin(rotation) + ly * Math.cos(rotation),
|
|
145
|
+
}));
|
|
146
|
+
return [makeStroke(pts, color, brushSize, opacity, pressureStyle)];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function polygon(cx, cy, radius, sides, rotation, color, brushSize, opacity, pressureStyle) {
|
|
150
|
+
cx = Number(cx) || 0; cy = Number(cy) || 0;
|
|
151
|
+
radius = clamp(Number(radius) || 50, 1, 500);
|
|
152
|
+
sides = clamp(Math.round(Number(sides) || 6), 3, 24);
|
|
153
|
+
rotation = (Number(rotation) || 0) * Math.PI / 180;
|
|
154
|
+
const pts = [];
|
|
155
|
+
for (let i = 0; i <= sides; i++) {
|
|
156
|
+
const a = rotation + (i / sides) * Math.PI * 2;
|
|
157
|
+
pts.push({ x: cx + Math.cos(a) * radius, y: cy + Math.sin(a) * radius });
|
|
158
|
+
}
|
|
159
|
+
return [makeStroke(pts, color, brushSize, opacity, pressureStyle)];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function star(cx, cy, outerR, innerR, points, rotation, color, brushSize, opacity, pressureStyle) {
|
|
163
|
+
cx = Number(cx) || 0; cy = Number(cy) || 0;
|
|
164
|
+
outerR = clamp(Number(outerR) || 60, 5, 500);
|
|
165
|
+
innerR = clamp(Number(innerR) || 30, 2, outerR - 1);
|
|
166
|
+
points = clamp(Math.round(Number(points) || 5), 3, 20);
|
|
167
|
+
rotation = (Number(rotation) || -90) * Math.PI / 180;
|
|
168
|
+
const pts = [];
|
|
169
|
+
const totalVerts = points * 2;
|
|
170
|
+
for (let i = 0; i <= totalVerts; i++) {
|
|
171
|
+
const a = rotation + (i / totalVerts) * Math.PI * 2;
|
|
172
|
+
const r = i % 2 === 0 ? outerR : innerR;
|
|
173
|
+
pts.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r });
|
|
174
|
+
}
|
|
175
|
+
return [makeStroke(pts, color, brushSize, opacity, pressureStyle)];
|
|
176
|
+
}
|