agentic-canvas 0.8.0 → 0.10.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,7 +7,7 @@
7
7
 
8
8
  A shared whiteboard for diagrams, drawn by people and agents.
9
9
 
10
- > Status: 0.8.0. The board model, the agent operations, the exports, the CLI, the
10
+ > Status: 0.10.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
13
  > The package exports `agentic-canvas/ui`, a board view you can mount in your own app, new in 0.5.0.
@@ -38,7 +38,7 @@ npx agentic-canvas export demo --out demo.svg
38
38
  npx agentic-canvas export demo --format png --out demo.png
39
39
  ```
40
40
 
41
- Other commands: `list`, `add`, `connect`, `update`, `move`, `rm`, `delete` (a whole board). Run `npx agentic-canvas --help`.
41
+ Other commands: `list`, `add`, `connect`, `update`, `move`, `rm`, `delete` (a whole board), and `access`, `grant`, `revoke`, `invite` (sharing, server only). Run `npx agentic-canvas --help`.
42
42
  `repair` is for the owner only. It needs `CANVAS_ACTOR=human:<id>`.
43
43
  Boards live in `./.agentic-canvas` (or `--dir`, or `CANVAS_DIR`). Every write
44
44
  needs an actor in `CANVAS_ACTOR`, as `<kind>:<id>`. The operator sets it. A write
@@ -52,8 +52,7 @@ CANVAS_ACTOR=agent:helper npx agentic-canvas --dir ./boards mcp
52
52
  ```
53
53
 
54
54
  The server speaks MCP on stdio. It has one tool for each CLI command: `create_board`,
55
- `delete_board`, `list_boards`, `read_board`, `add_shape`, `connect`, `update_element`, `move`, `remove`, `draw_flow`, `export_svg`, and
56
- `export_png`. `repair` is not a tool. Only a human operator runs it, from the CLI. The server will not start unless `CANVAS_ACTOR` is set to a valid
55
+ `delete_board`, `list_boards`, `read_board`, `add_shape`, `connect`, `update_element`, `move`, `remove`, `draw_flow`, `export_svg`, `export_png`, and the sharing tools `list_access`, `grant_access`, `revoke_access`, and `create_invite`. `repair` is not a tool. Only a human operator runs it, from the CLI. The server will not start unless `CANVAS_ACTOR` is set to a valid
57
56
  `<kind>:<id>`. That actor stamps every write. No tool takes an actor, author, or kind, so a
58
57
  caller cannot choose who it writes as. An error comes back as a tool error with a code.
59
58
 
@@ -184,6 +183,33 @@ HTTP routes, under `/api/v1/canvas`. Each needs a bearer token, except the link
184
183
  | `DELETE /boards/:id/links/:link` | Owner. Revoke a link of this board. |
185
184
  | `GET /shared/:id` and `GET /shared/:id/svg` | No bearer token needed. Send the link token in `x-link-token`. Read only. |
186
185
 
186
+ ### Share from the client, the CLI, and MCP
187
+
188
+ Sharing lives on the server, so these need a Worker and a token with `canvas:admin` that owns the board.
189
+ A local store has no owners: the CLI commands exit 2 there, and the MCP tools fail with `UNAVAILABLE`.
190
+
191
+ ```ts
192
+ const canvas = createClient({ baseUrl, token });
193
+ await canvas.grant("demo", { subjectKind: "agent", subjectId: "helper", role: "editor" });
194
+ await canvas.invite("demo", { role: "viewer", email: "ada@example.com", expiresInMs: 86_400_000 });
195
+ const { grants, invites } = await canvas.access("demo"); // an invite shows no email
196
+ await canvas.revoke("demo", { subjectKind: "agent", subjectId: "helper" });
197
+ ```
198
+
199
+ ```sh
200
+ export CANVAS_SERVER=https://canvas.example.com CANVAS_TOKEN=...
201
+ npx agentic-canvas access demo
202
+ npx agentic-canvas grant demo agent:helper --role editor
203
+ npx agentic-canvas invite demo --role viewer --handle helper --expires-in-ms 86400000
204
+ npx agentic-canvas revoke demo agent:helper
205
+ ```
206
+
207
+ With `CANVAS_SERVER` set, `npx agentic-canvas mcp` serves `list_access`, `grant_access`, `revoke_access`, and `create_invite` as well.
208
+ Each prints or returns the server's JSON. A refusal has the server's code, such as `NOT_FOUND`, `FORBIDDEN`, or `INVALID`.
209
+ Share links and invite acceptance stay on the HTTP API.
210
+
211
+ A live MCP server with an admin token can share boards: an agent that holds it can grant `owner` and revoke grants. Give an agent a token with `canvas:write` only, unless it must share.
212
+
187
213
  The static dev fallback holds no board roles. Its tokens reach every board and cannot share.
188
214
 
189
215
  ## UI
@@ -337,7 +363,7 @@ Every name that each entry point exports is listed here. A name marked (type) ex
337
363
  | `PRINCIPAL_KINDS`, `PrincipalKind` (type), `Principal` (type), `principalSchema`, `isMachineMade` | The principal kinds, the schema of a principal, and the rule that marks machine-made kinds. `principalSchema` uses the identity rule below. |
338
364
  | `operatorActor(env?)`, `ActorConfigError` | Read the actor from `CANVAS_ACTOR` as `<kind>:<id>`. It throws `ActorConfigError` when the value is missing or bad. |
339
365
  | `CanvasService` | The board operations that the CLI, the MCP server and the Worker share. It takes a `Store`. |
340
- | `createMcpServer(service, actor)`, `runMcp(service, actor)` | Make the MCP server, or run it on stdio. |
366
+ | `createMcpServer(service, actor, sharing?)`, `runMcp(service, actor, sharing?)` | Make the MCP server, or run it on stdio. `sharing` is a client from `agentic-canvas/client`: it turns on the sharing tools. |
341
367
  | `createFileStore`, `openLocalStore`, `FileStoreOptions` (type) | Stores on one local file. See `agentic-canvas/file-store`. |
342
368
 
343
369
  ### Actor ids
@@ -353,6 +379,8 @@ old author stays on its elements. Only new writes follow the strict rule.
353
379
  | Name | What it is |
354
380
  |---|---|
355
381
  | `createClient(options)`, `CanvasApiError` | The remote client. See Remote client above. |
382
+ | `parseSubject(text)` | Reads `human:<id>` or `agent:<id>` as `{ subjectKind, subjectId }`. It throws a `TypeError` for any other text. |
383
+ | `BoardSharing` (type), `BoardRole` (type), `SubjectKind` (type), `Subject` (type), `BoardGrant` (type), `BoardInvite` (type), `InviteInput` (type) | Types for sharing. |
356
384
  | `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`. |
357
385
 
358
386
  ### `agentic-canvas/file-store`
@@ -391,9 +419,10 @@ The stylesheet is a separate export, `agentic-canvas/ui.css`. Load `agentic-desi
391
419
  | `createApp(options?)` | Make the Hono app for the Worker. Pass `live` to turn on the live-room socket. |
392
420
  | `default` | The Worker, without live rooms. |
393
421
  | `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`. |
422
+ | `identify(request, env, store?)` | Like `authenticate`, with the whole caller: its actor, role, principal context, and whether the dev fallback signed it in. Return a promise of the caller, or `null`. |
394
423
  | `sniffImage(bytes)` | Find the image type from the first bytes, or return `null`. The client's claim of a type is not trusted. |
395
424
  | `WS_PROTOCOL` | The sub-protocol name for a WebSocket: `agentic-canvas`. The token travels in the entry `agentic-canvas.bearer.<token in base64url>`. |
396
- | `WorkerEnv` (type), `AppOptions` (type), `Role` (type) | Types for the Worker bindings, the app options and the role, `editor` or `viewer`. |
425
+ | `WorkerEnv` (type), `AppOptions` (type), `Role` (type), `Caller` (type) | Types for the Worker bindings, the app options, the role, `editor` or `viewer`, and the caller that `identify` returns. |
397
426
 
398
427
  ### `agentic-canvas/live-worker`
399
428
 
@@ -405,7 +434,7 @@ The stylesheet is a separate export, `agentic-canvas/ui.css`. Load `agentic-desi
405
434
 
406
435
  ## Limits
407
436
 
408
- - Canvas takes principal kinds, regions, and the Store and Blobs ports from `agentic-identity` 0.6.
437
+ - Canvas takes principal kinds, regions, and the Store and Blobs ports from `agentic-identity` 0.7.
409
438
  - A board made before sharing has no owner. Identity credentials get 404 on it. Only the dev fallback reaches it. Make it again, or add its
410
439
  `canvas` resource and an owner grant with `agentic-identity`.
411
440
  - An image is stored under the hash of its bytes, not under a board. Any credential with `canvas:read` can fetch an image if it knows the hash.
@@ -8,7 +8,7 @@ import {
8
8
 
9
9
  // src/version.ts
10
10
  import { createRequire } from "module";
11
- var VERSION = true ? "0.8.0" : readPackageVersion();
11
+ var VERSION = true ? "0.10.0" : readPackageVersion();
12
12
 
13
13
  // src/brand.ts
14
14
  var BRAND = {
@@ -70,9 +70,11 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
70
70
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
71
71
  import { z } from "zod";
72
72
  var json = (value) => ({ content: [{ type: "text", text: JSON.stringify(value) }] });
73
+ var isApiError = (error) => error instanceof Error && error.name === "CanvasApiError" && typeof error.code === "string";
73
74
  function failure(error) {
74
- if (!(error instanceof CanvasError)) console.error(error instanceof Error ? error.message : String(error));
75
- const body = error instanceof CanvasError ? { code: error.code, message: error.message } : { code: "INTERNAL", message: "the board operation failed" };
75
+ const safe = error instanceof CanvasError || isApiError(error);
76
+ if (!safe) console.error(error instanceof Error ? error.message : String(error));
77
+ const body = safe ? { code: error.code, message: error.message } : { code: "INTERNAL", message: "the board operation failed" };
76
78
  return { isError: true, content: [{ type: "text", text: JSON.stringify(body) }] };
77
79
  }
78
80
  function tool(run) {
@@ -86,7 +88,7 @@ function tool(run) {
86
88
  }
87
89
  var board = z.string().describe("Board id.");
88
90
  var num = z.number();
89
- function createMcpServer(service, actor) {
91
+ function createMcpServer(service, actor, sharing) {
90
92
  const server = new McpServer({ name: BRAND.command, version: BRAND.version });
91
93
  const read = { readOnlyHint: true };
92
94
  server.registerTool(
@@ -170,10 +172,38 @@ function createMcpServer(service, actor) {
170
172
  { description: "Export the board as a PNG image. Needs the optional playwright-core and a Chromium.", inputSchema: { board }, annotations: read },
171
173
  tool(async (a) => ({ content: [{ type: "image", data: Buffer.from(await svgToPng(await service.exportSvg(a.board))).toString("base64"), mimeType: "image/png" }] }))
172
174
  );
175
+ const shared = () => {
176
+ if (!sharing) throw new CanvasError("UNAVAILABLE", "sharing needs a server: set CANVAS_SERVER and CANVAS_TOKEN");
177
+ return sharing;
178
+ };
179
+ const kind = z.enum(["human", "agent"]).describe("human or agent. A person and an agent can share one id, and hold separate roles.");
180
+ server.registerTool(
181
+ "list_access",
182
+ { description: "List the grants and the open invites of a board. Needs the owner role.", inputSchema: { board }, annotations: read },
183
+ tool(async (a) => json(await shared().access(a.board)))
184
+ );
185
+ server.registerTool(
186
+ "grant_access",
187
+ { description: "Give a person or an agent a role on a board. A lower role replaces a higher one. Needs the owner role.", inputSchema: { board, subjectKind: kind, subjectId: z.string(), role: z.enum(["viewer", "commenter", "editor", "owner"]) }, annotations: { destructiveHint: true } },
188
+ tool(async ({ board: b, ...input }) => json(await shared().grant(b, input)))
189
+ );
190
+ server.registerTool(
191
+ "revoke_access",
192
+ { description: "Take a role back from a person or an agent. The last owner cannot be removed. Needs the owner role.", inputSchema: { board, subjectKind: kind, subjectId: z.string() }, annotations: { destructiveHint: true } },
193
+ tool(async ({ board: b, ...input }) => json(await shared().revoke(b, input)))
194
+ );
195
+ server.registerTool(
196
+ "create_invite",
197
+ {
198
+ description: "Invite a person by email or an agent by handle to a board. Give email or handle, not both. The role is not owner. Needs the owner role.",
199
+ inputSchema: { board, role: z.enum(["viewer", "commenter", "editor"]), email: z.string().optional(), handle: z.string().optional(), expiresInMs: z.number().optional() }
200
+ },
201
+ tool(async ({ board: b, ...input }) => json(await shared().invite(b, input)))
202
+ );
173
203
  return server;
174
204
  }
175
- async function runMcp(service, actor) {
176
- await createMcpServer(service, actor).connect(new StdioServerTransport());
205
+ async function runMcp(service, actor, sharing) {
206
+ await createMcpServer(service, actor, sharing).connect(new StdioServerTransport());
177
207
  }
178
208
 
179
209
  export {
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  operatorActor,
6
6
  runMcp,
7
7
  svgToPng
8
- } from "./chunk-7IOW6LML.js";
8
+ } from "./chunk-NLTN7J22.js";
9
9
  import {
10
10
  openLocalStore
11
11
  } from "./chunk-NRLF6MGR.js";
@@ -21,6 +21,96 @@ import "./chunk-JOBTIAFJ.js";
21
21
  // src/cli.ts
22
22
  import { readFileSync, writeFileSync } from "fs";
23
23
  import { parseArgs } from "util";
24
+
25
+ // src/client.ts
26
+ var CanvasApiError = class extends Error {
27
+ constructor(code, message, status) {
28
+ super(message);
29
+ this.code = code;
30
+ this.status = status;
31
+ this.name = "CanvasApiError";
32
+ }
33
+ code;
34
+ status;
35
+ };
36
+ function parseSubject(text) {
37
+ const at = text.indexOf(":");
38
+ const kind = text.slice(0, at);
39
+ if (at < 1 || kind !== "human" && kind !== "agent" || at === text.length - 1) throw new TypeError(`"${text}" is not human:<id> or agent:<id>`);
40
+ return { subjectKind: kind, subjectId: text.slice(at + 1) };
41
+ }
42
+ function createClient(options) {
43
+ const { token } = options;
44
+ const base = options.baseUrl.replace(/\/+$/, "");
45
+ if (!token) throw new TypeError("token is required");
46
+ const doFetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
47
+ const seg = encodeURIComponent;
48
+ async function send(method, path, init, call) {
49
+ const headers = { authorization: `Bearer ${token}` };
50
+ if (init.json !== void 0) headers["content-type"] = "application/json";
51
+ if (init.contentType) headers["content-type"] = init.contentType;
52
+ let response;
53
+ try {
54
+ response = await doFetch(`${base}${path}`, {
55
+ method,
56
+ headers,
57
+ body: init.json === void 0 ? init.body : JSON.stringify(init.json),
58
+ signal: call?.signal
59
+ });
60
+ } catch (error) {
61
+ if (call?.signal?.aborted) throw error;
62
+ throw new CanvasApiError("NETWORK", error instanceof Error ? error.message : "the request failed", 0);
63
+ }
64
+ if (response.ok) return response;
65
+ let code = "HTTP_ERROR";
66
+ let message = `the server answered ${response.status}`;
67
+ try {
68
+ const { error } = await response.json();
69
+ if (typeof error?.code === "string") code = error.code;
70
+ if (typeof error?.message === "string") message = error.message;
71
+ } catch {
72
+ if (call?.signal?.aborted) throw call.signal.reason;
73
+ }
74
+ throw new CanvasApiError(code, message, response.status);
75
+ }
76
+ const parse = async (response, call) => {
77
+ try {
78
+ return await response.json();
79
+ } catch (error) {
80
+ if (call?.signal?.aborted) throw error;
81
+ throw new CanvasApiError("HTTP_ERROR", "the server did not answer with JSON", response.status);
82
+ }
83
+ };
84
+ const json = async (method, path, body, call) => parse(await send(method, path, { json: body }, call), call);
85
+ const bytes = async (response) => new Uint8Array(await response.arrayBuffer());
86
+ return {
87
+ me: (o) => json("GET", "/me", void 0, o),
88
+ listBoards: (o) => json("GET", "/boards", void 0, o),
89
+ createBoard: (input = {}, o) => json("POST", "/boards", input, o),
90
+ readBoard: (id, o) => json("GET", `/boards/${seg(id)}`, void 0, o),
91
+ deleteBoard: (id, o) => json("DELETE", `/boards/${seg(id)}`, void 0, o),
92
+ exportSvg: async (id, o) => (await send("GET", `/boards/${seg(id)}/svg`, {}, o)).text(),
93
+ addShape: (id, shape, o) => json("POST", `/boards/${seg(id)}/shapes`, shape, o),
94
+ addConnector: (id, connector, o) => json("POST", `/boards/${seg(id)}/connectors`, connector, o),
95
+ drawFlow: (id, text, o) => json("POST", `/boards/${seg(id)}/flow`, { text }, o),
96
+ update: (id, element, patch, o) => json("PATCH", `/boards/${seg(id)}/elements/${seg(element)}`, patch, o),
97
+ move: (id, element, dx, dy, o) => json("POST", `/boards/${seg(id)}/move`, { element, dx, dy }, o),
98
+ remove: (id, element, o) => json("DELETE", `/boards/${seg(id)}/elements/${seg(element)}`, void 0, o),
99
+ access: (id, o) => json("GET", `/boards/${seg(id)}/access`, void 0, o),
100
+ grant: (id, input, o) => json("POST", `/boards/${seg(id)}/grants`, input, o),
101
+ revoke: (id, { subjectKind, subjectId }, o) => json("DELETE", `/boards/${seg(id)}/grants?subjectKind=${seg(subjectKind)}&subjectId=${seg(subjectId)}`, void 0, o),
102
+ invite: (id, input, o) => json("POST", `/boards/${seg(id)}/invites`, input, o),
103
+ uploadImage: async (data, o) => parse(await send("POST", "/images", { body: data, contentType: "application/octet-stream" }, o), o),
104
+ getImage: async (id, o) => {
105
+ const response = await send("GET", `/images/${seg(id)}`, {}, o);
106
+ return { bytes: await bytes(response), type: response.headers.get("content-type") ?? "application/octet-stream" };
107
+ },
108
+ state: async (id, o) => bytes(await send("GET", `/boards/${seg(id)}/state`, {}, o)),
109
+ checkpoint: async (id, update, o) => parse(await send("POST", `/boards/${seg(id)}/checkpoint`, { body: update, contentType: "application/octet-stream" }, o), o)
110
+ };
111
+ }
112
+
113
+ // src/cli.ts
24
114
  var UsageError = class extends Error {
25
115
  };
26
116
  var HELP = `${BRAND.command} ${BRAND.version}
@@ -41,11 +131,16 @@ Commands:
41
131
  flow <board> --text <t> | --file <p> Draw a flowchart from text ("A -> B: label"). Use --file - for stdin.
42
132
  export <board> [--format svg|png] [--out <path>] Print SVG, or write PNG to --out.
43
133
  repair <board> [--drop <element>]... Owner only: fix a broken or over-cap board. Needs CANVAS_ACTOR=human:<id>.
134
+ access <board> Server only. List the grants and open invites. Owner.
135
+ grant <board> <human|agent>:<id> --role <viewer|commenter|editor|owner> Server only. Give a role. Owner.
136
+ revoke <board> <human|agent>:<id> Server only. Take a role back. Owner.
137
+ invite <board> --role <viewer|commenter|editor> --email <e> | --handle <h> [--expires-in-ms <n>] Server only. Owner.
44
138
  mcp Serve these as MCP tools on stdio. Needs CANVAS_ACTOR.
45
139
 
46
140
  Live mode: set CANVAS_SERVER=<worker url> and CANVAS_TOKEN=<bearer token>. Every command then runs in the live room of
47
141
  the board on that server, so a person and an agent can edit one board at once. The token sets the actor, the kind,
48
142
  and the role, so CANVAS_ACTOR is not needed. If it is set, it must match the token. --dir, CANVAS_DIR, and repair are local only.
143
+ The sharing commands need CANVAS_SERVER: a local store has no owners.
49
144
 
50
145
  Write commands need an actor in CANVAS_ACTOR as <kind>:<id>, such as "agent:helper".
51
146
  The operator sets it. A caller cannot pick its own kind: there is no --actor flag.
@@ -69,6 +164,10 @@ var flags = {
69
164
  file: { type: "string" },
70
165
  prefix: { type: "string" },
71
166
  format: { type: "string" },
167
+ role: { type: "string" },
168
+ email: { type: "string" },
169
+ handle: { type: "string" },
170
+ "expires-in-ms": { type: "string" },
72
171
  out: { type: "string" },
73
172
  drop: { type: "string", multiple: true },
74
173
  help: { type: "boolean" },
@@ -117,6 +216,17 @@ async function run(argv) {
117
216
  throw error;
118
217
  }
119
218
  };
219
+ const sharing = () => {
220
+ if (!server) throw new UsageError(`"${command}" needs a server: set CANVAS_SERVER and CANVAS_TOKEN`);
221
+ return createClient({ baseUrl: `${server.replace(/\/$/, "")}/api/v1/canvas`, token: process.env.CANVAS_TOKEN });
222
+ };
223
+ const subject = (text) => {
224
+ try {
225
+ return parseSubject(text ?? "");
226
+ } catch (error) {
227
+ throw new UsageError(error.message);
228
+ }
229
+ };
120
230
  const print = (value) => console.log(JSON.stringify(value));
121
231
  switch (command) {
122
232
  case "new":
@@ -169,9 +279,23 @@ async function run(argv) {
169
279
  case "repair":
170
280
  need(1, "<board> [--drop <element>]...");
171
281
  return print(await service.repair(board, actor(), v.drop ?? []));
282
+ case "access":
283
+ need(1, "<board>");
284
+ return print(await sharing().access(board));
285
+ case "grant":
286
+ need(2, "<board> <human|agent>:<id> --role <role>");
287
+ return print(await sharing().grant(board, { ...subject(rest[0]), role: v.role }));
288
+ case "revoke":
289
+ need(2, "<board> <human|agent>:<id>");
290
+ return print(await sharing().revoke(board, subject(rest[0])));
291
+ case "invite": {
292
+ need(1, "<board> --role <role> --email <e> | --handle <h> [--expires-in-ms <n>]");
293
+ const expiresInMs = num(v["expires-in-ms"]);
294
+ return print(await sharing().invite(board, { role: v.role, email: v.email, handle: v.handle, ...expiresInMs === void 0 ? {} : { expiresInMs } }));
295
+ }
172
296
  case "mcp":
173
297
  need(0, "");
174
- return runMcp(service, actor());
298
+ return runMcp(service, actor(), server ? sharing() : void 0);
175
299
  default:
176
300
  throw new UsageError(`unknown command "${command}"`);
177
301
  }
@@ -182,7 +306,7 @@ run(process.argv.slice(2)).catch((error) => {
182
306
  console.error(`${error.message}
183
307
  Run "${BRAND.command} --help" for usage.`);
184
308
  process.exitCode = 2;
185
- } else if (error instanceof CanvasError) {
309
+ } else if (error instanceof CanvasError || error instanceof CanvasApiError) {
186
310
  console.error(`${error.code}: ${error.message}`);
187
311
  process.exitCode = 1;
188
312
  } else {
@@ -0,0 +1,153 @@
1
+ import { a as BoardData, c as ShapeType, S as Shape, E as Endpoint, C as Connector } from './read-UIJlrkwx.js';
2
+ import { PrincipalKind } from 'agentic-identity';
3
+
4
+ /** Options for every call. */
5
+ interface CallOptions {
6
+ signal?: AbortSignal;
7
+ }
8
+ interface ShapeInput {
9
+ id?: string;
10
+ type: ShapeType;
11
+ x: number;
12
+ y: number;
13
+ w?: number;
14
+ h?: number;
15
+ text?: string;
16
+ fill?: string;
17
+ stroke?: string;
18
+ /** A `data:` URL or a `blob:` ref, for an image. */
19
+ src?: string;
20
+ }
21
+ interface ConnectorInput {
22
+ id?: string;
23
+ from: Endpoint;
24
+ to: Endpoint;
25
+ text?: string;
26
+ startArrow?: boolean;
27
+ endArrow?: boolean;
28
+ stroke?: string;
29
+ }
30
+ type ShapePatch = Partial<Pick<ShapeInput, "x" | "y" | "w" | "h" | "text" | "fill" | "stroke" | "src">>;
31
+ type ConnectorPatch = Partial<Pick<ConnectorInput, "from" | "to" | "text" | "startArrow" | "endArrow" | "stroke">>;
32
+ /** A role on a board, from least to most. */
33
+ type BoardRole = "viewer" | "commenter" | "editor" | "owner";
34
+ /** Who holds a grant. A person and an agent may share one id and hold separate roles. */
35
+ type SubjectKind = "human" | "agent";
36
+ interface Subject {
37
+ subjectKind: SubjectKind;
38
+ subjectId: string;
39
+ }
40
+ interface BoardGrant {
41
+ subjectKind: SubjectKind;
42
+ subjectId: string;
43
+ role: BoardRole;
44
+ [field: string]: unknown;
45
+ }
46
+ /** An open invite. It never shows the email. */
47
+ interface BoardInvite {
48
+ id: string;
49
+ role: BoardRole;
50
+ [field: string]: unknown;
51
+ }
52
+ interface InviteInput {
53
+ /** Not `owner`: only a grant makes an owner. */
54
+ role: Exclude<BoardRole, "owner">;
55
+ /** The person to invite. Give this or `handle`. */
56
+ email?: string;
57
+ /** The agent to invite. Give this or `email`. */
58
+ handle?: string;
59
+ /** How long the invite lives, in milliseconds. */
60
+ expiresInMs?: number;
61
+ }
62
+ interface ImageInfo {
63
+ id: string;
64
+ ref: string;
65
+ type: string;
66
+ size: number;
67
+ /** False when the same bytes were stored before. */
68
+ created: boolean;
69
+ }
70
+ interface CanvasClient {
71
+ /** Who the token stands for. */
72
+ me(options?: CallOptions): Promise<{
73
+ actor: string;
74
+ kind: PrincipalKind;
75
+ role: "editor" | "viewer";
76
+ }>;
77
+ /** The first 100 boards, with titles. */
78
+ listBoards(options?: CallOptions): Promise<{
79
+ boards: {
80
+ id: string;
81
+ title: string;
82
+ }[];
83
+ }>;
84
+ createBoard(input?: {
85
+ id?: string;
86
+ title?: string;
87
+ }, options?: CallOptions): Promise<{
88
+ id: string;
89
+ title: string;
90
+ }>;
91
+ readBoard(id: string, options?: CallOptions): Promise<BoardData & {
92
+ id: string;
93
+ }>;
94
+ /** Delete a board for good, with its live room. Returns the id. */
95
+ deleteBoard(id: string, options?: CallOptions): Promise<{
96
+ deleted: string;
97
+ }>;
98
+ /** The board as an SVG document. */
99
+ exportSvg(id: string, options?: CallOptions): Promise<string>;
100
+ addShape(id: string, shape: ShapeInput, options?: CallOptions): Promise<Shape>;
101
+ addConnector(id: string, connector: ConnectorInput, options?: CallOptions): Promise<Connector>;
102
+ /** Draw a flowchart from text such as `A -> B: label`. Returns the new ids. */
103
+ drawFlow(id: string, text: string, options?: CallOptions): Promise<{
104
+ nodes: string[];
105
+ edges: string[];
106
+ }>;
107
+ /** Change fields of one shape or connector, such as its size or text. The patch holds at least one field. */
108
+ update(id: string, element: string, patch: ShapePatch | ConnectorPatch, options?: CallOptions): Promise<Shape | Connector>;
109
+ /** Move an element. Returns the ids that moved: a shape takes its connectors with it. */
110
+ move(id: string, element: string, dx: number, dy: number, options?: CallOptions): Promise<{
111
+ moved: string[];
112
+ }>;
113
+ /** Remove an element. Returns the ids removed. */
114
+ remove(id: string, element: string, options?: CallOptions): Promise<{
115
+ removed: string[];
116
+ }>;
117
+ /** The grants and the open invites of a board. Needs the owner role. */
118
+ access(id: string, options?: CallOptions): Promise<{
119
+ grants: BoardGrant[];
120
+ invites: BoardInvite[];
121
+ }>;
122
+ /** Give a person or an agent a role on a board. Needs the owner role. */
123
+ grant(id: string, input: Subject & {
124
+ role: BoardRole;
125
+ }, options?: CallOptions): Promise<{
126
+ ok: true;
127
+ }>;
128
+ /** Take a role back. The last owner cannot be removed. Needs the owner role. */
129
+ revoke(id: string, input: Subject, options?: CallOptions): Promise<{
130
+ ok: true;
131
+ }>;
132
+ /** Invite a person by email or an agent by handle. Returns the invite id. Needs the owner role. */
133
+ invite(id: string, input: InviteInput, options?: CallOptions): Promise<{
134
+ id: string;
135
+ }>;
136
+ uploadImage(bytes: Uint8Array | ArrayBuffer | Blob, options?: CallOptions): Promise<ImageInfo>;
137
+ /** The bytes of a stored image, and its content type. */
138
+ getImage(id: string, options?: CallOptions): Promise<{
139
+ bytes: Uint8Array;
140
+ type: string;
141
+ }>;
142
+ /** The stored board as a Yjs update. Only a server with live rooms serves it. */
143
+ state(id: string, options?: CallOptions): Promise<Uint8Array>;
144
+ /** Merge a live room's Yjs update into the stored board. Only a server with live rooms serves it. */
145
+ checkpoint(id: string, update: Uint8Array, options?: CallOptions): Promise<{
146
+ elements: number;
147
+ fixed: string[];
148
+ }>;
149
+ }
150
+ /** What the CLI and the MCP server need to share a board. Sharing lives on the server, so it needs a client. */
151
+ type BoardSharing = Pick<CanvasClient, "access" | "grant" | "revoke" | "invite">;
152
+
153
+ export type { BoardSharing as B, CanvasClient as C };
package/dist/client.d.ts CHANGED
@@ -1,7 +1,68 @@
1
- import { E as ErrorCode, a as BoardData, c as ShapeType, S as Shape, d as Endpoint, C as Connector } from './read--Jd3kE13.js';
2
1
  import { PrincipalKind } from 'agentic-identity';
3
- import 'yjs';
4
- import 'zod';
2
+
3
+ type Principal = {
4
+ kind: PrincipalKind;
5
+ id: string;
6
+ };
7
+
8
+ type ErrorCode = "INVALID" | "DUPLICATE_ID" | "NOT_FOUND" | "BAD_REFERENCE" | "LIMIT" | "UNAVAILABLE" | "FORBIDDEN";
9
+ declare const SHAPE_TYPES: readonly ["rect", "ellipse", "diamond", "text", "sticky", "frame", "image"];
10
+ type ShapeType = (typeof SHAPE_TYPES)[number];
11
+ type Endpoint = {
12
+ id: string;
13
+ } | {
14
+ x: number;
15
+ y: number;
16
+ };
17
+ interface Stamp {
18
+ id: string;
19
+ z: number;
20
+ groupId?: string;
21
+ /** Who made the element. Never changes. */
22
+ author: Principal;
23
+ /** Who made the last edit. */
24
+ editedBy: Principal;
25
+ }
26
+ interface Shape extends Stamp {
27
+ type: ShapeType;
28
+ x: number;
29
+ y: number;
30
+ w: number;
31
+ h: number;
32
+ text: string;
33
+ fill?: string;
34
+ stroke?: string;
35
+ src?: string;
36
+ }
37
+ interface Connector extends Stamp {
38
+ type: "connector";
39
+ from: Endpoint;
40
+ to: Endpoint;
41
+ text: string;
42
+ startArrow: boolean;
43
+ endArrow: boolean;
44
+ stroke?: string;
45
+ }
46
+
47
+ interface Labels {
48
+ /** The author is an agent or acts for one. */
49
+ machineMade: boolean;
50
+ /** The last edit came from an agent or one acting for one. */
51
+ lastEditMachineMade: boolean;
52
+ }
53
+ type NodeData = Shape & Labels;
54
+ type EdgeData = Connector & Labels;
55
+ interface BoardData {
56
+ title: string;
57
+ nodes: NodeData[];
58
+ edges: EdgeData[];
59
+ groups: {
60
+ id: string;
61
+ members: string[];
62
+ }[];
63
+ /** Every non-empty text, back to front. */
64
+ texts: string[];
65
+ }
5
66
 
6
67
  interface ClientOptions {
7
68
  /** The API root, for example `https://canvas.example.com/api/v1/canvas`. */
@@ -39,6 +100,36 @@ interface ConnectorInput {
39
100
  }
40
101
  type ShapePatch = Partial<Pick<ShapeInput, "x" | "y" | "w" | "h" | "text" | "fill" | "stroke" | "src">>;
41
102
  type ConnectorPatch = Partial<Pick<ConnectorInput, "from" | "to" | "text" | "startArrow" | "endArrow" | "stroke">>;
103
+ /** A role on a board, from least to most. */
104
+ type BoardRole = "viewer" | "commenter" | "editor" | "owner";
105
+ /** Who holds a grant. A person and an agent may share one id and hold separate roles. */
106
+ type SubjectKind = "human" | "agent";
107
+ interface Subject {
108
+ subjectKind: SubjectKind;
109
+ subjectId: string;
110
+ }
111
+ interface BoardGrant {
112
+ subjectKind: SubjectKind;
113
+ subjectId: string;
114
+ role: BoardRole;
115
+ [field: string]: unknown;
116
+ }
117
+ /** An open invite. It never shows the email. */
118
+ interface BoardInvite {
119
+ id: string;
120
+ role: BoardRole;
121
+ [field: string]: unknown;
122
+ }
123
+ interface InviteInput {
124
+ /** Not `owner`: only a grant makes an owner. */
125
+ role: Exclude<BoardRole, "owner">;
126
+ /** The person to invite. Give this or `handle`. */
127
+ email?: string;
128
+ /** The agent to invite. Give this or `email`. */
129
+ handle?: string;
130
+ /** How long the invite lives, in milliseconds. */
131
+ expiresInMs?: number;
132
+ }
42
133
  interface ImageInfo {
43
134
  id: string;
44
135
  ref: string;
@@ -104,6 +195,25 @@ interface CanvasClient {
104
195
  remove(id: string, element: string, options?: CallOptions): Promise<{
105
196
  removed: string[];
106
197
  }>;
198
+ /** The grants and the open invites of a board. Needs the owner role. */
199
+ access(id: string, options?: CallOptions): Promise<{
200
+ grants: BoardGrant[];
201
+ invites: BoardInvite[];
202
+ }>;
203
+ /** Give a person or an agent a role on a board. Needs the owner role. */
204
+ grant(id: string, input: Subject & {
205
+ role: BoardRole;
206
+ }, options?: CallOptions): Promise<{
207
+ ok: true;
208
+ }>;
209
+ /** Take a role back. The last owner cannot be removed. Needs the owner role. */
210
+ revoke(id: string, input: Subject, options?: CallOptions): Promise<{
211
+ ok: true;
212
+ }>;
213
+ /** Invite a person by email or an agent by handle. Returns the invite id. Needs the owner role. */
214
+ invite(id: string, input: InviteInput, options?: CallOptions): Promise<{
215
+ id: string;
216
+ }>;
107
217
  uploadImage(bytes: Uint8Array | ArrayBuffer | Blob, options?: CallOptions): Promise<ImageInfo>;
108
218
  /** The bytes of a stored image, and its content type. */
109
219
  getImage(id: string, options?: CallOptions): Promise<{
@@ -118,6 +228,10 @@ interface CanvasClient {
118
228
  fixed: string[];
119
229
  }>;
120
230
  }
231
+ /** What the CLI and the MCP server need to share a board. Sharing lives on the server, so it needs a client. */
232
+ type BoardSharing = Pick<CanvasClient, "access" | "grant" | "revoke" | "invite">;
233
+ /** `kind:id` of a person or an agent, as the CLI and the tools write it. */
234
+ declare function parseSubject(text: string): Subject;
121
235
  declare function createClient(options: ClientOptions): CanvasClient;
122
236
 
123
- export { type ApiErrorCode, type CallOptions, CanvasApiError, type CanvasClient, type ClientOptions, type ConnectorInput, type ConnectorPatch, type ImageInfo, type ShapeInput, type ShapePatch, createClient };
237
+ export { type ApiErrorCode, type BoardGrant, type BoardInvite, type BoardRole, type BoardSharing, type CallOptions, CanvasApiError, type CanvasClient, type ClientOptions, type ConnectorInput, type ConnectorPatch, type ImageInfo, type InviteInput, type ShapeInput, type ShapePatch, type Subject, type SubjectKind, createClient, parseSubject };
package/dist/client.js CHANGED
@@ -9,6 +9,12 @@ var CanvasApiError = class extends Error {
9
9
  code;
10
10
  status;
11
11
  };
12
+ function parseSubject(text) {
13
+ const at = text.indexOf(":");
14
+ const kind = text.slice(0, at);
15
+ if (at < 1 || kind !== "human" && kind !== "agent" || at === text.length - 1) throw new TypeError(`"${text}" is not human:<id> or agent:<id>`);
16
+ return { subjectKind: kind, subjectId: text.slice(at + 1) };
17
+ }
12
18
  function createClient(options) {
13
19
  const { token } = options;
14
20
  const base = options.baseUrl.replace(/\/+$/, "");
@@ -66,6 +72,10 @@ function createClient(options) {
66
72
  update: (id, element, patch, o) => json("PATCH", `/boards/${seg(id)}/elements/${seg(element)}`, patch, o),
67
73
  move: (id, element, dx, dy, o) => json("POST", `/boards/${seg(id)}/move`, { element, dx, dy }, o),
68
74
  remove: (id, element, o) => json("DELETE", `/boards/${seg(id)}/elements/${seg(element)}`, void 0, o),
75
+ access: (id, o) => json("GET", `/boards/${seg(id)}/access`, void 0, o),
76
+ grant: (id, input, o) => json("POST", `/boards/${seg(id)}/grants`, input, o),
77
+ revoke: (id, { subjectKind, subjectId }, o) => json("DELETE", `/boards/${seg(id)}/grants?subjectKind=${seg(subjectKind)}&subjectId=${seg(subjectId)}`, void 0, o),
78
+ invite: (id, input, o) => json("POST", `/boards/${seg(id)}/invites`, input, o),
69
79
  uploadImage: async (data, o) => parse(await send("POST", "/images", { body: data, contentType: "application/octet-stream" }, o), o),
70
80
  getImage: async (id, o) => {
71
81
  const response = await send("GET", `/images/${seg(id)}`, {}, o);
@@ -77,5 +87,6 @@ function createClient(options) {
77
87
  }
78
88
  export {
79
89
  CanvasApiError,
80
- createClient
90
+ createClient,
91
+ parseSubject
81
92
  };
package/dist/embed.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
- import { CanvasClient } from './client.js';
2
+ import { C as CanvasClient } from './client-Dyd30StF.js';
3
3
  import { PrincipalKind } from 'agentic-identity';
4
- import './read--Jd3kE13.js';
4
+ import './read-UIJlrkwx.js';
5
5
  import 'yjs';
6
6
  import 'zod';
7
7
 
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- import { B as Board, P as Principal } from './read--Jd3kE13.js';
2
- export { a as BoardData, b as CanvasElement, e as CanvasError, C as Connector, f as EdgeData, d as Endpoint, E as ErrorCode, G as Group, M as MAX_ELEMENTS, g as MAX_TEXT, N as NodeData, h as SHAPE_TYPES, S as Shape, c as ShapeType, p as principalSchema, r as readBoard } from './read--Jd3kE13.js';
3
- import { B as BoardAccess } from './service-OiTtNadu.js';
4
- export { C as CanvasService, D as DrawOptions, F as FlowEdge, a as FlowGraph, b as FlowNode, M as MAX_FLOW_EDGES, c as MAX_FLOW_NODES, P as Placement, d as drawFlow, l as layoutFlow, p as parseFlow } from './service-OiTtNadu.js';
1
+ import { B as Board, P as Principal } from './read-UIJlrkwx.js';
2
+ export { a as BoardData, b as CanvasElement, d as CanvasError, C as Connector, e as EdgeData, E as Endpoint, f as ErrorCode, G as Group, M as MAX_ELEMENTS, g as MAX_TEXT, N as NodeData, h as SHAPE_TYPES, S as Shape, c as ShapeType, p as principalSchema, r as readBoard } from './read-UIJlrkwx.js';
3
+ import { B as BoardAccess } from './service-ojpxVNVl.js';
4
+ export { C as CanvasService, D as DrawOptions, F as FlowEdge, a as FlowGraph, b as FlowNode, M as MAX_FLOW_EDGES, c as MAX_FLOW_NODES, P as Placement, d as drawFlow, l as layoutFlow, p as parseFlow } from './service-ojpxVNVl.js';
5
5
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { B as BoardSharing } from './client-Dyd30StF.js';
6
7
  export { FileStoreOptions, createFileStore, openLocalStore } from './store-file.js';
7
8
  export { PRINCIPAL_KINDS, PrincipalKind, isMachineMade } from 'agentic-identity';
8
9
  import 'yjs';
@@ -39,10 +40,11 @@ declare function operatorActor(env?: Record<string, string | undefined>): Princi
39
40
 
40
41
  /**
41
42
  * The tools mirror the CLI. The actor is fixed here by the operator. No tool takes an actor,
42
- * author, or kind, so a caller cannot choose who it writes as.
43
+ * author, or kind, so a caller cannot choose who it writes as. The sharing tools run on a server, as the owner of its token:
44
+ * with no `sharing` client they fail with UNAVAILABLE.
43
45
  */
44
- declare function createMcpServer(service: BoardAccess, actor: Principal): McpServer;
46
+ declare function createMcpServer(service: BoardAccess, actor: Principal, sharing?: BoardSharing): McpServer;
45
47
  /** Serve the tools on stdin and stdout. Stdout carries protocol frames only. */
46
- declare function runMcp(service: BoardAccess, actor: Principal): Promise<void>;
48
+ declare function runMcp(service: BoardAccess, actor: Principal, sharing?: BoardSharing): Promise<void>;
47
49
 
48
50
  export { ActorConfigError, BRAND, Board, Principal, VERSION, createMcpServer, operatorActor, runMcp, svgToPng, toSvg };
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  operatorActor,
7
7
  runMcp,
8
8
  svgToPng
9
- } from "./chunk-7IOW6LML.js";
9
+ } from "./chunk-NLTN7J22.js";
10
10
  import {
11
11
  createFileStore,
12
12
  openLocalStore
@@ -5,10 +5,10 @@ import 'agentic-identity';
5
5
  import 'agentic-realtime';
6
6
  import 'agentic-identity/cloudflare';
7
7
  import 'hono';
8
- import './read--Jd3kE13.js';
8
+ import './read-UIJlrkwx.js';
9
9
  import 'yjs';
10
10
  import 'zod';
11
- import './service-OiTtNadu.js';
11
+ import './service-ojpxVNVl.js';
12
12
 
13
13
  type Namespace = Parameters<typeof createDurableObjectRealtime>[0];
14
14
  interface LiveEnv extends WorkerEnv {
package/dist/live.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { B as Board, P as Principal, a as BoardData, S as Shape, C as Connector, b as CanvasElement } from './read--Jd3kE13.js';
1
+ import { B as Board, P as Principal, a as BoardData, S as Shape, C as Connector, b as CanvasElement } from './read-UIJlrkwx.js';
2
2
  import { Peer } from 'agentic-realtime/client';
3
- import { B as BoardAccess, D as DrawOptions } from './service-OiTtNadu.js';
3
+ import { B as BoardAccess, D as DrawOptions } from './service-ojpxVNVl.js';
4
4
  import 'yjs';
5
5
  import 'agentic-identity';
6
6
  import 'zod';
@@ -181,4 +181,4 @@ interface BoardData {
181
181
  /** The board as plain data for an agent. Reads only. Embedded image bytes are left out. */
182
182
  declare function readBoard(board: Board): BoardData;
183
183
 
184
- export { Board as B, type Connector as C, type ErrorCode as E, type Group as G, MAX_ELEMENTS as M, type NodeData as N, type Principal as P, type Shape as S, type BoardData as a, type CanvasElement as b, type ShapeType as c, type Endpoint as d, CanvasError as e, type EdgeData as f, MAX_TEXT as g, SHAPE_TYPES as h, principalSchema as p, readBoard as r };
184
+ export { Board as B, type Connector as C, type Endpoint as E, type Group as G, MAX_ELEMENTS as M, type NodeData as N, type Principal as P, type Shape as S, type BoardData as a, type CanvasElement as b, type ShapeType as c, CanvasError as d, type EdgeData as e, type ErrorCode as f, MAX_TEXT as g, SHAPE_TYPES as h, principalSchema as p, readBoard as r };
@@ -1,4 +1,4 @@
1
- import { c as ShapeType, B as Board, P as Principal, a as BoardData, S as Shape, C as Connector, b as CanvasElement } from './read--Jd3kE13.js';
1
+ import { c as ShapeType, B as Board, P as Principal, a as BoardData, S as Shape, C as Connector, b as CanvasElement } from './read-UIJlrkwx.js';
2
2
  import { Store } from 'agentic-identity';
3
3
 
4
4
  declare const MAX_FLOW_NODES = 500;
package/dist/ui.css CHANGED
@@ -1,38 +1,38 @@
1
1
  /* Every rule is nested under .cv-root, so the styles do not reach the host page. */
2
2
  .cv-root {
3
- .row { display: flex; align-items: center; justify-content: space-between; gap: var(--ad-space-4); flex-wrap: wrap; }
4
- .failure { border: 1px solid var(--ad-color-danger); border-radius: var(--ad-radius-md); padding: var(--ad-space-4); color: var(--ad-color-danger-text); background: var(--ad-color-danger-subtle); display: grid; gap: var(--ad-space-3); justify-items: start; }
5
- .note { color: var(--ad-color-text-muted); }
3
+ .row { display: flex; align-items: center; justify-content: space-between; gap: var(--agd-space-4); flex-wrap: wrap; }
4
+ .failure { border: 1px solid var(--agd-color-danger); border-radius: var(--agd-radius-md); padding: var(--agd-space-4); color: var(--agd-color-danger-text); background: var(--agd-color-danger-subtle); display: grid; gap: var(--agd-space-3); justify-items: start; }
5
+ .note { color: var(--agd-color-text-muted); }
6
6
  .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
7
7
 
8
- .board { display: grid; gap: var(--ad-space-3); }
9
- .stage { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 16rem; gap: var(--ad-space-3); height: 65vh; min-height: 22rem; }
8
+ .board { display: grid; gap: var(--agd-space-3); }
9
+ .stage { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 16rem; gap: var(--agd-space-3); height: 65vh; min-height: 22rem; }
10
10
  @media (max-width: 48rem) { .stage { grid-template-columns: 1fr; height: auto; } .canvas { height: 55vh; } }
11
- .canvas { background: var(--ad-color-surface-sunken); border: 1px solid var(--ad-color-border); border-radius: var(--ad-radius-md); overflow: hidden; touch-action: none; min-height: 18rem; }
12
- .canvas:focus-visible { outline: 3px solid var(--ad-color-focus); outline-offset: 2px; }
13
- .canvas svg { display: block; cursor: grab; font-family: var(--ad-font-family-sans); font-size: 14px; }
11
+ .canvas { background: var(--agd-color-surface-sunken); border: 1px solid var(--agd-color-border); border-radius: var(--agd-radius-md); overflow: hidden; touch-action: none; min-height: 18rem; }
12
+ .canvas:focus-visible { outline: 3px solid var(--agd-color-focus); outline-offset: 2px; }
13
+ .canvas svg { display: block; cursor: grab; font-family: var(--agd-font-family-sans); font-size: 14px; }
14
14
  .item { outline: none; cursor: pointer; }
15
- .shape { fill: var(--ad-color-surface-raised); stroke: var(--ad-color-border-strong); stroke-width: 1.5; }
15
+ .shape { fill: var(--agd-color-surface-raised); stroke: var(--agd-color-border-strong); stroke-width: 1.5; }
16
16
  .shape.text { fill: transparent; stroke: none; }
17
17
  .shape.frame, .shape.image { fill: none; stroke-dasharray: 6 4; pointer-events: visiblePainted; }
18
- .shape.sticky { fill: var(--ad-color-warning-subtle); stroke: var(--ad-color-warning); }
19
- .label { fill: var(--ad-color-text); pointer-events: none; }
20
- .sticky-label { fill: var(--ad-color-warning-text); }
21
- .edge { stroke: var(--ad-color-text-muted); stroke-width: 2; fill: none; pointer-events: none; }
18
+ .shape.sticky { fill: var(--agd-color-warning-subtle); stroke: var(--agd-color-warning); }
19
+ .label { fill: var(--agd-color-text); pointer-events: none; }
20
+ .sticky-label { fill: var(--agd-color-warning-text); }
21
+ .edge { stroke: var(--agd-color-text-muted); stroke-width: 2; fill: none; pointer-events: none; }
22
22
  .hit { stroke: transparent; stroke-width: 14; }
23
- .arrowhead { fill: var(--ad-color-text-muted); }
24
- .edge-text { fill: var(--ad-color-text); paint-order: stroke; stroke: var(--ad-color-surface-sunken); stroke-width: 4px; pointer-events: none; }
23
+ .arrowhead { fill: var(--agd-color-text-muted); }
24
+ .edge-text { fill: var(--agd-color-text); paint-order: stroke; stroke: var(--agd-color-surface-sunken); stroke-width: 4px; pointer-events: none; }
25
25
  .ring, .ring-line { fill: none; stroke: none; stroke-width: 3; pointer-events: none; }
26
- .item.selected .ring, .item.selected .ring-line, .item:focus-visible .ring, .item:focus-visible .ring-line { stroke: var(--ad-color-focus); }
26
+ .item.selected .ring, .item.selected .ring-line, .item:focus-visible .ring, .item:focus-visible .ring-line { stroke: var(--agd-color-focus); }
27
27
  .item:focus-visible .ring, .item:focus-visible .ring-line { stroke-width: 4; }
28
- .handle { fill: var(--ad-color-focus); stroke: var(--ad-color-bg); stroke-width: 2; cursor: nwse-resize; }
29
- .agent-badge rect { fill: var(--ad-color-agent-subtle); stroke: var(--ad-color-agent); }
30
- .agent-badge text { fill: var(--ad-color-agent-text); font-size: 11px; font-weight: 600; pointer-events: none; }
28
+ .handle { fill: var(--agd-color-focus); stroke: var(--agd-color-bg); stroke-width: 2; cursor: nwse-resize; }
29
+ .agent-badge rect { fill: var(--agd-color-agent-subtle); stroke: var(--agd-color-agent); }
30
+ .agent-badge text { fill: var(--agd-color-agent-text); font-size: 11px; font-weight: 600; pointer-events: none; }
31
31
  .empty-overlay { position: absolute; inset: 0 16rem 0 0; display: grid; place-items: center; pointer-events: none; }
32
32
  @media (max-width: 48rem) { .empty-overlay { inset: 0; bottom: auto; height: 55vh; } }
33
- .inspector { border: 1px solid var(--ad-color-border); border-radius: var(--ad-radius-md); padding: var(--ad-space-3); background: var(--ad-color-surface); display: grid; gap: var(--ad-space-3); align-content: start; overflow: auto; }
34
- .inspector h2 { margin: 0; font-size: var(--ad-font-size-lg); }
35
- .inspector form { display: grid; gap: var(--ad-space-3); }
36
- .inspector .by { margin: 0; color: var(--ad-color-text-muted); font-size: var(--ad-font-size-sm); }
37
- .sizes { display: grid; grid-template-columns: 1fr 1fr; gap: var(--ad-space-2); }
33
+ .inspector { border: 1px solid var(--agd-color-border); border-radius: var(--agd-radius-md); padding: var(--agd-space-3); background: var(--agd-color-surface); display: grid; gap: var(--agd-space-3); align-content: start; overflow: auto; }
34
+ .inspector h2 { margin: 0; font-size: var(--agd-font-size-lg); }
35
+ .inspector form { display: grid; gap: var(--agd-space-3); }
36
+ .inspector .by { margin: 0; color: var(--agd-color-text-muted); font-size: var(--agd-font-size-sm); }
37
+ .sizes { display: grid; grid-template-columns: 1fr 1fr; gap: var(--agd-space-2); }
38
38
  }
package/dist/worker.d.ts CHANGED
@@ -2,8 +2,8 @@ import { Store, PrincipalContext, Action, Grant, InviteSummary, Blobs } from 'ag
2
2
  import { Realtime } from 'agentic-realtime';
3
3
  import { D1Like, R2Like } from 'agentic-identity/cloudflare';
4
4
  import { Hono } from 'hono';
5
- import { P as Principal } from './read--Jd3kE13.js';
6
- import { C as CanvasService } from './service-OiTtNadu.js';
5
+ import { P as Principal } from './read-UIJlrkwx.js';
6
+ import { C as CanvasService } from './service-ojpxVNVl.js';
7
7
  import 'yjs';
8
8
  import 'zod';
9
9
 
package/llms.txt CHANGED
@@ -6,11 +6,11 @@ Keywords: whiteboard,canvas,diagrams,drawing,collaboration,agents,mcp,cli.
6
6
 
7
7
  - SDK: import { Board, drawFlow, readBoard, toSvg } from "agentic-canvas".
8
8
  - Exports: the README "Exports" section lists every name of each entry point: agentic-canvas, /client, /file-store, /live, /worker and /live-worker.
9
- - CLI: agentic-canvas new|list|read|add|connect|update|move|rm|delete|flow|export.
9
+ - CLI: agentic-canvas new|list|read|add|connect|update|move|rm|delete|flow|export, and with CANVAS_SERVER: access|grant|revoke|invite. MCP tools for sharing: list_access, grant_access, revoke_access, create_invite.
10
10
  - UI: the Worker serves a web UI at its root (run `npm run build`, then `wrangler dev`). A person signs in with a bearer token: an agentic-identity credential (scope canvas:write or canvas:read). A static CANVAS_API_TOKENS list works only with CANVAS_UNSAFE_STATIC_TOKENS=1 (dev).
11
11
  - Live: set CANVAS_SERVER and CANVAS_TOKEN, and the CLI and `agentic-canvas mcp` edit the board in its live room.
12
12
  - SDK: import { LiveBoard, LiveService } from "agentic-canvas/live".
13
- - SDK: import { createClient, CanvasApiError } from "agentic-canvas/client". It calls the Worker API with fetch. Methods: me, listBoards, createBoard, readBoard, deleteBoard, exportSvg, addShape, addConnector, drawFlow, update, move, remove, uploadImage, getImage, state, checkpoint. A failure throws CanvasApiError with `code` and `status`. No retries.
13
+ - SDK: import { createClient, CanvasApiError } from "agentic-canvas/client". It calls the Worker API with fetch. Methods: me, listBoards, createBoard, readBoard, deleteBoard, exportSvg, addShape, addConnector, drawFlow, update, move, remove, access, grant, revoke, invite, uploadImage, getImage, state, checkpoint. A failure throws CanvasApiError with `code` and `status`. No retries.
14
14
  - UI: import { mount } from "agentic-canvas/ui"; import "agentic-canvas/ui.css" (with agentic-design/styles.css). mount(element, { boardId, client, principal: { kind, name, role }, readOnly?, room?: { baseUrl, token } }) renders the board and returns { update(options), unmount() }. BoardEditor is the same as a React component. React 19 is a peer dependency.
15
15
 
16
16
  ## Docs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-canvas",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "A shared whiteboard for diagrams, drawn by people and agents.",
5
5
  "keywords": [
6
6
  "whiteboard",
@@ -70,7 +70,7 @@
70
70
  "agentic-canvas": "./dist/cli.js"
71
71
  },
72
72
  "scripts": {
73
- "build": "tsup && vite build && node ui/build-css.mjs",
73
+ "build": "rm -rf dist && tsup && vite build && node ui/build-css.mjs",
74
74
  "typecheck": "tsc --noEmit && tsc --noEmit -p ui",
75
75
  "test": "vitest run",
76
76
  "clean": "rm -rf dist",
@@ -81,8 +81,8 @@
81
81
  "dependencies": {
82
82
  "@dagrejs/dagre": "^3.1.3",
83
83
  "@modelcontextprotocol/sdk": "^1.12.0",
84
- "agentic-design": "^0.3.2",
85
- "agentic-identity": "^0.7.0",
84
+ "agentic-design": "^0.4.0",
85
+ "agentic-identity": "^0.8.0",
86
86
  "agentic-realtime": "^0.4.2",
87
87
  "yjs": "^13.6.0",
88
88
  "zod": "^4.6.7"