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/cli.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
interface CliIo {
|
|
3
|
+
out(text: string): void;
|
|
4
|
+
err(text: string): void;
|
|
5
|
+
env: Record<string, string | undefined>;
|
|
6
|
+
}
|
|
7
|
+
declare class UsageError extends Error {
|
|
8
|
+
}
|
|
9
|
+
declare function runCli(argv: string[], io: CliIo): Promise<number>;
|
|
10
|
+
|
|
11
|
+
export { type CliIo, UsageError, runCli };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
VERSION
|
|
4
|
+
} from "./chunk-4QE7E5UQ.js";
|
|
5
|
+
import {
|
|
6
|
+
DriveError,
|
|
7
|
+
buildExport,
|
|
8
|
+
openLocal,
|
|
9
|
+
publicNode,
|
|
10
|
+
upload
|
|
11
|
+
} from "./chunk-QRGKXPLX.js";
|
|
12
|
+
|
|
13
|
+
// src/cli.ts
|
|
14
|
+
import { createReadStream, realpathSync, writeFileSync } from "fs";
|
|
15
|
+
import { basename, join, resolve } from "path";
|
|
16
|
+
import { pathToFileURL } from "url";
|
|
17
|
+
import { parseArgs } from "util";
|
|
18
|
+
var HELP = `agentic-drive ${VERSION}
|
|
19
|
+
Store, sort, and share files and folders.
|
|
20
|
+
|
|
21
|
+
Usage: agentic-drive <command> [flags]
|
|
22
|
+
|
|
23
|
+
Commands:
|
|
24
|
+
ls [path] List a folder.
|
|
25
|
+
stat <path|id> Show one item.
|
|
26
|
+
mkdir <path> [-p] Make a folder.
|
|
27
|
+
upload <local> <remote> Upload a file. End remote with "/" to keep the name.
|
|
28
|
+
download <remote> [out] Save a file. Use "-" for stdout.
|
|
29
|
+
mv <src> <folder> Move an item. Use --name to rename it.
|
|
30
|
+
rm <path|id> Move an item to the trash.
|
|
31
|
+
trash List the trash.
|
|
32
|
+
restore <id> Put a trashed item back.
|
|
33
|
+
purge <id> Delete a trashed item for good.
|
|
34
|
+
search Find items. Flags: --name --mime --kind --min-size --max-size --created-by-kind --limit
|
|
35
|
+
export <out.zip> Write everything you own to a zip with a JSON manifest.
|
|
36
|
+
delete-all --yes Delete everything you own for good.
|
|
37
|
+
mcp Start the MCP server on stdio.
|
|
38
|
+
|
|
39
|
+
Flags:
|
|
40
|
+
--dir <path> Data folder. Env AGENTIC_DRIVE_DIR. Default ./.agentic-drive
|
|
41
|
+
--owner <id> Drive owner. Env AGENTIC_DRIVE_OWNER. Default "me"
|
|
42
|
+
--actor <id> Who acts. Env AGENTIC_DRIVE_ACTOR. Default the owner
|
|
43
|
+
--actor-kind <k> Actor kind. Env AGENTIC_DRIVE_KIND. Default human
|
|
44
|
+
--on-conflict <m> fail, rename, or replace
|
|
45
|
+
-p, --parents Create missing parent folders
|
|
46
|
+
`;
|
|
47
|
+
var UsageError = class extends Error {
|
|
48
|
+
};
|
|
49
|
+
var options = {
|
|
50
|
+
dir: { type: "string" },
|
|
51
|
+
owner: { type: "string" },
|
|
52
|
+
actor: { type: "string" },
|
|
53
|
+
"actor-kind": { type: "string" },
|
|
54
|
+
"on-conflict": { type: "string" },
|
|
55
|
+
parents: { type: "boolean", short: "p" },
|
|
56
|
+
mime: { type: "string" },
|
|
57
|
+
"max-bytes": { type: "string" },
|
|
58
|
+
kind: { type: "string" },
|
|
59
|
+
name: { type: "string" },
|
|
60
|
+
parent: { type: "string" },
|
|
61
|
+
"min-size": { type: "string" },
|
|
62
|
+
"max-size": { type: "string" },
|
|
63
|
+
"created-by-kind": { type: "string" },
|
|
64
|
+
limit: { type: "string" },
|
|
65
|
+
yes: { type: "boolean" },
|
|
66
|
+
help: { type: "boolean", short: "h" },
|
|
67
|
+
version: { type: "boolean", short: "V" }
|
|
68
|
+
};
|
|
69
|
+
function num(value, flag) {
|
|
70
|
+
if (value === void 0) return void 0;
|
|
71
|
+
const n = Number(value);
|
|
72
|
+
if (!Number.isFinite(n) || n < 0) throw new UsageError(`--${flag} needs a number of 0 or more`);
|
|
73
|
+
return n;
|
|
74
|
+
}
|
|
75
|
+
async function runCli(argv, io) {
|
|
76
|
+
try {
|
|
77
|
+
const { values, positionals } = parseArgs({ args: argv, options, allowPositionals: true, strict: true });
|
|
78
|
+
const [command, ...args] = positionals;
|
|
79
|
+
if (values.version) return io.out(VERSION), 0;
|
|
80
|
+
if (!command || values.help || command === "help") return io.out(HELP), 0;
|
|
81
|
+
const need = (n, usage) => {
|
|
82
|
+
if (args.length < n) throw new UsageError(`usage: agentic-drive ${usage}`);
|
|
83
|
+
};
|
|
84
|
+
const env = io.env;
|
|
85
|
+
const dir = resolve(values.dir ?? env.AGENTIC_DRIVE_DIR ?? ".agentic-drive");
|
|
86
|
+
const { drive, ctx } = openLocal({
|
|
87
|
+
dir,
|
|
88
|
+
owner: values.owner ?? env.AGENTIC_DRIVE_OWNER ?? "me",
|
|
89
|
+
actor: values.actor ?? env.AGENTIC_DRIVE_ACTOR,
|
|
90
|
+
kind: values["actor-kind"] ?? env.AGENTIC_DRIVE_KIND
|
|
91
|
+
});
|
|
92
|
+
const onConflict = values["on-conflict"];
|
|
93
|
+
if (onConflict && !["fail", "rename", "replace"].includes(onConflict)) throw new UsageError("--on-conflict needs fail, rename, or replace");
|
|
94
|
+
const print = (value) => io.out(JSON.stringify(value));
|
|
95
|
+
switch (command) {
|
|
96
|
+
case "ls":
|
|
97
|
+
print((await drive.list(ctx, args[0] ?? "/")).map((n) => publicNode(n)));
|
|
98
|
+
break;
|
|
99
|
+
case "stat":
|
|
100
|
+
need(1, "stat <path|id>");
|
|
101
|
+
print(publicNode(await drive.stat(ctx, args[0])));
|
|
102
|
+
break;
|
|
103
|
+
case "mkdir":
|
|
104
|
+
need(1, "mkdir <path> [-p]");
|
|
105
|
+
print(publicNode(await drive.mkdir(ctx, args[0], { parents: values.parents })));
|
|
106
|
+
break;
|
|
107
|
+
case "upload": {
|
|
108
|
+
need(2, "upload <local> <remote>");
|
|
109
|
+
const remote = args[1].endsWith("/") ? `${args[1]}${basename(args[0])}` : args[1];
|
|
110
|
+
const node = await upload(drive, ctx, remote, createReadStream(resolve(args[0])), {
|
|
111
|
+
mime: values.mime,
|
|
112
|
+
maxBytes: num(values["max-bytes"], "max-bytes"),
|
|
113
|
+
parents: values.parents,
|
|
114
|
+
onConflict
|
|
115
|
+
});
|
|
116
|
+
print(publicNode(node));
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case "download": {
|
|
120
|
+
need(1, "download <remote> [out]");
|
|
121
|
+
const { node, data } = await drive.read(ctx, args[0]);
|
|
122
|
+
const out = args[1] ?? node.name;
|
|
123
|
+
if (out === "-") process.stdout.write(data);
|
|
124
|
+
else {
|
|
125
|
+
writeFileSync(resolve(out), data);
|
|
126
|
+
print({ saved: resolve(out), size: data.byteLength, hash: node.hash });
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "mv":
|
|
131
|
+
need(2, "mv <src> <folder>");
|
|
132
|
+
print(publicNode(await drive.move(ctx, args[0], args[1], { name: values.name, onConflict })));
|
|
133
|
+
break;
|
|
134
|
+
case "rm":
|
|
135
|
+
need(1, "rm <path|id>");
|
|
136
|
+
print(publicNode(await drive.trash(ctx, args[0])));
|
|
137
|
+
break;
|
|
138
|
+
case "trash":
|
|
139
|
+
print((await drive.listTrash(ctx)).map((n) => publicNode(n)));
|
|
140
|
+
break;
|
|
141
|
+
case "restore":
|
|
142
|
+
need(1, "restore <id>");
|
|
143
|
+
print(publicNode(await drive.restore(ctx, args[0], { parentId: values.parent, onConflict })));
|
|
144
|
+
break;
|
|
145
|
+
case "purge":
|
|
146
|
+
need(1, "purge <id>");
|
|
147
|
+
print(await drive.purge(ctx, args[0]));
|
|
148
|
+
break;
|
|
149
|
+
case "search":
|
|
150
|
+
print((await drive.search(ctx, {
|
|
151
|
+
name: values.name,
|
|
152
|
+
mime: values.mime,
|
|
153
|
+
kind: values.kind,
|
|
154
|
+
minSize: num(values["min-size"], "min-size"),
|
|
155
|
+
maxSize: num(values["max-size"], "max-size"),
|
|
156
|
+
createdByKind: values["created-by-kind"],
|
|
157
|
+
limit: num(values.limit, "limit")
|
|
158
|
+
})).map((r) => publicNode(r.node, r.path)));
|
|
159
|
+
break;
|
|
160
|
+
case "export": {
|
|
161
|
+
need(1, "export <out.zip>");
|
|
162
|
+
const zip = await buildExport(drive, ctx);
|
|
163
|
+
writeFileSync(resolve(args[0]), zip);
|
|
164
|
+
print({ saved: resolve(args[0]), size: zip.byteLength });
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case "delete-all":
|
|
168
|
+
if (!values.yes) throw new UsageError("delete-all removes everything you own for good. Add --yes to confirm.");
|
|
169
|
+
print(await drive.deleteAll(ctx));
|
|
170
|
+
break;
|
|
171
|
+
case "mcp": {
|
|
172
|
+
const { startMcp } = await import("./mcp.js");
|
|
173
|
+
await startMcp({ drive, ctx, exportDir: join(dir, "exports") });
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
default:
|
|
177
|
+
throw new UsageError(`unknown command "${command}"`);
|
|
178
|
+
}
|
|
179
|
+
return 0;
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (error instanceof UsageError || error instanceof TypeError && "code" in error && String(error.code).startsWith("ERR_PARSE_ARGS")) {
|
|
182
|
+
io.err(JSON.stringify({ error: { code: "USAGE", message: error.message } }));
|
|
183
|
+
return 2;
|
|
184
|
+
}
|
|
185
|
+
if (error instanceof DriveError) {
|
|
186
|
+
io.err(JSON.stringify({ error: { code: error.code, message: error.message } }));
|
|
187
|
+
return 1;
|
|
188
|
+
}
|
|
189
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
190
|
+
io.err(JSON.stringify({ error: { code: "LOCAL_FILE_NOT_FOUND", message: error.message } }));
|
|
191
|
+
return 1;
|
|
192
|
+
}
|
|
193
|
+
io.err(JSON.stringify({ error: { code: "INTERNAL", message: error instanceof Error ? error.message : String(error) } }));
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function isMainModule() {
|
|
198
|
+
const invoked = process.argv[1];
|
|
199
|
+
if (typeof invoked !== "string") return false;
|
|
200
|
+
try {
|
|
201
|
+
return import.meta.url === pathToFileURL(realpathSync(invoked)).href;
|
|
202
|
+
} catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (isMainModule()) {
|
|
207
|
+
runCli(process.argv.slice(2), {
|
|
208
|
+
out: (t) => console.log(t),
|
|
209
|
+
err: (t) => console.error(t),
|
|
210
|
+
env: process.env
|
|
211
|
+
}).then((code) => {
|
|
212
|
+
if (code !== 0) process.exit(code);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
export {
|
|
216
|
+
UsageError,
|
|
217
|
+
runCli
|
|
218
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Store, Blobs } from 'agentic-identity';
|
|
2
|
+
|
|
3
|
+
declare const ROOT_ID = "root";
|
|
4
|
+
declare const ACTOR_KINDS: readonly ["human", "agent_of_human", "agent_of_agent", "agent", "user_token"];
|
|
5
|
+
type ActorKind = (typeof ACTOR_KINDS)[number];
|
|
6
|
+
type Actor = {
|
|
7
|
+
id: string;
|
|
8
|
+
kind: ActorKind;
|
|
9
|
+
};
|
|
10
|
+
/** `owner` is the drive's owner. `actor` is who acts for them. */
|
|
11
|
+
type Ctx = {
|
|
12
|
+
owner: string;
|
|
13
|
+
actor: Actor;
|
|
14
|
+
};
|
|
15
|
+
type Action = "read" | "write" | "delete";
|
|
16
|
+
/** The seam for sharing. A later release swaps this for a role check. */
|
|
17
|
+
type Can = (ctx: Ctx, action: Action, node: Node) => boolean | Promise<boolean>;
|
|
18
|
+
type Node = {
|
|
19
|
+
id: string;
|
|
20
|
+
owner: string;
|
|
21
|
+
parentId: string;
|
|
22
|
+
kind: "folder" | "file" | "ref";
|
|
23
|
+
name: string;
|
|
24
|
+
nameKey: string;
|
|
25
|
+
size: number;
|
|
26
|
+
mime: string | null;
|
|
27
|
+
hash: string | null;
|
|
28
|
+
/** A typed reference such as `docs:<id>`. Set when kind is "ref". */
|
|
29
|
+
ref: string | null;
|
|
30
|
+
blobKey: string | null;
|
|
31
|
+
createdAt: number;
|
|
32
|
+
updatedAt: number;
|
|
33
|
+
createdBy: Actor;
|
|
34
|
+
updatedBy: Actor;
|
|
35
|
+
trashed: boolean;
|
|
36
|
+
trashedAt: number | null;
|
|
37
|
+
/** Set while a purge deletes the item. Nothing can see or change it then. */
|
|
38
|
+
purging: boolean;
|
|
39
|
+
};
|
|
40
|
+
type ErrorCode = "NOT_FOUND" | "NAME_TAKEN" | "BLOB_EXISTS" | "INVALID_NAME" | "INVALID_PATH" | "INVALID_REF" | "NOT_A_FOLDER" | "NOT_A_FILE" | "MOVE_INTO_SELF" | "TOO_DEEP" | "PARENT_GONE" | "TRASHED" | "NOT_TRASHED" | "IS_ROOT" | "FORBIDDEN" | "BLOB_MISSING" | "TOO_LARGE" | "INVALID_EXPIRY" | "BUSY";
|
|
41
|
+
declare class DriveError extends Error {
|
|
42
|
+
readonly code: ErrorCode;
|
|
43
|
+
constructor(code: ErrorCode, message: string);
|
|
44
|
+
}
|
|
45
|
+
interface SearchQuery {
|
|
46
|
+
/** Part of the name. Case and Unicode form do not matter. */
|
|
47
|
+
name?: string;
|
|
48
|
+
/** An exact type, or a family such as `image/*`. */
|
|
49
|
+
mime?: string;
|
|
50
|
+
kind?: Node["kind"];
|
|
51
|
+
minSize?: number;
|
|
52
|
+
maxSize?: number;
|
|
53
|
+
/** Items first made by this kind of actor, such as `agent_of_human`. */
|
|
54
|
+
createdByKind?: ActorKind;
|
|
55
|
+
/** At most this many results. Defaults to 100. */
|
|
56
|
+
limit?: number;
|
|
57
|
+
}
|
|
58
|
+
type OnConflict = "fail" | "rename" | "replace";
|
|
59
|
+
interface DriveDeps {
|
|
60
|
+
store: Store;
|
|
61
|
+
blobs: Blobs;
|
|
62
|
+
now?: () => number;
|
|
63
|
+
newId?: () => string;
|
|
64
|
+
can?: Can;
|
|
65
|
+
/** Most parent steps any ancestor walk takes. A walk that hits it fails closed. */
|
|
66
|
+
maxDepth?: number;
|
|
67
|
+
/** Called with a blob key that no node points at and that could not be deleted. */
|
|
68
|
+
onOrphan?: (key: string, error: unknown) => void;
|
|
69
|
+
}
|
|
70
|
+
type Drive = ReturnType<typeof createDrive>;
|
|
71
|
+
/** Validates a name. Names are NFC-normalized. Two names clash when their keys match. */
|
|
72
|
+
declare function parseName(raw: string): {
|
|
73
|
+
name: string;
|
|
74
|
+
key: string;
|
|
75
|
+
};
|
|
76
|
+
declare function createDrive(deps: DriveDeps): {
|
|
77
|
+
stat(ctx: Ctx, loc: string): Promise<Node>;
|
|
78
|
+
/** Live children, sorted by name. */
|
|
79
|
+
list(ctx: Ctx, loc?: string): Promise<Node[]>;
|
|
80
|
+
mkdir(ctx: Ctx, path: string, opts?: {
|
|
81
|
+
parents?: boolean;
|
|
82
|
+
}): Promise<Node>;
|
|
83
|
+
/**
|
|
84
|
+
* Stores one blob and one node. The blob is written first. The new node commits before
|
|
85
|
+
* any old blob goes. A failed old-blob delete leaves an orphan and never undoes the commit.
|
|
86
|
+
*/
|
|
87
|
+
putFile(ctx: Ctx, path: string, data: Uint8Array, opts?: {
|
|
88
|
+
mime?: string;
|
|
89
|
+
parents?: boolean;
|
|
90
|
+
onConflict?: OnConflict;
|
|
91
|
+
}): Promise<Node>;
|
|
92
|
+
/** A reference points at a document in another app, such as `docs:<id>`. */
|
|
93
|
+
putRef(ctx: Ctx, path: string, ref: string, opts?: {
|
|
94
|
+
parents?: boolean;
|
|
95
|
+
onConflict?: OnConflict;
|
|
96
|
+
}): Promise<Node>;
|
|
97
|
+
read(ctx: Ctx, loc: string): Promise<{
|
|
98
|
+
node: Node;
|
|
99
|
+
data: Uint8Array;
|
|
100
|
+
contentType?: string;
|
|
101
|
+
}>;
|
|
102
|
+
/**
|
|
103
|
+
* A time-limited URL to fetch the bytes. The URL signs an attachment header and the stored
|
|
104
|
+
* type, so the link holder cannot change how the bytes are served. Trashing an item does
|
|
105
|
+
* not revoke a link already issued.
|
|
106
|
+
*/
|
|
107
|
+
downloadUrl(ctx: Ctx, loc: string, expiresInSeconds?: number): Promise<string>;
|
|
108
|
+
move(ctx: Ctx, src: string, destLoc: string, opts?: {
|
|
109
|
+
name?: string;
|
|
110
|
+
onConflict?: OnConflict;
|
|
111
|
+
}): Promise<Node>;
|
|
112
|
+
rename(ctx: Ctx, loc: string, name: string): Promise<Node>;
|
|
113
|
+
/** Hides an item and, for a folder, everything under it. Nothing else changes. */
|
|
114
|
+
trash(ctx: Ctx, loc: string): Promise<Node>;
|
|
115
|
+
/** Items trashed on their own, newest first. */
|
|
116
|
+
listTrash(ctx: Ctx): Promise<Node[]>;
|
|
117
|
+
/** Puts a trashed item back. It needs a live parent folder. */
|
|
118
|
+
restore(ctx: Ctx, id: string, opts?: {
|
|
119
|
+
parentId?: string;
|
|
120
|
+
onConflict?: OnConflict;
|
|
121
|
+
}): Promise<Node>;
|
|
122
|
+
/** Live items that match every field given, sorted by path. */
|
|
123
|
+
search(ctx: Ctx, q?: SearchQuery): Promise<{
|
|
124
|
+
node: Node;
|
|
125
|
+
path: string;
|
|
126
|
+
}[]>;
|
|
127
|
+
/** Every item of the owner, trashed or not, with its original path and bytes. For export. */
|
|
128
|
+
exportData(ctx: Ctx): Promise<{
|
|
129
|
+
node: Node;
|
|
130
|
+
path: string;
|
|
131
|
+
data: Uint8Array | null;
|
|
132
|
+
missing: boolean;
|
|
133
|
+
}[]>;
|
|
134
|
+
/** Deletes every item and blob of the owner for good. Only the owner may do this, never an agent for them. A failed call can run again. */
|
|
135
|
+
deleteAll(ctx: Ctx): Promise<{
|
|
136
|
+
removed: number;
|
|
137
|
+
}>;
|
|
138
|
+
/** Deletes a trashed item for good, with everything under it and all their bytes. */
|
|
139
|
+
purge(ctx: Ctx, id: string): Promise<{
|
|
140
|
+
removed: number;
|
|
141
|
+
}>;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export { type ActorKind as A, type Ctx as C, type Drive as D, type ErrorCode as E, type Node as N, type OnConflict as O, ROOT_ID as R, type SearchQuery as S, type Actor as a, ACTOR_KINDS as b, type Action as c, type Can as d, type DriveDeps as e, DriveError as f, createDrive as g, parseName as p };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { O as OnConflict, D as Drive, C as Ctx, N as Node, A as ActorKind, a as Actor } from './drive-DRQ8LiHl.js';
|
|
2
|
+
export { b as ACTOR_KINDS, c as Action, 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 } from './drive-DRQ8LiHl.js';
|
|
3
|
+
import { AdapterOptions, Blobs, Store } from 'agentic-identity';
|
|
4
|
+
|
|
5
|
+
declare const VERSION: string;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A Store kept in a journal file: one JSON line per committed write set, replayed on open.
|
|
9
|
+
* It is the shared memory store plus the journal, so it follows the same rules. One process at a time.
|
|
10
|
+
* A half-written last line (a crash during an append) is cut off on open. A bad line anywhere else
|
|
11
|
+
* throws, because the data cannot be trusted. If an append fails, the store refuses every later call.
|
|
12
|
+
* `shared` exists for the contract suites: stores that share a name share one live dataset, so do not
|
|
13
|
+
* open the same directory twice with it in production.
|
|
14
|
+
*/
|
|
15
|
+
declare function createFileStore(dir: string, options?: AdapterOptions & {
|
|
16
|
+
shared?: string;
|
|
17
|
+
}): Store;
|
|
18
|
+
/** Blobs kept as files under `dir`. One process at a time: a conditional put is atomic only inside one process. File names are key hashes, so no key escapes the folder. */
|
|
19
|
+
declare function createFileBlobs(dir: string, options?: AdapterOptions): Blobs;
|
|
20
|
+
|
|
21
|
+
declare const DEFAULT_MAX_BYTES: number;
|
|
22
|
+
/** The type named by the first bytes, or null when they match no known signature. */
|
|
23
|
+
declare function sniffMime(head: Uint8Array): string | null;
|
|
24
|
+
interface UploadOptions {
|
|
25
|
+
/** Refuse a stream longer than this. Defaults to 25 MiB. */
|
|
26
|
+
maxBytes?: number;
|
|
27
|
+
/** The type the caller claims. Known file signatures override it. */
|
|
28
|
+
mime?: string;
|
|
29
|
+
parents?: boolean;
|
|
30
|
+
onConflict?: OnConflict;
|
|
31
|
+
}
|
|
32
|
+
/** Reads the stream with a size cap, finds the type, hashes the bytes, then stores the file. */
|
|
33
|
+
declare function upload(drive: Drive, ctx: Ctx, path: string, source: AsyncIterable<Uint8Array> | ReadableStream<Uint8Array>, opts?: UploadOptions): Promise<Node>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Everything one owner has, as a zip. `manifest.json` lists each item with its
|
|
37
|
+
* metadata and, for a file, the entry that holds its bytes. Trashed items are included and flagged.
|
|
38
|
+
* Entries are named by item id, never by file name. A file whose bytes are gone is marked `missing`.
|
|
39
|
+
*/
|
|
40
|
+
declare function buildExport(drive: Drive, ctx: Ctx, now?: () => number): Promise<Uint8Array>;
|
|
41
|
+
|
|
42
|
+
interface ZipEntry {
|
|
43
|
+
name: string;
|
|
44
|
+
data: Uint8Array;
|
|
45
|
+
}
|
|
46
|
+
declare function writeZip(entries: ZipEntry[], at: number): Uint8Array;
|
|
47
|
+
/** Reads a zip made by `writeZip`. It checks every CRC. */
|
|
48
|
+
declare function readZip(bytes: Uint8Array): Map<string, Uint8Array>;
|
|
49
|
+
|
|
50
|
+
interface LocalOptions {
|
|
51
|
+
/** Where the drive keeps its data. */
|
|
52
|
+
dir: string;
|
|
53
|
+
/** Whose drive this is. */
|
|
54
|
+
owner: string;
|
|
55
|
+
/** Who acts. Defaults to the owner. */
|
|
56
|
+
actor?: string;
|
|
57
|
+
/** What kind of actor. Defaults to `human`. Set an agent kind when an agent runs the command. */
|
|
58
|
+
kind?: ActorKind;
|
|
59
|
+
}
|
|
60
|
+
declare function openLocal(options: LocalOptions): {
|
|
61
|
+
drive: Drive;
|
|
62
|
+
ctx: Ctx;
|
|
63
|
+
};
|
|
64
|
+
/** A node as the CLI and MCP print it: no storage keys. */
|
|
65
|
+
declare function publicNode(node: Node, path?: string): {
|
|
66
|
+
id: string;
|
|
67
|
+
owner: string;
|
|
68
|
+
parentId: string;
|
|
69
|
+
kind: "folder" | "file" | "ref";
|
|
70
|
+
name: string;
|
|
71
|
+
size: number;
|
|
72
|
+
mime: string | null;
|
|
73
|
+
hash: string | null;
|
|
74
|
+
ref: string | null;
|
|
75
|
+
createdAt: number;
|
|
76
|
+
updatedAt: number;
|
|
77
|
+
createdBy: Actor;
|
|
78
|
+
updatedBy: Actor;
|
|
79
|
+
trashed: boolean;
|
|
80
|
+
trashedAt: number | null;
|
|
81
|
+
} | {
|
|
82
|
+
id: string;
|
|
83
|
+
owner: string;
|
|
84
|
+
parentId: string;
|
|
85
|
+
kind: "folder" | "file" | "ref";
|
|
86
|
+
name: string;
|
|
87
|
+
size: number;
|
|
88
|
+
mime: string | null;
|
|
89
|
+
hash: string | null;
|
|
90
|
+
ref: string | null;
|
|
91
|
+
createdAt: number;
|
|
92
|
+
updatedAt: number;
|
|
93
|
+
createdBy: Actor;
|
|
94
|
+
updatedBy: Actor;
|
|
95
|
+
trashed: boolean;
|
|
96
|
+
trashedAt: number | null;
|
|
97
|
+
path: string;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export { Actor, ActorKind, Ctx, DEFAULT_MAX_BYTES, Drive, Node, OnConflict, type UploadOptions, VERSION, buildExport, createFileBlobs, createFileStore, openLocal, publicNode, readZip, sniffMime, upload, writeZip };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VERSION
|
|
3
|
+
} from "./chunk-4QE7E5UQ.js";
|
|
4
|
+
import {
|
|
5
|
+
ACTOR_KINDS,
|
|
6
|
+
DEFAULT_MAX_BYTES,
|
|
7
|
+
DriveError,
|
|
8
|
+
ROOT_ID,
|
|
9
|
+
buildExport,
|
|
10
|
+
createDrive,
|
|
11
|
+
createFileBlobs,
|
|
12
|
+
createFileStore,
|
|
13
|
+
openLocal,
|
|
14
|
+
parseName,
|
|
15
|
+
publicNode,
|
|
16
|
+
readZip,
|
|
17
|
+
sniffMime,
|
|
18
|
+
upload,
|
|
19
|
+
writeZip
|
|
20
|
+
} from "./chunk-QRGKXPLX.js";
|
|
21
|
+
export {
|
|
22
|
+
ACTOR_KINDS,
|
|
23
|
+
DEFAULT_MAX_BYTES,
|
|
24
|
+
DriveError,
|
|
25
|
+
ROOT_ID,
|
|
26
|
+
VERSION,
|
|
27
|
+
buildExport,
|
|
28
|
+
createDrive,
|
|
29
|
+
createFileBlobs,
|
|
30
|
+
createFileStore,
|
|
31
|
+
openLocal,
|
|
32
|
+
parseName,
|
|
33
|
+
publicNode,
|
|
34
|
+
readZip,
|
|
35
|
+
sniffMime,
|
|
36
|
+
upload,
|
|
37
|
+
writeZip
|
|
38
|
+
};
|
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-DRQ8LiHl.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,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VERSION
|
|
3
|
+
} from "./chunk-4QE7E5UQ.js";
|
|
4
|
+
import {
|
|
5
|
+
ACTOR_KINDS,
|
|
6
|
+
DriveError,
|
|
7
|
+
buildExport,
|
|
8
|
+
publicNode,
|
|
9
|
+
upload
|
|
10
|
+
} from "./chunk-QRGKXPLX.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 that starts with "/", 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_export",
|
|
74
|
+
"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.",
|
|
75
|
+
{ name: z.string() },
|
|
76
|
+
async ({ name }) => {
|
|
77
|
+
if (!EXPORT_NAME.test(name)) throw new DriveError("INVALID_PATH", "name must be a plain file name that ends in .zip");
|
|
78
|
+
const zip = await buildExport(drive, ctx);
|
|
79
|
+
mkdirSync(exportDir, { recursive: true });
|
|
80
|
+
const saved = join(exportDir, name);
|
|
81
|
+
try {
|
|
82
|
+
writeFileSync(saved, zip, { flag: "wx" });
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error.code === "EEXIST") throw new DriveError("NAME_TAKEN", `${name} already exists in the export folder`);
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
return { saved, size: zip.byteLength };
|
|
88
|
+
},
|
|
89
|
+
{ destructiveHint: false }
|
|
90
|
+
);
|
|
91
|
+
return server;
|
|
92
|
+
}
|
|
93
|
+
async function startMcp(deps) {
|
|
94
|
+
await createMcpServer(deps.drive, deps.ctx, deps.exportDir).connect(new StdioServerTransport());
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
createMcpServer,
|
|
98
|
+
startMcp
|
|
99
|
+
};
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { D1Like, R2Like } from 'agentic-identity/cloudflare';
|
|
3
|
+
import { C as Ctx, D as Drive } from './drive-DRQ8LiHl.js';
|
|
4
|
+
import 'agentic-identity';
|
|
5
|
+
|
|
6
|
+
interface WorkerEnv {
|
|
7
|
+
DB: D1Like;
|
|
8
|
+
FILES: R2Like;
|
|
9
|
+
/**
|
|
10
|
+
* JSON: `{ "<token>": { "owner": "u1", "actor": "u1", "kind": "human" } }`. A token is at least 32 characters.
|
|
11
|
+
* Every grant needs an owner, an actor id, and an actor kind. A token with a missing or bad part gets 401.
|
|
12
|
+
* Without the secret, every request fails with 401.
|
|
13
|
+
*/
|
|
14
|
+
DRIVE_API_TOKENS?: string;
|
|
15
|
+
DRIVE_MAX_BYTES?: string;
|
|
16
|
+
/** The origin of this Worker, such as `https://drive.example.com`. Signed download URLs start with it. */
|
|
17
|
+
DRIVE_PUBLIC_URL?: string;
|
|
18
|
+
/** At least 32 bytes. Signs download URLs. Without it, every request gets 503. */
|
|
19
|
+
DRIVE_URL_SECRET?: string;
|
|
20
|
+
DRIVE_REGION?: string;
|
|
21
|
+
}
|
|
22
|
+
type App = {
|
|
23
|
+
Bindings: WorkerEnv;
|
|
24
|
+
Variables: {
|
|
25
|
+
drive: Drive;
|
|
26
|
+
ctx: Ctx;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
/** The seam for sign-in. A later release swaps this for agentic-identity. */
|
|
30
|
+
declare function authenticate(request: Request, env: WorkerEnv): 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 };
|