agentic-canvas 0.9.0 → 0.11.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/dist/cli.js CHANGED
@@ -5,22 +5,112 @@ import {
5
5
  operatorActor,
6
6
  runMcp,
7
7
  svgToPng
8
- } from "./chunk-33ANMQEY.js";
8
+ } from "./chunk-EE725BJ3.js";
9
9
  import {
10
10
  openLocalStore
11
11
  } from "./chunk-NRLF6MGR.js";
12
12
  import {
13
13
  CanvasService
14
- } from "./chunk-M74ENUWR.js";
14
+ } from "./chunk-Z6KLGAFP.js";
15
15
  import "./chunk-NWQED42L.js";
16
+ import "./chunk-EDQNA2SU.js";
16
17
  import {
17
18
  CanvasError
18
- } from "./chunk-ZSPZMGHG.js";
19
- import "./chunk-JOBTIAFJ.js";
19
+ } from "./chunk-MVWUIBGV.js";
20
20
 
21
21
  // src/cli.ts
22
22
  import { readFileSync, writeFileSync } from "fs";
23
23
  import { parseArgs } from "util";
24
+
25
+ // src/client.ts
26
+ var CanvasApiError = class extends Error {
27
+ constructor(code, message, status) {
28
+ super(message);
29
+ this.code = code;
30
+ this.status = status;
31
+ this.name = "CanvasApiError";
32
+ }
33
+ code;
34
+ status;
35
+ };
36
+ function parseSubject(text) {
37
+ const at = text.indexOf(":");
38
+ const kind = text.slice(0, at);
39
+ if (at < 1 || kind !== "human" && kind !== "agent" || at === text.length - 1) throw new TypeError(`"${text}" is not human:<id> or agent:<id>`);
40
+ return { subjectKind: kind, subjectId: text.slice(at + 1) };
41
+ }
42
+ function createClient(options) {
43
+ const { token } = options;
44
+ const base = options.baseUrl.replace(/\/+$/, "");
45
+ if (!token) throw new TypeError("token is required");
46
+ const doFetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
47
+ const seg = encodeURIComponent;
48
+ async function send(method, path, init, call) {
49
+ const headers = { authorization: `Bearer ${token}` };
50
+ if (init.json !== void 0) headers["content-type"] = "application/json";
51
+ if (init.contentType) headers["content-type"] = init.contentType;
52
+ let response;
53
+ try {
54
+ response = await doFetch(`${base}${path}`, {
55
+ method,
56
+ headers,
57
+ body: init.json === void 0 ? init.body : JSON.stringify(init.json),
58
+ signal: call?.signal
59
+ });
60
+ } catch (error) {
61
+ if (call?.signal?.aborted) throw error;
62
+ throw new CanvasApiError("NETWORK", error instanceof Error ? error.message : "the request failed", 0);
63
+ }
64
+ if (response.ok) return response;
65
+ let code = "HTTP_ERROR";
66
+ let message = `the server answered ${response.status}`;
67
+ try {
68
+ const { error } = await response.json();
69
+ if (typeof error?.code === "string") code = error.code;
70
+ if (typeof error?.message === "string") message = error.message;
71
+ } catch {
72
+ if (call?.signal?.aborted) throw call.signal.reason;
73
+ }
74
+ throw new CanvasApiError(code, message, response.status);
75
+ }
76
+ const parse = async (response, call) => {
77
+ try {
78
+ return await response.json();
79
+ } catch (error) {
80
+ if (call?.signal?.aborted) throw error;
81
+ throw new CanvasApiError("HTTP_ERROR", "the server did not answer with JSON", response.status);
82
+ }
83
+ };
84
+ const json = async (method, path, body, call) => parse(await send(method, path, { json: body }, call), call);
85
+ const bytes = async (response) => new Uint8Array(await response.arrayBuffer());
86
+ return {
87
+ me: (o) => json("GET", "/me", void 0, o),
88
+ listBoards: (o) => json("GET", "/boards", void 0, o),
89
+ createBoard: (input = {}, o) => json("POST", "/boards", input, o),
90
+ readBoard: (id, o) => json("GET", `/boards/${seg(id)}`, void 0, o),
91
+ deleteBoard: (id, o) => json("DELETE", `/boards/${seg(id)}`, void 0, o),
92
+ exportSvg: async (id, o) => (await send("GET", `/boards/${seg(id)}/svg`, {}, o)).text(),
93
+ addShape: (id, shape, o) => json("POST", `/boards/${seg(id)}/shapes`, shape, o),
94
+ addConnector: (id, connector, o) => json("POST", `/boards/${seg(id)}/connectors`, connector, o),
95
+ drawFlow: (id, text, o) => json("POST", `/boards/${seg(id)}/flow`, { text }, o),
96
+ update: (id, element, patch, o) => json("PATCH", `/boards/${seg(id)}/elements/${seg(element)}`, patch, o),
97
+ move: (id, element, dx, dy, o) => json("POST", `/boards/${seg(id)}/move`, { element, dx, dy }, o),
98
+ remove: (id, element, o) => json("DELETE", `/boards/${seg(id)}/elements/${seg(element)}`, void 0, o),
99
+ access: (id, o) => json("GET", `/boards/${seg(id)}/access`, void 0, o),
100
+ grant: (id, input, o) => json("POST", `/boards/${seg(id)}/grants`, input, o),
101
+ revoke: (id, { subjectKind, subjectId }, o) => json("DELETE", `/boards/${seg(id)}/grants?subjectKind=${seg(subjectKind)}&subjectId=${seg(subjectId)}`, void 0, o),
102
+ invite: (id, input, o) => json("POST", `/boards/${seg(id)}/invites`, input, o),
103
+ uploadImage: async (data, o) => parse(await send("POST", "/images", { body: data, contentType: "application/octet-stream" }, o), o),
104
+ getImage: async (id, o) => {
105
+ const response = await send("GET", `/images/${seg(id)}`, {}, o);
106
+ return { bytes: await bytes(response), type: response.headers.get("content-type") ?? "application/octet-stream" };
107
+ },
108
+ state: async (id, o) => bytes(await send("GET", `/boards/${seg(id)}/state`, {}, o)),
109
+ checkpoint: async (id, update, o) => parse(await send("POST", `/boards/${seg(id)}/checkpoint`, { body: update, contentType: "application/octet-stream" }, o), o)
110
+ };
111
+ }
112
+
113
+ // src/cli.ts
24
114
  var UsageError = class extends Error {
25
115
  };
26
116
  var HELP = `${BRAND.command} ${BRAND.version}
@@ -41,11 +131,16 @@ Commands:
41
131
  flow <board> --text <t> | --file <p> Draw a flowchart from text ("A -> B: label"). Use --file - for stdin.
42
132
  export <board> [--format svg|png] [--out <path>] Print SVG, or write PNG to --out.
43
133
  repair <board> [--drop <element>]... Owner only: fix a broken or over-cap board. Needs CANVAS_ACTOR=human:<id>.
134
+ access <board> Server only. List the grants and open invites. Owner.
135
+ grant <board> <human|agent>:<id> --role <viewer|commenter|editor|owner> Server only. Give a role. Owner.
136
+ revoke <board> <human|agent>:<id> Server only. Take a role back. Owner.
137
+ invite <board> --role <viewer|commenter|editor> --email <e> | --handle <h> [--expires-in-ms <n>] Server only. Owner.
44
138
  mcp Serve these as MCP tools on stdio. Needs CANVAS_ACTOR.
45
139
 
46
140
  Live mode: set CANVAS_SERVER=<worker url> and CANVAS_TOKEN=<bearer token>. Every command then runs in the live room of
47
141
  the board on that server, so a person and an agent can edit one board at once. The token sets the actor, the kind,
48
142
  and the role, so CANVAS_ACTOR is not needed. If it is set, it must match the token. --dir, CANVAS_DIR, and repair are local only.
143
+ The sharing commands need CANVAS_SERVER: a local store has no owners.
49
144
 
50
145
  Write commands need an actor in CANVAS_ACTOR as <kind>:<id>, such as "agent:helper".
51
146
  The operator sets it. A caller cannot pick its own kind: there is no --actor flag.
@@ -69,6 +164,10 @@ var flags = {
69
164
  file: { type: "string" },
70
165
  prefix: { type: "string" },
71
166
  format: { type: "string" },
167
+ role: { type: "string" },
168
+ email: { type: "string" },
169
+ handle: { type: "string" },
170
+ "expires-in-ms": { type: "string" },
72
171
  out: { type: "string" },
73
172
  drop: { type: "string", multiple: true },
74
173
  help: { type: "boolean" },
@@ -117,6 +216,17 @@ async function run(argv) {
117
216
  throw error;
118
217
  }
119
218
  };
219
+ const sharing = () => {
220
+ if (!server) throw new UsageError(`"${command}" needs a server: set CANVAS_SERVER and CANVAS_TOKEN`);
221
+ return createClient({ baseUrl: `${server.replace(/\/$/, "")}/api/v1/canvas`, token: process.env.CANVAS_TOKEN });
222
+ };
223
+ const subject = (text) => {
224
+ try {
225
+ return parseSubject(text ?? "");
226
+ } catch (error) {
227
+ throw new UsageError(error.message);
228
+ }
229
+ };
120
230
  const print = (value) => console.log(JSON.stringify(value));
121
231
  switch (command) {
122
232
  case "new":
@@ -169,9 +279,23 @@ async function run(argv) {
169
279
  case "repair":
170
280
  need(1, "<board> [--drop <element>]...");
171
281
  return print(await service.repair(board, actor(), v.drop ?? []));
282
+ case "access":
283
+ need(1, "<board>");
284
+ return print(await sharing().access(board));
285
+ case "grant":
286
+ need(2, "<board> <human|agent>:<id> --role <role>");
287
+ return print(await sharing().grant(board, { ...subject(rest[0]), role: v.role }));
288
+ case "revoke":
289
+ need(2, "<board> <human|agent>:<id>");
290
+ return print(await sharing().revoke(board, subject(rest[0])));
291
+ case "invite": {
292
+ need(1, "<board> --role <role> --email <e> | --handle <h> [--expires-in-ms <n>]");
293
+ const expiresInMs = num(v["expires-in-ms"]);
294
+ return print(await sharing().invite(board, { role: v.role, email: v.email, handle: v.handle, ...expiresInMs === void 0 ? {} : { expiresInMs } }));
295
+ }
172
296
  case "mcp":
173
297
  need(0, "");
174
- return runMcp(service, actor());
298
+ return runMcp(service, actor(), server ? sharing() : void 0);
175
299
  default:
176
300
  throw new UsageError(`unknown command "${command}"`);
177
301
  }
@@ -182,7 +306,7 @@ run(process.argv.slice(2)).catch((error) => {
182
306
  console.error(`${error.message}
183
307
  Run "${BRAND.command} --help" for usage.`);
184
308
  process.exitCode = 2;
185
- } else if (error instanceof CanvasError) {
309
+ } else if (error instanceof CanvasError || error instanceof CanvasApiError) {
186
310
  console.error(`${error.code}: ${error.message}`);
187
311
  process.exitCode = 1;
188
312
  } else {
@@ -0,0 +1,153 @@
1
+ import { a as BoardData, c as ShapeType, S as Shape, E as Endpoint, C as Connector } from './read-UIJlrkwx.js';
2
+ import { PrincipalKind } from 'agentic-identity';
3
+
4
+ /** Options for every call. */
5
+ interface CallOptions {
6
+ signal?: AbortSignal;
7
+ }
8
+ interface ShapeInput {
9
+ id?: string;
10
+ type: ShapeType;
11
+ x: number;
12
+ y: number;
13
+ w?: number;
14
+ h?: number;
15
+ text?: string;
16
+ fill?: string;
17
+ stroke?: string;
18
+ /** A `data:` URL or a `blob:` ref, for an image. */
19
+ src?: string;
20
+ }
21
+ interface ConnectorInput {
22
+ id?: string;
23
+ from: Endpoint;
24
+ to: Endpoint;
25
+ text?: string;
26
+ startArrow?: boolean;
27
+ endArrow?: boolean;
28
+ stroke?: string;
29
+ }
30
+ type ShapePatch = Partial<Pick<ShapeInput, "x" | "y" | "w" | "h" | "text" | "fill" | "stroke" | "src">>;
31
+ type ConnectorPatch = Partial<Pick<ConnectorInput, "from" | "to" | "text" | "startArrow" | "endArrow" | "stroke">>;
32
+ /** A role on a board, from least to most. */
33
+ type BoardRole = "viewer" | "commenter" | "editor" | "owner";
34
+ /** Who holds a grant. A person and an agent may share one id and hold separate roles. */
35
+ type SubjectKind = "human" | "agent";
36
+ interface Subject {
37
+ subjectKind: SubjectKind;
38
+ subjectId: string;
39
+ }
40
+ interface BoardGrant {
41
+ subjectKind: SubjectKind;
42
+ subjectId: string;
43
+ role: BoardRole;
44
+ [field: string]: unknown;
45
+ }
46
+ /** An open invite. It never shows the email. */
47
+ interface BoardInvite {
48
+ id: string;
49
+ role: BoardRole;
50
+ [field: string]: unknown;
51
+ }
52
+ interface InviteInput {
53
+ /** Not `owner`: only a grant makes an owner. */
54
+ role: Exclude<BoardRole, "owner">;
55
+ /** The person to invite. Give this or `handle`. */
56
+ email?: string;
57
+ /** The agent to invite. Give this or `email`. */
58
+ handle?: string;
59
+ /** How long the invite lives, in milliseconds. */
60
+ expiresInMs?: number;
61
+ }
62
+ interface ImageInfo {
63
+ id: string;
64
+ ref: string;
65
+ type: string;
66
+ size: number;
67
+ /** False when the same bytes were stored before. */
68
+ created: boolean;
69
+ }
70
+ interface CanvasClient {
71
+ /** Who the token stands for. */
72
+ me(options?: CallOptions): Promise<{
73
+ actor: string;
74
+ kind: PrincipalKind;
75
+ role: "editor" | "viewer";
76
+ }>;
77
+ /** The first 100 boards, with titles. */
78
+ listBoards(options?: CallOptions): Promise<{
79
+ boards: {
80
+ id: string;
81
+ title: string;
82
+ }[];
83
+ }>;
84
+ createBoard(input?: {
85
+ id?: string;
86
+ title?: string;
87
+ }, options?: CallOptions): Promise<{
88
+ id: string;
89
+ title: string;
90
+ }>;
91
+ readBoard(id: string, options?: CallOptions): Promise<BoardData & {
92
+ id: string;
93
+ }>;
94
+ /** Delete a board for good, with its live room. Returns the id. */
95
+ deleteBoard(id: string, options?: CallOptions): Promise<{
96
+ deleted: string;
97
+ }>;
98
+ /** The board as an SVG document. */
99
+ exportSvg(id: string, options?: CallOptions): Promise<string>;
100
+ addShape(id: string, shape: ShapeInput, options?: CallOptions): Promise<Shape>;
101
+ addConnector(id: string, connector: ConnectorInput, options?: CallOptions): Promise<Connector>;
102
+ /** Draw a flowchart from text such as `A -> B: label`. Returns the new ids. */
103
+ drawFlow(id: string, text: string, options?: CallOptions): Promise<{
104
+ nodes: string[];
105
+ edges: string[];
106
+ }>;
107
+ /** Change fields of one shape or connector, such as its size or text. The patch holds at least one field. */
108
+ update(id: string, element: string, patch: ShapePatch | ConnectorPatch, options?: CallOptions): Promise<Shape | Connector>;
109
+ /** Move an element. Returns the ids that moved: a shape takes its connectors with it. */
110
+ move(id: string, element: string, dx: number, dy: number, options?: CallOptions): Promise<{
111
+ moved: string[];
112
+ }>;
113
+ /** Remove an element. Returns the ids removed. */
114
+ remove(id: string, element: string, options?: CallOptions): Promise<{
115
+ removed: string[];
116
+ }>;
117
+ /** The grants and the open invites of a board. Needs the owner role. */
118
+ access(id: string, options?: CallOptions): Promise<{
119
+ grants: BoardGrant[];
120
+ invites: BoardInvite[];
121
+ }>;
122
+ /** Give a person or an agent a role on a board. Needs the owner role. */
123
+ grant(id: string, input: Subject & {
124
+ role: BoardRole;
125
+ }, options?: CallOptions): Promise<{
126
+ ok: true;
127
+ }>;
128
+ /** Take a role back. The last owner cannot be removed. Needs the owner role. */
129
+ revoke(id: string, input: Subject, options?: CallOptions): Promise<{
130
+ ok: true;
131
+ }>;
132
+ /** Invite a person by email or an agent by handle. Returns the invite id. Needs the owner role. */
133
+ invite(id: string, input: InviteInput, options?: CallOptions): Promise<{
134
+ id: string;
135
+ }>;
136
+ uploadImage(bytes: Uint8Array | ArrayBuffer | Blob, options?: CallOptions): Promise<ImageInfo>;
137
+ /** The bytes of a stored image, and its content type. */
138
+ getImage(id: string, options?: CallOptions): Promise<{
139
+ bytes: Uint8Array;
140
+ type: string;
141
+ }>;
142
+ /** The stored board as a Yjs update. Only a server with live rooms serves it. */
143
+ state(id: string, options?: CallOptions): Promise<Uint8Array>;
144
+ /** Merge a live room's Yjs update into the stored board. Only a server with live rooms serves it. */
145
+ checkpoint(id: string, update: Uint8Array, options?: CallOptions): Promise<{
146
+ elements: number;
147
+ fixed: string[];
148
+ }>;
149
+ }
150
+ /** What the CLI and the MCP server need to share a board. Sharing lives on the server, so it needs a client. */
151
+ type BoardSharing = Pick<CanvasClient, "access" | "grant" | "revoke" | "invite">;
152
+
153
+ export type { BoardSharing as B, CanvasClient as C };
package/dist/client.d.ts CHANGED
@@ -1,7 +1,68 @@
1
- import { E as ErrorCode, a as BoardData, c as ShapeType, S as Shape, d as Endpoint, C as Connector } from './read--Jd3kE13.js';
2
1
  import { PrincipalKind } from 'agentic-identity';
3
- import 'yjs';
4
- import 'zod';
2
+
3
+ type Principal = {
4
+ kind: PrincipalKind;
5
+ id: string;
6
+ };
7
+
8
+ type ErrorCode = "INVALID" | "DUPLICATE_ID" | "NOT_FOUND" | "BAD_REFERENCE" | "LIMIT" | "UNAVAILABLE" | "FORBIDDEN";
9
+ declare const SHAPE_TYPES: readonly ["rect", "ellipse", "diamond", "text", "sticky", "frame", "image"];
10
+ type ShapeType = (typeof SHAPE_TYPES)[number];
11
+ type Endpoint = {
12
+ id: string;
13
+ } | {
14
+ x: number;
15
+ y: number;
16
+ };
17
+ interface Stamp {
18
+ id: string;
19
+ z: number;
20
+ groupId?: string;
21
+ /** Who made the element. Never changes. */
22
+ author: Principal;
23
+ /** Who made the last edit. */
24
+ editedBy: Principal;
25
+ }
26
+ interface Shape extends Stamp {
27
+ type: ShapeType;
28
+ x: number;
29
+ y: number;
30
+ w: number;
31
+ h: number;
32
+ text: string;
33
+ fill?: string;
34
+ stroke?: string;
35
+ src?: string;
36
+ }
37
+ interface Connector extends Stamp {
38
+ type: "connector";
39
+ from: Endpoint;
40
+ to: Endpoint;
41
+ text: string;
42
+ startArrow: boolean;
43
+ endArrow: boolean;
44
+ stroke?: string;
45
+ }
46
+
47
+ interface Labels {
48
+ /** The author is an agent or acts for one. */
49
+ machineMade: boolean;
50
+ /** The last edit came from an agent or one acting for one. */
51
+ lastEditMachineMade: boolean;
52
+ }
53
+ type NodeData = Shape & Labels;
54
+ type EdgeData = Connector & Labels;
55
+ interface BoardData {
56
+ title: string;
57
+ nodes: NodeData[];
58
+ edges: EdgeData[];
59
+ groups: {
60
+ id: string;
61
+ members: string[];
62
+ }[];
63
+ /** Every non-empty text, back to front. */
64
+ texts: string[];
65
+ }
5
66
 
6
67
  interface ClientOptions {
7
68
  /** The API root, for example `https://canvas.example.com/api/v1/canvas`. */
@@ -39,6 +100,36 @@ interface ConnectorInput {
39
100
  }
40
101
  type ShapePatch = Partial<Pick<ShapeInput, "x" | "y" | "w" | "h" | "text" | "fill" | "stroke" | "src">>;
41
102
  type ConnectorPatch = Partial<Pick<ConnectorInput, "from" | "to" | "text" | "startArrow" | "endArrow" | "stroke">>;
103
+ /** A role on a board, from least to most. */
104
+ type BoardRole = "viewer" | "commenter" | "editor" | "owner";
105
+ /** Who holds a grant. A person and an agent may share one id and hold separate roles. */
106
+ type SubjectKind = "human" | "agent";
107
+ interface Subject {
108
+ subjectKind: SubjectKind;
109
+ subjectId: string;
110
+ }
111
+ interface BoardGrant {
112
+ subjectKind: SubjectKind;
113
+ subjectId: string;
114
+ role: BoardRole;
115
+ [field: string]: unknown;
116
+ }
117
+ /** An open invite. It never shows the email. */
118
+ interface BoardInvite {
119
+ id: string;
120
+ role: BoardRole;
121
+ [field: string]: unknown;
122
+ }
123
+ interface InviteInput {
124
+ /** Not `owner`: only a grant makes an owner. */
125
+ role: Exclude<BoardRole, "owner">;
126
+ /** The person to invite. Give this or `handle`. */
127
+ email?: string;
128
+ /** The agent to invite. Give this or `email`. */
129
+ handle?: string;
130
+ /** How long the invite lives, in milliseconds. */
131
+ expiresInMs?: number;
132
+ }
42
133
  interface ImageInfo {
43
134
  id: string;
44
135
  ref: string;
@@ -104,6 +195,25 @@ interface CanvasClient {
104
195
  remove(id: string, element: string, options?: CallOptions): Promise<{
105
196
  removed: string[];
106
197
  }>;
198
+ /** The grants and the open invites of a board. Needs the owner role. */
199
+ access(id: string, options?: CallOptions): Promise<{
200
+ grants: BoardGrant[];
201
+ invites: BoardInvite[];
202
+ }>;
203
+ /** Give a person or an agent a role on a board. Needs the owner role. */
204
+ grant(id: string, input: Subject & {
205
+ role: BoardRole;
206
+ }, options?: CallOptions): Promise<{
207
+ ok: true;
208
+ }>;
209
+ /** Take a role back. The last owner cannot be removed. Needs the owner role. */
210
+ revoke(id: string, input: Subject, options?: CallOptions): Promise<{
211
+ ok: true;
212
+ }>;
213
+ /** Invite a person by email or an agent by handle. Returns the invite id. Needs the owner role. */
214
+ invite(id: string, input: InviteInput, options?: CallOptions): Promise<{
215
+ id: string;
216
+ }>;
107
217
  uploadImage(bytes: Uint8Array | ArrayBuffer | Blob, options?: CallOptions): Promise<ImageInfo>;
108
218
  /** The bytes of a stored image, and its content type. */
109
219
  getImage(id: string, options?: CallOptions): Promise<{
@@ -118,6 +228,10 @@ interface CanvasClient {
118
228
  fixed: string[];
119
229
  }>;
120
230
  }
231
+ /** What the CLI and the MCP server need to share a board. Sharing lives on the server, so it needs a client. */
232
+ type BoardSharing = Pick<CanvasClient, "access" | "grant" | "revoke" | "invite">;
233
+ /** `kind:id` of a person or an agent, as the CLI and the tools write it. */
234
+ declare function parseSubject(text: string): Subject;
121
235
  declare function createClient(options: ClientOptions): CanvasClient;
122
236
 
123
- export { type ApiErrorCode, type CallOptions, CanvasApiError, type CanvasClient, type ClientOptions, type ConnectorInput, type ConnectorPatch, type ImageInfo, type ShapeInput, type ShapePatch, createClient };
237
+ export { type ApiErrorCode, type BoardGrant, type BoardInvite, type BoardRole, type BoardSharing, type CallOptions, CanvasApiError, type CanvasClient, type ClientOptions, type ConnectorInput, type ConnectorPatch, type ImageInfo, type InviteInput, type ShapeInput, type ShapePatch, type Subject, type SubjectKind, createClient, parseSubject };
package/dist/client.js CHANGED
@@ -9,6 +9,12 @@ var CanvasApiError = class extends Error {
9
9
  code;
10
10
  status;
11
11
  };
12
+ function parseSubject(text) {
13
+ const at = text.indexOf(":");
14
+ const kind = text.slice(0, at);
15
+ if (at < 1 || kind !== "human" && kind !== "agent" || at === text.length - 1) throw new TypeError(`"${text}" is not human:<id> or agent:<id>`);
16
+ return { subjectKind: kind, subjectId: text.slice(at + 1) };
17
+ }
12
18
  function createClient(options) {
13
19
  const { token } = options;
14
20
  const base = options.baseUrl.replace(/\/+$/, "");
@@ -66,6 +72,10 @@ function createClient(options) {
66
72
  update: (id, element, patch, o) => json("PATCH", `/boards/${seg(id)}/elements/${seg(element)}`, patch, o),
67
73
  move: (id, element, dx, dy, o) => json("POST", `/boards/${seg(id)}/move`, { element, dx, dy }, o),
68
74
  remove: (id, element, o) => json("DELETE", `/boards/${seg(id)}/elements/${seg(element)}`, void 0, o),
75
+ access: (id, o) => json("GET", `/boards/${seg(id)}/access`, void 0, o),
76
+ grant: (id, input, o) => json("POST", `/boards/${seg(id)}/grants`, input, o),
77
+ revoke: (id, { subjectKind, subjectId }, o) => json("DELETE", `/boards/${seg(id)}/grants?subjectKind=${seg(subjectKind)}&subjectId=${seg(subjectId)}`, void 0, o),
78
+ invite: (id, input, o) => json("POST", `/boards/${seg(id)}/invites`, input, o),
69
79
  uploadImage: async (data, o) => parse(await send("POST", "/images", { body: data, contentType: "application/octet-stream" }, o), o),
70
80
  getImage: async (id, o) => {
71
81
  const response = await send("GET", `/images/${seg(id)}`, {}, o);
@@ -77,5 +87,6 @@ function createClient(options) {
77
87
  }
78
88
  export {
79
89
  CanvasApiError,
80
- createClient
90
+ createClient,
91
+ parseSubject
81
92
  };
package/dist/embed.d.ts CHANGED
@@ -1,10 +1,28 @@
1
1
  import * as react from 'react';
2
- import { CanvasClient } from './client.js';
2
+ import { C as CanvasClient } from './client-Dyd30StF.js';
3
3
  import { PrincipalKind } from 'agentic-identity';
4
- import './read--Jd3kE13.js';
5
- import 'yjs';
4
+ import { SyncClient, Status } from 'agentic-realtime/client';
5
+ import * as Y from 'yjs';
6
+ import './read-UIJlrkwx.js';
6
7
  import 'zod';
7
8
 
9
+ /** The presence of a room: who is here. A `SyncClient` of agentic-realtime has these. */
10
+ type RoomPresence = Pick<SyncClient, "peers" | "onPeers">;
11
+ /**
12
+ * A board that the app keeps itself, in its own Yjs room. The UI reads and writes it, and does not call a client, poll, or
13
+ * open a live room. The app keeps the document and its room.
14
+ */
15
+ interface HostBoard {
16
+ /** The Yjs document. The UI opens it as a `Board`: its `elements` and `meta` maps, as this package stores them. */
17
+ doc: Y.Doc;
18
+ /** The presence of the app's room. With it, the UI shows who else is here. */
19
+ presence?: RoomPresence;
20
+ /** The connection state now. With neither `status` nor `onStatus`, the UI shows the board as live. With only `onStatus`, it shows not live until the room reports "connected". */
21
+ status?: () => Status;
22
+ /** A way to hear when the connection state changes. It returns a function that stops the listener. The UI reads it once each time it opens `doc`. */
23
+ onStatus?: (listener: (status: Status) => void) => () => void;
24
+ }
25
+
8
26
  interface RoomOptions {
9
27
  /** The API root, for example `https://canvas.example.com/api/v1/canvas`. */
10
28
  baseUrl: string;
@@ -16,14 +34,21 @@ interface RoomOptions {
16
34
  interface BoardPrincipal {
17
35
  kind: PrincipalKind;
18
36
  name: string;
37
+ /** Stamped on each element the principal writes in host mode. The default is `name`, which must then be a valid id. A client mode board takes the actor from its token. */
38
+ id?: string;
19
39
  /** Only `editor` can change the board. Any other value, or no value, opens it read only. */
20
40
  role?: "editor" | "viewer";
21
41
  }
22
42
  interface BoardEditorOptions {
23
43
  /** The board id. */
24
44
  boardId: string;
25
- /** Loads and changes the board. A client from `createClient` in `agentic-canvas/client` works. */
26
- client: CanvasClient;
45
+ /** Loads and changes the board. A client from `createClient` in `agentic-canvas/client` works. Needed unless `host` is given. */
46
+ client?: CanvasClient;
47
+ /**
48
+ * The board in your own Yjs document. The UI then reads and writes that document, and calls no client, does not poll, and
49
+ * opens no live room. `client` or `room` together with `host` throws a `TypeError`.
50
+ */
51
+ host?: HostBoard;
27
52
  principal: BoardPrincipal;
28
53
  /** Open the board read only, even for an editor. */
29
54
  readOnly?: boolean;
@@ -43,4 +68,4 @@ interface MountedBoard {
43
68
  /** Render the board into an element, for an app that does not use React itself. */
44
69
  declare function mount(element: HTMLElement, options: BoardEditorOptions): MountedBoard;
45
70
 
46
- export { BoardEditor, type BoardEditorOptions, type BoardPrincipal, CanvasClient, type MountedBoard, type RoomOptions, mount };
71
+ export { BoardEditor, type BoardEditorOptions, type BoardPrincipal, CanvasClient, type HostBoard, type MountedBoard, type RoomOptions, mount };