agentic-drive 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/embed.css ADDED
@@ -0,0 +1,154 @@
1
+ /* ui/app.css */
2
+ .drive-main {
3
+ max-width: 72rem;
4
+ }
5
+ .drive-muted {
6
+ color: var(--ad-color-text-muted);
7
+ }
8
+ .drive-status {
9
+ display: flex;
10
+ align-items: center;
11
+ gap: var(--ad-space-2);
12
+ color: var(--ad-color-text-muted);
13
+ }
14
+ .drive-signin {
15
+ display: grid;
16
+ gap: var(--ad-space-4);
17
+ max-width: 24rem;
18
+ }
19
+ .drive-form {
20
+ display: grid;
21
+ gap: var(--ad-space-4);
22
+ }
23
+ .drive-form__actions {
24
+ display: flex;
25
+ justify-content: flex-end;
26
+ gap: var(--ad-space-2);
27
+ }
28
+ .drive-drop {
29
+ min-height: 60vh;
30
+ border: 2px dashed transparent;
31
+ border-radius: var(--ad-radius-md);
32
+ padding: var(--ad-space-2);
33
+ }
34
+ .drive-drop--over {
35
+ border-color: var(--ad-color-accent);
36
+ background: var(--ad-color-accent-subtle);
37
+ }
38
+ .drive-bar {
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: space-between;
42
+ gap: var(--ad-space-4);
43
+ flex-wrap: wrap;
44
+ margin-bottom: var(--ad-space-4);
45
+ }
46
+ .drive-bar h1 {
47
+ margin: 0;
48
+ font-size: var(--ad-font-size-xl);
49
+ overflow-wrap: anywhere;
50
+ }
51
+ .drive-crumbs ol {
52
+ display: flex;
53
+ flex-wrap: wrap;
54
+ gap: var(--ad-space-2);
55
+ list-style: none;
56
+ margin: 0 0 var(--ad-space-2);
57
+ padding: 0;
58
+ color: var(--ad-color-text-muted);
59
+ }
60
+ .drive-crumbs li + li::before {
61
+ content: "/";
62
+ margin-right: var(--ad-space-2);
63
+ }
64
+ .drive-crumbs [aria-current=page] {
65
+ color: var(--ad-color-text);
66
+ font-weight: var(--ad-font-weight-semibold);
67
+ }
68
+ .drive-split {
69
+ display: grid;
70
+ grid-template-columns: 14rem minmax(0, 1fr);
71
+ gap: var(--ad-space-6);
72
+ }
73
+ @media (max-width: 48rem) {
74
+ .drive-split {
75
+ grid-template-columns: 1fr;
76
+ }
77
+ }
78
+ .drive-tree {
79
+ list-style: none;
80
+ margin: 0;
81
+ padding: 0;
82
+ }
83
+ .drive-tree .drive-tree {
84
+ padding-left: var(--ad-space-4);
85
+ }
86
+ .drive-tree a {
87
+ display: flex;
88
+ align-items: center;
89
+ gap: var(--ad-space-2);
90
+ padding: var(--ad-space-1) var(--ad-space-2);
91
+ border-radius: var(--ad-radius-sm);
92
+ color: var(--ad-color-text);
93
+ text-decoration: none;
94
+ }
95
+ .drive-tree a:hover {
96
+ background: var(--ad-color-surface-hover);
97
+ }
98
+ .drive-tree a[aria-current=page] {
99
+ background: var(--ad-color-accent-subtle);
100
+ color: var(--ad-color-accent-text);
101
+ font-weight: var(--ad-font-weight-semibold);
102
+ }
103
+ .drive-tree svg {
104
+ width: 1rem;
105
+ height: 1rem;
106
+ flex: none;
107
+ }
108
+ .drive-grid {
109
+ display: grid;
110
+ grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
111
+ gap: var(--ad-space-4);
112
+ list-style: none;
113
+ margin: 0;
114
+ padding: 0;
115
+ }
116
+ .drive-card {
117
+ position: relative;
118
+ display: grid;
119
+ gap: var(--ad-space-2);
120
+ padding: var(--ad-space-4);
121
+ border: 1px solid var(--ad-color-border);
122
+ border-radius: var(--ad-radius-md);
123
+ background: var(--ad-color-surface);
124
+ }
125
+ .drive-card__icon svg {
126
+ width: 2rem;
127
+ height: 2rem;
128
+ }
129
+ .drive-card__name {
130
+ overflow-wrap: anywhere;
131
+ font-weight: var(--ad-font-weight-medium);
132
+ color: var(--ad-color-text);
133
+ }
134
+ .drive-card__meta {
135
+ font-size: var(--ad-font-size-sm);
136
+ color: var(--ad-color-text-muted);
137
+ }
138
+ .drive-card__actions {
139
+ display: flex;
140
+ align-items: center;
141
+ justify-content: space-between;
142
+ gap: var(--ad-space-2);
143
+ }
144
+ .drive-root {
145
+ display: grid;
146
+ grid-template-columns: 12rem minmax(0, 1fr);
147
+ gap: var(--ad-space-6);
148
+ color: var(--ad-color-text);
149
+ }
150
+ @media (max-width: 48rem) {
151
+ .drive-root {
152
+ grid-template-columns: 1fr;
153
+ }
154
+ }
@@ -0,0 +1,21 @@
1
+ import * as react from 'react';
2
+
3
+ interface DriveUiOptions {
4
+ /** The address of the drive server, for example `https://drive.example.com`. Only its origin is used. */
5
+ baseUrl: string;
6
+ /** Returns the bearer token for the next request. It runs before every request, so it can refresh a token. No token sends no `authorization` header. */
7
+ getToken: () => string | undefined | Promise<string | undefined>;
8
+ }
9
+ /** The folders and trash of the signed-in caller, with upload, share, and the machine-made label. It needs the tooltip and toast providers above it. `mount` adds them.
10
+ * It shows the root again when `baseUrl` changes. A bad `baseUrl` or `getToken` throws. Pass a stable `getToken`, because a new function makes a new client. */
11
+ declare function DriveBrowser({ baseUrl, getToken }: DriveUiOptions): react.JSX.Element | null;
12
+ interface MountedDrive {
13
+ /** Show the drive with another address or token callback. The folder the person has open resets. */
14
+ update(options: DriveUiOptions): void;
15
+ /** Remove the UI. A request that runs is aborted, and no later request starts or calls `getToken`. Calling it again does nothing. */
16
+ unmount(): void;
17
+ }
18
+ /** Render the drive into an element, for an app that does not use React itself. A bad `baseUrl` or `getToken` throws a `TypeError`. */
19
+ declare function mount(element: HTMLElement, options: DriveUiOptions): MountedDrive;
20
+
21
+ export { DriveBrowser, type DriveUiOptions, type MountedDrive, mount };
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
 
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-WQHJNHAR.js";
3
+ } from "./chunk-H6JK62SP.js";
4
4
  import {
5
5
  backfillAuthorIds,
6
6
  backfillResources
7
- } from "./chunk-B7E2Q3RB.js";
7
+ } from "./chunk-5Z3SFBUK.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-T6C5Y2WA.js";
32
+ } from "./chunk-EAKQK7ST.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-WQHJNHAR.js";
3
+ } from "./chunk-H6JK62SP.js";
4
4
  import {
5
5
  ACTOR_KINDS,
6
6
  DriveError,
7
7
  buildExport,
8
8
  publicNode,
9
9
  upload
10
- } from "./chunk-T6C5Y2WA.js";
10
+ } from "./chunk-EAKQK7ST.js";
11
11
 
12
12
  // src/mcp.ts
13
13
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";