agentic-drive 0.4.0 → 0.6.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/embed.js ADDED
@@ -0,0 +1,412 @@
1
+ import {
2
+ DriveApiError,
3
+ createClient
4
+ } from "./chunk-S4AGCKXW.js";
5
+
6
+ // ui/embed.tsx
7
+ import { useEffect as useEffect2, useState as useState2 } from "react";
8
+ import { createRoot } from "react-dom/client";
9
+ import { FolderIcon as FolderIcon2, SidebarNav as SidebarNav2, ToastProvider, TooltipProvider } from "agentic-design/react";
10
+
11
+ // ui/App.tsx
12
+ import { useEffect, useMemo, useRef, useState } from "react";
13
+ import {
14
+ AppShell,
15
+ Button,
16
+ Dialog,
17
+ DialogClose,
18
+ EmptyState,
19
+ FileIcon,
20
+ FileList,
21
+ FileRow,
22
+ FolderIcon,
23
+ IconButton,
24
+ Input,
25
+ Menu,
26
+ MoreIcon,
27
+ PlusIcon,
28
+ PresenceChip,
29
+ Select,
30
+ ShareDialog,
31
+ SidebarNav,
32
+ Toolbar,
33
+ ToolbarButton,
34
+ useTheme,
35
+ useToast
36
+ } from "agentic-design/react";
37
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
38
+ var errorText = (e) => e instanceof DriveApiError ? `${e.message} (${e.code})` : e instanceof Error ? e.message : "Something went wrong";
39
+ var byName = (a, b) => (a.kind === "folder" ? 0 : 1) - (b.kind === "folder" ? 0 : 1) || a.name.localeCompare(b.name);
40
+ var who = (a, owner) => a.kind === "human" ? { id: a.id, name: a.id, kind: "person" } : { id: a.id, name: a.id, kind: "agent", actingFor: owner };
41
+ var MachineLabel = ({ item }) => item.machineMade ? /* @__PURE__ */ jsx("span", { className: "ad-pill", children: "Created by agent" }) : null;
42
+ function useLoad(fn) {
43
+ const [state, set] = useState({ status: "loading" });
44
+ const [tick, setTick] = useState(0);
45
+ useEffect(() => {
46
+ let live = true;
47
+ fn().then((data) => live && set({ status: "ok", data }), (e) => live && set({ status: "error", error: errorText(e) }));
48
+ return () => {
49
+ live = false;
50
+ };
51
+ }, [tick]);
52
+ return [state, () => setTick((n) => n + 1)];
53
+ }
54
+ function Status({ state, retry, children }) {
55
+ if (state.status === "loading") return /* @__PURE__ */ jsxs("p", { role: "status", className: "drive-status", children: [
56
+ /* @__PURE__ */ jsx("span", { className: "ad-spinner", "aria-hidden": "true" }),
57
+ " Loading\u2026"
58
+ ] });
59
+ if (state.status === "error") return /* @__PURE__ */ jsx("div", { role: "alert", children: /* @__PURE__ */ jsx(EmptyState, { title: "Could not load this page", description: state.error, action: /* @__PURE__ */ jsx(Button, { onClick: retry, children: "Try again" }) }) });
60
+ return /* @__PURE__ */ jsx(Fragment, { children });
61
+ }
62
+ var routeParts = (hash) => hash.replace(/^#\/?/, "").split("/");
63
+ function Routes({ hash, client, origin, go }) {
64
+ const parts = routeParts(hash);
65
+ if (parts[0] === "trash") return /* @__PURE__ */ jsx(TrashView, { client }, "trash");
66
+ if (parts[0] === "f" && parts[1]) return /* @__PURE__ */ jsx(Download, { client, id: parts[1], origin, go }, hash);
67
+ return /* @__PURE__ */ jsx(Browser, { client, loc: parts[0] || "/", origin }, hash);
68
+ }
69
+ function Browser({ client, loc, origin }) {
70
+ const toast = useToast();
71
+ const [view, setView] = useState(() => localStorage.getItem("agentic-drive-view") === "grid" ? "grid" : "list");
72
+ const [dlg, setDlg] = useState(null);
73
+ const [drag, setDrag] = useState(false);
74
+ const [busy, setBusy] = useState(false);
75
+ const input = useRef(null);
76
+ const [state, reload] = useLoad(async () => {
77
+ const root = await client.stat("/");
78
+ const folder = loc === "/" ? root : await client.stat(loc);
79
+ const crumbs = [];
80
+ try {
81
+ for (let n = folder; n.id !== root.id; n = await client.stat(n.parentId)) crumbs.unshift(n);
82
+ } catch (e) {
83
+ if (!(e instanceof DriveApiError && (e.code === "NOT_FOUND" || e.code === "FORBIDDEN"))) throw e;
84
+ }
85
+ const children = async (f2) => (await client.list(f2.id === root.id ? "/" : f2.id)).sort(byName);
86
+ const items = await children(folder);
87
+ const tree = /* @__PURE__ */ new Map([[root.id, f(await children(root))]]);
88
+ for (const c of crumbs) tree.set(c.id, c.id === folder.id ? f(items) : f(await children(c)));
89
+ return { root, folder, crumbs, items, tree };
90
+ });
91
+ function f(items) {
92
+ return items.filter((i) => i.kind === "folder");
93
+ }
94
+ const act = async (work, done) => {
95
+ try {
96
+ await work();
97
+ toast({ title: done, tone: "success" });
98
+ setDlg(null);
99
+ } catch (e) {
100
+ toast({ title: "That did not work", description: errorText(e), tone: "danger" });
101
+ }
102
+ reload();
103
+ };
104
+ const upload = async (files) => {
105
+ if (state.status !== "ok") return;
106
+ if (busy) return void toast({ title: "An upload is running", description: "Wait for it to finish, then add more files.", tone: "info" });
107
+ const { folder, root } = state.data;
108
+ setBusy(true);
109
+ for (const file of Array.from(files)) {
110
+ await act(() => client.upload(folder.id === root.id ? `/${file.name}` : `${folder.id}/${file.name}`, file, { mime: file.type || void 0, onConflict: "rename" }), `Uploaded ${file.name}`);
111
+ }
112
+ setBusy(false);
113
+ };
114
+ const onDrop = (e) => {
115
+ e.preventDefault();
116
+ setDrag(false);
117
+ if (e.dataTransfer.files.length) void upload(e.dataTransfer.files);
118
+ };
119
+ const setViewMode = (v) => {
120
+ localStorage.setItem("agentic-drive-view", v);
121
+ setView(v);
122
+ };
123
+ return /* @__PURE__ */ jsx("section", { onDragOver: (e) => {
124
+ e.preventDefault();
125
+ setDrag(true);
126
+ }, onDragLeave: (e) => {
127
+ if (!e.currentTarget.contains(e.relatedTarget)) setDrag(false);
128
+ }, onDrop, className: drag ? "drive-drop drive-drop--over" : "drive-drop", "aria-label": "Files", children: /* @__PURE__ */ jsx(Status, { state, retry: reload, children: state.status === "ok" && (() => {
129
+ const { folder, root, crumbs, items, tree } = state.data;
130
+ const actions = (item) => /* @__PURE__ */ jsxs(Fragment, { children: [
131
+ /* @__PURE__ */ jsx(MachineLabel, { item }),
132
+ /* @__PURE__ */ jsx(Menu, { align: "end", trigger: /* @__PURE__ */ jsx(IconButton, { label: `Actions for ${item.name}`, icon: /* @__PURE__ */ jsx(MoreIcon, {}), variant: "ghost" }), items: [
133
+ { label: "Rename", onSelect: () => setDlg({ kind: "rename", item }) },
134
+ { label: "Move", onSelect: () => setDlg({ kind: "move", item }) },
135
+ { label: "Share", onSelect: () => setDlg({ kind: "share", item }) },
136
+ "separator",
137
+ { label: "Move to trash", danger: true, onSelect: () => void act(() => client.trash(item.id), `Moved ${item.name} to trash`) }
138
+ ] })
139
+ ] });
140
+ const href = (i) => i.kind === "folder" ? `#/${i.id}` : `#/f/${i.id}`;
141
+ const level = (id) => /* @__PURE__ */ jsx("ul", { className: "drive-tree", children: (tree.get(id) ?? []).map((n) => /* @__PURE__ */ jsxs("li", { children: [
142
+ /* @__PURE__ */ jsxs("a", { href: `#/${n.id}`, "aria-current": n.id === folder.id ? "page" : void 0, children: [
143
+ /* @__PURE__ */ jsx(FolderIcon, { "aria-hidden": "true" }),
144
+ " ",
145
+ n.name
146
+ ] }),
147
+ tree.has(n.id) && level(n.id)
148
+ ] }, n.id)) });
149
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
150
+ /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className: "drive-crumbs", children: /* @__PURE__ */ jsxs("ol", { children: [
151
+ /* @__PURE__ */ jsx("li", { children: crumbs.length ? /* @__PURE__ */ jsx("a", { href: "#/", children: "My files" }) : /* @__PURE__ */ jsx("span", { "aria-current": "page", children: "My files" }) }),
152
+ crumbs.map((c, i) => /* @__PURE__ */ jsx("li", { children: i === crumbs.length - 1 ? /* @__PURE__ */ jsx("span", { "aria-current": "page", children: c.name }) : /* @__PURE__ */ jsx("a", { href: `#/${c.id}`, children: c.name }) }, c.id))
153
+ ] }) }),
154
+ /* @__PURE__ */ jsxs("div", { className: "drive-bar", children: [
155
+ /* @__PURE__ */ jsx("h1", { children: folder.id === root.id ? "My files" : folder.name }),
156
+ /* @__PURE__ */ jsxs(Toolbar, { label: "Folder actions", children: [
157
+ /* @__PURE__ */ jsx(ToolbarButton, { label: "New folder", icon: /* @__PURE__ */ jsx(PlusIcon, {}), onClick: () => setDlg({ kind: "mkdir" }) }),
158
+ /* @__PURE__ */ jsx(ToolbarButton, { label: "Upload files", icon: /* @__PURE__ */ jsx(FileIcon, {}), onClick: () => input.current?.click(), disabled: busy }),
159
+ /* @__PURE__ */ jsx(ToolbarButton, { label: "List view", icon: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2630" }), pressed: view === "list", onClick: () => setViewMode("list") }),
160
+ /* @__PURE__ */ jsx(ToolbarButton, { label: "Grid view", icon: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u25A6" }), pressed: view === "grid", onClick: () => setViewMode("grid") })
161
+ ] }),
162
+ /* @__PURE__ */ jsx("input", { ref: input, type: "file", multiple: true, hidden: true, "aria-label": "Choose files to upload", onChange: (e) => {
163
+ if (e.target.files) void upload(e.target.files);
164
+ e.target.value = "";
165
+ } })
166
+ ] }),
167
+ /* @__PURE__ */ jsxs("div", { className: "drive-split", children: [
168
+ /* @__PURE__ */ jsx("nav", { "aria-label": "Folders", className: "drive-folders", children: level(root.id) }),
169
+ /* @__PURE__ */ jsx("div", { children: items.length === 0 ? /* @__PURE__ */ jsx(EmptyState, { title: "This folder is empty", description: "Drop files here, or use Upload files.", action: /* @__PURE__ */ jsx(Button, { onClick: () => input.current?.click(), disabled: busy, children: "Upload files" }) }) : view === "list" ? /* @__PURE__ */ jsx(FileList, { label: `Items in ${folder.name || "My files"}`, children: items.map((i) => /* @__PURE__ */ jsx(FileRow, { name: i.name, href: href(i), kind: i.kind === "folder" ? "folder" : "file", modified: new Date(i.updatedAt), editedBy: who(i.updatedBy, i.owner), actions: actions(i) }, i.id)) }) : /* @__PURE__ */ jsx("ul", { className: "drive-grid", "aria-label": `Items in ${folder.name || "My files"}`, children: items.map((i) => /* @__PURE__ */ jsxs("li", { className: "drive-card", children: [
170
+ /* @__PURE__ */ jsx("span", { className: "drive-card__icon", "aria-hidden": "true", children: i.kind === "folder" ? /* @__PURE__ */ jsx(FolderIcon, {}) : /* @__PURE__ */ jsx(FileIcon, {}) }),
171
+ /* @__PURE__ */ jsx("a", { href: href(i), className: "drive-card__name", children: i.name }),
172
+ /* @__PURE__ */ jsxs("span", { className: "drive-card__meta", children: [
173
+ "Edited by ",
174
+ /* @__PURE__ */ jsx(PresenceChip, { entry: who(i.updatedBy, i.owner) })
175
+ ] }),
176
+ /* @__PURE__ */ jsx("span", { className: "drive-card__actions", children: actions(i) })
177
+ ] }, i.id)) }) })
178
+ ] }),
179
+ dlg?.kind === "mkdir" && /* @__PURE__ */ jsx(Prompt, { open: true, title: "New folder", label: "Folder name", initial: "", submit: "Create", onClose: () => setDlg(null), onSubmit: (name) => act(() => client.mkdir(folder.id === root.id ? `/${name}` : `${folder.id}/${name}`), `Created ${name}`) }),
180
+ dlg?.kind === "rename" && /* @__PURE__ */ jsx(Prompt, { open: true, title: "Rename", label: "New name", initial: dlg.item.name, submit: "Rename", onClose: () => setDlg(null), onSubmit: (name) => act(() => client.move(dlg.item.id, dlg.item.parentId, { name }), `Renamed to ${name}`) }),
181
+ dlg?.kind === "move" && /* @__PURE__ */ jsx(MoveDialog, { client, item: dlg.item, onClose: () => setDlg(null), onMove: (to) => act(() => client.move(dlg.item.id, to), `Moved ${dlg.item.name}`) }),
182
+ dlg?.kind === "share" && /* @__PURE__ */ jsx(Sharing, { client, item: dlg.item, origin, onClose: () => setDlg(null) })
183
+ ] });
184
+ })() }) });
185
+ }
186
+ function Prompt({ open, title, label, initial, submit, onClose, onSubmit }) {
187
+ const [value, setValue] = useState(initial);
188
+ const sent = useRef(false);
189
+ const [problem, setProblem] = useState();
190
+ const send = (name) => {
191
+ if (!name) return;
192
+ if (name.includes("/")) return setProblem("A name cannot have a slash.");
193
+ setProblem(void 0);
194
+ sent.current = true;
195
+ void onSubmit(name).finally(() => {
196
+ sent.current = false;
197
+ });
198
+ };
199
+ return /* @__PURE__ */ jsx(Dialog, { open: !!open, onOpenChange: (o) => {
200
+ if (!o) onClose();
201
+ }, title, children: /* @__PURE__ */ jsxs("form", { onSubmit: (e) => {
202
+ e.preventDefault();
203
+ if (!sent.current) send(value.trim());
204
+ }, className: "drive-form", children: [
205
+ /* @__PURE__ */ jsx(Input, { label, value, onChange: (e) => setValue(e.target.value), error: problem, required: true }),
206
+ /* @__PURE__ */ jsxs("div", { className: "drive-form__actions", children: [
207
+ /* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "ghost", type: "button", children: "Cancel" }) }),
208
+ /* @__PURE__ */ jsx(Button, { type: "submit", children: submit })
209
+ ] })
210
+ ] }) });
211
+ }
212
+ function MoveDialog({ client, item, onClose, onMove }) {
213
+ const [folders, retry] = useLoad(async () => {
214
+ const root = await client.stat("/");
215
+ const all = await client.search({ kind: "folder", limit: 1e3 });
216
+ const own = all.find((x) => x.id === item.id)?.path;
217
+ return [{ id: "/", path: "/", name: "My files" }, ...all.filter((x) => x.id !== root.id && !(own && (x.path === own || x.path.startsWith(`${own}/`))))];
218
+ });
219
+ const [to, setTo] = useState("/");
220
+ const sent = useRef(false);
221
+ return /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (o) => {
222
+ if (!o) onClose();
223
+ }, title: `Move ${item.name}`, children: /* @__PURE__ */ jsx(Status, { state: folders, retry, children: folders.status === "ok" && /* @__PURE__ */ jsxs("form", { onSubmit: (e) => {
224
+ e.preventDefault();
225
+ if (sent.current) return;
226
+ sent.current = true;
227
+ void onMove(to).finally(() => {
228
+ sent.current = false;
229
+ });
230
+ }, className: "drive-form", children: [
231
+ /* @__PURE__ */ jsx(Select, { label: "Move to", value: to, onChange: (e) => setTo(e.target.value), children: folders.data.map((x) => /* @__PURE__ */ jsx("option", { value: x.id, children: x.path === "/" ? "My files" : x.path }, x.id)) }),
232
+ /* @__PURE__ */ jsxs("div", { className: "drive-form__actions", children: [
233
+ /* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "ghost", type: "button", children: "Cancel" }) }),
234
+ /* @__PURE__ */ jsx(Button, { type: "submit", children: "Move" })
235
+ ] })
236
+ ] }) }) });
237
+ }
238
+ var EXPIRY_MS = { never: void 0, "1d": 864e5, "7d": 7 * 864e5, "30d": 30 * 864e5 };
239
+ function Sharing({ client, item, origin, onClose }) {
240
+ const toast = useToast();
241
+ const [state, reload] = useLoad(() => client.shares(item.id));
242
+ const [link, setLink] = useState({ role: "viewer", expiry: "never", signedInOnly: false });
243
+ const liveLinks = state.status === "ok" ? state.data.links.filter((l) => !l.revoked && (l.expiresAt === null || l.expiresAt > Date.now())) : [];
244
+ const live = liveLinks[0];
245
+ const current = link.id ? link : live ? { ...link, id: live.id, role: live.role === "owner" ? "editor" : live.role, signedInOnly: live.signedInOnly } : link;
246
+ const run = async (work) => {
247
+ try {
248
+ await work();
249
+ } catch (e) {
250
+ toast({ title: "That did not work", description: errorText(e), tone: "danger" });
251
+ }
252
+ reload();
253
+ };
254
+ const entries = state.status === "ok" ? state.data.grants.map((g) => ({ entry: { id: g.subjectId, name: g.subjectId, kind: g.subjectKind === "agent" ? "agent" : "person" }, role: g.role })) : [];
255
+ const kindOf = (id) => state.status === "ok" && state.data.grants.find((g) => g.subjectId === id)?.subjectKind || "human";
256
+ const revokeAll = async () => {
257
+ for (const id of /* @__PURE__ */ new Set([...liveLinks.map((l) => l.id), ...current.id ? [current.id] : []])) await client.revokeLink(id);
258
+ };
259
+ const change = (next) => run(async () => {
260
+ await revokeAll();
261
+ const made = await client.createLink(item.id, { role: next.role, signedInOnly: next.signedInOnly, ...EXPIRY_MS[next.expiry] ? { expiresInMs: EXPIRY_MS[next.expiry] } : {} });
262
+ setLink({ ...next, id: made.id, token: made.token });
263
+ toast({ title: "New link made", description: current.id ? "The old link no longer works." : void 0, tone: "info" });
264
+ });
265
+ const shareLink = {
266
+ enabled: !!current.id,
267
+ role: current.role,
268
+ expiry: current.expiry,
269
+ signedInOnly: current.signedInOnly,
270
+ url: current.token ? `${origin}/#/s/${item.id}/${current.token}` : "Turn the link off and on to make a new one. The address shows once.",
271
+ expiresAt: live?.expiresAt ? new Date(live.expiresAt) : null
272
+ };
273
+ return /* @__PURE__ */ jsx(
274
+ ShareDialog,
275
+ {
276
+ open: true,
277
+ onOpenChange: (o) => {
278
+ if (!o) onClose();
279
+ },
280
+ resourceName: item.name,
281
+ people: entries,
282
+ onInvite: (inv) => void run(async () => {
283
+ await client.invite(item.id, { role: inv.role, ...inv.kind === "person" ? { email: inv.to } : { handle: inv.to.replace(/^@/, "") } });
284
+ toast({ title: "Invite created", tone: "success" });
285
+ }),
286
+ onChangeRole: (id, role) => void run(() => client.share(item.id, { kind: kindOf(id), id }, role)),
287
+ onRemove: (id) => void run(() => client.unshare(item.id, { kind: kindOf(id), id })),
288
+ link: shareLink,
289
+ onChangeLink: (next) => {
290
+ if (!next.enabled) return void run(async () => {
291
+ await revokeAll();
292
+ setLink({ ...current, id: void 0, token: void 0 });
293
+ });
294
+ void change({ role: next.role, expiry: next.expiry, signedInOnly: next.signedInOnly });
295
+ },
296
+ snapshot: { onCreate: () => toast({ title: "Snapshot links are not available in Drive yet", tone: "info" }) },
297
+ onCopy: (url) => current.token && void navigator.clipboard?.writeText(url).then(() => toast({ title: "Link copied", tone: "success" }), () => toast({ title: "Could not copy", tone: "warning" }))
298
+ }
299
+ );
300
+ }
301
+ function TrashView({ client }) {
302
+ const toast = useToast();
303
+ const [state, reload] = useLoad(() => client.listTrash());
304
+ const restore = async (i) => {
305
+ try {
306
+ await client.restore(i.id, { onConflict: "rename" });
307
+ toast({ title: `Restored ${i.name}`, tone: "success" });
308
+ } catch (e) {
309
+ toast({ title: "Could not restore", description: errorText(e), tone: "danger" });
310
+ }
311
+ reload();
312
+ };
313
+ return /* @__PURE__ */ jsxs("section", { "aria-label": "Trash", children: [
314
+ /* @__PURE__ */ jsx("h1", { children: "Trash" }),
315
+ /* @__PURE__ */ jsx(Status, { state, retry: reload, children: state.status === "ok" && (state.data.length === 0 ? /* @__PURE__ */ jsx(EmptyState, { title: "The trash is empty", description: "Items you move to the trash show up here." }) : /* @__PURE__ */ jsx(FileList, { label: "Items in the trash", children: [...state.data].sort(byName).map((i) => /* @__PURE__ */ jsx(FileRow, { name: i.name, href: "#/trash", kind: i.kind === "folder" ? "folder" : "file", modified: new Date(i.trashedAt ?? i.updatedAt), editedBy: who(i.updatedBy, i.owner), actions: /* @__PURE__ */ jsxs(Fragment, { children: [
316
+ /* @__PURE__ */ jsx(MachineLabel, { item: i }),
317
+ /* @__PURE__ */ jsx(Button, { size: "sm", onClick: () => void restore(i), children: `Restore ${i.name}` })
318
+ ] }) }, i.id)) })) })
319
+ ] });
320
+ }
321
+ function Download({ client, id, origin, go }) {
322
+ const [state, retry] = useLoad(async () => {
323
+ const item = await client.stat(id);
324
+ const { url } = await client.url(id);
325
+ if (new URL(url, origin).origin !== origin) throw new Error("The download link points to another site.");
326
+ const a = document.createElement("a");
327
+ a.href = url;
328
+ a.download = item.name;
329
+ a.click();
330
+ go(item.parentId);
331
+ return item;
332
+ });
333
+ return /* @__PURE__ */ jsx(Status, { state, retry, children: null });
334
+ }
335
+
336
+ // ui/embed.tsx
337
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
338
+ var PLACEHOLDER = "from-get-token";
339
+ function makeClient({ baseUrl, getToken }) {
340
+ if (typeof getToken !== "function") throw new TypeError("getToken must be a function");
341
+ const closed = new AbortController();
342
+ const fetchWithToken = async (input, init) => {
343
+ closed.signal.throwIfAborted();
344
+ const token = await getToken();
345
+ closed.signal.throwIfAborted();
346
+ if (token !== void 0 && (typeof token !== "string" || !/^[A-Za-z0-9_-]{1,512}$/.test(token))) throw new TypeError("getToken must return 1 to 512 letters, digits, - or _");
347
+ const headers = new Headers(init?.headers);
348
+ if (token === void 0) headers.delete("authorization");
349
+ else headers.set("authorization", `Bearer ${token}`);
350
+ const signal = init?.signal ? AbortSignal.any([init.signal, closed.signal]) : closed.signal;
351
+ return globalThis.fetch(input, { ...init, headers, signal });
352
+ };
353
+ return { client: createClient({ baseUrl, token: PLACEHOLDER, fetch: fetchWithToken }), close: () => closed.abort() };
354
+ }
355
+ var clients = 0;
356
+ function DriveBrowser({ baseUrl, getToken }) {
357
+ const [made, setMade] = useState2();
358
+ useEffect2(() => {
359
+ const next = makeClient({ baseUrl, getToken });
360
+ setMade({ id: ++clients, client: next.client, origin: new URL(baseUrl).origin });
361
+ return () => next.close();
362
+ }, [baseUrl, getToken]);
363
+ return made ? /* @__PURE__ */ jsx2(DriveView, { client: made.client, origin: made.origin }, made.id) : null;
364
+ }
365
+ function DriveView({ client, origin }) {
366
+ const [hash, setHash] = useState2("#/");
367
+ const go = (loc) => setHash(`#/${loc === "/" ? "" : loc}`);
368
+ const follow = (e) => {
369
+ if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
370
+ const link = e.target.closest?.("a[href^='#/']");
371
+ if (!link || !e.currentTarget.contains(link)) return;
372
+ e.preventDefault();
373
+ setHash(link.getAttribute("href"));
374
+ };
375
+ const place = hash.startsWith("#/trash") ? "trash" : "files";
376
+ return /* @__PURE__ */ jsxs2("div", { className: "drive-root", onClick: follow, children: [
377
+ /* @__PURE__ */ jsx2(SidebarNav2, { label: "Places", items: [
378
+ { href: "#/", label: "My files", icon: /* @__PURE__ */ jsx2(FolderIcon2, { "aria-hidden": "true" }), current: place === "files" },
379
+ { href: "#/trash", label: "Trash", current: place === "trash" }
380
+ ] }),
381
+ /* @__PURE__ */ jsx2("div", { className: "drive-main", children: /* @__PURE__ */ jsx2(Routes, { hash, client, origin, go }) })
382
+ ] });
383
+ }
384
+ function mount(element, options) {
385
+ let current = makeClient(options);
386
+ const root = createRoot(element);
387
+ let open = true;
388
+ let version = 0;
389
+ const render = (next, client) => root.render(
390
+ /* @__PURE__ */ jsx2(TooltipProvider, { children: /* @__PURE__ */ jsx2(ToastProvider, { children: /* @__PURE__ */ jsx2(DriveView, { client, origin: new URL(next.baseUrl).origin }, version++) }) })
391
+ );
392
+ render(options, current.client);
393
+ return {
394
+ update: (next) => {
395
+ if (!open) return;
396
+ const made = makeClient(next);
397
+ current.close();
398
+ current = made;
399
+ render(next, made.client);
400
+ },
401
+ unmount: () => {
402
+ if (!open) return;
403
+ open = false;
404
+ current.close();
405
+ root.unmount();
406
+ }
407
+ };
408
+ }
409
+ export {
410
+ DriveBrowser,
411
+ mount
412
+ };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { O as OnConflict, D as Drive, C as Ctx, N as Node, A as Actor } from './drive-Cn3mpuDW.js';
2
- export { a as ACTOR_KINDS, b as Action, c as ActorKind, d as Can, e as DriveDeps, f as DriveError, E as ErrorCode, R as ROOT_ID, S as SearchQuery, g as createDrive, p as parseName, h as parseSubject, s as subjectKindOf } from './drive-Cn3mpuDW.js';
1
+ import { O as OnConflict, D as Drive, C as Ctx, N as Node, A as Actor } from './drive-BtRyexUP.js';
2
+ export { a as ACTOR_KINDS, b as Action, c as ActorKind, d as Can, e as DriveDeps, f as DriveError, E as ErrorCode, R as ROOT_ID, S as SearchQuery, g as createDrive, p as parseName, h as parseSubject, s as subjectKindOf } from './drive-BtRyexUP.js';
3
3
  import { AdapterOptions, Blobs, Store, Doc } from 'agentic-identity';
4
4
  export { B as BackfillOptions, a as BackfillResult, b as backfillAuthorIds, c as backfillResources } from './migrate-B79gRPMu.js';
5
5
 
@@ -75,8 +75,9 @@ declare function revokePrincipal(store: Store, id: string): Promise<void>;
75
75
  /** Issue an API key for a principal. The token is shown once. */
76
76
  declare function issueKey(store: Store, principalId: string, label?: string): Promise<string>;
77
77
  declare function revokeKey(store: Store, token: string): Promise<void>;
78
- /** A node as the CLI and MCP print it: no storage keys. */
78
+ /** A node as the CLI and MCP print it: no storage keys. `machineMade` is true when the creator is not a human principal. */
79
79
  declare function publicNode(node: Node, path?: string): {
80
+ machineMade: boolean;
80
81
  id: string;
81
82
  owner: string;
82
83
  parentId: string;
@@ -95,6 +96,7 @@ declare function publicNode(node: Node, path?: string): {
95
96
  trashed: boolean;
96
97
  trashedAt: number | null;
97
98
  } | {
99
+ machineMade: boolean;
98
100
  id: string;
99
101
  owner: string;
100
102
  parentId: string;
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-DRACF66Y.js";
3
+ } from "./chunk-ES3NZ7SX.js";
4
4
  import {
5
5
  backfillAuthorIds,
6
6
  backfillResources
7
- } from "./chunk-L7GASKBQ.js";
7
+ } from "./chunk-BXFK3QXK.js";
8
8
  import {
9
9
  ACTOR_KINDS,
10
10
  AuthError,
@@ -29,7 +29,7 @@ import {
29
29
  subjectKindOf,
30
30
  upload,
31
31
  writeZip
32
- } from "./chunk-ATJDMCRV.js";
32
+ } from "./chunk-WJZNXLTR.js";
33
33
  export {
34
34
  ACTOR_KINDS,
35
35
  AuthError,
package/dist/mcp.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { D as Drive, C as Ctx } from './drive-Cn3mpuDW.js';
2
+ import { D as Drive, C as Ctx } from './drive-BtRyexUP.js';
3
3
  import 'agentic-identity';
4
4
 
5
5
  declare function createMcpServer(drive: Drive, ctx: Ctx, exportDir: string): McpServer;
package/dist/mcp.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-DRACF66Y.js";
3
+ } from "./chunk-ES3NZ7SX.js";
4
4
  import {
5
5
  ACTOR_KINDS,
6
6
  DriveError,
7
7
  buildExport,
8
8
  publicNode,
9
9
  upload
10
- } from "./chunk-ATJDMCRV.js";
10
+ } from "./chunk-WJZNXLTR.js";
11
11
 
12
12
  // src/mcp.ts
13
13
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
package/dist/worker.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Hono } from 'hono';
2
2
  import { D1Like, R2Like } from 'agentic-identity/cloudflare';
3
- import { Store } from 'agentic-identity';
4
- import { C as Ctx, D as Drive } from './drive-Cn3mpuDW.js';
3
+ import { Store, Blobs } from 'agentic-identity';
4
+ import { C as Ctx, D as Drive } from './drive-BtRyexUP.js';
5
5
  export { B as BackfillOptions, a as BackfillResult, b as backfillAuthorIds, c as backfillResources } from './migrate-B79gRPMu.js';
6
6
 
7
7
  /**
@@ -21,6 +21,22 @@ interface WorkerEnv {
21
21
  DRIVE_URL_SECRET?: string;
22
22
  DRIVE_REGION?: string;
23
23
  }
24
+ /**
25
+ * What a host injects into `createApp`. Each part is optional, and the default is the D1 store, the R2 blobs,
26
+ * and the bearer-token check of agentic-identity. A part may be a function of the Worker env, which runs on every request.
27
+ */
28
+ interface AppOptions<Env = WorkerEnv> {
29
+ /** The store that holds items, shares, and credentials. With it, no `DB` binding is read and the table is not created. */
30
+ store?: Store | ((env: Env) => Store | Promise<Store>);
31
+ /** The blob store. With it, no `FILES` binding is read. It signs download URLs, so it must serve them from this Worker's `/api/v1/drive/blob`. */
32
+ blobs?: Blobs | ((env: Env) => Blobs | Promise<Blobs>);
33
+ /**
34
+ * Resolves the caller. Return null for no valid caller (the answer is 401). Return a whole Ctx: a `principal`, and an
35
+ * `owner` and `actor` that match it, or the request gets 401. With it, bearer tokens are not read from the store.
36
+ * A callback that reads a session cookie must check the request origin itself, because every write route is open to a cross-site form.
37
+ */
38
+ auth?: (request: Request, store: Store, env: Env) => Ctx | null | Promise<Ctx | null>;
39
+ }
24
40
  type App = {
25
41
  Bindings: WorkerEnv;
26
42
  Variables: {
@@ -29,9 +45,12 @@ type App = {
29
45
  ctx: Ctx;
30
46
  };
31
47
  };
32
- declare function createApp(): Hono<App>;
48
+ declare function createApp<Env extends object = WorkerEnv>(options?: AppOptions<Env>): Hono<{
49
+ Bindings: Env;
50
+ Variables: App["Variables"];
51
+ }>;
33
52
  declare const _default: {
34
53
  fetch: (request: Request, env: WorkerEnv, ctx: unknown) => Response | Promise<Response>;
35
54
  };
36
55
 
37
- export { type WorkerEnv, authenticate, createApp, _default as default };
56
+ export { type AppOptions, type WorkerEnv, authenticate, createApp, _default as default };