@cyanheads/pixoo-mcp-server 1.2.1 → 1.2.2
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/AGENTS.md +5 -2
- package/CLAUDE.md +5 -2
- package/README.md +13 -9
- package/changelog/1.2.x/1.2.2.md +40 -0
- package/dist/mcp-server/resources/definitions/pixoo-design-guide.resource.d.ts.map +1 -1
- package/dist/mcp-server/resources/definitions/pixoo-design-guide.resource.js +4 -1
- package/dist/mcp-server/resources/definitions/pixoo-design-guide.resource.js.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.d.ts +96 -2
- package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.js +85 -10
- package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-design-brief.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-design-brief.tool.js +14 -6
- package/dist/mcp-server/tools/definitions/pixoo-design-brief.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.d.ts +2 -0
- package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.js +27 -12
- package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.d.ts +30 -1
- package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.js +105 -26
- package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.js.map +1 -1
- package/dist/mcp-server/tools/finish-schema.d.ts +28 -0
- package/dist/mcp-server/tools/finish-schema.d.ts.map +1 -0
- package/dist/mcp-server/tools/finish-schema.js +42 -0
- package/dist/mcp-server/tools/finish-schema.js.map +1 -0
- package/dist/renderer/finish.d.ts +34 -0
- package/dist/renderer/finish.d.ts.map +1 -0
- package/dist/renderer/finish.js +62 -0
- package/dist/renderer/finish.js.map +1 -0
- package/dist/renderer/keyframes.d.ts +5 -1
- package/dist/renderer/keyframes.d.ts.map +1 -1
- package/dist/renderer/keyframes.js +16 -2
- package/dist/renderer/keyframes.js.map +1 -1
- package/dist/renderer/remote-image.d.ts +9 -7
- package/dist/renderer/remote-image.d.ts.map +1 -1
- package/dist/renderer/remote-image.js +13 -21
- package/dist/renderer/remote-image.js.map +1 -1
- package/dist/renderer/scene-renderer.d.ts +45 -6
- package/dist/renderer/scene-renderer.d.ts.map +1 -1
- package/dist/renderer/scene-renderer.js +203 -65
- package/dist/renderer/scene-renderer.js.map +1 -1
- package/dist/renderer/text-engine.d.ts +24 -5
- package/dist/renderer/text-engine.d.ts.map +1 -1
- package/dist/renderer/text-engine.js +51 -28
- package/dist/renderer/text-engine.js.map +1 -1
- package/package.json +2 -2
- package/server.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** pixoo-mcp-server
|
|
4
|
-
**Version:** 1.2.
|
|
4
|
+
**Version:** 1.2.2
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.9`
|
|
6
6
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/server` ^2.1.0 (protocol revision 2026-07-28 alongside the 2025 era)
|
|
@@ -187,6 +187,7 @@ Pixoo-specific error reasons declared on tools:
|
|
|
187
187
|
| `device_rejected` | `ServiceUnavailable` | Firmware returned non-zero `error_code` | — |
|
|
188
188
|
| `no_device_configured` | `InvalidParams` | Device tool called without `PIXOO_IP` | — |
|
|
189
189
|
| `asset_not_found` | `NotFound` | Image/sprite path or URL unreadable | — |
|
|
190
|
+
| `invalid_image` | `InvalidParams` | Image source or sprite path read but not decodable | — |
|
|
190
191
|
| `invalid_color` | `InvalidParams` | `resolveColor` throw — invalid color name or format | — |
|
|
191
192
|
| `unknown_icon` | `InvalidParams` | Icon name not in registry | — |
|
|
192
193
|
| `discovery_failed` | `ServiceUnavailable` | Divoom cloud unreachable | `true` |
|
|
@@ -234,11 +235,13 @@ src/
|
|
|
234
235
|
text-engine.ts # Gradient ramp + shadow + outline text engine, overflow handling
|
|
235
236
|
scene-renderer.ts # Element vocabulary, layout resolver, frame rendering
|
|
236
237
|
keyframes.ts # Keyframe interpolation + animation preset compiler
|
|
238
|
+
finish.ts # Palette finish (quantize + dither) for one frame, or frames sharing one palette
|
|
237
239
|
preview.ts # PNG/contact-sheet/GIF encoding
|
|
238
|
-
remote-image.ts # https image fetch
|
|
240
|
+
remote-image.ts # https image fetch into memory for the toolkit loader; stops on ctx.signal
|
|
239
241
|
mcp-server/
|
|
240
242
|
tools/
|
|
241
243
|
device-push.ts # Shared post-render push: preview kept on failure, visibility notice
|
|
244
|
+
finish-schema.ts # Shared `finish` input schema (colors | palette, plus dither)
|
|
242
245
|
tools/definitions/
|
|
243
246
|
pixoo-display-text.tool.ts
|
|
244
247
|
pixoo-compose-scene.tool.ts
|
package/CLAUDE.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** pixoo-mcp-server
|
|
4
|
-
**Version:** 1.2.
|
|
4
|
+
**Version:** 1.2.2
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.9`
|
|
6
6
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/server` ^2.1.0 (protocol revision 2026-07-28 alongside the 2025 era)
|
|
@@ -187,6 +187,7 @@ Pixoo-specific error reasons declared on tools:
|
|
|
187
187
|
| `device_rejected` | `ServiceUnavailable` | Firmware returned non-zero `error_code` | — |
|
|
188
188
|
| `no_device_configured` | `InvalidParams` | Device tool called without `PIXOO_IP` | — |
|
|
189
189
|
| `asset_not_found` | `NotFound` | Image/sprite path or URL unreadable | — |
|
|
190
|
+
| `invalid_image` | `InvalidParams` | Image source or sprite path read but not decodable | — |
|
|
190
191
|
| `invalid_color` | `InvalidParams` | `resolveColor` throw — invalid color name or format | — |
|
|
191
192
|
| `unknown_icon` | `InvalidParams` | Icon name not in registry | — |
|
|
192
193
|
| `discovery_failed` | `ServiceUnavailable` | Divoom cloud unreachable | `true` |
|
|
@@ -234,11 +235,13 @@ src/
|
|
|
234
235
|
text-engine.ts # Gradient ramp + shadow + outline text engine, overflow handling
|
|
235
236
|
scene-renderer.ts # Element vocabulary, layout resolver, frame rendering
|
|
236
237
|
keyframes.ts # Keyframe interpolation + animation preset compiler
|
|
238
|
+
finish.ts # Palette finish (quantize + dither) for one frame, or frames sharing one palette
|
|
237
239
|
preview.ts # PNG/contact-sheet/GIF encoding
|
|
238
|
-
remote-image.ts # https image fetch
|
|
240
|
+
remote-image.ts # https image fetch into memory for the toolkit loader; stops on ctx.signal
|
|
239
241
|
mcp-server/
|
|
240
242
|
tools/
|
|
241
243
|
device-push.ts # Shared post-render push: preview kept on failure, visibility notice
|
|
244
|
+
finish-schema.ts # Shared `finish` input schema (colors | palette, plus dither)
|
|
242
245
|
tools/definitions/
|
|
243
246
|
pixoo-display-text.tool.ts
|
|
244
247
|
pixoo-compose-scene.tool.ts
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/pixoo-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/pixoo-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -31,7 +31,7 @@ Divoom Pixoo LED matrix displays on the local network, with the Pixoo-64 as the
|
|
|
31
31
|
|:---|:---|
|
|
32
32
|
| `pixoo_display_text` | Render styled text with themes, gradients, shadows, and auto-fit, static or animated, and push it |
|
|
33
33
|
| `pixoo_compose_scene` | Compose layered scenes of text, icons, widgets, shapes, bitmaps, images, and sprites, static or animated |
|
|
34
|
-
| `pixoo_push_image` | Resize a local or https image to the LED grid and push it |
|
|
34
|
+
| `pixoo_push_image` | Resize a local or https image to the LED grid and push it, an animated GIF or WebP as an animation |
|
|
35
35
|
| `pixoo_overlay_text` | Set or clear a device-rendered scrolling text overlay |
|
|
36
36
|
| `pixoo_control_device` | Read or change brightness, screen state, channel, or clock face |
|
|
37
37
|
| `pixoo_discover_devices` | Find Pixoo devices and their LAN IPs through Divoom's cloud discovery |
|
|
@@ -53,15 +53,17 @@ Tools cover the same ground for tool-only clients: `pixoo_control_device` reads
|
|
|
53
53
|
### `pixoo_display_text` <sub>tool</sub>
|
|
54
54
|
|
|
55
55
|
- `text` as a string or an array of lines; `theme` (`midnight`, `ember`, `claude`, `ice`, `neon`, `forest`, `mono`) sets the background and default palette. `style` takes a `palette` ramp (`ember`, `ice`, `neon`, `fire`, `lavender`, `claude`, `mono`) or a custom `{ from, to }`, plus `shadow`, `outline`, and `scale` 1–8; `position` is semantic or in pixels, and `align` lines up multi-line text
|
|
56
|
-
- `
|
|
56
|
+
- `font`: `standard` (5×7) and `compact` (3×5) draw printable ASCII plus `° ← ↑ → ↓ ▲ ▼ ♥ · …`; `numerals` is an 11×18 digit face for clocks and readouts that draws 0–9, space, and `: . - + / % ° ?`, and text holding any other character fails validation, naming those characters
|
|
57
|
+
- `layout[]` reports every fit decision as an `action` (`none`, `shrunk-to-compact`, or `scrolling`), the `font` used, and whether each line's box `fits` on the panel; a scrolling line's box starts at x 0, where its static frame draws it. Single-line text falls back from the standard to the compact font unless `font` is set — never to `numerals` — and text still too wide only scrolls under `effect: "auto"` or `"scroll"`
|
|
57
58
|
- `effect`: `scroll` makes one pass in up to 40 frames, `auto` scrolls only on overflow, and `float` and `pulse` loop over 20 frames; `frames` reports the count, and the animation pushes as one device animation
|
|
58
59
|
|
|
59
60
|
---
|
|
60
61
|
|
|
61
62
|
### `pixoo_compose_scene` <sub>tool</sub>
|
|
62
63
|
|
|
63
|
-
- Up to 50 `elements` drawn back-to-front: `text
|
|
64
|
-
-
|
|
64
|
+
- Up to 50 `elements` drawn back-to-front: `text` (in the same three fonts as `pixoo_display_text`), `icon`, `rect`, `circle`, `line`, `progress`, `sparkline`, `bitmap`, `pixels`, `image` (absolute path or https URL, with the same `finish` as `pixoo_push_image`), `sprite` (absolute path). The `background` is a solid color, a `v` / `h` / `r` gradient, or a `theme`
|
|
65
|
+
- Every element takes `opacity` (each pixel lands at its own alpha × `opacity`, so soft edges fade evenly) and `blend`: `normal`, `add` (glows and light beams), `screen`, or `multiply`. `line` and outline `circle` take `strokeWidth` and `antialias`, and a `rect` border takes `strokeWidth`, growing inward; either field on a shape that draws no stroke fails validation, naming it
|
|
66
|
+
- Returns `layout[]`: each element's placed box — for a wide or anti-aliased stroke, every pixel it draws — and whether it `fits` on the panel. An absolute `output` path saves the first frame as a PNG in place of the `PIXOO_OUTPUT_DIR` auto-save. Typed failures: `asset_not_found`, `invalid_image` (an image or sprite that was read but does not decode), `invalid_color`, `unknown_icon`, `invalid_output_path`
|
|
65
67
|
- Animation through per-element `effect` presets (`float`, `scroll-left`, `scroll-right`, `pulse`, `blink`, `twinkle`, `drift`, `fade-in`, `fade-out`) or raw `animate` keyframes over `dx`, `dy`, `opacity` (numbers or numeric strings), `visible` (`true`/`false`), and `color` (interpolated through RGB on any element with a `color`), each track holding at least one keyframe; `frames` 1–40, `speed` 10–2000 ms per frame (default 150). An effect's `amplitude` sets the movement of `float`, `scroll-*`, and `drift`, and the 0–1 depth of the `pulse` and `twinkle` opacity dip
|
|
66
68
|
|
|
67
69
|
---
|
|
@@ -69,7 +71,9 @@ Tools cover the same ground for tool-only clients: `pixoo_control_device` reads
|
|
|
69
71
|
### `pixoo_push_image` <sub>tool</sub>
|
|
70
72
|
|
|
71
73
|
- `source` is an absolute local path or an https URL, with downloads capped at 10 MB; `fit` is `contain` (default), `cover`, or `fill`, and `kernel` is `nearest` (default, for pixel art), `lanczos3` (photos), or `mitchell`
|
|
72
|
-
-
|
|
74
|
+
- A source that decodes as an animated GIF or WebP, whatever its file name, pushes as an animation of up to 40 frames, sampled evenly from a longer source. It plays at the source's total duration over the pushed frame count (150 ms when the source records no delays), or at `speed` (10–2000 ms per frame); `frames`, `sourceFrames`, and `speed` report what was pushed
|
|
75
|
+
- `finish` reduces the image to a palette before the push: exactly one of `colors` (2–256, built from the image) or `palette` (1–256 hex or named colors), plus `dither` (`none`, `bayer4`, `floyd-steinberg`). Transparent pixels stay unlit, and an animation's `colors` palette is shared by every frame
|
|
76
|
+
- An unreadable path or URL fails as `asset_not_found`, a source that is read but does not decode (a text file, an HTML page, a truncated download) fails as `invalid_image`, and an unresolvable `finish` palette entry fails as `invalid_color`; the preview is the exact frame the device receives, or a grid of every frame for an animation
|
|
73
77
|
|
|
74
78
|
---
|
|
75
79
|
|
|
@@ -135,7 +139,7 @@ Pixoo-specific:
|
|
|
135
139
|
|
|
136
140
|
- All composition happens on the host in an RGBA canvas pipeline (`@cyanheads/pixoo-toolkit`); the device receives finished RGB frames
|
|
137
141
|
- Pushes switch the device to the custom channel and run one at a time, spaced by `PIXOO_PUSH_MIN_INTERVAL_MS` (default 1000) so rapid pushes don't freeze the device
|
|
138
|
-
- Animations cap at 40 frames, past which the device becomes unstable
|
|
142
|
+
- Animations cap at 40 frames, past which the device becomes unstable; `pixoo_push_image` samples a longer GIF or WebP down to 40
|
|
139
143
|
|
|
140
144
|
Agent-friendly output:
|
|
141
145
|
|
|
@@ -300,10 +304,10 @@ The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `
|
|
|
300
304
|
|:---|:---|
|
|
301
305
|
| `src/index.ts` | `createApp()` entry point: registers tools and resources and initializes the Pixoo service. |
|
|
302
306
|
| `src/config/` | Server-specific environment variable parsing and validation with Zod. |
|
|
303
|
-
| `src/mcp-server/tools/` | Tool definitions (`*.tool.ts`)
|
|
307
|
+
| `src/mcp-server/tools/` | Tool definitions (`*.tool.ts`), the shared post-render push path, and the shared `finish` input schema. |
|
|
304
308
|
| `src/mcp-server/resources/` | Resource definitions (`*.resource.ts`). |
|
|
305
309
|
| `src/services/pixoo/` | `PixooService`: wraps `@cyanheads/pixoo-toolkit` with push pacing, result mapping, and device state reads. |
|
|
306
|
-
| `src/renderer/` | Pure rendering pipeline with no device dependency: element renderers, styled-text engine, themes, icons, effect compiler, preview encoding, remote image fetch. |
|
|
310
|
+
| `src/renderer/` | Pure rendering pipeline with no device dependency: element renderers, styled-text engine, themes, icons, effect compiler, palette finishing, preview encoding, remote image fetch. |
|
|
307
311
|
| `tests/` | Unit and integration tests mirroring `src/`. |
|
|
308
312
|
|
|
309
313
|
## Development guide
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Adopts @cyanheads/pixoo-toolkit 0.10.0: animated GIF and WebP push as animations, images take a palette finish, scene elements take blend modes and stroke widths, and text gains a numerals face. Undecodable images fail as invalid_image, and filled circles and JPEG/WebP images render differently."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 1.2.2 — 2026-09-26
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **`finish` on `pixoo_push_image` and scene `image` elements** reduces the image to a palette: `colors` (2–256) or `palette` (1–256 colors), plus `dither` (`none`, `bayer4`, `floyd-steinberg`) ([#27](https://github.com/cyanheads/pixoo-mcp-server/issues/27)). An animation's `colors` palette is shared across frames; a bad palette entry fails as `invalid_color`.
|
|
12
|
+
- **`blend` on every `pixoo_compose_scene` element** — `normal` (default), `add`, `screen`, or `multiply` ([#29](https://github.com/cyanheads/pixoo-mcp-server/issues/29)).
|
|
13
|
+
- **`strokeWidth` and `antialias` on `line` and outline `circle`; `strokeWidth` on a `rect` border**, growing inward (#29). Either on a shape that draws no stroke fails `-32602` naming it; a wide or anti-aliased stroke's layout box covers every pixel drawn.
|
|
14
|
+
- **`font: "numerals"`** on `pixoo_display_text` and scene `text` — an 11×18 digit face drawing 0–9, space, and `: . - + / % ° ?`; any other character fails `-32602` naming it, and auto-fit never picks it ([#30](https://github.com/cyanheads/pixoo-mcp-server/issues/30)).
|
|
15
|
+
- **`pixoo_push_image` `speed` input and `frames`, `sourceFrames`, `speed` output fields** for animated sources ([#28](https://github.com/cyanheads/pixoo-mcp-server/issues/28)).
|
|
16
|
+
- **`pixoo_output_dir` and `pixoo_push_min_interval_ms` options in `manifest.json`**, so a Claude Desktop bundle install can set `PIXOO_OUTPUT_DIR` and `PIXOO_PUSH_MIN_INTERVAL_MS`.
|
|
17
|
+
|
|
18
|
+
## Changed
|
|
19
|
+
|
|
20
|
+
- **An animated GIF or WebP given to `pixoo_push_image` pushes as an animation** instead of its first frame, whatever its file extension (#28). Up to 40 frames, sampled evenly from a longer source, play at the source's total duration over the pushed frame count.
|
|
21
|
+
- **Filled `circle` elements draw a rounder disc** (`dx² + dy² ≤ r² + r`, from the toolkit), so every filled circle's pixels change.
|
|
22
|
+
- **JPEG and WebP images decode at full resolution before resizing** (from the toolkit), so their pushed pixels change.
|
|
23
|
+
- **An image source that is read but does not decode fails as `-32602` `invalid_image`** naming the source and the decoder's reason, instead of an unclassified `-32603`, in `pixoo_push_image` and compose `image` and `sprite` elements ([#34](https://github.com/cyanheads/pixoo-mcp-server/issues/34)).
|
|
24
|
+
- **`asset_not_found` carries its tool's declared recovery hint, reworded, on both image tools**, and a sprite `path` given as a URL says sprite sheets take a local path ([#51](https://github.com/cyanheads/pixoo-mcp-server/issues/51)).
|
|
25
|
+
- **https image sources load from memory** — they render when the temp dir is unwritable, and an https SVG renders like the same local file instead of rasterizing at its intrinsic size ([#23](https://github.com/cyanheads/pixoo-mcp-server/issues/23)).
|
|
26
|
+
- **A partially transparent pixel under `opacity` below 100 fades at its own alpha × opacity**, so soft-edged images no longer brighten as they fade (#29).
|
|
27
|
+
- **`twinkle` renders the same frames for the same input**; its jitter comes from the frame index and element position ([#50](https://github.com/cyanheads/pixoo-mcp-server/issues/50)).
|
|
28
|
+
- **`pixoo_display_text` reports `fits: false` for text leaving the panel on any edge** ([#49](https://github.com/cyanheads/pixoo-mcp-server/issues/49)), and a scrolling line reports `box.x: 0`, where its frame draws it ([#55](https://github.com/cyanheads/pixoo-mcp-server/issues/55)).
|
|
29
|
+
- **`standard` and `compact` draw `° ← ↑ → ↓ ▲ ▼ ♥ · …` as glyphs** instead of `?` (#30).
|
|
30
|
+
- **`Object.prototype` names such as `constructor` fail as `invalid_color`** on every color input of `pixoo_compose_scene`, `pixoo_display_text`, and `pixoo_overlay_text`, which now rejects them before any device call ([#32](https://github.com/cyanheads/pixoo-mcp-server/issues/32)).
|
|
31
|
+
- **`pixoo_design_brief` and the design guide** describe dithering, anti-aliasing, the new glyphs and numerals face, a glow recipe, and the Pixoo-64's measured dark and mid-level response.
|
|
32
|
+
|
|
33
|
+
## Fixed
|
|
34
|
+
|
|
35
|
+
- **`pixoo_compose_scene` loads each distinct asset once per call** — elements sharing a sprite sheet, or an image source and placement, share one decode, and a URL is fetched once ([#52](https://github.com/cyanheads/pixoo-mcp-server/issues/52)).
|
|
36
|
+
- **Standard text at scale 2 is 14 px tall** in the design brief and guide, which said 10 px.
|
|
37
|
+
|
|
38
|
+
## Dependencies
|
|
39
|
+
|
|
40
|
+
- `@cyanheads/pixoo-toolkit` ^0.8.2 → ^0.10.0, exempt from the `bunfig.toml` release-age hold beside the framework
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pixoo-design-guide.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/pixoo-design-guide.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"pixoo-design-guide.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/pixoo-design-guide.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AA2GrD,eAAO,MAAM,wBAAwB,2GAyBnC,CAAC"}
|
|
@@ -13,8 +13,10 @@ At typical viewing distance for a 64×64 LED display:
|
|
|
13
13
|
- **Minimum eye size:** 1–2px across; 2×2 fills are more reliable than single pixels
|
|
14
14
|
- **Minimum limb gap:** 2 rows between horizontal elements prevents blur
|
|
15
15
|
- **Font choices:**
|
|
16
|
-
- Standard (5×7): default for all text. Readable at 1× scale. Excellent at 2× (
|
|
16
|
+
- Standard (5×7): default for all text. Readable at 1× scale. Excellent at 2× (14px tall = chunky block letters)
|
|
17
17
|
- Compact (3×5): secondary text, captions, labels when space is tight
|
|
18
|
+
- Standard and compact both draw printable ASCII plus \`° ← ↑ → ↓ ▲ ▼ ♥ · …\` for units, trends, and separators
|
|
19
|
+
- Numerals (11×18): clocks and hero readouts. Draws 0–9, space, and \`: . - + / % ° ?\` only, each digit on one 13px advance so a changing value keeps its width. Auto-fit never picks it; text holding any other character is rejected, so units and labels go in their own standard or compact text element
|
|
18
20
|
- Scale ≥2: decorative or hero use only — verify fit before pushing
|
|
19
21
|
|
|
20
22
|
## Palette Discipline
|
|
@@ -103,6 +105,7 @@ At typical viewing distance for a 64×64 LED display:
|
|
|
103
105
|
- **getConfig() omits SelectIndex** on current Pixoo-64 firmware — use getChannel() for reliable channel reads
|
|
104
106
|
- **Text overlays (pixoo_overlay_text)** persist across channel switches until explicitly cleared
|
|
105
107
|
- **Discovery requires internet** — Divoom cloud endpoint even for local device control
|
|
108
|
+
- **Dark and mid levels render darker than on a monitor** — on a Pixoo-64 at brightness 100, channel levels 0–4 stay dark, and mid-levels render darker than on an sRGB monitor, shifting muted warm colors toward red (\`#D97757\` reads red). Previews show the sRGB values pushed, not the panel's response.
|
|
106
109
|
`;
|
|
107
110
|
export const pixooDesignGuideResource = resource('pixoo://reference/design-guide', {
|
|
108
111
|
name: 'design-guide',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pixoo-design-guide.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/pixoo-design-guide.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAErD,MAAM,YAAY,GAAG
|
|
1
|
+
{"version":3,"file":"pixoo-design-guide.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/pixoo-design-guide.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAErD,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuGpB,CAAC;AAEF,MAAM,CAAC,MAAM,wBAAwB,GAAG,QAAQ,CAAC,gCAAgC,EAAE;IACjF,IAAI,EAAE,cAAc;IACpB,KAAK,EAAE,oBAAoB;IAC3B,WAAW,EACT,uNAAuN;IACzN,QAAQ,EAAE,eAAe;IACzB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IACpB,qEAAqE;IACrE,SAAS,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;IAEtD,OAAO,CAAC,OAAO,EAAE,IAAI;QACnB,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QACjB,SAAS,EAAE;YACT;gBACE,GAAG,EAAE,gCAAgC;gBACrC,IAAI,EAAE,cAAc;gBACpB,QAAQ,EAAE,eAAe;gBACzB,WAAW,EACT,iFAAiF;aACpF;SACF;KACF,CAAC;CACH,CAAC,CAAC"}
|
|
@@ -29,6 +29,12 @@ export declare const pixooComposeScene: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
29
29
|
elements: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
30
30
|
visible: z.ZodOptional<z.ZodBoolean>;
|
|
31
31
|
opacity: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
blend: z.ZodOptional<z.ZodEnum<{
|
|
33
|
+
add: "add";
|
|
34
|
+
multiply: "multiply";
|
|
35
|
+
normal: "normal";
|
|
36
|
+
screen: "screen";
|
|
37
|
+
}>>;
|
|
32
38
|
dx: z.ZodOptional<z.ZodNumber>;
|
|
33
39
|
dy: z.ZodOptional<z.ZodNumber>;
|
|
34
40
|
effect: z.ZodOptional<z.ZodObject<{
|
|
@@ -69,6 +75,7 @@ export declare const pixooComposeScene: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
69
75
|
color: z.ZodOptional<z.ZodString>;
|
|
70
76
|
font: z.ZodOptional<z.ZodEnum<{
|
|
71
77
|
compact: "compact";
|
|
78
|
+
numerals: "numerals";
|
|
72
79
|
standard: "standard";
|
|
73
80
|
}>>;
|
|
74
81
|
style: z.ZodOptional<z.ZodObject<{
|
|
@@ -92,6 +99,12 @@ export declare const pixooComposeScene: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
92
99
|
}, z.core.$strip>, z.ZodObject<{
|
|
93
100
|
visible: z.ZodOptional<z.ZodBoolean>;
|
|
94
101
|
opacity: z.ZodOptional<z.ZodNumber>;
|
|
102
|
+
blend: z.ZodOptional<z.ZodEnum<{
|
|
103
|
+
add: "add";
|
|
104
|
+
multiply: "multiply";
|
|
105
|
+
normal: "normal";
|
|
106
|
+
screen: "screen";
|
|
107
|
+
}>>;
|
|
95
108
|
dx: z.ZodOptional<z.ZodNumber>;
|
|
96
109
|
dy: z.ZodOptional<z.ZodNumber>;
|
|
97
110
|
effect: z.ZodOptional<z.ZodObject<{
|
|
@@ -146,6 +159,12 @@ export declare const pixooComposeScene: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
146
159
|
}, z.core.$strip>, z.ZodObject<{
|
|
147
160
|
visible: z.ZodOptional<z.ZodBoolean>;
|
|
148
161
|
opacity: z.ZodOptional<z.ZodNumber>;
|
|
162
|
+
blend: z.ZodOptional<z.ZodEnum<{
|
|
163
|
+
add: "add";
|
|
164
|
+
multiply: "multiply";
|
|
165
|
+
normal: "normal";
|
|
166
|
+
screen: "screen";
|
|
167
|
+
}>>;
|
|
149
168
|
dx: z.ZodOptional<z.ZodNumber>;
|
|
150
169
|
dy: z.ZodOptional<z.ZodNumber>;
|
|
151
170
|
effect: z.ZodOptional<z.ZodObject<{
|
|
@@ -186,9 +205,16 @@ export declare const pixooComposeScene: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
186
205
|
to: z.ZodString;
|
|
187
206
|
}, z.core.$strip>>;
|
|
188
207
|
borderColor: z.ZodOptional<z.ZodString>;
|
|
208
|
+
strokeWidth: z.ZodOptional<z.ZodNumber>;
|
|
189
209
|
}, z.core.$strip>, z.ZodObject<{
|
|
190
210
|
visible: z.ZodOptional<z.ZodBoolean>;
|
|
191
211
|
opacity: z.ZodOptional<z.ZodNumber>;
|
|
212
|
+
blend: z.ZodOptional<z.ZodEnum<{
|
|
213
|
+
add: "add";
|
|
214
|
+
multiply: "multiply";
|
|
215
|
+
normal: "normal";
|
|
216
|
+
screen: "screen";
|
|
217
|
+
}>>;
|
|
192
218
|
dx: z.ZodOptional<z.ZodNumber>;
|
|
193
219
|
dy: z.ZodOptional<z.ZodNumber>;
|
|
194
220
|
effect: z.ZodOptional<z.ZodObject<{
|
|
@@ -220,9 +246,17 @@ export declare const pixooComposeScene: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
220
246
|
radius: z.ZodNumber;
|
|
221
247
|
color: z.ZodOptional<z.ZodString>;
|
|
222
248
|
fill: z.ZodOptional<z.ZodBoolean>;
|
|
249
|
+
strokeWidth: z.ZodOptional<z.ZodNumber>;
|
|
250
|
+
antialias: z.ZodOptional<z.ZodBoolean>;
|
|
223
251
|
}, z.core.$strip>, z.ZodObject<{
|
|
224
252
|
visible: z.ZodOptional<z.ZodBoolean>;
|
|
225
253
|
opacity: z.ZodOptional<z.ZodNumber>;
|
|
254
|
+
blend: z.ZodOptional<z.ZodEnum<{
|
|
255
|
+
add: "add";
|
|
256
|
+
multiply: "multiply";
|
|
257
|
+
normal: "normal";
|
|
258
|
+
screen: "screen";
|
|
259
|
+
}>>;
|
|
226
260
|
dx: z.ZodOptional<z.ZodNumber>;
|
|
227
261
|
dy: z.ZodOptional<z.ZodNumber>;
|
|
228
262
|
effect: z.ZodOptional<z.ZodObject<{
|
|
@@ -254,9 +288,17 @@ export declare const pixooComposeScene: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
254
288
|
x1: z.ZodNumber;
|
|
255
289
|
y1: z.ZodNumber;
|
|
256
290
|
color: z.ZodOptional<z.ZodString>;
|
|
291
|
+
strokeWidth: z.ZodOptional<z.ZodNumber>;
|
|
292
|
+
antialias: z.ZodOptional<z.ZodBoolean>;
|
|
257
293
|
}, z.core.$strip>, z.ZodObject<{
|
|
258
294
|
visible: z.ZodOptional<z.ZodBoolean>;
|
|
259
295
|
opacity: z.ZodOptional<z.ZodNumber>;
|
|
296
|
+
blend: z.ZodOptional<z.ZodEnum<{
|
|
297
|
+
add: "add";
|
|
298
|
+
multiply: "multiply";
|
|
299
|
+
normal: "normal";
|
|
300
|
+
screen: "screen";
|
|
301
|
+
}>>;
|
|
260
302
|
dx: z.ZodOptional<z.ZodNumber>;
|
|
261
303
|
dy: z.ZodOptional<z.ZodNumber>;
|
|
262
304
|
effect: z.ZodOptional<z.ZodObject<{
|
|
@@ -303,6 +345,12 @@ export declare const pixooComposeScene: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
303
345
|
}, z.core.$strip>, z.ZodObject<{
|
|
304
346
|
visible: z.ZodOptional<z.ZodBoolean>;
|
|
305
347
|
opacity: z.ZodOptional<z.ZodNumber>;
|
|
348
|
+
blend: z.ZodOptional<z.ZodEnum<{
|
|
349
|
+
add: "add";
|
|
350
|
+
multiply: "multiply";
|
|
351
|
+
normal: "normal";
|
|
352
|
+
screen: "screen";
|
|
353
|
+
}>>;
|
|
306
354
|
dx: z.ZodOptional<z.ZodNumber>;
|
|
307
355
|
dy: z.ZodOptional<z.ZodNumber>;
|
|
308
356
|
effect: z.ZodOptional<z.ZodObject<{
|
|
@@ -342,6 +390,12 @@ export declare const pixooComposeScene: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
342
390
|
}, z.core.$strip>, z.ZodObject<{
|
|
343
391
|
visible: z.ZodOptional<z.ZodBoolean>;
|
|
344
392
|
opacity: z.ZodOptional<z.ZodNumber>;
|
|
393
|
+
blend: z.ZodOptional<z.ZodEnum<{
|
|
394
|
+
add: "add";
|
|
395
|
+
multiply: "multiply";
|
|
396
|
+
normal: "normal";
|
|
397
|
+
screen: "screen";
|
|
398
|
+
}>>;
|
|
345
399
|
dx: z.ZodOptional<z.ZodNumber>;
|
|
346
400
|
dy: z.ZodOptional<z.ZodNumber>;
|
|
347
401
|
effect: z.ZodOptional<z.ZodObject<{
|
|
@@ -375,6 +429,12 @@ export declare const pixooComposeScene: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
375
429
|
}, z.core.$strip>, z.ZodObject<{
|
|
376
430
|
visible: z.ZodOptional<z.ZodBoolean>;
|
|
377
431
|
opacity: z.ZodOptional<z.ZodNumber>;
|
|
432
|
+
blend: z.ZodOptional<z.ZodEnum<{
|
|
433
|
+
add: "add";
|
|
434
|
+
multiply: "multiply";
|
|
435
|
+
normal: "normal";
|
|
436
|
+
screen: "screen";
|
|
437
|
+
}>>;
|
|
378
438
|
dx: z.ZodOptional<z.ZodNumber>;
|
|
379
439
|
dy: z.ZodOptional<z.ZodNumber>;
|
|
380
440
|
effect: z.ZodOptional<z.ZodObject<{
|
|
@@ -409,6 +469,12 @@ export declare const pixooComposeScene: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
409
469
|
}, z.core.$strip>, z.ZodObject<{
|
|
410
470
|
visible: z.ZodOptional<z.ZodBoolean>;
|
|
411
471
|
opacity: z.ZodOptional<z.ZodNumber>;
|
|
472
|
+
blend: z.ZodOptional<z.ZodEnum<{
|
|
473
|
+
add: "add";
|
|
474
|
+
multiply: "multiply";
|
|
475
|
+
normal: "normal";
|
|
476
|
+
screen: "screen";
|
|
477
|
+
}>>;
|
|
412
478
|
dx: z.ZodOptional<z.ZodNumber>;
|
|
413
479
|
dy: z.ZodOptional<z.ZodNumber>;
|
|
414
480
|
effect: z.ZodOptional<z.ZodObject<{
|
|
@@ -450,9 +516,30 @@ export declare const pixooComposeScene: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
450
516
|
mitchell: "mitchell";
|
|
451
517
|
nearest: "nearest";
|
|
452
518
|
}>>;
|
|
519
|
+
finish: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
520
|
+
colors: z.ZodNumber;
|
|
521
|
+
dither: z.ZodDefault<z.ZodEnum<{
|
|
522
|
+
bayer4: "bayer4";
|
|
523
|
+
"floyd-steinberg": "floyd-steinberg";
|
|
524
|
+
none: "none";
|
|
525
|
+
}>>;
|
|
526
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
527
|
+
palette: z.ZodArray<z.ZodString>;
|
|
528
|
+
dither: z.ZodDefault<z.ZodEnum<{
|
|
529
|
+
bayer4: "bayer4";
|
|
530
|
+
"floyd-steinberg": "floyd-steinberg";
|
|
531
|
+
none: "none";
|
|
532
|
+
}>>;
|
|
533
|
+
}, z.core.$strict>]>>;
|
|
453
534
|
}, z.core.$strip>, z.ZodObject<{
|
|
454
535
|
visible: z.ZodOptional<z.ZodBoolean>;
|
|
455
536
|
opacity: z.ZodOptional<z.ZodNumber>;
|
|
537
|
+
blend: z.ZodOptional<z.ZodEnum<{
|
|
538
|
+
add: "add";
|
|
539
|
+
multiply: "multiply";
|
|
540
|
+
normal: "normal";
|
|
541
|
+
screen: "screen";
|
|
542
|
+
}>>;
|
|
456
543
|
dx: z.ZodOptional<z.ZodNumber>;
|
|
457
544
|
dy: z.ZodOptional<z.ZodNumber>;
|
|
458
545
|
effect: z.ZodOptional<z.ZodObject<{
|
|
@@ -519,6 +606,7 @@ export declare const pixooComposeScene: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
519
606
|
}>;
|
|
520
607
|
font: z.ZodOptional<z.ZodEnum<{
|
|
521
608
|
compact: "compact";
|
|
609
|
+
numerals: "numerals";
|
|
522
610
|
standard: "standard";
|
|
523
611
|
}>>;
|
|
524
612
|
scale: z.ZodOptional<z.ZodNumber>;
|
|
@@ -559,8 +647,14 @@ export declare const pixooComposeScene: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
559
647
|
}, {
|
|
560
648
|
readonly reason: "asset_not_found";
|
|
561
649
|
readonly code: JsonRpcErrorCode.NotFound;
|
|
562
|
-
readonly when: "An image or sprite path could not be read.";
|
|
563
|
-
readonly recovery: "
|
|
650
|
+
readonly when: "An image source or sprite path could not be read, a sprite path was a URL, or an image URL could not be fetched.";
|
|
651
|
+
readonly recovery: "Pass an absolute path to an existing, readable image file, or for an image source a reachable https URL serving 10 MiB or less; sprite sheets take an absolute local path.";
|
|
652
|
+
readonly thrownBy: "service";
|
|
653
|
+
}, {
|
|
654
|
+
readonly reason: "invalid_image";
|
|
655
|
+
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
656
|
+
readonly when: "An image source or sprite path was read but did not decode as an image.";
|
|
657
|
+
readonly recovery: "Each image source and sprite path must be a complete PNG, JPEG, GIF, WebP, AVIF, TIFF, or SVG image; a text file, an HTML page, or a truncated download will not decode.";
|
|
564
658
|
readonly thrownBy: "service";
|
|
565
659
|
}, {
|
|
566
660
|
readonly reason: "invalid_color";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pixoo-compose-scene.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/pixoo-compose-scene.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"pixoo-compose-scene.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/pixoo-compose-scene.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AAwb3E,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8T5B,CAAC"}
|
|
@@ -8,10 +8,12 @@ import { JsonRpcErrorCode, McpError } from '@cyanheads/mcp-ts-core/errors';
|
|
|
8
8
|
import { Canvas, NAMED_COLORS, savePng } from '@cyanheads/pixoo-toolkit';
|
|
9
9
|
import { getServerConfig } from '../../../config/server-config.js';
|
|
10
10
|
import { pushKeepingPreview, visibilityNotice } from '../device-push.js';
|
|
11
|
+
import { FinishSchema } from '../finish-schema.js';
|
|
11
12
|
import { ICONS } from '../../../renderer/icons.js';
|
|
12
13
|
import { numericValue } from '../../../renderer/keyframes.js';
|
|
13
14
|
import { autoSavePreview, buildContactSheet, saveGifPreview, savePngPreview, } from '../../../renderer/preview.js';
|
|
14
15
|
import { renderScene } from '../../../renderer/scene-renderer.js';
|
|
16
|
+
import { describeMissingNumerals, FONT_VARIANTS, missingNumeralGlyphs, } from '../../../renderer/text-engine.js';
|
|
15
17
|
import { getPixooService } from '../../../services/pixoo/pixoo-service.js';
|
|
16
18
|
// --- Shared sub-schemas ---
|
|
17
19
|
const EffectSchema = z
|
|
@@ -67,6 +69,10 @@ const AnimateSchema = z
|
|
|
67
69
|
const BaseElementProps = {
|
|
68
70
|
visible: z.boolean().optional().describe('Whether the element is visible (default: true).'),
|
|
69
71
|
opacity: z.number().int().min(0).max(100).optional().describe('Opacity 0–100 (default: 100).'),
|
|
72
|
+
blend: z
|
|
73
|
+
.enum(['normal', 'add', 'screen', 'multiply'])
|
|
74
|
+
.optional()
|
|
75
|
+
.describe('How the element combines with what lies beneath it (default: normal, drawn over it). add sums the light of both, clamped at full brightness — for glows and light beams; screen brightens more gently; multiply darkens.'),
|
|
70
76
|
dx: z.number().int().optional().describe('X offset nudge in pixels.'),
|
|
71
77
|
dy: z.number().int().optional().describe('Y offset nudge in pixels.'),
|
|
72
78
|
effect: EffectSchema.optional().describe('Named animation preset for this element.'),
|
|
@@ -107,15 +113,31 @@ const StyleSchema = z
|
|
|
107
113
|
})
|
|
108
114
|
.describe('Text style options.');
|
|
109
115
|
// --- Element schemas ---
|
|
110
|
-
const TextElementSchema = z
|
|
116
|
+
const TextElementSchema = z
|
|
117
|
+
.object({
|
|
111
118
|
type: z.literal('text').describe('Text element type.'),
|
|
112
119
|
text: z.string().describe('Text content to render.'),
|
|
113
120
|
x: XPosSchema.optional().describe('X position (default: 0).'),
|
|
114
121
|
y: YPosSchema.optional().describe('Y position (default: 0).'),
|
|
115
122
|
color: z.string().optional().describe('Flat text color.'),
|
|
116
|
-
font: z
|
|
123
|
+
font: z
|
|
124
|
+
.enum(FONT_VARIANTS)
|
|
125
|
+
.optional()
|
|
126
|
+
.describe('Font variant (default: standard): standard (5×7) or compact (3×5), each printable ASCII plus ° ← ↑ → ↓ ▲ ▼ ♥ · …; or numerals (11×18 digits for clocks and readouts), which draws only 0–9, space, and : . - + / % ° ?, so text holding any other character is rejected — put units and labels in a separate standard or compact text element.'),
|
|
117
127
|
style: StyleSchema.optional().describe('Text style options.'),
|
|
118
128
|
...BaseElementProps,
|
|
129
|
+
})
|
|
130
|
+
.superRefine((el, ctx) => {
|
|
131
|
+
if (el.font !== 'numerals')
|
|
132
|
+
return;
|
|
133
|
+
const missing = missingNumeralGlyphs(el.text);
|
|
134
|
+
if (missing.length === 0)
|
|
135
|
+
return;
|
|
136
|
+
ctx.addIssue({
|
|
137
|
+
code: 'custom',
|
|
138
|
+
path: ['text'],
|
|
139
|
+
message: `${describeMissingNumerals(missing)} Put units and labels in a separate text element in the standard or compact font.`,
|
|
140
|
+
});
|
|
119
141
|
});
|
|
120
142
|
const IconElementSchema = z.object({
|
|
121
143
|
type: z.literal('icon').describe('Icon element type.'),
|
|
@@ -133,7 +155,8 @@ const IconElementSchema = z.object({
|
|
|
133
155
|
.describe("Named palette painted as a top-to-bottom color ramp from the icon's top row to its bottom row. Takes precedence over color."),
|
|
134
156
|
...BaseElementProps,
|
|
135
157
|
});
|
|
136
|
-
const RectElementSchema = z
|
|
158
|
+
const RectElementSchema = z
|
|
159
|
+
.object({
|
|
137
160
|
type: z.literal('rect').describe('Rectangle element type.'),
|
|
138
161
|
x: z.number().int().describe('X coordinate.'),
|
|
139
162
|
y: z.number().int().describe('Y coordinate.'),
|
|
@@ -148,17 +171,51 @@ const RectElementSchema = z.object({
|
|
|
148
171
|
})
|
|
149
172
|
.optional()
|
|
150
173
|
.describe('Gradient fill.'),
|
|
151
|
-
borderColor: z
|
|
174
|
+
borderColor: z
|
|
175
|
+
.string()
|
|
176
|
+
.optional()
|
|
177
|
+
.describe('Border color. The border is drawn inside the rect, strokeWidth pixels thick.'),
|
|
178
|
+
strokeWidth: z
|
|
179
|
+
.number()
|
|
180
|
+
.int()
|
|
181
|
+
.min(1)
|
|
182
|
+
.optional()
|
|
183
|
+
.describe('Border thickness in whole pixels, growing inward from the rect edge (default: 1). Requires borderColor.'),
|
|
152
184
|
...BaseElementProps,
|
|
185
|
+
})
|
|
186
|
+
.refine((el) => el.strokeWidth === undefined || Boolean(el.borderColor), {
|
|
187
|
+
error: 'strokeWidth sets the thickness of the borderColor border; set borderColor, or drop strokeWidth.',
|
|
188
|
+
path: ['strokeWidth'],
|
|
153
189
|
});
|
|
154
|
-
|
|
190
|
+
/** The error for a stroke field on a circle that fills, which draws no outline. */
|
|
191
|
+
const filledCircleError = (field) => `${field} applies to an outline circle only; set fill: false, or drop ${field}.`;
|
|
192
|
+
const CircleElementSchema = z
|
|
193
|
+
.object({
|
|
155
194
|
type: z.literal('circle').describe('Circle element type.'),
|
|
156
195
|
cx: z.number().int().describe('Center X coordinate.'),
|
|
157
196
|
cy: z.number().int().describe('Center Y coordinate.'),
|
|
158
197
|
radius: z.number().int().min(1).describe('Radius in pixels.'),
|
|
159
198
|
color: z.string().optional().describe('Circle color.'),
|
|
160
199
|
fill: z.boolean().optional().describe('Filled or outline only (default: true).'),
|
|
200
|
+
strokeWidth: z
|
|
201
|
+
.number()
|
|
202
|
+
.int()
|
|
203
|
+
.min(1)
|
|
204
|
+
.optional()
|
|
205
|
+
.describe('Outline thickness in whole pixels, centered on the circle (default: 1). Outline circles only (fill: false).'),
|
|
206
|
+
antialias: z
|
|
207
|
+
.boolean()
|
|
208
|
+
.optional()
|
|
209
|
+
.describe('Shade each outline pixel by how much of it the ring covers, for a smooth curve (default: false, hard pixel edges). Outline circles only (fill: false).'),
|
|
161
210
|
...BaseElementProps,
|
|
211
|
+
})
|
|
212
|
+
.refine((el) => el.fill === false || el.strokeWidth === undefined, {
|
|
213
|
+
error: filledCircleError('strokeWidth'),
|
|
214
|
+
path: ['strokeWidth'],
|
|
215
|
+
})
|
|
216
|
+
.refine((el) => el.fill === false || el.antialias === undefined, {
|
|
217
|
+
error: filledCircleError('antialias'),
|
|
218
|
+
path: ['antialias'],
|
|
162
219
|
});
|
|
163
220
|
const LineElementSchema = z.object({
|
|
164
221
|
type: z.literal('line').describe('Line element type.'),
|
|
@@ -167,6 +224,16 @@ const LineElementSchema = z.object({
|
|
|
167
224
|
x1: z.number().int().describe('End X coordinate.'),
|
|
168
225
|
y1: z.number().int().describe('End Y coordinate.'),
|
|
169
226
|
color: z.string().optional().describe('Line color.'),
|
|
227
|
+
strokeWidth: z
|
|
228
|
+
.number()
|
|
229
|
+
.int()
|
|
230
|
+
.min(1)
|
|
231
|
+
.optional()
|
|
232
|
+
.describe('Line thickness in whole pixels, centered on the line (default: 1).'),
|
|
233
|
+
antialias: z
|
|
234
|
+
.boolean()
|
|
235
|
+
.optional()
|
|
236
|
+
.describe('Shade each pixel by how much of it the line covers, for smooth diagonals (default: false, hard pixel edges).'),
|
|
170
237
|
...BaseElementProps,
|
|
171
238
|
});
|
|
172
239
|
const ProgressElementSchema = z.object({
|
|
@@ -240,6 +307,7 @@ const ImageElementSchema = z.object({
|
|
|
240
307
|
.enum(['nearest', 'lanczos3', 'mitchell'])
|
|
241
308
|
.optional()
|
|
242
309
|
.describe('Resize kernel: nearest for pixel art, lanczos3 for photos (default: nearest).'),
|
|
310
|
+
finish: FinishSchema.optional(),
|
|
243
311
|
...BaseElementProps,
|
|
244
312
|
});
|
|
245
313
|
const SpriteElementSchema = z.object({
|
|
@@ -264,9 +332,9 @@ const ElementSchema = z
|
|
|
264
332
|
.discriminatedUnion('type', [
|
|
265
333
|
TextElementSchema.describe('Styled text element rendered at a position.'),
|
|
266
334
|
IconElementSchema.describe('Built-in or custom SVG icon element.'),
|
|
267
|
-
RectElementSchema.describe('Filled or
|
|
335
|
+
RectElementSchema.describe('Filled, gradient, or bordered rectangle element.'),
|
|
268
336
|
CircleElementSchema.describe('Filled or outline circle element.'),
|
|
269
|
-
LineElementSchema.describe('
|
|
337
|
+
LineElementSchema.describe('Straight line element, 1px wide unless strokeWidth is set.'),
|
|
270
338
|
ProgressElementSchema.describe('Horizontal progress bar widget.'),
|
|
271
339
|
SparklineElementSchema.describe('Sparkline chart widget (line or bar).'),
|
|
272
340
|
BitmapElementSchema.describe('Bitmap element using explicit palette indices.'),
|
|
@@ -296,7 +364,7 @@ const LayoutEntrySchema = z
|
|
|
296
364
|
action: z
|
|
297
365
|
.enum(['none', 'shrunk-to-compact', 'scrolling', 'wrapped', 'truncated', 'clipped'])
|
|
298
366
|
.describe('Overflow action taken by the renderer.'),
|
|
299
|
-
font: z.enum(
|
|
367
|
+
font: z.enum(FONT_VARIANTS).optional().describe('Font variant used (text only).'),
|
|
300
368
|
scale: z.number().optional().describe('Scale factor applied (text only).'),
|
|
301
369
|
})
|
|
302
370
|
.describe('Layout report entry.');
|
|
@@ -423,8 +491,15 @@ export const pixooComposeScene = tool('pixoo_compose_scene', {
|
|
|
423
491
|
{
|
|
424
492
|
reason: 'asset_not_found',
|
|
425
493
|
code: JsonRpcErrorCode.NotFound,
|
|
426
|
-
when: 'An image or sprite path could not be read.',
|
|
427
|
-
recovery: '
|
|
494
|
+
when: 'An image source or sprite path could not be read, a sprite path was a URL, or an image URL could not be fetched.',
|
|
495
|
+
recovery: 'Pass an absolute path to an existing, readable image file, or for an image source a reachable https URL serving 10 MiB or less; sprite sheets take an absolute local path.',
|
|
496
|
+
thrownBy: 'service',
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
reason: 'invalid_image',
|
|
500
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
501
|
+
when: 'An image source or sprite path was read but did not decode as an image.',
|
|
502
|
+
recovery: 'Each image source and sprite path must be a complete PNG, JPEG, GIF, WebP, AVIF, TIFF, or SVG image; a text file, an HTML page, or a truncated download will not decode.',
|
|
428
503
|
thrownBy: 'service',
|
|
429
504
|
},
|
|
430
505
|
{
|