agentic-canvas 0.13.0 → 0.14.1

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.13.0. The board model, the agent operations, the exports, the CLI, the
10
+ > Status: 0.14.1. 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 `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
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 `create_invite` as well.
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
- Share links and invite acceptance stay on the HTTP API.
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
  RESOURCE_TYPE
3
- } from "./chunk-LMSMUGBF.js";
3
+ } from "./chunk-A2F73EQJ.js";
4
4
  import {
5
5
  fromBase64,
6
6
  isBoardId,
@@ -6,7 +6,7 @@ import {
6
6
 
7
7
  // src/version.ts
8
8
  import { createRequire } from "module";
9
- var VERSION = true ? "0.13.0" : readPackageVersion();
9
+ var VERSION = true ? "0.14.1" : 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) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CanvasAccess
3
- } from "./chunk-LMSMUGBF.js";
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. Links are not listed: a token shows once. */
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);
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  operatorActor,
6
6
  runMcp,
7
7
  svgToPng
8
- } from "./chunk-AI5NWPEW.js";
8
+ } from "./chunk-5HFMEWR4.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-DBb1jpxl.js';
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-DBb1jpxl.js';
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
@@ -6,7 +6,7 @@ import {
6
6
  operatorActor,
7
7
  runMcp,
8
8
  svgToPng
9
- } from "./chunk-AI5NWPEW.js";
9
+ } from "./chunk-5HFMEWR4.js";
10
10
  import {
11
11
  createFileStore,
12
12
  openLocalStore
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createApp
3
- } from "./chunk-4PY5SN2N.js";
4
- import "./chunk-LMSMUGBF.js";
3
+ } from "./chunk-5QUL3URJ.js";
4
+ import "./chunk-A2F73EQJ.js";
5
5
  import "./chunk-GLNF43N4.js";
6
6
  import "./chunk-NWQED42L.js";
7
7
  import "./chunk-BKG3FXX3.js";
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. Links are not listed: a token shows once. */
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-4PY5SN2N.js";
9
- import "./chunk-LMSMUGBF.js";
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-canvas",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "A shared whiteboard for diagrams, drawn by people and agents.",
5
5
  "keywords": [
6
6
  "whiteboard",
@@ -87,7 +87,7 @@
87
87
  "dependencies": {
88
88
  "@dagrejs/dagre": "^3.1.3",
89
89
  "@modelcontextprotocol/sdk": "^1.12.0",
90
- "agentic-design": "^0.5.0",
90
+ "agentic-design": "^0.8.0",
91
91
  "agentic-identity": "^0.9.0",
92
92
  "agentic-realtime": "^0.4.4",
93
93
  "yjs": "^13.6.0",