agentic-drive 0.0.0-stage → 0.2.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/index.js ADDED
@@ -0,0 +1,44 @@
1
+ import {
2
+ VERSION
3
+ } from "./chunk-RWMBGIAS.js";
4
+ import {
5
+ backfillAuthorIds,
6
+ backfillResources
7
+ } from "./chunk-3LISWG7H.js";
8
+ import {
9
+ ACTOR_KINDS,
10
+ DEFAULT_MAX_BYTES,
11
+ DriveError,
12
+ ROOT_ID,
13
+ buildExport,
14
+ createDrive,
15
+ createFileBlobs,
16
+ createFileStore,
17
+ openLocal,
18
+ parseName,
19
+ publicNode,
20
+ readZip,
21
+ sniffMime,
22
+ upload,
23
+ writeZip
24
+ } from "./chunk-6CWBHSJ7.js";
25
+ export {
26
+ ACTOR_KINDS,
27
+ DEFAULT_MAX_BYTES,
28
+ DriveError,
29
+ ROOT_ID,
30
+ VERSION,
31
+ backfillAuthorIds,
32
+ backfillResources,
33
+ buildExport,
34
+ createDrive,
35
+ createFileBlobs,
36
+ createFileStore,
37
+ openLocal,
38
+ parseName,
39
+ publicNode,
40
+ readZip,
41
+ sniffMime,
42
+ upload,
43
+ writeZip
44
+ };
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { D as Drive, C as Ctx } from './drive-DKOs1FyM.js';
3
+ import 'agentic-identity';
4
+
5
+ declare function createMcpServer(drive: Drive, ctx: Ctx, exportDir: string): McpServer;
6
+ declare function startMcp(deps: {
7
+ drive: Drive;
8
+ ctx: Ctx;
9
+ exportDir: string;
10
+ }): Promise<void>;
11
+
12
+ export { createMcpServer, startMcp };
package/dist/mcp.js ADDED
@@ -0,0 +1,116 @@
1
+ import {
2
+ VERSION
3
+ } from "./chunk-RWMBGIAS.js";
4
+ import {
5
+ ACTOR_KINDS,
6
+ DriveError,
7
+ buildExport,
8
+ publicNode,
9
+ upload
10
+ } from "./chunk-6CWBHSJ7.js";
11
+
12
+ // src/mcp.ts
13
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
+ import { mkdirSync, writeFileSync } from "fs";
16
+ import { join } from "path";
17
+ import { z } from "zod";
18
+ var conflict = z.enum(["fail", "rename", "replace"]).optional();
19
+ var loc = z.string().describe('A path, such as "/x" or "<folder id>/x", or an item id.');
20
+ var ok = (value) => ({ content: [{ type: "text", text: JSON.stringify(value) }] });
21
+ function failure(error) {
22
+ if (!(error instanceof DriveError)) throw error;
23
+ return { isError: true, content: [{ type: "text", text: JSON.stringify({ error: { code: error.code, message: error.message } }) }] };
24
+ }
25
+ var EXPORT_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}\.zip$/;
26
+ function createMcpServer(drive, ctx, exportDir) {
27
+ const server = new McpServer({ name: "agentic-drive", version: VERSION });
28
+ const tool = (name, description, shape, run, annotations = {}) => server.registerTool(name, { description, inputSchema: shape, annotations }, (async (args) => {
29
+ try {
30
+ return ok(await run(args));
31
+ } catch (error) {
32
+ return failure(error);
33
+ }
34
+ }));
35
+ tool("drive_ls", "List a folder.", { path: loc.optional() }, async ({ path }) => (await drive.list(ctx, path ?? "/")).map((n) => publicNode(n)), { readOnlyHint: true });
36
+ tool("drive_stat", "Show one item with its metadata and who made it.", { path: loc }, async ({ path }) => publicNode(await drive.stat(ctx, path)), { readOnlyHint: true });
37
+ tool("drive_mkdir", "Make a folder.", { path: z.string(), parents: z.boolean().optional() }, async ({ path, parents }) => publicNode(await drive.mkdir(ctx, path, { parents })));
38
+ tool(
39
+ "drive_upload",
40
+ "Store a file. Give `text` or `contentBase64`.",
41
+ { path: z.string(), text: z.string().optional(), contentBase64: z.string().optional(), mime: z.string().optional(), parents: z.boolean().optional(), onConflict: conflict },
42
+ async ({ path, text, contentBase64, mime, parents, onConflict }) => {
43
+ if (text === void 0 === (contentBase64 === void 0)) throw new DriveError("INVALID_PATH", "give exactly one of text or contentBase64");
44
+ const bytes = text !== void 0 ? new TextEncoder().encode(text) : new Uint8Array(Buffer.from(contentBase64, "base64"));
45
+ return publicNode(await upload(drive, ctx, path, (async function* () {
46
+ yield bytes;
47
+ })(), { mime, parents, onConflict }));
48
+ }
49
+ );
50
+ tool("drive_download", "Read a file. Text comes back as `text`, other bytes as `contentBase64`.", { path: loc }, async ({ path }) => {
51
+ const { node, data } = await drive.read(ctx, path);
52
+ let text = null;
53
+ try {
54
+ text = new TextDecoder("utf-8", { fatal: true }).decode(data);
55
+ } catch {
56
+ text = null;
57
+ }
58
+ return { node: publicNode(node), ...text !== null ? { text } : { contentBase64: Buffer.from(data).toString("base64") } };
59
+ }, { readOnlyHint: true });
60
+ tool("drive_mv", "Move an item into a folder. Pass `name` to rename it.", { src: loc, folder: loc, name: z.string().optional(), onConflict: conflict }, async ({ src, folder, name, onConflict }) => publicNode(await drive.move(ctx, src, folder, { name, onConflict })));
61
+ tool("drive_rm", "Move an item to the trash. It stays restorable.", { path: loc }, async ({ path }) => publicNode(await drive.trash(ctx, path)));
62
+ tool("drive_trash", "List items in the trash.", {}, async () => (await drive.listTrash(ctx)).map((n) => publicNode(n)), { readOnlyHint: true });
63
+ tool("drive_restore", "Put a trashed item back.", { id: z.string(), parentId: z.string().optional(), onConflict: conflict }, async ({ id, parentId, onConflict }) => publicNode(await drive.restore(ctx, id, { parentId, onConflict })));
64
+ tool("drive_purge", "Delete a trashed item for good.", { id: z.string() }, async ({ id }) => drive.purge(ctx, id), { destructiveHint: true });
65
+ tool(
66
+ "drive_search",
67
+ "Find items by name and metadata.",
68
+ { name: z.string().optional(), mime: z.string().optional(), kind: z.enum(["folder", "file", "ref"]).optional(), minSize: z.number().optional(), maxSize: z.number().optional(), createdByKind: z.enum(ACTOR_KINDS).optional(), limit: z.number().int().min(1).optional() },
69
+ async (q) => (await drive.search(ctx, q)).map((r) => publicNode(r.node, r.path)),
70
+ { readOnlyHint: true }
71
+ );
72
+ tool(
73
+ "drive_home",
74
+ "List files and app documents together, newest change first. Pass `app` (such as docs) to see one app's documents.",
75
+ { app: z.string().optional(), limit: z.number().int().min(1).optional() },
76
+ async ({ app, limit }) => {
77
+ const home = await drive.home(ctx, { app, limit });
78
+ return { apps: home.apps, files: home.files, items: home.items.map((i) => ({ ...publicNode(i.node, i.path), app: i.app })) };
79
+ },
80
+ { readOnlyHint: true }
81
+ );
82
+ tool(
83
+ "drive_register_document",
84
+ "Put an app document in the drive, as a reference such as docs:<id>. A document that is already there comes back as it is.",
85
+ { ref: z.string(), name: z.string(), folder: z.string().optional() },
86
+ async ({ ref, name, folder }) => publicNode(await drive.registerDocument(ctx, { ref, name, folder }))
87
+ );
88
+ tool("drive_forget_document", "Move every reference to an app document to the trash.", { ref: z.string() }, async ({ ref }) => drive.forgetDocument(ctx, ref));
89
+ tool(
90
+ "drive_export",
91
+ "Write everything the owner has to a new zip file in the export folder. Give a plain file name that ends in .zip. It never overwrites a file.",
92
+ { name: z.string() },
93
+ async ({ name }) => {
94
+ if (!EXPORT_NAME.test(name)) throw new DriveError("INVALID_PATH", "name must be a plain file name that ends in .zip");
95
+ const zip = await buildExport(drive, ctx);
96
+ mkdirSync(exportDir, { recursive: true });
97
+ const saved = join(exportDir, name);
98
+ try {
99
+ writeFileSync(saved, zip, { flag: "wx" });
100
+ } catch (error) {
101
+ if (error.code === "EEXIST") throw new DriveError("NAME_TAKEN", `${name} already exists in the export folder`);
102
+ throw error;
103
+ }
104
+ return { saved, size: zip.byteLength };
105
+ },
106
+ { destructiveHint: false }
107
+ );
108
+ return server;
109
+ }
110
+ async function startMcp(deps) {
111
+ await createMcpServer(deps.drive, deps.ctx, deps.exportDir).connect(new StdioServerTransport());
112
+ }
113
+ export {
114
+ createMcpServer,
115
+ startMcp
116
+ };
@@ -0,0 +1,29 @@
1
+ import { Store } from 'agentic-identity';
2
+
3
+ type BackfillOptions = {
4
+ /** Where the last call stopped. Pass the `nextCursor` it returned. */
5
+ cursor?: string;
6
+ /** How many pages this call runs. The default runs them all. */
7
+ maxPages?: number;
8
+ };
9
+ type BackfillResult = {
10
+ fixed: number;
11
+ nextCursor?: string;
12
+ };
13
+ /**
14
+ * Items made before drive kept `createdById` and `updatedById` have no such fields, so an export or an erase
15
+ * cannot find what a person made in other drives. It sets the two fields from `createdBy` and `updatedBy`. It
16
+ * changes nothing else. Run it on the store once, after the old Worker has stopped writing. It returns how
17
+ * many items it fixed, and `nextCursor` while items remain.
18
+ */
19
+ declare function backfillAuthorIds(store: Store, options?: BackfillOptions): Promise<BackfillResult>;
20
+ /**
21
+ * Items made before sharing was on have no `resources` doc. With sharing on, their owner gets NOT_FOUND. This
22
+ * writes the doc of each such item, with the parent a new item gets: its folder, or the drive root. An item
23
+ * that has a doc is left alone, so a second run changes nothing. A damaged item stops the run, because a doc
24
+ * with a guessed parent would give access that nobody granted. Run it after the old Worker has stopped
25
+ * writing, and before the new Worker serves the drive.
26
+ */
27
+ declare function backfillResources(store: Store, options?: BackfillOptions): Promise<BackfillResult>;
28
+
29
+ export { type BackfillOptions as B, type BackfillResult as a, backfillAuthorIds as b, backfillResources as c };
@@ -0,0 +1,36 @@
1
+ import { Hono } from 'hono';
2
+ import { D1Like, R2Like } from 'agentic-identity/cloudflare';
3
+ import { Store } from 'agentic-identity';
4
+ import { C as Ctx, D as Drive } from './drive-DKOs1FyM.js';
5
+ export { B as BackfillOptions, a as BackfillResult, b as backfillAuthorIds, c as backfillResources } from './migrate-B79gRPMu.js';
6
+
7
+ interface WorkerEnv {
8
+ DB: D1Like;
9
+ FILES: R2Like;
10
+ DRIVE_MAX_BYTES?: string;
11
+ /** The origin of this Worker, such as `https://drive.example.com`. Signed download URLs start with it. */
12
+ DRIVE_PUBLIC_URL?: string;
13
+ /** At least 32 bytes. Signs download URLs. Without it, every request gets 503. */
14
+ DRIVE_URL_SECRET?: string;
15
+ DRIVE_REGION?: string;
16
+ }
17
+ type App = {
18
+ Bindings: WorkerEnv;
19
+ Variables: {
20
+ drive: Drive;
21
+ store: Store;
22
+ ctx: Ctx;
23
+ };
24
+ };
25
+ /**
26
+ * The caller behind a bearer token, resolved by the store-backed Auth of agentic-identity: the credential
27
+ * names a principal, and the whole delegation chain must be live. Only a bearer token counts. A session
28
+ * cookie is refused, because no check of the request origin stands behind it here. Any problem gives null.
29
+ */
30
+ declare function authenticate(request: Request, store: Store, now?: () => number): Promise<Ctx | null>;
31
+ declare function createApp(): Hono<App>;
32
+ declare const _default: {
33
+ fetch: (request: Request, env: WorkerEnv, ctx: unknown) => Response | Promise<Response>;
34
+ };
35
+
36
+ export { type WorkerEnv, authenticate, createApp, _default as default };
package/dist/worker.js ADDED
@@ -0,0 +1,307 @@
1
+ import {
2
+ backfillAuthorIds,
3
+ backfillResources
4
+ } from "./chunk-3LISWG7H.js";
5
+ import {
6
+ DEFAULT_MAX_BYTES,
7
+ DEFAULT_URL_SECONDS,
8
+ DriveError,
9
+ buildExport,
10
+ createDrive,
11
+ publicNode,
12
+ safeMime,
13
+ upload
14
+ } from "./chunk-6CWBHSJ7.js";
15
+
16
+ // src/worker.ts
17
+ import { Hono } from "hono";
18
+ import { D1_SCHEMA, createD1Store, createR2Blobs, verifySignedBlobUrl } from "agentic-identity/cloudflare";
19
+ import { createSharing, createStoreAuth, parseRegion } from "agentic-identity";
20
+ var STATUS = {
21
+ CROSS_DRIVE: 409,
22
+ INVALID_SHARE: 400,
23
+ TOO_MANY_ITEMS: 409,
24
+ TOO_DEEP: 400,
25
+ INVALID_EXPIRY: 400,
26
+ BUSY: 409,
27
+ BLOB_EXISTS: 500,
28
+ NOT_FOUND: 404,
29
+ NAME_TAKEN: 409,
30
+ INVALID_NAME: 400,
31
+ INVALID_PATH: 400,
32
+ INVALID_REF: 400,
33
+ NOT_A_FOLDER: 400,
34
+ NOT_A_FILE: 400,
35
+ MOVE_INTO_SELF: 409,
36
+ PARENT_GONE: 409,
37
+ TRASHED: 409,
38
+ NOT_TRASHED: 409,
39
+ IS_ROOT: 400,
40
+ FORBIDDEN: 403,
41
+ BLOB_MISSING: 500,
42
+ TOO_LARGE: 413
43
+ };
44
+ var fail = (code, message, status) => new Response(JSON.stringify({ error: { code, message } }), { status, headers: { "content-type": "application/json" } });
45
+ async function authenticate(request, store, now) {
46
+ if (!/^Bearer [A-Za-z0-9_-]{1,512}$/.test(request.headers.get("authorization") ?? "")) return null;
47
+ const principal = await createStoreAuth(store, now ? { now } : {}).authenticate(request);
48
+ return principal ? { owner: principal.chain[0].id, actor: { id: principal.principalId, kind: principal.kind }, principal } : null;
49
+ }
50
+ var schemas = /* @__PURE__ */ new WeakMap();
51
+ function settings(env) {
52
+ let publicUrl;
53
+ try {
54
+ publicUrl = new URL(env.DRIVE_PUBLIC_URL ?? "").origin;
55
+ } catch {
56
+ return null;
57
+ }
58
+ const urlSecret = env.DRIVE_URL_SECRET;
59
+ return typeof urlSecret === "string" && new TextEncoder().encode(urlSecret).length >= 32 ? { publicUrl, urlSecret } : null;
60
+ }
61
+ var regionOf = (env) => parseRegion(env.DRIVE_REGION ?? "eu");
62
+ function blobsFor(env, config) {
63
+ return createR2Blobs(env.FILES, { region: regionOf(env), urlBase: `${config.publicUrl}/api/v1/drive/blob`, urlSecret: config.urlSecret });
64
+ }
65
+ async function driveFor(env, config) {
66
+ let ready = schemas.get(env.DB);
67
+ if (!ready) {
68
+ ready = env.DB.prepare(D1_SCHEMA).run().catch((error) => {
69
+ schemas.delete(env.DB);
70
+ throw error;
71
+ });
72
+ schemas.set(env.DB, ready);
73
+ }
74
+ await ready;
75
+ const store = createD1Store(env.DB, { region: regionOf(env) });
76
+ return { store, drive: createDrive({ store, blobs: blobsFor(env, config), sharing: createSharing(store) }) };
77
+ }
78
+ var number = (v, name) => {
79
+ if (v === void 0) return void 0;
80
+ const n = Number(v);
81
+ if (!Number.isFinite(n) || n < 0) throw new DriveError("INVALID_PATH", `${name} needs a number of 0 or more`);
82
+ return n;
83
+ };
84
+ var claimedType = (header) => header === void 0 || /^(application\/x-www-form-urlencoded|application\/octet-stream|multipart\/form-data)\b/i.test(header) ? void 0 : header;
85
+ function createApp() {
86
+ const app2 = new Hono();
87
+ app2.get("/health", (c) => c.json({ ok: true }));
88
+ const api = new Hono();
89
+ api.use("*", async (c, next) => {
90
+ const config = settings(c.env);
91
+ if (!config) return fail("NOT_CONFIGURED", "set DRIVE_PUBLIC_URL and DRIVE_URL_SECRET (32 bytes or more)", 503);
92
+ const { drive, store } = await driveFor(c.env, config);
93
+ const ctx = await authenticate(c.req.raw, store);
94
+ if (!ctx) return fail("UNAUTHORIZED", "send a valid bearer token", 401);
95
+ c.set("ctx", ctx);
96
+ c.set("drive", drive);
97
+ c.set("store", store);
98
+ await next();
99
+ });
100
+ const q = (c, k) => c.req.query(k);
101
+ const need = (value, name) => {
102
+ if (value === void 0) throw new DriveError("INVALID_PATH", `${name} is required`);
103
+ return value;
104
+ };
105
+ const body = async (c) => {
106
+ let parsed;
107
+ try {
108
+ parsed = await c.req.json();
109
+ } catch {
110
+ throw new DriveError("INVALID_PATH", "body must be JSON");
111
+ }
112
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new DriveError("INVALID_PATH", "body must be a JSON object");
113
+ return parsed;
114
+ };
115
+ const str = (v, name) => {
116
+ if (typeof v !== "string") throw new DriveError("INVALID_PATH", `${name} must be a string`);
117
+ return v;
118
+ };
119
+ const opt = (v, name) => v === void 0 ? void 0 : str(v, name);
120
+ api.get("/items", async (c) => c.json((await c.var.drive.list(c.var.ctx, q(c, "path") ?? "/")).map((n) => publicNode(n))));
121
+ api.get("/stat", async (c) => c.json(publicNode(await c.var.drive.stat(c.var.ctx, need(q(c, "path"), "path")))));
122
+ api.post("/folders", async (c) => {
123
+ const b = await body(c);
124
+ return c.json(publicNode(await c.var.drive.mkdir(c.var.ctx, str(b.path, "path"), { parents: b.parents === true })), 201);
125
+ });
126
+ api.put("/files", async (c) => {
127
+ const max = number(c.env.DRIVE_MAX_BYTES, "DRIVE_MAX_BYTES") ?? DEFAULT_MAX_BYTES;
128
+ const declared = Number(c.req.header("content-length"));
129
+ if (Number.isFinite(declared) && declared > max) return fail("TOO_LARGE", `upload is over the limit of ${max} bytes`, 413);
130
+ const stream = c.req.raw.body ?? new Blob([]).stream();
131
+ const node = await upload(c.var.drive, c.var.ctx, need(q(c, "path"), "path"), stream, {
132
+ maxBytes: max,
133
+ mime: q(c, "mime") ?? claimedType(c.req.header("content-type")),
134
+ parents: q(c, "parents") === "true",
135
+ onConflict: q(c, "onConflict")
136
+ });
137
+ return c.json(publicNode(node), 201);
138
+ });
139
+ api.get("/files", async (c) => {
140
+ const { node, data } = await c.var.drive.read(c.var.ctx, need(q(c, "path"), "path"));
141
+ return new Response(data, {
142
+ headers: {
143
+ "content-type": node.mime ?? "application/octet-stream",
144
+ "content-disposition": `attachment; filename*=UTF-8''${encodeURIComponent(node.name)}`,
145
+ "x-content-type-options": "nosniff",
146
+ "x-content-sha256": node.hash ?? ""
147
+ }
148
+ });
149
+ });
150
+ api.get("/url", async (c) => {
151
+ const expires = number(q(c, "expires"), "expires") ?? DEFAULT_URL_SECONDS;
152
+ return c.json({ url: await c.var.drive.downloadUrl(c.var.ctx, need(q(c, "path"), "path"), expires), expiresInSeconds: expires });
153
+ });
154
+ api.post("/move", async (c) => {
155
+ const b = await body(c);
156
+ return c.json(publicNode(await c.var.drive.move(c.var.ctx, str(b.src, "src"), str(b.folder, "folder"), { name: opt(b.name, "name"), onConflict: opt(b.onConflict, "onConflict") })));
157
+ });
158
+ api.post("/trash", async (c) => c.json(publicNode(await c.var.drive.trash(c.var.ctx, str((await body(c)).path, "path")))));
159
+ api.get("/trash", async (c) => c.json((await c.var.drive.listTrash(c.var.ctx)).map((n) => publicNode(n))));
160
+ api.post("/restore", async (c) => {
161
+ const b = await body(c);
162
+ return c.json(publicNode(await c.var.drive.restore(c.var.ctx, str(b.id, "id"), { parentId: opt(b.parentId, "parentId"), onConflict: opt(b.onConflict, "onConflict") })));
163
+ });
164
+ api.post("/purge", async (c) => c.json(await c.var.drive.purge(c.var.ctx, str((await body(c)).id, "id"))));
165
+ api.get("/search", async (c) => {
166
+ const query = {
167
+ name: q(c, "name"),
168
+ mime: q(c, "mime"),
169
+ kind: q(c, "kind"),
170
+ minSize: number(q(c, "minSize"), "minSize"),
171
+ maxSize: number(q(c, "maxSize"), "maxSize"),
172
+ createdByKind: q(c, "createdByKind"),
173
+ app: q(c, "app"),
174
+ limit: number(q(c, "limit"), "limit")
175
+ };
176
+ return c.json((await c.var.drive.search(c.var.ctx, query)).map((r) => publicNode(r.node, r.path)));
177
+ });
178
+ api.get("/export", async (c) => new Response(await buildExport(c.var.drive, c.var.ctx), { headers: { "content-type": "application/zip", "content-disposition": 'attachment; filename="drive-export.zip"' } }));
179
+ api.delete("/account", async (c) => {
180
+ if (q(c, "confirm") !== "yes") throw new DriveError("INVALID_PATH", "add ?confirm=yes to erase you for good: your drive, and what you shared or held");
181
+ return c.json(await c.var.drive.erasePerson(c.var.ctx));
182
+ });
183
+ api.get("/home", async (c) => {
184
+ const home = await c.var.drive.home(c.var.ctx, { app: q(c, "app"), limit: number(q(c, "limit"), "limit") });
185
+ return c.json({ apps: home.apps, files: home.files, items: home.items.map((i) => ({ ...publicNode(i.node, i.path), app: i.app })) });
186
+ });
187
+ api.post("/documents", async (c) => {
188
+ const b = await body(c);
189
+ return c.json(publicNode(await c.var.drive.registerDocument(c.var.ctx, { ref: str(b.ref, "ref"), name: str(b.name, "name"), folder: opt(b.folder, "folder") })), 201);
190
+ });
191
+ api.delete("/documents", async (c) => c.json(await c.var.drive.forgetDocument(c.var.ctx, need(q(c, "ref"), "ref"))));
192
+ app2.get("/api/v1/drive/blob", async (c) => {
193
+ const config = settings(c.env);
194
+ if (!config) return fail("NOT_CONFIGURED", "set DRIVE_PUBLIC_URL and DRIVE_URL_SECRET (32 bytes or more)", 503);
195
+ const signed = await verifySignedBlobUrl(c.req.url, { secret: config.urlSecret });
196
+ if (!signed || signed.method !== "GET") return fail("INVALID_LINK", "this link is not valid, or it has expired", 403);
197
+ const blob = await blobsFor(c.env, config).get(signed.key);
198
+ if (!blob) return fail("NOT_FOUND", "the bytes are gone", 404);
199
+ return new Response(blob.data, {
200
+ headers: {
201
+ "content-type": safeMime(signed.responseContentType ?? blob.contentType),
202
+ "content-disposition": signed.responseContentDisposition ?? "attachment",
203
+ "x-content-type-options": "nosniff",
204
+ "cache-control": "private, no-store"
205
+ }
206
+ });
207
+ });
208
+ const roleOf = (v) => v;
209
+ const lifetime = (v, name) => {
210
+ if (v === void 0) return void 0;
211
+ if (typeof v !== "number") throw new DriveError("INVALID_SHARE", `${name} must be a number of milliseconds`);
212
+ return v;
213
+ };
214
+ api.get("/shares", async (c) => c.json(await c.var.drive.shares(c.var.ctx, need(q(c, "path"), "path"))));
215
+ api.post("/shares", async (c) => {
216
+ const b = await body(c);
217
+ await c.var.drive.shareWith(c.var.ctx, str(b.path, "path"), str(b.subjectId, "subjectId"), roleOf(b.role));
218
+ return c.json({ ok: true });
219
+ });
220
+ api.delete("/shares", async (c) => {
221
+ await c.var.drive.unshare(c.var.ctx, need(q(c, "path"), "path"), need(q(c, "subjectId"), "subjectId"));
222
+ return c.json({ ok: true });
223
+ });
224
+ api.post("/invites", async (c) => {
225
+ const b = await body(c);
226
+ const expiresInMs = lifetime(b.expiresInMs, "expiresInMs");
227
+ const invite = await c.var.drive.invite(c.var.ctx, str(b.path, "path"), {
228
+ role: roleOf(b.role),
229
+ ...b.email === void 0 ? {} : { email: str(b.email, "email") },
230
+ ...b.handle === void 0 ? {} : { handle: str(b.handle, "handle") },
231
+ ...expiresInMs === void 0 ? {} : { expiresInMs }
232
+ });
233
+ return c.json(invite, 201);
234
+ });
235
+ api.post("/invites/accept", async (c) => {
236
+ const id = str((await body(c)).id, "id");
237
+ const user = await c.var.store.get("users", c.var.ctx.actor.id);
238
+ const agent = await c.var.store.get("principals", c.var.ctx.actor.id);
239
+ await c.var.drive.acceptInvite(c.var.ctx, id, {
240
+ ...user?.emailVerified === true && typeof user.email === "string" ? { email: user.email } : {},
241
+ ...typeof agent?.handle === "string" ? { handle: agent.handle } : {}
242
+ });
243
+ return c.json({ ok: true });
244
+ });
245
+ api.post("/links", async (c) => {
246
+ const b = await body(c);
247
+ const expiresInMs = lifetime(b.expiresInMs, "expiresInMs");
248
+ const link = await c.var.drive.createLink(c.var.ctx, str(b.path, "path"), {
249
+ role: roleOf(b.role),
250
+ signedInOnly: b.signedInOnly === true,
251
+ ...expiresInMs === void 0 ? {} : { expiresAt: Date.now() + expiresInMs }
252
+ });
253
+ return c.json(link, 201);
254
+ });
255
+ api.delete("/links", async (c) => {
256
+ await c.var.drive.revokeLink(c.var.ctx, need(q(c, "id"), "id"));
257
+ return c.json({ ok: true });
258
+ });
259
+ const shared = new Hono();
260
+ const visit = async (c) => {
261
+ const config = settings(c.env);
262
+ if (!config) return { stop: fail("NOT_CONFIGURED", "set DRIVE_PUBLIC_URL and DRIVE_URL_SECRET (32 bytes or more)", 503) };
263
+ const { drive, store } = await driveFor(c.env, config);
264
+ const ctx = await authenticate(c.req.raw, store);
265
+ if (!ctx && c.req.header("authorization")) return { stop: fail("UNAUTHORIZED", "send a valid bearer token", 401) };
266
+ return { drive, principal: ctx?.principal ?? null, token: c.req.header("x-link-token") };
267
+ };
268
+ shared.get("/:id", async (c) => {
269
+ const v = await visit(c);
270
+ return "stop" in v ? v.stop : c.json(publicNode(await v.drive.openShared(v.principal, c.req.param("id"), v.token)));
271
+ });
272
+ shared.get("/:id/items", async (c) => {
273
+ const v = await visit(c);
274
+ return "stop" in v ? v.stop : c.json((await v.drive.listShared(v.principal, c.req.param("id"), v.token)).map((n) => publicNode(n)));
275
+ });
276
+ shared.get("/:id/content", async (c) => {
277
+ const v = await visit(c);
278
+ if ("stop" in v) return v.stop;
279
+ const { node, data } = await v.drive.readShared(v.principal, c.req.param("id"), v.token);
280
+ return new Response(data, {
281
+ headers: {
282
+ "content-type": safeMime(node.mime),
283
+ "content-disposition": `attachment; filename*=UTF-8''${encodeURIComponent(node.name)}`,
284
+ "x-content-type-options": "nosniff",
285
+ "cache-control": "private, no-store"
286
+ }
287
+ });
288
+ });
289
+ app2.route("/api/v1/drive/shared", shared);
290
+ app2.route("/api/v1/drive", api);
291
+ app2.notFound(() => fail("NOT_FOUND", "no such route", 404));
292
+ app2.onError((error) => {
293
+ if (error instanceof DriveError) return new Response(JSON.stringify({ error: { code: error.code, message: error.message } }), { status: STATUS[error.code], headers: { "content-type": "application/json" } });
294
+ console.error("internal error", error instanceof Error ? error.name : "unknown");
295
+ return new Response(JSON.stringify({ error: { code: "INTERNAL", message: "the server could not finish the request" } }), { status: 500, headers: { "content-type": "application/json" } });
296
+ });
297
+ return app2;
298
+ }
299
+ var app = createApp();
300
+ var worker_default = { fetch: (request, env, ctx) => app.fetch(request, env, ctx) };
301
+ export {
302
+ authenticate,
303
+ backfillAuthorIds,
304
+ backfillResources,
305
+ createApp,
306
+ worker_default as default
307
+ };
package/llms.txt ADDED
@@ -0,0 +1,14 @@
1
+ # agentic-drive
2
+
3
+ Store, sort, and share files and folders.
4
+
5
+ Keywords: drive,files,folders,storage,upload,sharing,agents,mcp,cli,r2.
6
+
7
+ - SDK: import { createDrive } from "agentic-drive".
8
+ - CLI: agentic-drive ls /.
9
+ - MCP: run agentic-drive mcp, then call drive_ls.
10
+
11
+ ## Docs
12
+
13
+ - README: https://github.com/pooriaarab/agentic-drive
14
+ - License: MIT
package/package.json CHANGED
@@ -1,6 +1,77 @@
1
1
  {
2
2
  "name": "agentic-drive",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Store, sort, and share files and folders.",
5
+ "keywords": [
6
+ "drive",
7
+ "files",
8
+ "folders",
9
+ "storage",
10
+ "upload",
11
+ "sharing",
12
+ "agents",
13
+ "mcp",
14
+ "cli",
15
+ "r2"
16
+ ],
17
+ "license": "MIT",
18
+ "author": "Pooria Arab",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/pooriaarab/agentic-drive.git"
22
+ },
23
+ "type": "module",
24
+ "private": false,
25
+ "engines": {
26
+ "node": ">=22"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "CHANGELOG.md",
32
+ "LICENSE",
33
+ "llms.txt",
34
+ "SKILL.md"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "mcpName": "io.github.pooriaarab/agentic-drive",
40
+ "main": "./dist/index.js",
41
+ "types": "./dist/index.d.ts",
42
+ "exports": {
43
+ ".": {
44
+ "types": "./dist/index.d.ts",
45
+ "import": "./dist/index.js"
46
+ },
47
+ "./worker": {
48
+ "types": "./dist/worker.d.ts",
49
+ "import": "./dist/worker.js"
50
+ }
51
+ },
52
+ "scripts": {
53
+ "build": "tsup",
54
+ "typecheck": "tsc --noEmit",
55
+ "test": "vitest run",
56
+ "clean": "rm -rf dist",
57
+ "prepack": "npm run build --silent >&2",
58
+ "ci:local": "npm run build && npm run typecheck && npm test"
59
+ },
60
+ "dependencies": {
61
+ "@modelcontextprotocol/sdk": "^1.12.0",
62
+ "agentic-identity": "^0.3.0",
63
+ "hono": "^4.13.14",
64
+ "zod": "^3.25.0"
65
+ },
66
+ "devDependencies": {
67
+ "@types/node": "^22.0.0",
68
+ "miniflare": "^4.20260730.0",
69
+ "tsup": "^8.0.0",
70
+ "typescript": "^5.6.0",
71
+ "vitest": "^3.0.0",
72
+ "wrangler": "^4.155.0"
73
+ },
74
+ "bin": {
75
+ "agentic-drive": "./dist/cli.js"
76
+ }
77
+ }