agentic-canvas 0.4.0 → 0.6.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/README.md +112 -1
- package/dist/{chunk-HPUYT5TC.js → chunk-BIIC2DPZ.js} +41 -39
- package/dist/{chunk-J52RPFRP.js → chunk-G4YTESPS.js} +6 -4
- package/dist/{chunk-JZA525X5.js → chunk-JCYNPYYJ.js} +35 -38
- package/dist/{chunk-TSWXD4YG.js → chunk-SIDWRC3P.js} +23 -20
- package/dist/chunk-SPP6PINW.js +10 -0
- package/dist/cli.js +5 -4
- package/dist/client.d.ts +1 -1
- package/dist/embed.d.ts +46 -0
- package/dist/embed.js +535 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +9 -7
- package/dist/live-worker.d.ts +2 -2
- package/dist/live-worker.js +5 -4
- package/dist/live.d.ts +2 -2
- package/dist/live.js +10 -5
- package/dist/{read-Bdg1uiFC.d.ts → read-BupIJSPJ.d.ts} +3 -3
- package/dist/{service-Cim7TWnJ.d.ts → service-CR4-r1yx.d.ts} +1 -1
- package/dist/ui.css +38 -0
- package/dist/worker.d.ts +6 -6
- package/dist/worker.js +4 -3
- package/llms.txt +2 -0
- package/package.json +21 -6
package/README.md
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
A shared whiteboard for diagrams, drawn by people and agents.
|
|
9
9
|
|
|
10
|
-
> Status: 0.
|
|
10
|
+
> Status: 0.6.0. The board model, the agent operations, the exports, the CLI, the
|
|
11
11
|
> MCP server, a small Worker API, and live co-editing work. The CLI, the MCP server,
|
|
12
12
|
> and the SDK can join a live room. A remote client calls the Worker API. A small web UI, served by the Worker, is new in 0.4.0.
|
|
13
|
+
> The package exports `agentic-canvas/ui`, a board view you can mount in your own app, new in 0.5.0.
|
|
13
14
|
|
|
14
15
|
## Install
|
|
15
16
|
|
|
@@ -170,6 +171,37 @@ What it does:
|
|
|
170
171
|
and connects shapes, with fields, and the toolbar has pan buttons. A person who cannot drag
|
|
171
172
|
can do everything a drag does.
|
|
172
173
|
|
|
174
|
+
### Mount the board in your app
|
|
175
|
+
|
|
176
|
+
`agentic-canvas/ui` renders the same board UI into any element. It needs no framework in your app.
|
|
177
|
+
React and react-dom (version 19 or later) are peer dependencies. The Worker's own page uses this code.
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import "agentic-design/styles.css"; // tokens and fonts
|
|
181
|
+
import "agentic-canvas/ui.css"; // the board styles
|
|
182
|
+
import { createClient } from "agentic-canvas/client";
|
|
183
|
+
import { mount } from "agentic-canvas/ui";
|
|
184
|
+
|
|
185
|
+
const baseUrl = "https://canvas.example.com/api/v1/canvas";
|
|
186
|
+
const token = "...";
|
|
187
|
+
const board = mount(document.getElementById("board")!, {
|
|
188
|
+
boardId: "b1",
|
|
189
|
+
client: createClient({ baseUrl, token }),
|
|
190
|
+
principal: { kind: "human", name: "Ada", role: "editor" },
|
|
191
|
+
readOnly: false,
|
|
192
|
+
room: { baseUrl, token }, // optional: shows who else is in the board
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
board.update({ /* same options, for another board */ });
|
|
196
|
+
board.unmount(); // leaves the room and stops the refresh timer
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The principal needs a known `kind` and a `name`. `mount` throws a `TypeError` without them. Only the
|
|
200
|
+
role `editor` can change the board, and `readOnly: true` opens it read only for an editor too. The
|
|
201
|
+
server still checks every write against the token. The host page owns the theme. `ui.css` uses CSS nesting under `.cv-root`, so it does not change the rest of your page. It needs a browser from 2023 or later.
|
|
202
|
+
For a React app, `BoardEditor` is the same component without a root. Wrap it in the providers of
|
|
203
|
+
`agentic-design/react`.
|
|
204
|
+
|
|
173
205
|
Develop and test the UI:
|
|
174
206
|
|
|
175
207
|
```sh
|
|
@@ -229,6 +261,85 @@ at the same time keep the last write. The board list shows the first 100 boards.
|
|
|
229
261
|
- Yjs underneath: two copies of a board merge without conflicts.
|
|
230
262
|
- The board model has no UI code. It is plain TypeScript on Yjs, with no outbound calls.
|
|
231
263
|
|
|
264
|
+
## Exports
|
|
265
|
+
|
|
266
|
+
Every name that each entry point exports is listed here. A name marked (type) exists only at compile time.
|
|
267
|
+
|
|
268
|
+
### `agentic-canvas`
|
|
269
|
+
|
|
270
|
+
| Name | What it is |
|
|
271
|
+
|---|---|
|
|
272
|
+
| `VERSION`, `BRAND` | The package version. `BRAND` holds the command name, the description and the version that the CLI and the MCP server show. |
|
|
273
|
+
| `Board`, `CanvasElement` (type), `Shape` (type), `Connector` (type), `Group` (type), `Endpoint` (type), `ShapeType` (type) | The board model. `Board` holds the elements. Each element records its `author`, which never changes, and its `editedBy`. |
|
|
274
|
+
| `SHAPE_TYPES` | The shape kinds: `rect`, `ellipse`, `diamond`, `text`, `sticky`, `frame` and `image`. |
|
|
275
|
+
| `MAX_ELEMENTS`, `MAX_TEXT` | The limits: 10,000 elements on a board, and 10,000 UTF-16 code units in one text. |
|
|
276
|
+
| `CanvasError`, `ErrorCode` (type) | The error that the board throws, with a `code` such as `INVALID`, `NOT_FOUND` or `LIMIT`. |
|
|
277
|
+
| `parseFlow(source)`, `layoutFlow(graph)`, `drawFlow(board, source, actor, options?)` | `parseFlow` reads the flow text. `layoutFlow` returns placements with the top-left at (0, 0). `drawFlow` draws the flow onto the board as the actor. |
|
|
278
|
+
| `MAX_FLOW_NODES`, `MAX_FLOW_EDGES` | The flow limits: 500 nodes and 2000 edges. |
|
|
279
|
+
| `FlowGraph` (type), `FlowNode` (type), `FlowEdge` (type), `Placement` (type), `DrawOptions` (type) | Types for a flow and its layout. |
|
|
280
|
+
| `readBoard`, `BoardData` (type), `NodeData` (type), `EdgeData` (type) | Read a board as plain data. |
|
|
281
|
+
| `toSvg(board)`, `svgToPng(svg)` | `toSvg` returns the board as SVG. `svgToPng` turns an SVG string into PNG through Chromium. |
|
|
282
|
+
| `PRINCIPAL_KINDS`, `PrincipalKind` (type), `Principal` (type), `principalSchema`, `isMachineMade` | The principal kinds, the schema of a principal, and the rule that marks machine-made kinds. |
|
|
283
|
+
| `operatorActor(env?)`, `ActorConfigError` | Read the actor from `CANVAS_ACTOR` as `<kind>:<id>`. It throws `ActorConfigError` when the value is missing or bad. |
|
|
284
|
+
| `CanvasService` | The board operations that the CLI, the MCP server and the Worker share. It takes a `Store`. |
|
|
285
|
+
| `createMcpServer(service, actor)`, `runMcp(service, actor)` | Make the MCP server, or run it on stdio. |
|
|
286
|
+
| `createFileStore`, `openLocalStore`, `FileStoreOptions` (type) | Stores on one local file. See `agentic-canvas/file-store`. |
|
|
287
|
+
|
|
288
|
+
### `agentic-canvas/client`
|
|
289
|
+
|
|
290
|
+
| Name | What it is |
|
|
291
|
+
|---|---|
|
|
292
|
+
| `createClient(options)`, `CanvasApiError` | The remote client. See Remote client above. |
|
|
293
|
+
| `ClientOptions` (type), `CallOptions` (type), `CanvasClient` (type), `ShapeInput` (type), `ConnectorInput` (type), `ShapePatch` (type), `ConnectorPatch` (type), `ImageInfo` (type), `ApiErrorCode` (type) | Types for the client. `ApiErrorCode` adds the client's own codes, such as `NETWORK`, to `ErrorCode`. |
|
|
294
|
+
|
|
295
|
+
### `agentic-canvas/file-store`
|
|
296
|
+
|
|
297
|
+
| Name | What it is |
|
|
298
|
+
|---|---|
|
|
299
|
+
| `createFileStore(path, options?)`, `openLocalStore(dir)` | A store on one JSON file, or on `canvas.json` in a directory. |
|
|
300
|
+
| `FileStoreError` | The error that the store throws. Its `code` is `LOCKED` or `DAMAGED`. |
|
|
301
|
+
| `FileStoreOptions` (type) | `lockTimeoutMs` (default 10000) and `staleAfterMs` (default 30000), plus the adapter options. |
|
|
302
|
+
| `STORE_FILE` | The file name that `openLocalStore` uses, `canvas.json`. |
|
|
303
|
+
|
|
304
|
+
### `agentic-canvas/live`
|
|
305
|
+
|
|
306
|
+
| Name | What it is |
|
|
307
|
+
|---|---|
|
|
308
|
+
| `LiveBoard.join(options)` | Join one board's live room. Read the board, change it, `save` to the stored board, then `leave`. |
|
|
309
|
+
| `LiveService` | The CLI and MCP commands, run in a live room. A change joins the room, runs as the token's actor, saves, and leaves. `read` and `exportSvg` join and leave without a save. `create` and `list` call the HTTP API. |
|
|
310
|
+
| `whoAmI(server, token)`, `Me` (type) | Ask the Worker who a token is. It returns the actor and the role. |
|
|
311
|
+
| `LiveOptions` (type) | The server, the board id, the token, and the optional name and reconnect settings. |
|
|
312
|
+
|
|
313
|
+
### `agentic-canvas/ui`
|
|
314
|
+
|
|
315
|
+
| Name | What it is |
|
|
316
|
+
|---|---|
|
|
317
|
+
| `mount(element, options)` | Render the board UI into an element. It returns `update(options)` and `unmount()`. See Mount the board in your app. |
|
|
318
|
+
| `BoardEditor(options)` | The same board UI as a React component, without a root. |
|
|
319
|
+
| `BoardEditorOptions` (type), `BoardPrincipal` (type), `MountedBoard` (type) | Types for the options, the principal, and the object that `mount` returns. |
|
|
320
|
+
| `CanvasClient` (type), `RoomOptions` (type) | The client type, as in `agentic-canvas/client`, and the live room's base URL and token for the `room` option. |
|
|
321
|
+
|
|
322
|
+
The stylesheet is a separate export, `agentic-canvas/ui.css`. Load `agentic-design/styles.css` too, for the tokens.
|
|
323
|
+
|
|
324
|
+
### `agentic-canvas/worker`
|
|
325
|
+
|
|
326
|
+
| Name | What it is |
|
|
327
|
+
|---|---|
|
|
328
|
+
| `createApp(options?)` | Make the Hono app for the Worker. Pass `live` to turn on the live-room socket. |
|
|
329
|
+
| `default` | The Worker, without live rooms. |
|
|
330
|
+
| `authenticate(request, env)` | Read a bearer token from the `Authorization` header, or from a WebSocket sub-protocol. Return a promise of the actor and the role, or `null`. |
|
|
331
|
+
| `sniffImage(bytes)` | Find the image type from the first bytes, or return `null`. The client's claim of a type is not trusted. |
|
|
332
|
+
| `WS_PROTOCOL` | The sub-protocol name for a WebSocket: `agentic-canvas`. The token travels in the entry `agentic-canvas.bearer.<token in base64url>`. |
|
|
333
|
+
| `WorkerEnv` (type), `AppOptions` (type), `Role` (type) | Types for the Worker bindings, the app options and the role, `editor` or `viewer`. |
|
|
334
|
+
|
|
335
|
+
### `agentic-canvas/live-worker`
|
|
336
|
+
|
|
337
|
+
| Name | What it is |
|
|
338
|
+
|---|---|
|
|
339
|
+
| `default` | The Worker with live rooms. Bind the `SyncRoom` class as `SYNC_ROOM`. |
|
|
340
|
+
| `SyncRoom` | The Durable Object class of the live rooms, re-exported from `agentic-realtime/worker`. |
|
|
341
|
+
| `LiveEnv` (type) | The Worker bindings, plus `SYNC_ROOM`. |
|
|
342
|
+
|
|
232
343
|
## Limits
|
|
233
344
|
|
|
234
345
|
- Canvas takes principal kinds, regions, and the Store and Blobs ports from `agentic-identity` 0.5.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CanvasService,
|
|
3
3
|
MAX_LIVE_BYTES
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-G4YTESPS.js";
|
|
5
5
|
import {
|
|
6
|
-
CanvasError
|
|
7
|
-
|
|
8
|
-
} from "./chunk-JZA525X5.js";
|
|
6
|
+
CanvasError
|
|
7
|
+
} from "./chunk-JCYNPYYJ.js";
|
|
9
8
|
|
|
10
9
|
// src/worker.ts
|
|
11
|
-
import { parseRegion } from "agentic-identity";
|
|
10
|
+
import { hasScope, parsePrincipal, parseRegion } from "agentic-identity";
|
|
12
11
|
import { createD1Store, createR2Blobs, D1_SCHEMA } from "agentic-identity/cloudflare";
|
|
12
|
+
import { acceptWebSocketProtocol, principalFromWebSocketRequest } from "agentic-identity/http";
|
|
13
13
|
|
|
14
14
|
// node_modules/hono/dist/request/constants.js
|
|
15
15
|
var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
|
|
@@ -1948,36 +1948,41 @@ function sameText(a, b) {
|
|
|
1948
1948
|
for (let i = 0; i < Math.max(x.length, y.length); i++) diff |= (x[i] ?? 0) ^ (y[i] ?? 0);
|
|
1949
1949
|
return diff === 0;
|
|
1950
1950
|
}
|
|
1951
|
-
var WS_PROTOCOL = "agentic-canvas
|
|
1952
|
-
function
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1951
|
+
var WS_PROTOCOL = "agentic-canvas";
|
|
1952
|
+
function tokenAuth(env) {
|
|
1953
|
+
return {
|
|
1954
|
+
async authenticate(request) {
|
|
1955
|
+
const token = /^Bearer (.+)$/.exec(request.headers.get("authorization") ?? "")?.[1];
|
|
1956
|
+
if (!token || !env.CANVAS_API_TOKENS) return null;
|
|
1957
|
+
let grants;
|
|
1958
|
+
try {
|
|
1959
|
+
grants = JSON.parse(env.CANVAS_API_TOKENS);
|
|
1960
|
+
} catch {
|
|
1961
|
+
return null;
|
|
1962
|
+
}
|
|
1963
|
+
if (typeof grants !== "object" || grants === null || Array.isArray(grants)) return null;
|
|
1964
|
+
let found;
|
|
1965
|
+
for (const [candidate, grant] of Object.entries(grants)) {
|
|
1966
|
+
if (sameText(candidate, token) && candidate.length >= MIN_TOKEN_LENGTH) found = grant;
|
|
1967
|
+
}
|
|
1968
|
+
if (typeof found !== "object" || found === null) return null;
|
|
1969
|
+
const { actor, kind, role = "editor" } = found;
|
|
1970
|
+
let step;
|
|
1971
|
+
try {
|
|
1972
|
+
step = parsePrincipal({ kind, id: actor });
|
|
1973
|
+
} catch {
|
|
1974
|
+
return null;
|
|
1975
|
+
}
|
|
1976
|
+
if (step.kind === SEEDER.kind && step.id === SEEDER.id) return null;
|
|
1977
|
+
if (role !== "editor" && role !== "viewer") return null;
|
|
1978
|
+
return { principalId: step.id, kind: step.kind, chain: [step], scopes: role === "viewer" ? ["canvas:read"] : ["canvas:read", "canvas:write"] };
|
|
1979
|
+
}
|
|
1980
|
+
};
|
|
1961
1981
|
}
|
|
1962
|
-
function authenticate(request, env) {
|
|
1963
|
-
const
|
|
1964
|
-
if (
|
|
1965
|
-
|
|
1966
|
-
try {
|
|
1967
|
-
grants = JSON.parse(env.CANVAS_API_TOKENS);
|
|
1968
|
-
} catch {
|
|
1969
|
-
return null;
|
|
1970
|
-
}
|
|
1971
|
-
if (typeof grants !== "object" || grants === null || Array.isArray(grants)) return null;
|
|
1972
|
-
let found;
|
|
1973
|
-
for (const [candidate, grant] of Object.entries(grants)) {
|
|
1974
|
-
if (sameText(candidate, token) && candidate.length >= MIN_TOKEN_LENGTH) found = grant;
|
|
1975
|
-
}
|
|
1976
|
-
if (typeof found !== "object" || found === null) return null;
|
|
1977
|
-
const { actor, kind, role = "editor" } = found;
|
|
1978
|
-
const parsed = principalSchema.safeParse({ kind, id: actor });
|
|
1979
|
-
const taken = parsed.success && parsed.data.kind === SEEDER.kind && parsed.data.id === SEEDER.id;
|
|
1980
|
-
return parsed.success && !taken && (role === "editor" || role === "viewer") ? { actor: parsed.data, role } : null;
|
|
1982
|
+
async function authenticate(request, env) {
|
|
1983
|
+
const found = await principalFromWebSocketRequest(request, tokenAuth(env), { protocol: WS_PROTOCOL });
|
|
1984
|
+
if (found instanceof Response) return null;
|
|
1985
|
+
return { actor: { kind: found.kind, id: found.principalId }, role: hasScope(found.scopes, "canvas:write") ? "editor" : "viewer" };
|
|
1981
1986
|
}
|
|
1982
1987
|
function sniffImage(bytes) {
|
|
1983
1988
|
const at = (offset, ...sig) => sig.every((b, i) => bytes[offset + i] === b);
|
|
@@ -2056,7 +2061,7 @@ function createApp(options = {}) {
|
|
|
2056
2061
|
return fail("NOT_CONFIGURED", 'set CANVAS_REGION to "eu", "us", or "unknown"', 503);
|
|
2057
2062
|
}
|
|
2058
2063
|
if (!c.env.DB || !c.env.IMAGES) return fail("NOT_CONFIGURED", "bind DB (D1) and IMAGES (R2)", 503);
|
|
2059
|
-
const who = authenticate(c.req.raw, c.env);
|
|
2064
|
+
const who = await authenticate(c.req.raw, c.env);
|
|
2060
2065
|
if (!who) return fail("UNAUTHORIZED", "send a valid bearer token", 401);
|
|
2061
2066
|
if (who.role === "viewer" && c.req.method !== "GET") return fail("FORBIDDEN", "this token is a viewer; it cannot change a board", 403);
|
|
2062
2067
|
c.set("actor", who.actor);
|
|
@@ -2092,10 +2097,7 @@ function createApp(options = {}) {
|
|
|
2092
2097
|
await saveRoom(c, true);
|
|
2093
2098
|
const { actor, role } = c.var;
|
|
2094
2099
|
const opened = await options.live(c.env).connect(id, c.req.raw, { principal: { id: actor.id, kind: actor.kind }, role, name: actor.id });
|
|
2095
|
-
|
|
2096
|
-
const headers = new Headers(opened.headers);
|
|
2097
|
-
headers.set("sec-websocket-protocol", WS_PROTOCOL);
|
|
2098
|
-
return new Response(null, { status: 101, headers, webSocket: opened.webSocket });
|
|
2100
|
+
return acceptWebSocketProtocol(c.req.raw, opened, WS_PROTOCOL);
|
|
2099
2101
|
});
|
|
2100
2102
|
api.get("/boards/:id/state", async (c) => !options.live ? fail("NOT_FOUND", "this server hosts no live rooms", 404) : c.body(new Uint8Array(await c.var.service.state(c.req.param("id"))), 200, { "content-type": "application/octet-stream" }));
|
|
2101
2103
|
api.post("/boards/:id/checkpoint", async (c) => {
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FileStoreError
|
|
3
|
+
} from "./chunk-NWQED42L.js";
|
|
1
4
|
import {
|
|
2
5
|
Board,
|
|
3
6
|
CanvasError,
|
|
4
7
|
drawFlow,
|
|
5
|
-
principalSchema,
|
|
6
8
|
readBoard,
|
|
7
9
|
toSvg
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-JCYNPYYJ.js";
|
|
9
11
|
import {
|
|
10
|
-
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
principalSchema
|
|
13
|
+
} from "./chunk-SPP6PINW.js";
|
|
12
14
|
|
|
13
15
|
// src/service.ts
|
|
14
16
|
import * as Y from "yjs";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
isMachineMade,
|
|
3
|
+
principalSchema
|
|
4
|
+
} from "./chunk-SPP6PINW.js";
|
|
5
5
|
|
|
6
6
|
// src/core/board.ts
|
|
7
7
|
import { randomUUID } from "crypto";
|
|
8
8
|
import * as Y from "yjs";
|
|
9
|
-
import { z
|
|
9
|
+
import { z } from "zod";
|
|
10
10
|
var CanvasError = class extends Error {
|
|
11
11
|
constructor(code, message) {
|
|
12
12
|
super(message);
|
|
@@ -18,18 +18,18 @@ var CanvasError = class extends Error {
|
|
|
18
18
|
var SHAPE_TYPES = ["rect", "ellipse", "diamond", "text", "sticky", "frame", "image"];
|
|
19
19
|
var MAX_ELEMENTS = 1e4;
|
|
20
20
|
var MAX_TEXT = 1e4;
|
|
21
|
-
var id =
|
|
22
|
-
var coord =
|
|
23
|
-
var size =
|
|
24
|
-
var text =
|
|
21
|
+
var id = z.string().regex(/^[A-Za-z0-9_-]{1,64}$/);
|
|
22
|
+
var coord = z.number().finite().min(-1e7).max(1e7);
|
|
23
|
+
var size = z.number().finite().gt(0).max(1e7);
|
|
24
|
+
var text = z.string().max(MAX_TEXT);
|
|
25
25
|
var COLOR_PATTERN = /^(#[0-9a-fA-F]{3,8}|[a-z]{3,20})$/;
|
|
26
26
|
var IMAGE_PATTERN = /^(data:image\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/]+=*|blob:[A-Za-z0-9._-]{1,200})$/;
|
|
27
|
-
var color =
|
|
28
|
-
var imageSrc =
|
|
29
|
-
var endpoint =
|
|
30
|
-
var shapeInput =
|
|
27
|
+
var color = z.string().regex(COLOR_PATTERN);
|
|
28
|
+
var imageSrc = z.string().max(2e6).regex(IMAGE_PATTERN);
|
|
29
|
+
var endpoint = z.union([z.object({ id }).strict(), z.object({ x: coord, y: coord }).strict()]);
|
|
30
|
+
var shapeInput = z.object({
|
|
31
31
|
id: id.optional(),
|
|
32
|
-
type:
|
|
32
|
+
type: z.enum(SHAPE_TYPES),
|
|
33
33
|
x: coord,
|
|
34
34
|
y: coord,
|
|
35
35
|
w: size.default(120),
|
|
@@ -41,40 +41,40 @@ var shapeInput = z2.object({
|
|
|
41
41
|
}).strict().refine((s) => s.type === "image" === (s.src !== void 0), {
|
|
42
42
|
message: "src is required for image shapes and allowed on no other type"
|
|
43
43
|
});
|
|
44
|
-
var connectorInput =
|
|
44
|
+
var connectorInput = z.object({
|
|
45
45
|
id: id.optional(),
|
|
46
46
|
from: endpoint,
|
|
47
47
|
to: endpoint,
|
|
48
48
|
text: text.default(""),
|
|
49
|
-
startArrow:
|
|
50
|
-
endArrow:
|
|
49
|
+
startArrow: z.boolean().default(false),
|
|
50
|
+
endArrow: z.boolean().default(true),
|
|
51
51
|
stroke: color.optional()
|
|
52
52
|
}).strict();
|
|
53
|
-
var shapePatch =
|
|
54
|
-
var connectorPatch =
|
|
55
|
-
var delta =
|
|
53
|
+
var shapePatch = z.object({ x: coord, y: coord, w: size, h: size, text, fill: color, stroke: color, src: imageSrc }).partial().strict();
|
|
54
|
+
var connectorPatch = z.object({ from: endpoint, to: endpoint, text, startArrow: z.boolean(), endArrow: z.boolean(), stroke: color }).partial().strict();
|
|
55
|
+
var delta = z.number().finite().min(-2e7).max(2e7);
|
|
56
56
|
var MAX_Z = 1e9;
|
|
57
57
|
var stampFields = {
|
|
58
58
|
id,
|
|
59
|
-
z:
|
|
59
|
+
z: z.number().finite().min(-MAX_Z).max(MAX_Z),
|
|
60
60
|
groupId: id.optional(),
|
|
61
61
|
author: principalSchema,
|
|
62
62
|
editedBy: principalSchema
|
|
63
63
|
};
|
|
64
|
-
var storedShape =
|
|
65
|
-
var storedConnector =
|
|
64
|
+
var storedShape = z.object({ ...stampFields, type: z.enum(SHAPE_TYPES), x: coord, y: coord, w: size, h: size, text, fill: color.optional(), stroke: color.optional(), src: imageSrc.optional() }).refine((s) => s.type === "image" === (s.src !== void 0), { message: "src is required for image shapes and allowed on no other type" });
|
|
65
|
+
var storedConnector = z.object({
|
|
66
66
|
...stampFields,
|
|
67
|
-
type:
|
|
67
|
+
type: z.literal("connector"),
|
|
68
68
|
from: endpoint,
|
|
69
69
|
to: endpoint,
|
|
70
70
|
text,
|
|
71
|
-
startArrow:
|
|
72
|
-
endArrow:
|
|
71
|
+
startArrow: z.boolean(),
|
|
72
|
+
endArrow: z.boolean(),
|
|
73
73
|
stroke: color.optional()
|
|
74
74
|
});
|
|
75
|
-
var storedGroup =
|
|
76
|
-
var storedElement =
|
|
77
|
-
var titleSchema =
|
|
75
|
+
var storedGroup = z.object({ ...stampFields, type: z.literal("group") });
|
|
76
|
+
var storedElement = z.union([storedShape, storedConnector, storedGroup]);
|
|
77
|
+
var titleSchema = z.string().max(200);
|
|
78
78
|
function parse(schema, value) {
|
|
79
79
|
const result = schema.safeParse(value);
|
|
80
80
|
if (!result.success) {
|
|
@@ -133,7 +133,7 @@ var Board = class _Board {
|
|
|
133
133
|
return value.success ? value.data : void 0;
|
|
134
134
|
}
|
|
135
135
|
setTitle(title, actor) {
|
|
136
|
-
const t = parse(
|
|
136
|
+
const t = parse(z.string().max(200), title);
|
|
137
137
|
const by = parse(principalSchema, actor);
|
|
138
138
|
this.doc.transact(() => {
|
|
139
139
|
this.meta.set("title", t);
|
|
@@ -324,7 +324,7 @@ var Board = class _Board {
|
|
|
324
324
|
/** Shift an element, or a whole group. Returns the ids that moved. Bound connector ends follow their shapes. */
|
|
325
325
|
move(elementId, dx, dy, actor) {
|
|
326
326
|
const by = parse(principalSchema, actor);
|
|
327
|
-
const [mx, my] = parse(
|
|
327
|
+
const [mx, my] = parse(z.tuple([delta, delta]), [dx, dy]);
|
|
328
328
|
this.require(elementId);
|
|
329
329
|
const free = (e) => hasId(e) ? e : { x: e.x + mx, y: e.y + my };
|
|
330
330
|
const plan = [];
|
|
@@ -353,7 +353,7 @@ var Board = class _Board {
|
|
|
353
353
|
}
|
|
354
354
|
group(ids, actor, groupId) {
|
|
355
355
|
const by = parse(principalSchema, actor);
|
|
356
|
-
const members = parse(
|
|
356
|
+
const members = parse(z.array(id).min(2).max(MAX_ELEMENTS), ids);
|
|
357
357
|
const gid = parse(id.optional(), groupId);
|
|
358
358
|
if (new Set(members).size !== members.length) throw new CanvasError("INVALID", "member ids repeat");
|
|
359
359
|
const parents = new Set(members.map((m) => this.require(m).groupId));
|
|
@@ -423,12 +423,12 @@ var Board = class _Board {
|
|
|
423
423
|
}
|
|
424
424
|
/** The z value just past the top or the bottom. Not clamped: the caller checks the range. */
|
|
425
425
|
edgeZ(direction) {
|
|
426
|
-
let
|
|
426
|
+
let z2 = 0;
|
|
427
427
|
for (const m of this.elements.values()) {
|
|
428
428
|
const value = m instanceof Y.Map ? m.get("z") : void 0;
|
|
429
|
-
if (isValidZ(value))
|
|
429
|
+
if (isValidZ(value)) z2 = direction === 1 ? Math.max(z2, value) : Math.min(z2, value);
|
|
430
430
|
}
|
|
431
|
-
return
|
|
431
|
+
return z2 + direction;
|
|
432
432
|
}
|
|
433
433
|
/** The element and, for a group, everything below it. Cycle-safe. */
|
|
434
434
|
subtree(elementId) {
|
|
@@ -764,9 +764,6 @@ function toSvg(board) {
|
|
|
764
764
|
}
|
|
765
765
|
|
|
766
766
|
export {
|
|
767
|
-
PRINCIPAL_KINDS,
|
|
768
|
-
isMachineMade,
|
|
769
|
-
principalSchema,
|
|
770
767
|
CanvasError,
|
|
771
768
|
SHAPE_TYPES,
|
|
772
769
|
MAX_ELEMENTS,
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CanvasError,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
SHAPE_TYPES
|
|
4
|
+
} from "./chunk-JCYNPYYJ.js";
|
|
5
|
+
import {
|
|
6
|
+
PRINCIPAL_KINDS
|
|
7
|
+
} from "./chunk-SPP6PINW.js";
|
|
7
8
|
|
|
8
9
|
// src/version.ts
|
|
9
10
|
import { createRequire } from "module";
|
|
10
|
-
var VERSION = true ? "0.
|
|
11
|
+
var VERSION = true ? "0.6.0" : readPackageVersion();
|
|
11
12
|
|
|
12
13
|
// src/brand.ts
|
|
13
14
|
var BRAND = {
|
|
@@ -16,20 +17,6 @@ var BRAND = {
|
|
|
16
17
|
version: VERSION
|
|
17
18
|
};
|
|
18
19
|
|
|
19
|
-
// src/actor.ts
|
|
20
|
-
var ActorConfigError = class extends Error {
|
|
21
|
-
};
|
|
22
|
-
function operatorActor(env = process.env) {
|
|
23
|
-
const raw = env.CANVAS_ACTOR;
|
|
24
|
-
if (!raw) throw new ActorConfigError("the operator must set CANVAS_ACTOR=<kind>:<id>");
|
|
25
|
-
const at = raw.indexOf(":");
|
|
26
|
-
const parsed = principalSchema.safeParse({ kind: raw.slice(0, at), id: raw.slice(at + 1) });
|
|
27
|
-
if (at < 0 || !parsed.success) {
|
|
28
|
-
throw new ActorConfigError(`bad CANVAS_ACTOR "${raw}": use <kind>:<id>, kind is one of ${PRINCIPAL_KINDS.join(", ")}`);
|
|
29
|
-
}
|
|
30
|
-
return parsed.data;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
20
|
// src/png.ts
|
|
34
21
|
var MAX_SIDE = 16384;
|
|
35
22
|
var MAX_PIXELS = 16e6;
|
|
@@ -62,6 +49,22 @@ async function svgToPng(svg) {
|
|
|
62
49
|
}
|
|
63
50
|
}
|
|
64
51
|
|
|
52
|
+
// src/actor.ts
|
|
53
|
+
import { parsePrincipal } from "agentic-identity";
|
|
54
|
+
var ActorConfigError = class extends Error {
|
|
55
|
+
};
|
|
56
|
+
function operatorActor(env = process.env) {
|
|
57
|
+
const raw = env.CANVAS_ACTOR;
|
|
58
|
+
if (!raw) throw new ActorConfigError("the operator must set CANVAS_ACTOR=<kind>:<id>");
|
|
59
|
+
const at = raw.indexOf(":");
|
|
60
|
+
try {
|
|
61
|
+
if (at < 0) throw new Error("no kind");
|
|
62
|
+
return parsePrincipal({ kind: raw.slice(0, at), id: raw.slice(at + 1) });
|
|
63
|
+
} catch {
|
|
64
|
+
throw new ActorConfigError(`bad CANVAS_ACTOR "${raw}": use <kind>:<id>, kind is one of ${PRINCIPAL_KINDS.join(", ")}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
65
68
|
// src/mcp.ts
|
|
66
69
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
67
70
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -171,9 +174,9 @@ async function runMcp(service, actor) {
|
|
|
171
174
|
export {
|
|
172
175
|
VERSION,
|
|
173
176
|
BRAND,
|
|
177
|
+
svgToPng,
|
|
174
178
|
ActorConfigError,
|
|
175
179
|
operatorActor,
|
|
176
|
-
svgToPng,
|
|
177
180
|
createMcpServer,
|
|
178
181
|
runMcp
|
|
179
182
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/core/principal.ts
|
|
2
|
+
import { PRINCIPAL_KINDS, isMachineMade } from "agentic-identity";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
var principalSchema = z.object({ kind: z.enum(PRINCIPAL_KINDS), id: z.string().min(1).max(200) }).strict();
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
PRINCIPAL_KINDS,
|
|
8
|
+
isMachineMade,
|
|
9
|
+
principalSchema
|
|
10
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -5,17 +5,18 @@ import {
|
|
|
5
5
|
operatorActor,
|
|
6
6
|
runMcp,
|
|
7
7
|
svgToPng
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-SIDWRC3P.js";
|
|
9
9
|
import {
|
|
10
10
|
openLocalStore
|
|
11
11
|
} from "./chunk-UKST5A57.js";
|
|
12
12
|
import {
|
|
13
13
|
CanvasService
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-G4YTESPS.js";
|
|
15
|
+
import "./chunk-NWQED42L.js";
|
|
15
16
|
import {
|
|
16
17
|
CanvasError
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import "./chunk-
|
|
18
|
+
} from "./chunk-JCYNPYYJ.js";
|
|
19
|
+
import "./chunk-SPP6PINW.js";
|
|
19
20
|
|
|
20
21
|
// src/cli.ts
|
|
21
22
|
import { readFileSync, writeFileSync } from "fs";
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as ErrorCode,
|
|
1
|
+
import { E as ErrorCode, a as BoardData, c as ShapeType, S as Shape, d as Endpoint, C as Connector } from './read-BupIJSPJ.js';
|
|
2
2
|
import { PrincipalKind } from 'agentic-identity';
|
|
3
3
|
import 'yjs';
|
|
4
4
|
import 'zod';
|
package/dist/embed.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { CanvasClient } from './client.js';
|
|
3
|
+
import { PrincipalKind } from 'agentic-identity';
|
|
4
|
+
import './read-BupIJSPJ.js';
|
|
5
|
+
import 'yjs';
|
|
6
|
+
import 'zod';
|
|
7
|
+
|
|
8
|
+
interface RoomOptions {
|
|
9
|
+
/** The API root, for example `https://canvas.example.com/api/v1/canvas`. */
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
/** The bearer token for the room. */
|
|
12
|
+
token: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Who uses the board. The server still checks every write against its own token. */
|
|
16
|
+
interface BoardPrincipal {
|
|
17
|
+
kind: PrincipalKind;
|
|
18
|
+
name: string;
|
|
19
|
+
/** Only `editor` can change the board. Any other value, or no value, opens it read only. */
|
|
20
|
+
role?: "editor" | "viewer";
|
|
21
|
+
}
|
|
22
|
+
interface BoardEditorOptions {
|
|
23
|
+
/** The board id. */
|
|
24
|
+
boardId: string;
|
|
25
|
+
/** Loads and changes the board. A client from `createClient` in `agentic-canvas/client` works. */
|
|
26
|
+
client: CanvasClient;
|
|
27
|
+
principal: BoardPrincipal;
|
|
28
|
+
/** Open the board read only, even for an editor. */
|
|
29
|
+
readOnly?: boolean;
|
|
30
|
+
/** The live room. With one, the board shows who else is here and reads again when they save. */
|
|
31
|
+
room?: RoomOptions;
|
|
32
|
+
/** A link for a missing board to go back to. */
|
|
33
|
+
backHref?: string;
|
|
34
|
+
}
|
|
35
|
+
/** The board for one id, with its toolbar, presence, and side panel. It needs the providers of `agentic-design`. A bad principal shows an alert. */
|
|
36
|
+
declare function BoardEditor(options: BoardEditorOptions): react.JSX.Element;
|
|
37
|
+
interface MountedBoard {
|
|
38
|
+
/** Show another board, principal, or room in the same place. A bad principal throws and keeps the board as it is. */
|
|
39
|
+
update(options: BoardEditorOptions): void;
|
|
40
|
+
/** Remove the board, leave the live room, and stop its timers. */
|
|
41
|
+
unmount(): void;
|
|
42
|
+
}
|
|
43
|
+
/** Render the board into an element, for an app that does not use React itself. */
|
|
44
|
+
declare function mount(element: HTMLElement, options: BoardEditorOptions): MountedBoard;
|
|
45
|
+
|
|
46
|
+
export { BoardEditor, type BoardEditorOptions, type BoardPrincipal, CanvasClient, type MountedBoard, type RoomOptions, mount };
|