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 +36 -7
- package/dist/{chunk-7IOW6LML.js → chunk-NLTN7J22.js} +36 -6
- package/dist/cli.js +127 -3
- package/dist/client-Dyd30StF.d.ts +153 -0
- package/dist/client.d.ts +118 -4
- package/dist/client.js +12 -1
- package/dist/embed.d.ts +2 -2
- package/dist/index.d.ts +9 -7
- package/dist/index.js +1 -1
- package/dist/live-worker.d.ts +2 -2
- package/dist/live.d.ts +2 -2
- package/dist/{read--Jd3kE13.d.ts → read-UIJlrkwx.d.ts} +1 -1
- package/dist/{service-OiTtNadu.d.ts → service-ojpxVNVl.d.ts} +1 -1
- package/dist/ui.css +24 -24
- package/dist/worker.d.ts +2 -2
- package/llms.txt +2 -2
- package/package.json +4 -4
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.
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
75
|
-
|
|
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-
|
|
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
|
-
|
|
4
|
-
|
|
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
|
|
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
|
|
2
|
-
export { a as BoardData, b as CanvasElement,
|
|
3
|
-
import { B as BoardAccess } from './service-
|
|
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-
|
|
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
package/dist/live-worker.d.ts
CHANGED
|
@@ -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
|
|
8
|
+
import './read-UIJlrkwx.js';
|
|
9
9
|
import 'yjs';
|
|
10
10
|
import 'zod';
|
|
11
|
-
import './service-
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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(--
|
|
4
|
-
.failure { border: 1px solid var(--
|
|
5
|
-
.note { color: var(--
|
|
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(--
|
|
9
|
-
.stage { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 16rem; gap: var(--
|
|
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(--
|
|
12
|
-
.canvas:focus-visible { outline: 3px solid var(--
|
|
13
|
-
.canvas svg { display: block; cursor: grab; font-family: var(--
|
|
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(--
|
|
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(--
|
|
19
|
-
.label { fill: var(--
|
|
20
|
-
.sticky-label { fill: var(--
|
|
21
|
-
.edge { stroke: var(--
|
|
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(--
|
|
24
|
-
.edge-text { fill: var(--
|
|
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(--
|
|
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(--
|
|
29
|
-
.agent-badge rect { fill: var(--
|
|
30
|
-
.agent-badge text { fill: var(--
|
|
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(--
|
|
34
|
-
.inspector h2 { margin: 0; font-size: var(--
|
|
35
|
-
.inspector form { display: grid; gap: var(--
|
|
36
|
-
.inspector .by { margin: 0; color: var(--
|
|
37
|
-
.sizes { display: grid; grid-template-columns: 1fr 1fr; gap: var(--
|
|
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
|
|
6
|
-
import { C as CanvasService } from './service-
|
|
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.
|
|
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.
|
|
85
|
-
"agentic-identity": "^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"
|