agentic-drive 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +114 -2
- package/SKILL.md +25 -0
- package/dist/chunk-4QE7E5UQ.js +7 -0
- package/dist/chunk-QRGKXPLX.js +935 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +218 -0
- package/dist/drive-DRQ8LiHl.d.ts +144 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.js +38 -0
- package/dist/mcp.d.ts +12 -0
- package/dist/mcp.js +99 -0
- package/dist/worker.d.ts +36 -0
- package/dist/worker.js +230 -0
- package/llms.txt +14 -0
- package/package.json +73 -4
package/dist/worker.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACTOR_KINDS,
|
|
3
|
+
DEFAULT_MAX_BYTES,
|
|
4
|
+
DEFAULT_URL_SECONDS,
|
|
5
|
+
DriveError,
|
|
6
|
+
buildExport,
|
|
7
|
+
createDrive,
|
|
8
|
+
publicNode,
|
|
9
|
+
safeMime,
|
|
10
|
+
upload
|
|
11
|
+
} from "./chunk-QRGKXPLX.js";
|
|
12
|
+
|
|
13
|
+
// src/worker.ts
|
|
14
|
+
import { Hono } from "hono";
|
|
15
|
+
import { parseRegion } from "agentic-identity";
|
|
16
|
+
import { D1_SCHEMA, createD1Store, createR2Blobs, verifySignedBlobUrl } from "agentic-identity/cloudflare";
|
|
17
|
+
var MIN_TOKEN_LENGTH = 32;
|
|
18
|
+
var STATUS = {
|
|
19
|
+
TOO_DEEP: 400,
|
|
20
|
+
INVALID_EXPIRY: 400,
|
|
21
|
+
BUSY: 409,
|
|
22
|
+
BLOB_EXISTS: 500,
|
|
23
|
+
NOT_FOUND: 404,
|
|
24
|
+
NAME_TAKEN: 409,
|
|
25
|
+
INVALID_NAME: 400,
|
|
26
|
+
INVALID_PATH: 400,
|
|
27
|
+
INVALID_REF: 400,
|
|
28
|
+
NOT_A_FOLDER: 400,
|
|
29
|
+
NOT_A_FILE: 400,
|
|
30
|
+
MOVE_INTO_SELF: 409,
|
|
31
|
+
PARENT_GONE: 409,
|
|
32
|
+
TRASHED: 409,
|
|
33
|
+
NOT_TRASHED: 409,
|
|
34
|
+
IS_ROOT: 400,
|
|
35
|
+
FORBIDDEN: 403,
|
|
36
|
+
BLOB_MISSING: 500,
|
|
37
|
+
TOO_LARGE: 413
|
|
38
|
+
};
|
|
39
|
+
var fail = (code, message, status) => new Response(JSON.stringify({ error: { code, message } }), { status, headers: { "content-type": "application/json" } });
|
|
40
|
+
function sameText(a, b) {
|
|
41
|
+
const x = new TextEncoder().encode(a);
|
|
42
|
+
const y = new TextEncoder().encode(b);
|
|
43
|
+
let diff = x.length ^ y.length;
|
|
44
|
+
for (let i = 0; i < Math.max(x.length, y.length); i++) diff |= (x[i] ?? 0) ^ (y[i] ?? 0);
|
|
45
|
+
return diff === 0;
|
|
46
|
+
}
|
|
47
|
+
var isText = (v) => typeof v === "string" && v !== "";
|
|
48
|
+
function principalOf(grant) {
|
|
49
|
+
if (typeof grant !== "object" || grant === null) return null;
|
|
50
|
+
const { owner, actor, kind } = grant;
|
|
51
|
+
if (!isText(owner) || !isText(actor) || !ACTOR_KINDS.includes(kind)) return null;
|
|
52
|
+
return { owner, actor: { id: actor, kind } };
|
|
53
|
+
}
|
|
54
|
+
function authenticate(request, env) {
|
|
55
|
+
const token = /^Bearer (.+)$/.exec(request.headers.get("authorization") ?? "")?.[1];
|
|
56
|
+
if (!token || !env.DRIVE_API_TOKENS) return null;
|
|
57
|
+
let grants;
|
|
58
|
+
try {
|
|
59
|
+
grants = JSON.parse(env.DRIVE_API_TOKENS);
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (typeof grants !== "object" || grants === null) return null;
|
|
64
|
+
let found;
|
|
65
|
+
for (const [candidate, grant] of Object.entries(grants)) {
|
|
66
|
+
if (sameText(candidate, token) && candidate.length >= MIN_TOKEN_LENGTH) found = grant;
|
|
67
|
+
}
|
|
68
|
+
return principalOf(found);
|
|
69
|
+
}
|
|
70
|
+
var schemas = /* @__PURE__ */ new WeakMap();
|
|
71
|
+
function settings(env) {
|
|
72
|
+
let publicUrl;
|
|
73
|
+
try {
|
|
74
|
+
publicUrl = new URL(env.DRIVE_PUBLIC_URL ?? "").origin;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const urlSecret = env.DRIVE_URL_SECRET;
|
|
79
|
+
return typeof urlSecret === "string" && new TextEncoder().encode(urlSecret).length >= 32 ? { publicUrl, urlSecret } : null;
|
|
80
|
+
}
|
|
81
|
+
var regionOf = (env) => parseRegion(env.DRIVE_REGION ?? "eu");
|
|
82
|
+
function blobsFor(env, config) {
|
|
83
|
+
return createR2Blobs(env.FILES, { region: regionOf(env), urlBase: `${config.publicUrl}/api/v1/drive/blob`, urlSecret: config.urlSecret });
|
|
84
|
+
}
|
|
85
|
+
async function driveFor(env, config) {
|
|
86
|
+
let ready = schemas.get(env.DB);
|
|
87
|
+
if (!ready) {
|
|
88
|
+
ready = env.DB.prepare(D1_SCHEMA).run().catch((error) => {
|
|
89
|
+
schemas.delete(env.DB);
|
|
90
|
+
throw error;
|
|
91
|
+
});
|
|
92
|
+
schemas.set(env.DB, ready);
|
|
93
|
+
}
|
|
94
|
+
await ready;
|
|
95
|
+
return createDrive({ store: createD1Store(env.DB, { region: regionOf(env) }), blobs: blobsFor(env, config) });
|
|
96
|
+
}
|
|
97
|
+
var number = (v, name) => {
|
|
98
|
+
if (v === void 0) return void 0;
|
|
99
|
+
const n = Number(v);
|
|
100
|
+
if (!Number.isFinite(n) || n < 0) throw new DriveError("INVALID_PATH", `${name} needs a number of 0 or more`);
|
|
101
|
+
return n;
|
|
102
|
+
};
|
|
103
|
+
var claimedType = (header) => header === void 0 || /^(application\/x-www-form-urlencoded|application\/octet-stream|multipart\/form-data)\b/i.test(header) ? void 0 : header;
|
|
104
|
+
function createApp() {
|
|
105
|
+
const app2 = new Hono();
|
|
106
|
+
app2.get("/health", (c) => c.json({ ok: true }));
|
|
107
|
+
const api = new Hono();
|
|
108
|
+
api.use("*", async (c, next) => {
|
|
109
|
+
const config = settings(c.env);
|
|
110
|
+
if (!config) return fail("NOT_CONFIGURED", "set DRIVE_PUBLIC_URL and DRIVE_URL_SECRET (32 bytes or more)", 503);
|
|
111
|
+
const ctx = authenticate(c.req.raw, c.env);
|
|
112
|
+
if (!ctx) return fail("UNAUTHORIZED", "send a valid bearer token", 401);
|
|
113
|
+
c.set("ctx", ctx);
|
|
114
|
+
c.set("drive", await driveFor(c.env, config));
|
|
115
|
+
await next();
|
|
116
|
+
});
|
|
117
|
+
const q = (c, k) => c.req.query(k);
|
|
118
|
+
const need = (value, name) => {
|
|
119
|
+
if (value === void 0) throw new DriveError("INVALID_PATH", `${name} is required`);
|
|
120
|
+
return value;
|
|
121
|
+
};
|
|
122
|
+
const body = async (c) => {
|
|
123
|
+
let parsed;
|
|
124
|
+
try {
|
|
125
|
+
parsed = await c.req.json();
|
|
126
|
+
} catch {
|
|
127
|
+
throw new DriveError("INVALID_PATH", "body must be JSON");
|
|
128
|
+
}
|
|
129
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new DriveError("INVALID_PATH", "body must be a JSON object");
|
|
130
|
+
return parsed;
|
|
131
|
+
};
|
|
132
|
+
const str = (v, name) => {
|
|
133
|
+
if (typeof v !== "string") throw new DriveError("INVALID_PATH", `${name} must be a string`);
|
|
134
|
+
return v;
|
|
135
|
+
};
|
|
136
|
+
const opt = (v, name) => v === void 0 ? void 0 : str(v, name);
|
|
137
|
+
api.get("/items", async (c) => c.json((await c.var.drive.list(c.var.ctx, q(c, "path") ?? "/")).map((n) => publicNode(n))));
|
|
138
|
+
api.get("/stat", async (c) => c.json(publicNode(await c.var.drive.stat(c.var.ctx, need(q(c, "path"), "path")))));
|
|
139
|
+
api.post("/folders", async (c) => {
|
|
140
|
+
const b = await body(c);
|
|
141
|
+
return c.json(publicNode(await c.var.drive.mkdir(c.var.ctx, str(b.path, "path"), { parents: b.parents === true })), 201);
|
|
142
|
+
});
|
|
143
|
+
api.put("/files", async (c) => {
|
|
144
|
+
const max = number(c.env.DRIVE_MAX_BYTES, "DRIVE_MAX_BYTES") ?? DEFAULT_MAX_BYTES;
|
|
145
|
+
const declared = Number(c.req.header("content-length"));
|
|
146
|
+
if (Number.isFinite(declared) && declared > max) return fail("TOO_LARGE", `upload is over the limit of ${max} bytes`, 413);
|
|
147
|
+
const stream = c.req.raw.body ?? new Blob([]).stream();
|
|
148
|
+
const node = await upload(c.var.drive, c.var.ctx, need(q(c, "path"), "path"), stream, {
|
|
149
|
+
maxBytes: max,
|
|
150
|
+
mime: q(c, "mime") ?? claimedType(c.req.header("content-type")),
|
|
151
|
+
parents: q(c, "parents") === "true",
|
|
152
|
+
onConflict: q(c, "onConflict")
|
|
153
|
+
});
|
|
154
|
+
return c.json(publicNode(node), 201);
|
|
155
|
+
});
|
|
156
|
+
api.get("/files", async (c) => {
|
|
157
|
+
const { node, data } = await c.var.drive.read(c.var.ctx, need(q(c, "path"), "path"));
|
|
158
|
+
return new Response(data, {
|
|
159
|
+
headers: {
|
|
160
|
+
"content-type": node.mime ?? "application/octet-stream",
|
|
161
|
+
"content-disposition": `attachment; filename*=UTF-8''${encodeURIComponent(node.name)}`,
|
|
162
|
+
"x-content-type-options": "nosniff",
|
|
163
|
+
"x-content-sha256": node.hash ?? ""
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
api.get("/url", async (c) => {
|
|
168
|
+
const expires = number(q(c, "expires"), "expires") ?? DEFAULT_URL_SECONDS;
|
|
169
|
+
return c.json({ url: await c.var.drive.downloadUrl(c.var.ctx, need(q(c, "path"), "path"), expires), expiresInSeconds: expires });
|
|
170
|
+
});
|
|
171
|
+
api.post("/move", async (c) => {
|
|
172
|
+
const b = await body(c);
|
|
173
|
+
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") })));
|
|
174
|
+
});
|
|
175
|
+
api.post("/trash", async (c) => c.json(publicNode(await c.var.drive.trash(c.var.ctx, str((await body(c)).path, "path")))));
|
|
176
|
+
api.get("/trash", async (c) => c.json((await c.var.drive.listTrash(c.var.ctx)).map((n) => publicNode(n))));
|
|
177
|
+
api.post("/restore", async (c) => {
|
|
178
|
+
const b = await body(c);
|
|
179
|
+
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") })));
|
|
180
|
+
});
|
|
181
|
+
api.post("/purge", async (c) => c.json(await c.var.drive.purge(c.var.ctx, str((await body(c)).id, "id"))));
|
|
182
|
+
api.get("/search", async (c) => {
|
|
183
|
+
const query = {
|
|
184
|
+
name: q(c, "name"),
|
|
185
|
+
mime: q(c, "mime"),
|
|
186
|
+
kind: q(c, "kind"),
|
|
187
|
+
minSize: number(q(c, "minSize"), "minSize"),
|
|
188
|
+
maxSize: number(q(c, "maxSize"), "maxSize"),
|
|
189
|
+
createdByKind: q(c, "createdByKind"),
|
|
190
|
+
limit: number(q(c, "limit"), "limit")
|
|
191
|
+
};
|
|
192
|
+
return c.json((await c.var.drive.search(c.var.ctx, query)).map((r) => publicNode(r.node, r.path)));
|
|
193
|
+
});
|
|
194
|
+
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"' } }));
|
|
195
|
+
api.delete("/account", async (c) => {
|
|
196
|
+
if (q(c, "confirm") !== "yes") throw new DriveError("INVALID_PATH", "add ?confirm=yes to delete everything you own for good");
|
|
197
|
+
return c.json(await c.var.drive.deleteAll(c.var.ctx));
|
|
198
|
+
});
|
|
199
|
+
app2.get("/api/v1/drive/blob", async (c) => {
|
|
200
|
+
const config = settings(c.env);
|
|
201
|
+
if (!config) return fail("NOT_CONFIGURED", "set DRIVE_PUBLIC_URL and DRIVE_URL_SECRET (32 bytes or more)", 503);
|
|
202
|
+
const signed = await verifySignedBlobUrl(c.req.url, { secret: config.urlSecret });
|
|
203
|
+
if (!signed || signed.method !== "GET") return fail("INVALID_LINK", "this link is not valid, or it has expired", 403);
|
|
204
|
+
const blob = await blobsFor(c.env, config).get(signed.key);
|
|
205
|
+
if (!blob) return fail("NOT_FOUND", "the bytes are gone", 404);
|
|
206
|
+
return new Response(blob.data, {
|
|
207
|
+
headers: {
|
|
208
|
+
"content-type": safeMime(signed.responseContentType ?? blob.contentType),
|
|
209
|
+
"content-disposition": signed.responseContentDisposition ?? "attachment",
|
|
210
|
+
"x-content-type-options": "nosniff",
|
|
211
|
+
"cache-control": "private, no-store"
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
app2.route("/api/v1/drive", api);
|
|
216
|
+
app2.notFound(() => fail("NOT_FOUND", "no such route", 404));
|
|
217
|
+
app2.onError((error) => {
|
|
218
|
+
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" } });
|
|
219
|
+
console.error("internal error", error instanceof Error ? error.name : "unknown");
|
|
220
|
+
return new Response(JSON.stringify({ error: { code: "INTERNAL", message: "the server could not finish the request" } }), { status: 500, headers: { "content-type": "application/json" } });
|
|
221
|
+
});
|
|
222
|
+
return app2;
|
|
223
|
+
}
|
|
224
|
+
var app = createApp();
|
|
225
|
+
var worker_default = { fetch: (request, env, ctx) => app.fetch(request, env, ctx) };
|
|
226
|
+
export {
|
|
227
|
+
authenticate,
|
|
228
|
+
createApp,
|
|
229
|
+
worker_default as default
|
|
230
|
+
};
|
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,75 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-drive",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.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
|
+
"LICENSE",
|
|
32
|
+
"llms.txt",
|
|
33
|
+
"SKILL.md"
|
|
34
|
+
],
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"mcpName": "io.github.pooriaarab/agentic-drive",
|
|
39
|
+
"main": "./dist/index.js",
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"exports": {
|
|
42
|
+
".": {
|
|
43
|
+
"types": "./dist/index.d.ts",
|
|
44
|
+
"import": "./dist/index.js"
|
|
45
|
+
},
|
|
46
|
+
"./worker": {
|
|
47
|
+
"types": "./dist/worker.d.ts",
|
|
48
|
+
"import": "./dist/worker.js"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"clean": "rm -rf dist",
|
|
56
|
+
"ci:local": "npm run build && npm run typecheck && npm test"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
60
|
+
"agentic-identity": "^0.2.0",
|
|
61
|
+
"hono": "^4.13.14",
|
|
62
|
+
"zod": "^3.25.0"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@types/node": "^22.0.0",
|
|
66
|
+
"miniflare": "^4.20260730.0",
|
|
67
|
+
"tsup": "^8.0.0",
|
|
68
|
+
"typescript": "^5.6.0",
|
|
69
|
+
"vitest": "^3.0.0",
|
|
70
|
+
"wrangler": "^4.155.0"
|
|
71
|
+
},
|
|
72
|
+
"bin": {
|
|
73
|
+
"agentic-drive": "./dist/cli.js"
|
|
74
|
+
}
|
|
75
|
+
}
|