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 CHANGED
@@ -7,9 +7,10 @@
7
7
 
8
8
  A shared whiteboard for diagrams, drawn by people and agents.
9
9
 
10
- > Status: 0.4.0. The board model, the agent operations, the exports, the CLI, the
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-J52RPFRP.js";
4
+ } from "./chunk-G4YTESPS.js";
5
5
  import {
6
- CanvasError,
7
- principalSchema
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.bearer";
1952
- function socketToken(request) {
1953
- if (request.headers.get("upgrade") !== "websocket") return void 0;
1954
- const [name, encoded, ...more] = (request.headers.get("sec-websocket-protocol") ?? "").split(",").map((p) => p.trim());
1955
- if (name !== WS_PROTOCOL || !encoded || more.length > 0 || !/^[A-Za-z0-9_-]+$/.test(encoded)) return void 0;
1956
- try {
1957
- return new TextDecoder("utf-8", { fatal: true }).decode(Uint8Array.from(atob(encoded.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0)));
1958
- } catch {
1959
- return void 0;
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 token = /^Bearer (.+)$/.exec(request.headers.get("authorization") ?? "")?.[1] ?? socketToken(request);
1964
- if (!token || !env.CANVAS_API_TOKENS) return null;
1965
- let grants;
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
- if (opened.status !== 101 || c.req.header("authorization") || !socketToken(c.req.raw)) return opened;
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-JZA525X5.js";
10
+ } from "./chunk-JCYNPYYJ.js";
9
11
  import {
10
- FileStoreError
11
- } from "./chunk-NWQED42L.js";
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
- // 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();
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 as z2 } from "zod";
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 = z2.string().regex(/^[A-Za-z0-9_-]{1,64}$/);
22
- var coord = z2.number().finite().min(-1e7).max(1e7);
23
- var size = z2.number().finite().gt(0).max(1e7);
24
- var text = z2.string().max(MAX_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 = z2.string().regex(COLOR_PATTERN);
28
- var imageSrc = z2.string().max(2e6).regex(IMAGE_PATTERN);
29
- var endpoint = z2.union([z2.object({ id }).strict(), z2.object({ x: coord, y: coord }).strict()]);
30
- var shapeInput = z2.object({
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: z2.enum(SHAPE_TYPES),
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 = z2.object({
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: z2.boolean().default(false),
50
- endArrow: z2.boolean().default(true),
49
+ startArrow: z.boolean().default(false),
50
+ endArrow: z.boolean().default(true),
51
51
  stroke: color.optional()
52
52
  }).strict();
53
- var shapePatch = z2.object({ x: coord, y: coord, w: size, h: size, text, fill: color, stroke: color, src: imageSrc }).partial().strict();
54
- var connectorPatch = z2.object({ from: endpoint, to: endpoint, text, startArrow: z2.boolean(), endArrow: z2.boolean(), stroke: color }).partial().strict();
55
- var delta = z2.number().finite().min(-2e7).max(2e7);
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: z2.number().finite().min(-MAX_Z).max(MAX_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 = z2.object({ ...stampFields, type: z2.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 = z2.object({
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: z2.literal("connector"),
67
+ type: z.literal("connector"),
68
68
  from: endpoint,
69
69
  to: endpoint,
70
70
  text,
71
- startArrow: z2.boolean(),
72
- endArrow: z2.boolean(),
71
+ startArrow: z.boolean(),
72
+ endArrow: z.boolean(),
73
73
  stroke: color.optional()
74
74
  });
75
- var storedGroup = z2.object({ ...stampFields, type: z2.literal("group") });
76
- var storedElement = z2.union([storedShape, storedConnector, storedGroup]);
77
- var titleSchema = z2.string().max(200);
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(z2.string().max(200), title);
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(z2.tuple([delta, delta]), [dx, dy]);
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(z2.array(id).min(2).max(MAX_ELEMENTS), ids);
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 z3 = 0;
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)) z3 = direction === 1 ? Math.max(z3, value) : Math.min(z3, value);
429
+ if (isValidZ(value)) z2 = direction === 1 ? Math.max(z2, value) : Math.min(z2, value);
430
430
  }
431
- return z3 + direction;
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
- PRINCIPAL_KINDS,
4
- SHAPE_TYPES,
5
- principalSchema
6
- } from "./chunk-JZA525X5.js";
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.4.0" : readPackageVersion();
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-TSWXD4YG.js";
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-J52RPFRP.js";
14
+ } from "./chunk-G4YTESPS.js";
15
+ import "./chunk-NWQED42L.js";
15
16
  import {
16
17
  CanvasError
17
- } from "./chunk-JZA525X5.js";
18
- import "./chunk-NWQED42L.js";
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, B as BoardData, S as ShapeType, a as Shape, b as Endpoint, C as Connector } from './read-Bdg1uiFC.js';
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';
@@ -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 };