agentic-canvas 0.12.1 → 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 +44 -7
- package/dist/account.d.ts +33 -0
- package/dist/account.js +213 -0
- package/dist/{chunk-5II2WZIL.js → chunk-5QUL3URJ.js} +9 -151
- package/dist/chunk-A2F73EQJ.js +188 -0
- package/dist/{chunk-EDQNA2SU.js → chunk-BKG3FXX3.js} +1 -1
- package/dist/{chunk-Z6KLGAFP.js → chunk-GLNF43N4.js} +4 -2
- package/dist/{chunk-7EWHZCKY.js → chunk-YKUHPOQV.js} +20 -2
- package/dist/{chunk-MVWUIBGV.js → chunk-ZP6BLCB7.js} +57 -0
- package/dist/cli.js +22 -4
- package/dist/{client-Dyd30StF.d.ts → client-BKTZ1pM5.d.ts} +34 -2
- package/dist/client.d.ts +34 -2
- package/dist/client.js +3 -0
- package/dist/embed.d.ts +3 -2
- package/dist/embed.js +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -4
- package/dist/live-worker.d.ts +4 -3
- package/dist/live-worker.js +5 -4
- package/dist/live.d.ts +3 -2
- package/dist/live.js +2 -2
- package/dist/principal-blhsFbdS.d.ts +23 -0
- package/dist/{read-UIJlrkwx.d.ts → read-DeS2Zaz8.d.ts} +24 -22
- package/dist/{service-ojpxVNVl.d.ts → service-BNEH2Z12.d.ts} +2 -1
- package/dist/worker.d.ts +20 -5
- package/dist/worker.js +5 -4
- package/llms.txt +2 -2
- package/package.json +5 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isBoardId,
|
|
3
|
+
isStoreId
|
|
4
|
+
} from "./chunk-GLNF43N4.js";
|
|
5
|
+
import {
|
|
6
|
+
CanvasError
|
|
7
|
+
} from "./chunk-ZP6BLCB7.js";
|
|
8
|
+
|
|
9
|
+
// src/access.ts
|
|
10
|
+
import { createSharing, EraseRefusedError, hasScope, ROLES, SUBJECT_KINDS } from "agentic-identity";
|
|
11
|
+
var RESOURCE_TYPE = "canvas";
|
|
12
|
+
var SHARE_ROLES = ROLES.filter((r) => r !== "owner");
|
|
13
|
+
var BAD_INPUT = /^(give an email or a handle|invalid handle|invalid email|invalid subject( kind "[^"]*")?|the last owner cannot be removed|expiresInMs must be .*|expiresAt must be .*|invalid role .*)$/;
|
|
14
|
+
function refuse(error) {
|
|
15
|
+
if (error instanceof EraseRefusedError) throw new CanvasError("INVALID", error.message);
|
|
16
|
+
if (error instanceof Error && error.message === "forbidden") throw new CanvasError("FORBIDDEN", "you may not do this on this board");
|
|
17
|
+
if (error instanceof Error && BAD_INPUT.test(error.message)) throw new CanvasError("INVALID", error.message);
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
var oneOf = (value, list, name) => {
|
|
21
|
+
if (typeof value !== "string" || !list.includes(value)) throw new CanvasError("INVALID", `${name} must be one of: ${list.join(", ")}`);
|
|
22
|
+
return value;
|
|
23
|
+
};
|
|
24
|
+
var storeId = (value, name) => {
|
|
25
|
+
if (typeof value !== "string" || !isStoreId(value)) throw new CanvasError("INVALID", `${name} must be 1 to 128 of A-Z, a-z, 0-9, _ and -, and not like __x__`);
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
var word = (value, name) => {
|
|
29
|
+
if (typeof value !== "string" || value === "") throw new CanvasError("INVALID", `${name} must be a non-empty string`);
|
|
30
|
+
return value;
|
|
31
|
+
};
|
|
32
|
+
var lifetime = (value, name) => {
|
|
33
|
+
if (value === void 0) return void 0;
|
|
34
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new CanvasError("INVALID", `${name} must be a whole number of milliseconds above 0`);
|
|
35
|
+
return value;
|
|
36
|
+
};
|
|
37
|
+
var CanvasAccess = class {
|
|
38
|
+
constructor(store, options = {}) {
|
|
39
|
+
this.store = store;
|
|
40
|
+
this.sharing = createSharing(store, options);
|
|
41
|
+
this.now = options.now ?? Date.now;
|
|
42
|
+
}
|
|
43
|
+
store;
|
|
44
|
+
sharing;
|
|
45
|
+
now;
|
|
46
|
+
/** A board id that the Store refuses names no board. The gate checks this for a role holder: a token of the dev list skips the gate. */
|
|
47
|
+
known(id) {
|
|
48
|
+
if (!isBoardId(id)) throw new CanvasError("NOT_FOUND", `no board "${id}"`);
|
|
49
|
+
}
|
|
50
|
+
/** Make the actor the owner of a new board. */
|
|
51
|
+
async register(ctx, id) {
|
|
52
|
+
await this.sharing.createResource(ctx, { id, type: RESOURCE_TYPE }).catch((error) => {
|
|
53
|
+
if (error instanceof Error && error.message === "resource exists") throw new CanvasError("DUPLICATE_ID", `board "${id}" already exists`);
|
|
54
|
+
if (error instanceof Error && error.message === "resource id has old audit entries") throw new CanvasError("UNAVAILABLE", "this board id was used a moment ago. Try again, or use another id");
|
|
55
|
+
return refuse(error);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** Remove the sharing records of a board. Needs an owner. */
|
|
59
|
+
async unregister(ctx, id) {
|
|
60
|
+
await this.sharing.deleteResource(ctx, id).catch(refuse);
|
|
61
|
+
}
|
|
62
|
+
/** True if the board has sharing records: it was made by an identity credential. */
|
|
63
|
+
async registered(id) {
|
|
64
|
+
if (!isBoardId(id)) return false;
|
|
65
|
+
return (await this.store.get("resources", id))?.type === RESOURCE_TYPE;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* True if the caller may do `action` on the board. A link token adds the role of its link. It writes an audit entry.
|
|
69
|
+
* An id that belongs to a resource of another type, in a Store shared with another app, is no board: it is denied.
|
|
70
|
+
*/
|
|
71
|
+
async allowed(ctx, action, id, linkToken) {
|
|
72
|
+
if (!await this.registered(id)) return false;
|
|
73
|
+
return (await this.sharing.can(ctx, action, id, linkToken === void 0 ? {} : { linkToken })).allowed;
|
|
74
|
+
}
|
|
75
|
+
/** Throw NOT_FOUND for a caller who cannot read the board, and FORBIDDEN for one who can read it but not do `action`. */
|
|
76
|
+
async require(ctx, action, id, linkToken) {
|
|
77
|
+
if (await this.allowed(ctx, action, id, linkToken)) return;
|
|
78
|
+
if (action === "read" || !await this.allowed(ctx, "read", id, linkToken)) throw new CanvasError("NOT_FOUND", `no board "${id}"`);
|
|
79
|
+
throw new CanvasError("FORBIDDEN", `this needs the ${action === "admin" ? "owner" : "editor"} role on the board`);
|
|
80
|
+
}
|
|
81
|
+
/** The ids of the boards a person or agent holds a role on, the first 100. A credential without `canvas:read` gets none. */
|
|
82
|
+
async readable(ctx) {
|
|
83
|
+
const root = ctx.chain[0];
|
|
84
|
+
if (!root || !SUBJECT_KINDS.includes(root.kind) || !hasScope(ctx.scopes, `${RESOURCE_TYPE}:read`)) return [];
|
|
85
|
+
const ids = [];
|
|
86
|
+
let cursor;
|
|
87
|
+
for (let pages = 0; pages < 10 && ids.length < 100; pages++) {
|
|
88
|
+
const page = await this.store.query("grants", { where: { subjectKind: root.kind, subjectId: root.id }, limit: 100, ...cursor === void 0 ? {} : { cursor } });
|
|
89
|
+
for (const { data } of page.items) {
|
|
90
|
+
if (ids.length >= 100 || typeof data.resourceId !== "string" || !ROLES.includes(data.role)) continue;
|
|
91
|
+
if (await this.registered(data.resourceId)) ids.push(data.resourceId);
|
|
92
|
+
}
|
|
93
|
+
if ((cursor = page.nextCursor) === void 0) break;
|
|
94
|
+
}
|
|
95
|
+
return ids;
|
|
96
|
+
}
|
|
97
|
+
async grant(ctx, id, input) {
|
|
98
|
+
this.known(id);
|
|
99
|
+
const subject = { kind: oneOf(input.subjectKind, SUBJECT_KINDS, "subjectKind"), id: storeId(input.subjectId, "subjectId") };
|
|
100
|
+
await this.sharing.grant(ctx, id, subject, oneOf(input.role, ROLES, "role")).catch(refuse);
|
|
101
|
+
return { ok: true };
|
|
102
|
+
}
|
|
103
|
+
async revoke(ctx, id, input) {
|
|
104
|
+
this.known(id);
|
|
105
|
+
const subject = { kind: oneOf(input.subjectKind, SUBJECT_KINDS, "subjectKind"), id: storeId(input.subjectId, "subjectId") };
|
|
106
|
+
await this.sharing.revokeGrant(ctx, id, subject).catch(refuse);
|
|
107
|
+
return { ok: true };
|
|
108
|
+
}
|
|
109
|
+
/** The grants and the invites on a board. An invite never shows its email. `listLinks` lists the links, and never a token. */
|
|
110
|
+
async list(ctx, id) {
|
|
111
|
+
this.known(id);
|
|
112
|
+
const [grants, invites] = await Promise.all([this.sharing.listGrants(ctx, id, { limit: 200 }), this.sharing.listInvites(ctx, id, { limit: 200 })]).catch(refuse);
|
|
113
|
+
return { grants: grants.grants, invites: invites.invites };
|
|
114
|
+
}
|
|
115
|
+
async invite(ctx, id, input) {
|
|
116
|
+
this.known(id);
|
|
117
|
+
const role = oneOf(input.role, SHARE_ROLES, "role");
|
|
118
|
+
const expiresInMs = lifetime(input.expiresInMs, "expiresInMs");
|
|
119
|
+
const who = input.email !== void 0 ? { email: word(input.email, "email") } : input.handle !== void 0 ? { handle: word(input.handle, "handle") } : {};
|
|
120
|
+
if (input.email !== void 0 && input.handle !== void 0) throw new CanvasError("INVALID", "give an email or a handle, not both");
|
|
121
|
+
return this.sharing.createInvite(ctx, id, { role, ...who, ...expiresInMs === void 0 ? {} : { expiresInMs } }).catch(refuse);
|
|
122
|
+
}
|
|
123
|
+
/** The email comes from a verified sign-in and the handle from the agent's own record, never from the request. */
|
|
124
|
+
async accept(ctx, inviteId) {
|
|
125
|
+
if (!isStoreId(inviteId)) throw new CanvasError("FORBIDDEN", "you may not do this on this board");
|
|
126
|
+
const user = await this.store.get("users", ctx.principalId);
|
|
127
|
+
const agent = await this.store.get("principals", ctx.principalId);
|
|
128
|
+
const identity = {
|
|
129
|
+
...user?.emailVerified === true && typeof user.email === "string" ? { email: user.email } : {},
|
|
130
|
+
...typeof agent?.handle === "string" ? { handle: agent.handle } : {}
|
|
131
|
+
};
|
|
132
|
+
await this.sharing.acceptInvite(ctx, word(inviteId, "id"), identity).catch(refuse);
|
|
133
|
+
return { ok: true };
|
|
134
|
+
}
|
|
135
|
+
/** The token shows once. Only its hash is stored. */
|
|
136
|
+
async createLink(ctx, id, input) {
|
|
137
|
+
this.known(id);
|
|
138
|
+
const role = oneOf(input.role, SHARE_ROLES, "role");
|
|
139
|
+
const expiresInMs = lifetime(input.expiresInMs, "expiresInMs");
|
|
140
|
+
if (input.signedInOnly !== void 0 && typeof input.signedInOnly !== "boolean") throw new CanvasError("INVALID", "signedInOnly must be true or false");
|
|
141
|
+
return this.sharing.createLink(ctx, id, { role, signedInOnly: input.signedInOnly === true, ...expiresInMs === void 0 ? {} : { expiresAt: this.now() + expiresInMs } }).catch(refuse);
|
|
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
|
+
}
|
|
175
|
+
/** Revoke a link of this board. A link of another board is NOT_FOUND here, even for an owner of both. */
|
|
176
|
+
async revokeLink(ctx, id, linkId) {
|
|
177
|
+
this.known(id);
|
|
178
|
+
const link = isStoreId(linkId) ? await this.store.get("links", linkId) : null;
|
|
179
|
+
if (!link || link.resourceId !== id) throw new CanvasError("NOT_FOUND", "no such link on this board");
|
|
180
|
+
await this.sharing.revokeLink(ctx, linkId).catch(refuse);
|
|
181
|
+
return { ok: true };
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export {
|
|
186
|
+
RESOURCE_TYPE,
|
|
187
|
+
CanvasAccess
|
|
188
|
+
};
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
drawFlow,
|
|
6
6
|
toSvg
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BKG3FXX3.js";
|
|
8
8
|
import {
|
|
9
9
|
Board,
|
|
10
10
|
CanvasError,
|
|
11
11
|
principalSchema,
|
|
12
12
|
readBoard
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-ZP6BLCB7.js";
|
|
14
14
|
|
|
15
15
|
// src/service.ts
|
|
16
16
|
import * as Y from "yjs";
|
|
@@ -224,6 +224,8 @@ function decodeStored(doc, id, decode) {
|
|
|
224
224
|
|
|
225
225
|
export {
|
|
226
226
|
MAX_LIVE_BYTES,
|
|
227
|
+
toBase64,
|
|
228
|
+
fromBase64,
|
|
227
229
|
isStoreId,
|
|
228
230
|
isBoardId,
|
|
229
231
|
CanvasService
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
CanvasError,
|
|
3
3
|
PRINCIPAL_KINDS,
|
|
4
4
|
SHAPE_TYPES
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ZP6BLCB7.js";
|
|
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) {
|
|
@@ -94,6 +94,7 @@ var isBindable = (type) => typeof type === "string" && type !== "connector" && t
|
|
|
94
94
|
var isValidZ = (value) => typeof value === "number" && Number.isFinite(value) && Math.abs(value) <= MAX_Z;
|
|
95
95
|
var clampZ = (value) => Math.min(MAX_Z, Math.max(-MAX_Z, value));
|
|
96
96
|
var samePrincipal = (a, b) => a.kind === b.kind && a.id === b.id;
|
|
97
|
+
var isStamp = (value, who) => typeof value === "object" && value !== null && value.kind === who.kind && value.id === who.id;
|
|
97
98
|
var hasId = (e) => typeof e === "object" && e !== null && typeof e.id === "string";
|
|
98
99
|
var Board = class _Board {
|
|
99
100
|
doc;
|
|
@@ -348,6 +349,62 @@ var Board = class _Board {
|
|
|
348
349
|
this.write(plan, by);
|
|
349
350
|
return plan.map(([memberId]) => memberId);
|
|
350
351
|
}
|
|
352
|
+
/** Every stored entry as it is, valid or not, with its key as `id`. */
|
|
353
|
+
rawElements() {
|
|
354
|
+
return [...this.elements].map(([key, entry]) => ({ ...entry instanceof Y.Map ? entry.toJSON() : {}, id: key }));
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* The raw stored elements that `who` made (`author`) and the ids of those that `who` last edited but did not make.
|
|
358
|
+
* It reads entries as they are, with no element checks, so a damaged entry still shows. Account export uses it.
|
|
359
|
+
*/
|
|
360
|
+
stampedBy(who) {
|
|
361
|
+
const authored = [];
|
|
362
|
+
const edited = [];
|
|
363
|
+
for (const [key, entry] of this.elements) {
|
|
364
|
+
if (!(entry instanceof Y.Map)) continue;
|
|
365
|
+
if (isStamp(entry.get("author"), who)) authored.push({ ...entry.toJSON(), id: key });
|
|
366
|
+
else if (isStamp(entry.get("editedBy"), who)) edited.push(key);
|
|
367
|
+
}
|
|
368
|
+
return { authored, edited, titleBy: isStamp(this.meta.get("titleBy"), who) };
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Erase `who` from the board. An element they made is deleted alone: its members go to its parent group, and a
|
|
372
|
+
* connector of someone else that was bound to it goes too, because it would be hidden. An element they only edited
|
|
373
|
+
* keeps its content, and its editor becomes `(erased)`. A title they set is cleared. Idempotent. Returns the counts.
|
|
374
|
+
*/
|
|
375
|
+
eraseStamps(who, erased) {
|
|
376
|
+
const mine = /* @__PURE__ */ new Set();
|
|
377
|
+
for (const [key, entry] of this.elements) if (entry instanceof Y.Map && isStamp(entry.get("author"), who)) mine.add(key);
|
|
378
|
+
const counts = { authored: mine.size, edited: 0, dangling: 0, title: isStamp(this.meta.get("titleBy"), who) };
|
|
379
|
+
this.doc.transact(() => {
|
|
380
|
+
for (const key of mine) {
|
|
381
|
+
const parent = this.elements.get(key).get("groupId");
|
|
382
|
+
for (const [, other] of this.elements) {
|
|
383
|
+
if (other instanceof Y.Map && other.get("groupId") === key) {
|
|
384
|
+
if (typeof parent === "string") other.set("groupId", parent);
|
|
385
|
+
else other.delete("groupId");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
mine.forEach((key) => this.elements.delete(key));
|
|
390
|
+
for (const [key, entry] of [...this.elements]) {
|
|
391
|
+
if (!(entry instanceof Y.Map)) continue;
|
|
392
|
+
const ends = [entry.get("from"), entry.get("to")];
|
|
393
|
+
if (entry.get("type") === "connector" && ends.some((e) => hasId(e) && mine.has(e.id))) {
|
|
394
|
+
this.elements.delete(key);
|
|
395
|
+
counts.dangling += 1;
|
|
396
|
+
} else if (isStamp(entry.get("editedBy"), who)) {
|
|
397
|
+
entry.set("editedBy", erased);
|
|
398
|
+
counts.edited += 1;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (counts.title) {
|
|
402
|
+
this.meta.delete("title");
|
|
403
|
+
this.meta.set("titleBy", erased);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
return counts;
|
|
407
|
+
}
|
|
351
408
|
/** Delete an element. A shape takes its connectors and a group takes its members. Returns the ids removed. */
|
|
352
409
|
remove(elementId) {
|
|
353
410
|
this.require(elementId);
|
package/dist/cli.js
CHANGED
|
@@ -5,18 +5,18 @@ 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";
|
|
12
12
|
import {
|
|
13
13
|
CanvasService
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-GLNF43N4.js";
|
|
15
15
|
import "./chunk-NWQED42L.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-BKG3FXX3.js";
|
|
17
17
|
import {
|
|
18
18
|
CanvasError
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-ZP6BLCB7.js";
|
|
20
20
|
|
|
21
21
|
// src/cli.ts
|
|
22
22
|
import { readFileSync, writeFileSync } from "fs";
|
|
@@ -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);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as BoardData, c as ShapeType, S as Shape, E as Endpoint, C as Connector } from './read-
|
|
1
|
+
import { a as BoardData, c as ShapeType, S as Shape, E as Endpoint, C as Connector } from './read-DeS2Zaz8.js';
|
|
2
2
|
import { PrincipalKind } from 'agentic-identity';
|
|
3
3
|
|
|
4
4
|
/** Options for every call. */
|
|
@@ -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,9 +1,10 @@
|
|
|
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';
|
|
6
|
-
import './read-
|
|
6
|
+
import './read-DeS2Zaz8.js';
|
|
7
|
+
import './principal-blhsFbdS.js';
|
|
7
8
|
import 'zod';
|
|
8
9
|
|
|
9
10
|
/** The presence of a room: who is here. A `SyncClient` of agentic-realtime has these. */
|
package/dist/embed.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { B as Board
|
|
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,
|
|
3
|
-
import {
|
|
4
|
-
export {
|
|
1
|
+
import { B as Board } from './read-DeS2Zaz8.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, r as readBoard } from './read-DeS2Zaz8.js';
|
|
3
|
+
import { P as Principal } from './principal-blhsFbdS.js';
|
|
4
|
+
export { p as principalSchema } from './principal-blhsFbdS.js';
|
|
5
|
+
import { B as BoardAccess } from './service-BNEH2Z12.js';
|
|
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';
|
|
5
7
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
-
import { B as BoardSharing } from './client-
|
|
8
|
+
import { B as BoardSharing } from './client-BKTZ1pM5.js';
|
|
7
9
|
export { FileStoreOptions, createFileStore, openLocalStore } from './store-file.js';
|
|
8
10
|
export { PRINCIPAL_KINDS, PrincipalKind, isMachineMade } from 'agentic-identity';
|
|
9
11
|
import 'yjs';
|
package/dist/index.js
CHANGED
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
operatorActor,
|
|
7
7
|
runMcp,
|
|
8
8
|
svgToPng
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-YKUHPOQV.js";
|
|
10
10
|
import {
|
|
11
11
|
createFileStore,
|
|
12
12
|
openLocalStore
|
|
13
13
|
} from "./chunk-NRLF6MGR.js";
|
|
14
14
|
import {
|
|
15
15
|
CanvasService
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-GLNF43N4.js";
|
|
17
17
|
import "./chunk-NWQED42L.js";
|
|
18
18
|
import {
|
|
19
19
|
MAX_FLOW_EDGES,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
layoutFlow,
|
|
23
23
|
parseFlow,
|
|
24
24
|
toSvg
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-BKG3FXX3.js";
|
|
26
26
|
import {
|
|
27
27
|
Board,
|
|
28
28
|
CanvasError,
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
isMachineMade,
|
|
34
34
|
principalSchema,
|
|
35
35
|
readBoard
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-ZP6BLCB7.js";
|
|
37
37
|
export {
|
|
38
38
|
ActorConfigError,
|
|
39
39
|
BRAND,
|
package/dist/live-worker.d.ts
CHANGED
|
@@ -5,10 +5,11 @@ import 'agentic-identity';
|
|
|
5
5
|
import 'agentic-realtime';
|
|
6
6
|
import 'agentic-identity/cloudflare';
|
|
7
7
|
import 'hono';
|
|
8
|
-
import './
|
|
9
|
-
import 'yjs';
|
|
8
|
+
import './principal-blhsFbdS.js';
|
|
10
9
|
import 'zod';
|
|
11
|
-
import './service-
|
|
10
|
+
import './service-BNEH2Z12.js';
|
|
11
|
+
import './read-DeS2Zaz8.js';
|
|
12
|
+
import 'yjs';
|
|
12
13
|
|
|
13
14
|
type Namespace = Parameters<typeof createDurableObjectRealtime>[0];
|
|
14
15
|
interface LiveEnv extends WorkerEnv {
|
package/dist/live-worker.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createApp
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-5QUL3URJ.js";
|
|
4
|
+
import "./chunk-A2F73EQJ.js";
|
|
5
|
+
import "./chunk-GLNF43N4.js";
|
|
5
6
|
import "./chunk-NWQED42L.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-BKG3FXX3.js";
|
|
7
8
|
import {
|
|
8
9
|
CanvasError
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-ZP6BLCB7.js";
|
|
10
11
|
|
|
11
12
|
// src/live-worker.ts
|
|
12
13
|
import { parseRegion } from "agentic-identity";
|
package/dist/live.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { B as Board,
|
|
1
|
+
import { B as Board, a as BoardData, S as Shape, C as Connector, b as CanvasElement } from './read-DeS2Zaz8.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-BNEH2Z12.js';
|
|
4
|
+
import { P as Principal } from './principal-blhsFbdS.js';
|
|
4
5
|
import 'yjs';
|
|
5
6
|
import 'agentic-identity';
|
|
6
7
|
import 'zod';
|