agentic-canvas 0.13.0 → 0.14.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 +14 -7
- package/dist/account.js +1 -1
- package/dist/{chunk-4PY5SN2N.js → chunk-5QUL3URJ.js} +2 -1
- package/dist/{chunk-LMSMUGBF.js → chunk-A2F73EQJ.js} +33 -1
- package/dist/{chunk-AI5NWPEW.js → chunk-YKUHPOQV.js} +19 -1
- package/dist/cli.js +19 -1
- package/dist/{client-DBb1jpxl.d.ts → client-BKTZ1pM5.d.ts} +33 -1
- package/dist/client.d.ts +34 -2
- package/dist/client.js +3 -0
- package/dist/embed.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/live-worker.js +2 -2
- package/dist/worker.d.ts +16 -2
- package/dist/worker.js +2 -2
- package/llms.txt +2 -2
- package/package.json +1 -1
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.14.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), and `access`, `grant`, `revoke`, `invite` (sharing, server only). Run `npx agentic-canvas --help`.
|
|
41
|
+
Other commands: `list`, `add`, `connect`, `update`, `move`, `rm`, `delete` (a whole board), and `access`, `grant`, `revoke`, `invite`, `links`, `link`, `revoke-link` (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,7 +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`, `export_png`, and the sharing tools `list_access`, `grant_access`, `revoke_access`, and `
|
|
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`, `create_invite`, `list_links`, `create_link`, and `revoke_link`. `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
|
|
56
56
|
`<kind>:<id>`. That actor stamps every write. No tool takes an actor, author, or kind, so a
|
|
57
57
|
caller cannot choose who it writes as. An error comes back as a tool error with a code.
|
|
58
58
|
|
|
@@ -183,7 +183,7 @@ Roles, from least to most: `viewer`, `commenter`, `editor`, `owner`.
|
|
|
183
183
|
|---|---|
|
|
184
184
|
| viewer, commenter | Read the board, its SVG, and join its live room to read. A board has no comments yet, so a commenter reads as a viewer. |
|
|
185
185
|
| editor | Also draw: add, update, move, and remove elements. |
|
|
186
|
-
| owner | Also delete the board, grant and revoke roles, list access, invite, and make or revoke share links. |
|
|
186
|
+
| owner | Also delete the board, grant and revoke roles, list access, invite, and list, make, or revoke share links. |
|
|
187
187
|
|
|
188
188
|
A role never exceeds the scopes of the credential. An agent of an owner holds the owner's role only inside its own
|
|
189
189
|
scopes: it needs `canvas:admin` to share, and `canvas:write` to draw. A caller with no role on a board gets 404, so a read or a write tells a stranger nothing. (A create with an id that is taken answers 409.) A caller with a role that is too low gets 403. `GET /boards` lists the boards you hold a role on.
|
|
@@ -197,6 +197,7 @@ HTTP routes, under `/api/v1/canvas`. Each needs a bearer token, except the link
|
|
|
197
197
|
| `DELETE /boards/:id/grants?subjectKind=&subjectId=` | Owner. Revoke. The last owner cannot be removed. |
|
|
198
198
|
| `POST /boards/:id/invites` | Owner. Body `{ role, email }` or `{ role, handle }`, and optional `expiresInMs`. The role is not `owner`. Returns `{ id }`. |
|
|
199
199
|
| `POST /invites/accept` | Body `{ id }`. The caller must be the person (verified email) or the agent (handle) the invite names. |
|
|
200
|
+
| `GET /boards/:id/links` | Owner. The links of the board: `{ id, role, signedInOnly, expiresAt?, revokedAt? }`. No token. Up to 200, and `truncated: true` if there are more. `skipped` counts damaged records left out. |
|
|
200
201
|
| `POST /boards/:id/links` | Owner. Body `{ role, signedInOnly?, expiresInMs? }`. Returns `{ id, token }`. The token shows once. |
|
|
201
202
|
| `DELETE /boards/:id/links/:link` | Owner. Revoke a link of this board. |
|
|
202
203
|
| `GET /shared/:id` and `GET /shared/:id/svg` | No bearer token needed. Send the link token in `x-link-token`. Read only. |
|
|
@@ -212,6 +213,9 @@ await canvas.grant("demo", { subjectKind: "agent", subjectId: "helper", role: "e
|
|
|
212
213
|
await canvas.invite("demo", { role: "viewer", email: "ada@example.com", expiresInMs: 86_400_000 });
|
|
213
214
|
const { grants, invites } = await canvas.access("demo"); // an invite shows no email
|
|
214
215
|
await canvas.revoke("demo", { subjectKind: "agent", subjectId: "helper" });
|
|
216
|
+
const link = await canvas.createLink("demo", { role: "viewer", expiresInMs: 86_400_000 }); // link.token shows once
|
|
217
|
+
const { links } = await canvas.links("demo"); // no tokens
|
|
218
|
+
await canvas.revokeLink("demo", link.id);
|
|
215
219
|
```
|
|
216
220
|
|
|
217
221
|
```sh
|
|
@@ -220,11 +224,14 @@ npx agentic-canvas access demo
|
|
|
220
224
|
npx agentic-canvas grant demo agent:helper --role editor
|
|
221
225
|
npx agentic-canvas invite demo --role viewer --handle helper --expires-in-ms 86400000
|
|
222
226
|
npx agentic-canvas revoke demo agent:helper
|
|
227
|
+
npx agentic-canvas link demo --role viewer --expires-in-ms 86400000 # prints the token once
|
|
228
|
+
npx agentic-canvas links demo
|
|
229
|
+
npx agentic-canvas revoke-link demo <link-id>
|
|
223
230
|
```
|
|
224
231
|
|
|
225
|
-
With `CANVAS_SERVER` set, `npx agentic-canvas mcp` serves `list_access`, `grant_access`, `revoke_access`, and `
|
|
232
|
+
With `CANVAS_SERVER` set, `npx agentic-canvas mcp` serves `list_access`, `grant_access`, `revoke_access`, `create_invite`, `list_links`, `create_link`, and `revoke_link` as well.
|
|
226
233
|
Each prints or returns the server's JSON. A refusal has the server's code, such as `NOT_FOUND`, `FORBIDDEN`, or `INVALID`.
|
|
227
|
-
|
|
234
|
+
Invite acceptance stays on the HTTP API.
|
|
228
235
|
|
|
229
236
|
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.
|
|
230
237
|
|
|
@@ -461,7 +468,7 @@ old author stays on its elements. Only new writes follow the strict rule.
|
|
|
461
468
|
|---|---|
|
|
462
469
|
| `createClient(options)`, `CanvasApiError` | The remote client. See Remote client above. |
|
|
463
470
|
| `parseSubject(text)` | Reads `human:<id>` or `agent:<id>` as `{ subjectKind, subjectId }`. It throws a `TypeError` for any other text. |
|
|
464
|
-
| `BoardSharing` (type), `BoardRole` (type), `SubjectKind` (type), `Subject` (type), `BoardGrant` (type), `BoardInvite` (type), `InviteInput` (type) | Types for sharing. |
|
|
471
|
+
| `BoardSharing` (type), `BoardRole` (type), `SubjectKind` (type), `Subject` (type), `BoardGrant` (type), `BoardInvite` (type), `InviteInput` (type), `BoardLink` (type), `LinkInput` (type) | Types for sharing. |
|
|
465
472
|
| `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`. |
|
|
466
473
|
|
|
467
474
|
### `agentic-canvas/file-store`
|
package/dist/account.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CanvasAccess
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-A2F73EQJ.js";
|
|
4
4
|
import {
|
|
5
5
|
CanvasService,
|
|
6
6
|
MAX_LIVE_BYTES
|
|
@@ -2254,6 +2254,7 @@ function createApp(options = {}) {
|
|
|
2254
2254
|
return c.json(done);
|
|
2255
2255
|
});
|
|
2256
2256
|
api.post("/boards/:id/invites", async (c) => c.json(await c.var.access.invite(c.var.caller.principal, c.req.param("id"), await jsonBody(c)), 201));
|
|
2257
|
+
api.get("/boards/:id/links", async (c) => c.json(await c.var.access.listLinks(c.var.caller.principal, c.req.param("id"))));
|
|
2257
2258
|
api.post("/boards/:id/links", async (c) => c.json(await c.var.access.createLink(c.var.caller.principal, c.req.param("id"), await jsonBody(c)), 201));
|
|
2258
2259
|
api.delete("/boards/:id/links/:link", async (c) => c.json(await c.var.access.revokeLink(c.var.caller.principal, c.req.param("id"), c.req.param("link"))));
|
|
2259
2260
|
api.post("/invites/accept", async (c) => c.json(await c.var.access.accept(c.var.caller.principal, text((await jsonBody(c)).id, "id"))));
|
|
@@ -106,7 +106,7 @@ var CanvasAccess = class {
|
|
|
106
106
|
await this.sharing.revokeGrant(ctx, id, subject).catch(refuse);
|
|
107
107
|
return { ok: true };
|
|
108
108
|
}
|
|
109
|
-
/** The grants and the invites on a board. An invite never shows its email.
|
|
109
|
+
/** The grants and the invites on a board. An invite never shows its email. `listLinks` lists the links, and never a token. */
|
|
110
110
|
async list(ctx, id) {
|
|
111
111
|
this.known(id);
|
|
112
112
|
const [grants, invites] = await Promise.all([this.sharing.listGrants(ctx, id, { limit: 200 }), this.sharing.listInvites(ctx, id, { limit: 200 })]).catch(refuse);
|
|
@@ -140,6 +140,38 @@ var CanvasAccess = class {
|
|
|
140
140
|
if (input.signedInOnly !== void 0 && typeof input.signedInOnly !== "boolean") throw new CanvasError("INVALID", "signedInOnly must be true or false");
|
|
141
141
|
return this.sharing.createLink(ctx, id, { role, signedInOnly: input.signedInOnly === true, ...expiresInMs === void 0 ? {} : { expiresAt: this.now() + expiresInMs } }).catch(refuse);
|
|
142
142
|
}
|
|
143
|
+
/** The links of a board. A summary never holds the token or its hash. Needs an owner. */
|
|
144
|
+
async listLinks(ctx, id) {
|
|
145
|
+
this.known(id);
|
|
146
|
+
await this.require(ctx, "admin", id);
|
|
147
|
+
const all = [];
|
|
148
|
+
let cursor;
|
|
149
|
+
let skipped = 0;
|
|
150
|
+
for (let pages = 0; pages < 10; pages++) {
|
|
151
|
+
const page = await this.store.query("links", { where: { resourceId: id }, limit: 100, ...cursor === void 0 ? {} : { cursor } });
|
|
152
|
+
for (const { id: key, data } of page.items) {
|
|
153
|
+
const expiresOk = data.expiresAt == null || typeof data.expiresAt === "number" && Number.isFinite(data.expiresAt);
|
|
154
|
+
if (data.id !== key || data.resourceId !== id || typeof data.tokenHash !== "string" || typeof data.id !== "string" || !SHARE_ROLES.includes(data.role) || !expiresOk) {
|
|
155
|
+
skipped++;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
all.push({
|
|
159
|
+
id: data.id,
|
|
160
|
+
role: data.role,
|
|
161
|
+
signedInOnly: data.signedInOnly === true,
|
|
162
|
+
...typeof data.expiresAt === "number" ? { expiresAt: data.expiresAt } : {},
|
|
163
|
+
// Any value but null means revoked, as in agentic-identity. A value that is not a time shows as 0.
|
|
164
|
+
...data.revokedAt == null ? {} : { revokedAt: typeof data.revokedAt === "number" ? data.revokedAt : 0 }
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if ((cursor = page.nextCursor) === void 0) break;
|
|
168
|
+
}
|
|
169
|
+
const now = this.now();
|
|
170
|
+
const live = (l) => l.revokedAt === void 0 && (l.expiresAt === void 0 || l.expiresAt > now);
|
|
171
|
+
all.sort((x, y) => Number(live(y)) - Number(live(x)) || (y.expiresAt ?? Infinity) - (x.expiresAt ?? Infinity) || x.id.localeCompare(y.id));
|
|
172
|
+
const links = all.slice(0, 200);
|
|
173
|
+
return { links, ...cursor !== void 0 || all.length > 200 ? { truncated: true } : {}, ...skipped > 0 ? { skipped } : {} };
|
|
174
|
+
}
|
|
143
175
|
/** Revoke a link of this board. A link of another board is NOT_FOUND here, even for an owner of both. */
|
|
144
176
|
async revokeLink(ctx, id, linkId) {
|
|
145
177
|
this.known(id);
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
|
|
7
7
|
// src/version.ts
|
|
8
8
|
import { createRequire } from "module";
|
|
9
|
-
var VERSION = true ? "0.
|
|
9
|
+
var VERSION = true ? "0.14.0" : readPackageVersion();
|
|
10
10
|
|
|
11
11
|
// src/brand.ts
|
|
12
12
|
var BRAND = {
|
|
@@ -198,6 +198,24 @@ function createMcpServer(service, actor, sharing) {
|
|
|
198
198
|
},
|
|
199
199
|
tool(async ({ board: b, ...input }) => json(await shared().invite(b, input)))
|
|
200
200
|
);
|
|
201
|
+
server.registerTool(
|
|
202
|
+
"list_links",
|
|
203
|
+
{ description: "List the share links of a board, without their tokens. Needs the owner role.", inputSchema: { board }, annotations: read },
|
|
204
|
+
tool(async (a) => json(await shared().links(a.board)))
|
|
205
|
+
);
|
|
206
|
+
server.registerTool(
|
|
207
|
+
"create_link",
|
|
208
|
+
{
|
|
209
|
+
description: "Make a share link. Anyone with the token can read the board, and only read: the role does not widen a link visit. With signedInOnly, only a signed-in visitor may use it. The token shows once: keep it. The role is not owner. Needs the owner role.",
|
|
210
|
+
inputSchema: { board, role: z.enum(["viewer", "commenter", "editor"]), signedInOnly: z.boolean().optional(), expiresInMs: z.number().int().positive().optional() }
|
|
211
|
+
},
|
|
212
|
+
tool(async ({ board: b, ...input }) => json(await shared().createLink(b, input)))
|
|
213
|
+
);
|
|
214
|
+
server.registerTool(
|
|
215
|
+
"revoke_link",
|
|
216
|
+
{ description: "Revoke a share link of a board. Needs the owner role.", inputSchema: { board, link: z.string() }, annotations: { destructiveHint: true } },
|
|
217
|
+
tool(async (a) => json(await shared().revokeLink(a.board, a.link)))
|
|
218
|
+
);
|
|
201
219
|
return server;
|
|
202
220
|
}
|
|
203
221
|
async function runMcp(service, actor, sharing) {
|
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-YKUHPOQV.js";
|
|
9
9
|
import {
|
|
10
10
|
openLocalStore
|
|
11
11
|
} from "./chunk-NRLF6MGR.js";
|
|
@@ -100,6 +100,9 @@ function createClient(options) {
|
|
|
100
100
|
grant: (id, input, o) => json("POST", `/boards/${seg(id)}/grants`, input, o),
|
|
101
101
|
revoke: (id, { subjectKind, subjectId }, o) => json("DELETE", `/boards/${seg(id)}/grants?subjectKind=${seg(subjectKind)}&subjectId=${seg(subjectId)}`, void 0, o),
|
|
102
102
|
invite: (id, input, o) => json("POST", `/boards/${seg(id)}/invites`, input, o),
|
|
103
|
+
links: (id, o) => json("GET", `/boards/${seg(id)}/links`, void 0, o),
|
|
104
|
+
createLink: (id, input, o) => json("POST", `/boards/${seg(id)}/links`, input, o),
|
|
105
|
+
revokeLink: (id, linkId, o) => json("DELETE", `/boards/${seg(id)}/links/${seg(linkId)}`, void 0, o),
|
|
103
106
|
uploadImage: async (data, o) => parse(await send("POST", "/images", { body: data, contentType: "application/octet-stream" }, o), o),
|
|
104
107
|
getImage: async (id, o) => {
|
|
105
108
|
const response = await send("GET", `/images/${seg(id)}`, {}, o);
|
|
@@ -135,6 +138,9 @@ Commands:
|
|
|
135
138
|
grant <board> <human|agent>:<id> --role <viewer|commenter|editor|owner> Server only. Give a role. Owner.
|
|
136
139
|
revoke <board> <human|agent>:<id> Server only. Take a role back. Owner.
|
|
137
140
|
invite <board> --role <viewer|commenter|editor> --email <e> | --handle <h> [--expires-in-ms <n>] Server only. Owner.
|
|
141
|
+
links <board> Server only. List the share links, without tokens. Owner.
|
|
142
|
+
link <board> --role <viewer|commenter|editor> [--signed-in-only] [--expires-in-ms <n>] Server only. Make a share link. Prints the token once. Owner.
|
|
143
|
+
revoke-link <board> <link-id> Server only. Revoke a share link. Owner.
|
|
138
144
|
mcp Serve these as MCP tools on stdio. Needs CANVAS_ACTOR.
|
|
139
145
|
|
|
140
146
|
Live mode: set CANVAS_SERVER=<worker url> and CANVAS_TOKEN=<bearer token>. Every command then runs in the live room of
|
|
@@ -168,6 +174,7 @@ var flags = {
|
|
|
168
174
|
email: { type: "string" },
|
|
169
175
|
handle: { type: "string" },
|
|
170
176
|
"expires-in-ms": { type: "string" },
|
|
177
|
+
"signed-in-only": { type: "boolean" },
|
|
171
178
|
out: { type: "string" },
|
|
172
179
|
drop: { type: "string", multiple: true },
|
|
173
180
|
help: { type: "boolean" },
|
|
@@ -293,6 +300,17 @@ async function run(argv) {
|
|
|
293
300
|
const expiresInMs = num(v["expires-in-ms"]);
|
|
294
301
|
return print(await sharing().invite(board, { role: v.role, email: v.email, handle: v.handle, ...expiresInMs === void 0 ? {} : { expiresInMs } }));
|
|
295
302
|
}
|
|
303
|
+
case "links":
|
|
304
|
+
need(1, "<board>");
|
|
305
|
+
return print(await sharing().links(board));
|
|
306
|
+
case "link": {
|
|
307
|
+
need(1, "<board> --role <role> [--signed-in-only] [--expires-in-ms <n>]");
|
|
308
|
+
const expiresInMs = num(v["expires-in-ms"]);
|
|
309
|
+
return print(await sharing().createLink(board, { role: v.role, ...v["signed-in-only"] ? { signedInOnly: true } : {}, ...expiresInMs === void 0 ? {} : { expiresInMs } }));
|
|
310
|
+
}
|
|
311
|
+
case "revoke-link":
|
|
312
|
+
need(2, "<board> <link-id>");
|
|
313
|
+
return print(await sharing().revokeLink(board, rest[0]));
|
|
296
314
|
case "mcp":
|
|
297
315
|
need(0, "");
|
|
298
316
|
return runMcp(service, actor(), server ? sharing() : void 0);
|
|
@@ -43,6 +43,23 @@ interface BoardGrant {
|
|
|
43
43
|
role: BoardRole;
|
|
44
44
|
[field: string]: unknown;
|
|
45
45
|
}
|
|
46
|
+
/** A share link of a board. It never shows the token: the token shows once, when the link is made. */
|
|
47
|
+
interface BoardLink {
|
|
48
|
+
id: string;
|
|
49
|
+
role: Exclude<BoardRole, "owner">;
|
|
50
|
+
signedInOnly: boolean;
|
|
51
|
+
expiresAt?: number;
|
|
52
|
+
/** Set once the link is revoked. Test it with `!== undefined`: a damaged record shows as 0, and 0 is falsy. */
|
|
53
|
+
revokedAt?: number;
|
|
54
|
+
}
|
|
55
|
+
interface LinkInput {
|
|
56
|
+
/** Not `owner`: only a grant makes an owner. */
|
|
57
|
+
role: Exclude<BoardRole, "owner">;
|
|
58
|
+
/** Only a signed-in visitor may use the link. */
|
|
59
|
+
signedInOnly?: boolean;
|
|
60
|
+
/** How long the link lives, in milliseconds. */
|
|
61
|
+
expiresInMs?: number;
|
|
62
|
+
}
|
|
46
63
|
/** An open invite. It never shows the email. */
|
|
47
64
|
interface BoardInvite {
|
|
48
65
|
id: string;
|
|
@@ -133,6 +150,21 @@ interface CanvasClient {
|
|
|
133
150
|
invite(id: string, input: InviteInput, options?: CallOptions): Promise<{
|
|
134
151
|
id: string;
|
|
135
152
|
}>;
|
|
153
|
+
/** The share links of a board, without their tokens, up to 200. `truncated` is set if there are more. `skipped` counts damaged records left out. Needs the owner role. */
|
|
154
|
+
links(id: string, options?: CallOptions): Promise<{
|
|
155
|
+
links: BoardLink[];
|
|
156
|
+
truncated?: true;
|
|
157
|
+
skipped?: number;
|
|
158
|
+
}>;
|
|
159
|
+
/** Make a share link. The token comes back once and is never stored. Needs the owner role. */
|
|
160
|
+
createLink(id: string, input: LinkInput, options?: CallOptions): Promise<{
|
|
161
|
+
id: string;
|
|
162
|
+
token: string;
|
|
163
|
+
}>;
|
|
164
|
+
/** Revoke a share link. Needs the owner role. */
|
|
165
|
+
revokeLink(id: string, linkId: string, options?: CallOptions): Promise<{
|
|
166
|
+
ok: true;
|
|
167
|
+
}>;
|
|
136
168
|
uploadImage(bytes: Uint8Array | ArrayBuffer | Blob, options?: CallOptions): Promise<ImageInfo>;
|
|
137
169
|
/** The bytes of a stored image, and its content type. */
|
|
138
170
|
getImage(id: string, options?: CallOptions): Promise<{
|
|
@@ -148,6 +180,6 @@ interface CanvasClient {
|
|
|
148
180
|
}>;
|
|
149
181
|
}
|
|
150
182
|
/** 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">;
|
|
183
|
+
type BoardSharing = Pick<CanvasClient, "access" | "grant" | "revoke" | "invite" | "links" | "createLink" | "revokeLink">;
|
|
152
184
|
|
|
153
185
|
export type { BoardSharing as B, CanvasClient as C };
|
package/dist/client.d.ts
CHANGED
|
@@ -114,6 +114,23 @@ interface BoardGrant {
|
|
|
114
114
|
role: BoardRole;
|
|
115
115
|
[field: string]: unknown;
|
|
116
116
|
}
|
|
117
|
+
/** A share link of a board. It never shows the token: the token shows once, when the link is made. */
|
|
118
|
+
interface BoardLink {
|
|
119
|
+
id: string;
|
|
120
|
+
role: Exclude<BoardRole, "owner">;
|
|
121
|
+
signedInOnly: boolean;
|
|
122
|
+
expiresAt?: number;
|
|
123
|
+
/** Set once the link is revoked. Test it with `!== undefined`: a damaged record shows as 0, and 0 is falsy. */
|
|
124
|
+
revokedAt?: number;
|
|
125
|
+
}
|
|
126
|
+
interface LinkInput {
|
|
127
|
+
/** Not `owner`: only a grant makes an owner. */
|
|
128
|
+
role: Exclude<BoardRole, "owner">;
|
|
129
|
+
/** Only a signed-in visitor may use the link. */
|
|
130
|
+
signedInOnly?: boolean;
|
|
131
|
+
/** How long the link lives, in milliseconds. */
|
|
132
|
+
expiresInMs?: number;
|
|
133
|
+
}
|
|
117
134
|
/** An open invite. It never shows the email. */
|
|
118
135
|
interface BoardInvite {
|
|
119
136
|
id: string;
|
|
@@ -214,6 +231,21 @@ interface CanvasClient {
|
|
|
214
231
|
invite(id: string, input: InviteInput, options?: CallOptions): Promise<{
|
|
215
232
|
id: string;
|
|
216
233
|
}>;
|
|
234
|
+
/** The share links of a board, without their tokens, up to 200. `truncated` is set if there are more. `skipped` counts damaged records left out. Needs the owner role. */
|
|
235
|
+
links(id: string, options?: CallOptions): Promise<{
|
|
236
|
+
links: BoardLink[];
|
|
237
|
+
truncated?: true;
|
|
238
|
+
skipped?: number;
|
|
239
|
+
}>;
|
|
240
|
+
/** Make a share link. The token comes back once and is never stored. Needs the owner role. */
|
|
241
|
+
createLink(id: string, input: LinkInput, options?: CallOptions): Promise<{
|
|
242
|
+
id: string;
|
|
243
|
+
token: string;
|
|
244
|
+
}>;
|
|
245
|
+
/** Revoke a share link. Needs the owner role. */
|
|
246
|
+
revokeLink(id: string, linkId: string, options?: CallOptions): Promise<{
|
|
247
|
+
ok: true;
|
|
248
|
+
}>;
|
|
217
249
|
uploadImage(bytes: Uint8Array | ArrayBuffer | Blob, options?: CallOptions): Promise<ImageInfo>;
|
|
218
250
|
/** The bytes of a stored image, and its content type. */
|
|
219
251
|
getImage(id: string, options?: CallOptions): Promise<{
|
|
@@ -229,9 +261,9 @@ interface CanvasClient {
|
|
|
229
261
|
}>;
|
|
230
262
|
}
|
|
231
263
|
/** 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">;
|
|
264
|
+
type BoardSharing = Pick<CanvasClient, "access" | "grant" | "revoke" | "invite" | "links" | "createLink" | "revokeLink">;
|
|
233
265
|
/** `kind:id` of a person or an agent, as the CLI and the tools write it. */
|
|
234
266
|
declare function parseSubject(text: string): Subject;
|
|
235
267
|
declare function createClient(options: ClientOptions): CanvasClient;
|
|
236
268
|
|
|
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 };
|
|
269
|
+
export { type ApiErrorCode, type BoardGrant, type BoardInvite, type BoardLink, type BoardRole, type BoardSharing, type CallOptions, CanvasApiError, type CanvasClient, type ClientOptions, type ConnectorInput, type ConnectorPatch, type ImageInfo, type InviteInput, type LinkInput, type ShapeInput, type ShapePatch, type Subject, type SubjectKind, createClient, parseSubject };
|
package/dist/client.js
CHANGED
|
@@ -76,6 +76,9 @@ function createClient(options) {
|
|
|
76
76
|
grant: (id, input, o) => json("POST", `/boards/${seg(id)}/grants`, input, o),
|
|
77
77
|
revoke: (id, { subjectKind, subjectId }, o) => json("DELETE", `/boards/${seg(id)}/grants?subjectKind=${seg(subjectKind)}&subjectId=${seg(subjectId)}`, void 0, o),
|
|
78
78
|
invite: (id, input, o) => json("POST", `/boards/${seg(id)}/invites`, input, o),
|
|
79
|
+
links: (id, o) => json("GET", `/boards/${seg(id)}/links`, void 0, o),
|
|
80
|
+
createLink: (id, input, o) => json("POST", `/boards/${seg(id)}/links`, input, o),
|
|
81
|
+
revokeLink: (id, linkId, o) => json("DELETE", `/boards/${seg(id)}/links/${seg(linkId)}`, void 0, o),
|
|
79
82
|
uploadImage: async (data, o) => parse(await send("POST", "/images", { body: data, contentType: "application/octet-stream" }, o), o),
|
|
80
83
|
getImage: async (id, o) => {
|
|
81
84
|
const response = await send("GET", `/images/${seg(id)}`, {}, o);
|
package/dist/embed.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { C as CanvasClient } from './client-
|
|
2
|
+
import { C as CanvasClient } from './client-BKTZ1pM5.js';
|
|
3
3
|
import { PrincipalKind } from 'agentic-identity';
|
|
4
4
|
import { SyncClient, Status } from 'agentic-realtime/client';
|
|
5
5
|
import * as Y from 'yjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { p as principalSchema } from './principal-blhsFbdS.js';
|
|
|
5
5
|
import { B as BoardAccess } from './service-BNEH2Z12.js';
|
|
6
6
|
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-BNEH2Z12.js';
|
|
7
7
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
-
import { B as BoardSharing } from './client-
|
|
8
|
+
import { B as BoardSharing } from './client-BKTZ1pM5.js';
|
|
9
9
|
export { FileStoreOptions, createFileStore, openLocalStore } from './store-file.js';
|
|
10
10
|
export { PRINCIPAL_KINDS, PrincipalKind, isMachineMade } from 'agentic-identity';
|
|
11
11
|
import 'yjs';
|
package/dist/index.js
CHANGED
package/dist/live-worker.js
CHANGED
package/dist/worker.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Store, PrincipalContext, Action, Grant, InviteSummary, Blobs } from 'agentic-identity';
|
|
1
|
+
import { Store, PrincipalContext, Action, Grant, InviteSummary, Role as Role$1, Blobs } from 'agentic-identity';
|
|
2
2
|
import { Realtime } from 'agentic-realtime';
|
|
3
3
|
import { D1Like, R2Like } from 'agentic-identity/cloudflare';
|
|
4
4
|
import { Hono } from 'hono';
|
|
@@ -8,6 +8,14 @@ import 'zod';
|
|
|
8
8
|
import './read-DeS2Zaz8.js';
|
|
9
9
|
import 'yjs';
|
|
10
10
|
|
|
11
|
+
/** A share link without its token. `revokedAt` is set once the link is revoked. */
|
|
12
|
+
interface LinkSummary {
|
|
13
|
+
id: string;
|
|
14
|
+
role: Exclude<Role$1, "owner">;
|
|
15
|
+
signedInOnly: boolean;
|
|
16
|
+
expiresAt?: number;
|
|
17
|
+
revokedAt?: number;
|
|
18
|
+
}
|
|
11
19
|
/**
|
|
12
20
|
* Who may do what on a board. A board is an identity resource of type `canvas`. The roles, the grants, the invites,
|
|
13
21
|
* and the links are those of agentic-identity: this class checks input and maps its failures to `CanvasError`.
|
|
@@ -51,7 +59,7 @@ declare class CanvasAccess {
|
|
|
51
59
|
}): Promise<{
|
|
52
60
|
ok: true;
|
|
53
61
|
}>;
|
|
54
|
-
/** The grants and the invites on a board. An invite never shows its email.
|
|
62
|
+
/** The grants and the invites on a board. An invite never shows its email. `listLinks` lists the links, and never a token. */
|
|
55
63
|
list(ctx: PrincipalContext, id: string): Promise<{
|
|
56
64
|
grants: Grant[];
|
|
57
65
|
invites: InviteSummary[];
|
|
@@ -77,6 +85,12 @@ declare class CanvasAccess {
|
|
|
77
85
|
id: string;
|
|
78
86
|
token: string;
|
|
79
87
|
}>;
|
|
88
|
+
/** The links of a board. A summary never holds the token or its hash. Needs an owner. */
|
|
89
|
+
listLinks(ctx: PrincipalContext, id: string): Promise<{
|
|
90
|
+
links: LinkSummary[];
|
|
91
|
+
truncated?: true;
|
|
92
|
+
skipped?: number;
|
|
93
|
+
}>;
|
|
80
94
|
/** Revoke a link of this board. A link of another board is NOT_FOUND here, even for an owner of both. */
|
|
81
95
|
revokeLink(ctx: PrincipalContext, id: string, linkId: string): Promise<{
|
|
82
96
|
ok: true;
|
package/dist/worker.js
CHANGED
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
identify,
|
|
6
6
|
sniffImage,
|
|
7
7
|
worker_default
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-5QUL3URJ.js";
|
|
9
|
+
import "./chunk-A2F73EQJ.js";
|
|
10
10
|
import "./chunk-GLNF43N4.js";
|
|
11
11
|
import "./chunk-NWQED42L.js";
|
|
12
12
|
import "./chunk-BKG3FXX3.js";
|
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, and with CANVAS_SERVER: access|grant|revoke|invite. MCP tools for sharing: list_access, grant_access, revoke_access, create_invite.
|
|
9
|
+
- CLI: agentic-canvas new|list|read|add|connect|update|move|rm|delete|flow|export, and with CANVAS_SERVER: access|grant|revoke|invite|links|link|revoke-link. MCP tools for sharing: list_access, grant_access, revoke_access, create_invite, list_links, create_link, revoke_link.
|
|
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, access, grant, revoke, invite, 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, links, createLink, revokeLink, 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, id? }, readOnly?, room?: { baseUrl, token } }) (or host: { doc, presence?, status?, onStatus? } instead of client and room; in host mode id (default name) must be letters, digits, . _ -, to edit the app's own Yjs document with no HTTP call) 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
|