agentic-drive 0.3.0 → 0.4.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/CHANGELOG.md +33 -0
- package/README.md +94 -5
- package/dist/{chunk-VSVIJBMJ.js → chunk-ATJDMCRV.js} +61 -9
- package/dist/{chunk-GMA5I5L6.js → chunk-DRACF66Y.js} +1 -1
- package/dist/{chunk-5KEISBS4.js → chunk-L7GASKBQ.js} +1 -1
- package/dist/cli.js +44 -14
- package/dist/client.d.ts +1 -1
- package/dist/client.js +2 -2
- package/dist/{drive-Dlzwukjq.d.ts → drive-Cn3mpuDW.d.ts} +3 -3
- package/dist/index.d.ts +26 -16
- package/dist/index.js +15 -3
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +2 -2
- package/dist/worker.d.ts +8 -7
- package/dist/worker.js +4 -8
- package/llms.txt +1 -1
- package/package.json +16 -3
- package/ui-dist/_headers +4 -0
- package/ui-dist/assets/index-e_cefh0u.js +49 -0
- package/ui-dist/assets/index-xUEd8lQS.css +1 -0
- package/ui-dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/ui-dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/ui-dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/ui-dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/ui-dist/index.html +14 -0
- package/ui-dist/theme-init.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
Read the breaking changes before you upgrade or deploy.
|
|
6
|
+
|
|
7
|
+
### Breaking changes
|
|
8
|
+
|
|
9
|
+
- **The CLI acts as a real principal.** The `--owner`, `--actor`, and `--actor-kind` flags and the `AGENTIC_DRIVE_OWNER`, `_ACTOR`, and `_KIND` variables are gone. Pass an API key with `--token` or `AGENTIC_DRIVE_TOKEN`. agentic-identity resolves the key. With no key, or a revoked or unknown key, the CLI and `mcp` exit with code 3. The default owner `me` is gone.
|
|
10
|
+
- **`openLocal(dir)` takes a folder and returns `{ drive, store }`.** Use `resolveToken(store, token)` to get the `ctx`.
|
|
11
|
+
- **The Worker serves the web UI from `ui-dist`.** `wrangler.jsonc` names `ui-dist` as the assets folder. Run `npm run build:ui` before each `wrangler deploy`. Without the build, the deploy fails because `ui-dist` does not exist. A `ui-dist` left from an older build ships an old UI.
|
|
12
|
+
- **Paths other than `/api/*` and `/health` get the UI.** The assets answer every other path. An unknown GET path returns the UI page with status 200, not a 404 from the Worker. A client that expects a 404 for such a path must change.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`principal add`, `principal revoke`, `key issue`, and `key revoke`** make local principals and keys. `principal add` takes `--of <person>` for an agent and `--scope <s>`, which repeats. `key issue` takes `--label`. The package root also exports `addPrincipal`, `revokePrincipal`, `issueKey`, `revokeKey`, and `AuthError`.
|
|
17
|
+
- **A web UI.** The Worker serves it from `ui-dist` (`npm run build:ui`). It uses agentic-design and the typed client. It has a folder tree, breadcrumbs, a list and a grid, upload by button and by drop, new folder, rename, move, trash and restore, and a share dialog. An item that an agent made shows a "Created by agent" label while a person is its last editor. "Edited by" names the last editor, a person or an agent. A Playwright test with an axe WCAG 2.2 AA check covers each view in light and dark.
|
|
18
|
+
- **A content security policy.** The UI loads scripts and fonts only from its own origin. It allows inline styles. The UI refuses to be framed. It also sends `X-Content-Type-Options: nosniff` and `Referrer-Policy: no-referrer`.
|
|
19
|
+
- **Same-origin downloads.** The UI refuses a download link on another origin. Set `DRIVE_PUBLIC_URL` to the origin that serves the UI.
|
|
20
|
+
- **The UI ships in the package.** `npm pack` runs `build:ui` in `prepack`, and the tarball has `ui-dist`.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- `npm run ci:local` and CI also build the UI and run `npm run test:e2e`. The E2E test needs Chromium.
|
|
25
|
+
- The package adds no runtime dependency. The UI build and test tools are dev dependencies.
|
|
26
|
+
- The MCP registry entry names `AGENTIC_DRIVE_TOKEN` as a required secret, and `AGENTIC_DRIVE_DIR` as optional.
|
|
27
|
+
|
|
28
|
+
### Migration
|
|
29
|
+
|
|
30
|
+
Local data from 0.3.0 belongs to the owner `me`, or to the id you passed to `--owner`. The data folder does not change. Without the key of that owner, the CLI shows an empty drive. Make the principal and a key, then use the key:
|
|
31
|
+
|
|
32
|
+
1. Run `npx agentic-drive principal add me`, with your old owner id if it was not `me`. Pass the same `--dir` or `AGENTIC_DRIVE_DIR` as before, if you set one.
|
|
33
|
+
2. Run `export AGENTIC_DRIVE_TOKEN=$(npx agentic-drive key issue me | jq -r .token)`.
|
|
34
|
+
3. Put the same key in each MCP client config as `AGENTIC_DRIVE_TOKEN`, and the same data folder as `AGENTIC_DRIVE_DIR`, if you set one.
|
|
35
|
+
|
|
3
36
|
## 0.3.0
|
|
4
37
|
|
|
5
38
|
Read the breaking changes before you upgrade. The migration steps follow them.
|
package/README.md
CHANGED
|
@@ -7,8 +7,22 @@
|
|
|
7
7
|
|
|
8
8
|
Store, sort, and share files and folders.
|
|
9
9
|
|
|
10
|
-
> Status: early. The library, CLI, MCP server, and HTTP API work, with sharing in the SDK and the HTTP API.
|
|
11
|
-
>
|
|
10
|
+
> Status: 0.4.0, early. The library, CLI, MCP server, and HTTP API work, with sharing in the SDK and the HTTP API.
|
|
11
|
+
> A small web UI works. See [UI](#ui).
|
|
12
|
+
|
|
13
|
+
## Live demo
|
|
14
|
+
|
|
15
|
+
A demo Worker runs at https://agentic-drive-demo.hello-fd1.workers.dev. `GET /health` answers 200, and the web UI is served at `/`.
|
|
16
|
+
The API needs a bearer token, so a call without one gets 401. The demo holds throwaway data only, and it can be reset at any time.
|
|
17
|
+
|
|
18
|
+
To redeploy it, set `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` in your shell, then run:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm run deploy:demo
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This uses `wrangler.demo.jsonc` (D1 in `weur`, R2 in the EU jurisdiction, `workers_dev` on). The URL signing secret lives in the Worker:
|
|
25
|
+
`openssl rand -hex 32 | npx wrangler secret put DRIVE_URL_SECRET -c wrangler.demo.jsonc`. Deploys are manual. No CI secret is used.
|
|
12
26
|
|
|
13
27
|
## Install
|
|
14
28
|
|
|
@@ -42,8 +56,26 @@ npx agentic-drive mcp
|
|
|
42
56
|
```
|
|
43
57
|
|
|
44
58
|
Every command prints JSON. A failure prints `{"error":{"code","message"}}` on stderr and exits 1.
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
Exit code 2 means bad usage. Exit code 3 means no principal, or a key that is not valid.
|
|
60
|
+
|
|
61
|
+
Every command except `help`, `version`, `principal`, and `key` needs a principal. Pass an API key with
|
|
62
|
+
`--token <key>` or `AGENTIC_DRIVE_TOKEN`. Prefer the variable: a flag shows in `ps`.
|
|
63
|
+
`agentic-identity` resolves the key. The drive belongs to the person at the top of the
|
|
64
|
+
principal's chain. An agent key marks each item it makes as made by a machine. A revoked,
|
|
65
|
+
expired, or unknown key fails with exit code 3.
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
npx agentic-drive principal add alice
|
|
69
|
+
export AGENTIC_DRIVE_TOKEN=$(npx agentic-drive key issue alice | jq -r .token)
|
|
70
|
+
npx agentic-drive principal add helper --of alice # an agent of alice
|
|
71
|
+
npx agentic-drive key issue helper # the key shows once
|
|
72
|
+
npx agentic-drive key revoke <key>
|
|
73
|
+
npx agentic-drive principal revoke helper # stops its keys, and its agents
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`principal` and `key` need no key: anyone who can read the `--dir` folder can run them.
|
|
77
|
+
Set `--dir` (or `AGENTIC_DRIVE_DIR`) to choose the data folder.
|
|
78
|
+
The local drive checks the owner only. It does not apply the scopes of an agent key.
|
|
47
79
|
|
|
48
80
|
## HTTP API
|
|
49
81
|
|
|
@@ -70,7 +102,7 @@ Sharing routes: `GET /shares?path=`, `POST /shares`, `DELETE /shares?path=&subje
|
|
|
70
102
|
`POST /invites/accept`, `POST /links`, `DELETE /links?id=`. A link visitor reads with the header
|
|
71
103
|
`x-link-token` on `GET /shared/:id`, `GET /shared/:id/items`, and `GET /shared/:id/content`.
|
|
72
104
|
Name a shared item by its id. A path can start at a folder id: `<folder id>/notes/a.txt`.
|
|
73
|
-
Without `DRIVE_URL_SECRET` or `DRIVE_PUBLIC_URL`, every request gets 503.
|
|
105
|
+
Without `DRIVE_URL_SECRET` or `DRIVE_PUBLIC_URL`, every API request gets 503. `/health` still answers.
|
|
74
106
|
Create the R2 bucket with `--jurisdiction eu` and the D1 database with `--location weur`.
|
|
75
107
|
The Worker declares region `eu` and refuses a D1 query that a non-EU region served. `DRIVE_REGION=unknown` turns this off for local runs.
|
|
76
108
|
|
|
@@ -101,6 +133,63 @@ try {
|
|
|
101
133
|
- A browser page needs the Worker on its own origin for now: the Worker sends no CORS headers.
|
|
102
134
|
- Share with a subject: `share("/pub", { kind: "human", id: "u2" }, "viewer")`.
|
|
103
135
|
|
|
136
|
+
## UI
|
|
137
|
+
|
|
138
|
+
The Worker serves a small web UI. It uses the [agentic-design](https://www.npmjs.com/package/agentic-design) package and the typed client of this package (`agentic-drive/client`). It has a folder tree, breadcrumbs, a file list and grid, upload by button or by drag and drop, new folder, rename, move, trash and restore, and a share dialog with roles and a link. An item that an agent made shows a "Created by agent" label while a person is its last editor. "Edited by" names the last editor, a person or an agent. It works with the keyboard. An automated axe check for WCAG 2.2 AA passes on each view in the light and dark themes.
|
|
139
|
+
|
|
140
|
+
Run it locally:
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
npm ci
|
|
144
|
+
npm run build:ui
|
|
145
|
+
WRANGLER_SEND_METRICS=false npx wrangler dev --local \
|
|
146
|
+
--var DRIVE_PUBLIC_URL:http://127.0.0.1:8787 \
|
|
147
|
+
--var DRIVE_URL_SECRET:<32 or more characters> --var DRIVE_REGION:unknown
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Open `http://127.0.0.1:8787` and sign in with a bearer token. The tab keeps the token until you close it. A fresh local database has no tokens. `e2e/serve.mjs` shows how to seed a person and an agent.
|
|
151
|
+
|
|
152
|
+
Test it with `npm run test:e2e`. The test starts `wrangler dev` on a seeded database, drives the UI in Chromium, and runs an axe WCAG 2.2 AA check on each view in both themes. Run `npx playwright install chromium` once first. The run also writes the screenshots in [docs/ui](docs/ui).
|
|
153
|
+
|
|
154
|
+
Run `npm run build:ui` before `npm run test:e2e` or `wrangler deploy`, because the Worker config names the `ui-dist` folder. The UI answers with a content security policy that loads scripts and fonts only from its own origin, and it refuses to be framed.
|
|
155
|
+
|
|
156
|
+
Set `DRIVE_PUBLIC_URL` to the origin that serves the UI. A download link on another origin is refused.
|
|
157
|
+
|
|
158
|
+
### Serve the UI from the package
|
|
159
|
+
|
|
160
|
+
The npm package ships the built UI in `ui-dist`. The package does not ship a `wrangler.jsonc`, so a project that deploys its own Worker adds the assets block to its config. Its Worker re-exports the package Worker. In a workspace, the package can sit in the root `node_modules`. Then point `assets.directory` at that folder, such as `../../node_modules/agentic-drive/ui-dist`:
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
// src/worker.js
|
|
164
|
+
export { default } from "agentic-drive/worker";
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```jsonc
|
|
168
|
+
// wrangler.jsonc: the Worker reads the bindings DB (D1) and FILES (R2). Name yours the same.
|
|
169
|
+
// Create the bucket with `wrangler r2 bucket create agentic-drive-files --jurisdiction eu`.
|
|
170
|
+
// Create the database with `wrangler d1 create agentic-drive --location weur`, then put its id below.
|
|
171
|
+
{
|
|
172
|
+
"name": "agentic-drive",
|
|
173
|
+
"main": "src/worker.js",
|
|
174
|
+
"compatibility_date": "2026-01-01",
|
|
175
|
+
"compatibility_flags": ["nodejs_compat"],
|
|
176
|
+
"d1_databases": [{ "binding": "DB", "database_name": "agentic-drive", "database_id": "<your id>" }],
|
|
177
|
+
"r2_buckets": [{ "binding": "FILES", "bucket_name": "agentic-drive-files", "jurisdiction": "eu" }],
|
|
178
|
+
// In a workspace, point this at the root node_modules instead.
|
|
179
|
+
"assets": { "directory": "./node_modules/agentic-drive/ui-dist", "not_found_handling": "single-page-application", "run_worker_first": ["/api/*", "/health"] }
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Set `DRIVE_URL_SECRET` and `DRIVE_PUBLIC_URL` with `npx wrangler secret put`. `DRIVE_URL_SECRET` needs 32 bytes or more. `DRIVE_PUBLIC_URL` is the origin of this Worker. The Worker declares region `eu` unless `DRIVE_REGION` says otherwise, so keep the EU jurisdiction and location above. Sign-in uses agentic-identity credentials in the same D1 database. Create a token there first, or every bearer API request gets 401. In the D1 database:
|
|
184
|
+
|
|
185
|
+
1. Create the table, unless a request has already run: `wrangler d1 execute agentic-drive --remote --command "CREATE TABLE IF NOT EXISTS store_docs (collection TEXT NOT NULL, id TEXT NOT NULL, body TEXT NOT NULL, version INTEGER NOT NULL DEFAULT 1, PRIMARY KEY (collection, id)) WITHOUT ROWID"`.
|
|
186
|
+
2. Insert a row into `store_docs` with `collection` `principals` and `id` `me`. Its body is `{ "id": "me", "kind": "human", "scopes": ["drive:admin"] }`.
|
|
187
|
+
3. Make the token with `openssl rand -hex 32`. A token may use only letters, digits, `_`, and `-`, up to 512 characters. Insert a row into `store_docs` with `collection` `credentials`. Its `id` is the lowercase hex SHA-256 of the token, which `printf %s "$TOKEN" | sha256sum` prints. Its body is `{ "principalId": "me", "kind": "api_key" }`.
|
|
188
|
+
|
|
189
|
+
The [seed script](https://github.com/pooriaarab/agentic-drive/blob/main/e2e/serve.mjs) shows these rows. Its tokens are for tests only. Never use them in a deployment. Without both secrets, the API answers 503. With this config, `/` serves the UI, and `/health` and `/api/v1/drive/*` answer from the Worker.
|
|
190
|
+
|
|
191
|
+
The UI does not make snapshot links yet. The address of a share link shows once, when you create it.
|
|
192
|
+
|
|
104
193
|
## Features
|
|
105
194
|
|
|
106
195
|
- Folders, files, and typed references to documents in other apps (`docs:<id>`).
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// src/drive.ts
|
|
2
|
-
import { EraseRefusedError, MAX_FOLDER_DEPTH, SUBJECT_KINDS } from "agentic-identity";
|
|
2
|
+
import { EraseRefusedError, MAX_FOLDER_DEPTH, PRINCIPAL_KINDS, SUBJECT_KINDS } from "agentic-identity";
|
|
3
3
|
var ROOT_ID = "root";
|
|
4
4
|
var NODES = "drive_nodes";
|
|
5
5
|
var NAMES = "drive_names";
|
|
6
|
-
var ACTOR_KINDS =
|
|
6
|
+
var ACTOR_KINDS = PRINCIPAL_KINDS;
|
|
7
7
|
var DriveError = class extends Error {
|
|
8
8
|
constructor(code, message) {
|
|
9
9
|
super(message);
|
|
@@ -1283,15 +1283,59 @@ function createFileBlobs(dir, options = {}) {
|
|
|
1283
1283
|
};
|
|
1284
1284
|
}
|
|
1285
1285
|
|
|
1286
|
+
// src/auth.ts
|
|
1287
|
+
import { createStoreAuth } from "agentic-identity";
|
|
1288
|
+
async function authenticate(request, store, now) {
|
|
1289
|
+
if (!/^Bearer [A-Za-z0-9_-]{1,512}$/.test(request.headers.get("authorization") ?? "")) return null;
|
|
1290
|
+
const principal = await createStoreAuth(store, now ? { now } : {}).authenticate(request);
|
|
1291
|
+
return principal ? { owner: principal.chain[0].id, actor: { id: principal.principalId, kind: principal.kind }, principal } : null;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1286
1294
|
// src/local.ts
|
|
1295
|
+
import { createHash as createHash2 } from "crypto";
|
|
1287
1296
|
import { join as join2 } from "path";
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
return {
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1297
|
+
import { isScope, issueCredential, revokeCredential } from "agentic-identity";
|
|
1298
|
+
function openLocal(dir) {
|
|
1299
|
+
const store = createFileStore(join2(dir, "db"));
|
|
1300
|
+
return { store, drive: createDrive({ store, blobs: createFileBlobs(join2(dir, "blobs")) }) };
|
|
1301
|
+
}
|
|
1302
|
+
var AuthError = class extends Error {
|
|
1303
|
+
code = "UNAUTHENTICATED";
|
|
1304
|
+
};
|
|
1305
|
+
var NO_TOKEN = "no principal: pass --token <key> or set AGENTIC_DRIVE_TOKEN to an API key";
|
|
1306
|
+
var BAD_TOKEN = "the key is not valid: it is unknown, revoked, or expired, or its principal is revoked";
|
|
1307
|
+
async function resolveToken(store, token) {
|
|
1308
|
+
if (!token) throw new AuthError(NO_TOKEN);
|
|
1309
|
+
if (!/^[A-Za-z0-9_-]{1,512}$/.test(token)) throw new AuthError(BAD_TOKEN);
|
|
1310
|
+
const ctx = await authenticate(new Request("http://local", { headers: { authorization: `Bearer ${token}` } }), store);
|
|
1311
|
+
if (!ctx) throw new AuthError(BAD_TOKEN);
|
|
1312
|
+
return ctx;
|
|
1313
|
+
}
|
|
1314
|
+
async function addPrincipal(store, id, opts) {
|
|
1315
|
+
if (!/^[A-Za-z0-9_.@-]{1,128}$/.test(id)) throw new Error(`invalid principal id "${id}"`);
|
|
1316
|
+
const scopes = opts.scopes?.length ? opts.scopes : [opts.parent ? "drive:write" : "drive:admin"];
|
|
1317
|
+
const bad = scopes.find((s) => !isScope(s));
|
|
1318
|
+
if (bad !== void 0) throw new Error(`invalid scope "${bad}"`);
|
|
1319
|
+
if (await store.get("principals", id)) throw new Error(`principal "${id}" exists`);
|
|
1320
|
+
if (opts.parent && !await store.get("principals", opts.parent)) throw new Error(`parent "${opts.parent}" does not exist`);
|
|
1321
|
+
const parent = opts.parent ? await store.get("principals", opts.parent) : null;
|
|
1322
|
+
if (parent && parent.kind !== "human") throw new Error(`parent "${opts.parent}" is not a person`);
|
|
1323
|
+
const doc = opts.parent ? { id, kind: "agent_of_human", parentId: opts.parent, scopes } : { id, kind: "human", scopes };
|
|
1324
|
+
await store.put("principals", id, doc);
|
|
1325
|
+
return doc;
|
|
1326
|
+
}
|
|
1327
|
+
async function revokePrincipal(store, id) {
|
|
1328
|
+
const doc = await store.get("principals", id);
|
|
1329
|
+
if (!doc) throw new DriveError("NOT_FOUND", `principal "${id}" does not exist`);
|
|
1330
|
+
await store.put("principals", id, { ...doc, revokedAt: Date.now() });
|
|
1331
|
+
}
|
|
1332
|
+
async function issueKey(store, principalId, label) {
|
|
1333
|
+
if (!await store.get("principals", principalId)) throw new Error(`principal "${principalId}" does not exist`);
|
|
1334
|
+
return issueCredential(store, { principalId, kind: "api_key", ...label ? { label } : {} });
|
|
1335
|
+
}
|
|
1336
|
+
async function revokeKey(store, token) {
|
|
1337
|
+
if (!await store.get("credentials", createHash2("sha256").update(token).digest("hex"))) throw new DriveError("NOT_FOUND", "no such key");
|
|
1338
|
+
await revokeCredential(store, token, Date.now());
|
|
1295
1339
|
}
|
|
1296
1340
|
function publicNode(node, path) {
|
|
1297
1341
|
const { nameKey: _nameKey, blobKey: _blobKey, purging: _purging, ...rest } = node;
|
|
@@ -1374,7 +1418,15 @@ export {
|
|
|
1374
1418
|
buildExport,
|
|
1375
1419
|
createFileStore,
|
|
1376
1420
|
createFileBlobs,
|
|
1421
|
+
authenticate,
|
|
1377
1422
|
openLocal,
|
|
1423
|
+
AuthError,
|
|
1424
|
+
NO_TOKEN,
|
|
1425
|
+
resolveToken,
|
|
1426
|
+
addPrincipal,
|
|
1427
|
+
revokePrincipal,
|
|
1428
|
+
issueKey,
|
|
1429
|
+
revokeKey,
|
|
1378
1430
|
publicNode,
|
|
1379
1431
|
DEFAULT_MAX_BYTES,
|
|
1380
1432
|
sniffMime,
|
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
VERSION
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-DRACF66Y.js";
|
|
5
5
|
import {
|
|
6
|
+
AuthError,
|
|
6
7
|
DriveError,
|
|
8
|
+
NO_TOKEN,
|
|
9
|
+
addPrincipal,
|
|
7
10
|
buildExport,
|
|
11
|
+
issueKey,
|
|
8
12
|
openLocal,
|
|
9
13
|
publicNode,
|
|
14
|
+
resolveToken,
|
|
15
|
+
revokeKey,
|
|
16
|
+
revokePrincipal,
|
|
10
17
|
upload
|
|
11
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-ATJDMCRV.js";
|
|
12
19
|
|
|
13
20
|
// src/cli.ts
|
|
14
21
|
import { createReadStream, realpathSync, writeFileSync } from "fs";
|
|
@@ -37,23 +44,33 @@ Commands:
|
|
|
37
44
|
register <ref> <name> Put an app document in the drive. Flag: --parent <folder>
|
|
38
45
|
forget <ref> Move the references to an app document to the trash.
|
|
39
46
|
delete-all --yes Erase you for good: your drive, and what you shared or held.
|
|
47
|
+
principal add <id> Make a person, or an agent with --of <person>. Flag: --scope <s> (repeat). No key needed.
|
|
48
|
+
principal revoke <id> Stop a principal, and every agent below it. No key needed.
|
|
49
|
+
key issue <principal> Print a new API key for a principal. It shows once. Flag: --label. No key needed.
|
|
50
|
+
key revoke <key> Stop a key from working. No key needed.
|
|
40
51
|
mcp Start the MCP server on stdio.
|
|
41
52
|
|
|
42
53
|
Flags:
|
|
43
54
|
--dir <path> Data folder. Env AGENTIC_DRIVE_DIR. Default ./.agentic-drive
|
|
44
|
-
--
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
--token <key> Who you are: an API key. Env AGENTIC_DRIVE_TOKEN. Needed by every command
|
|
56
|
+
except help, version, principal, and key. Prefer the env var: a flag shows in ps.
|
|
57
|
+
The key names a principal, found through agentic-identity. The drive belongs to
|
|
58
|
+
the person at the top of that principal's chain. An agent key marks items as
|
|
59
|
+
made by a machine. If the key is missing or not valid, the command exits with 3.
|
|
47
60
|
--on-conflict <m> fail, rename, or replace
|
|
48
61
|
-p, --parents Create missing parent folders
|
|
62
|
+
|
|
63
|
+
Exit codes: 0 done, 1 error, 2 bad usage, 3 no principal or an invalid key.
|
|
49
64
|
`;
|
|
65
|
+
var COMMANDS = ["ls", "stat", "mkdir", "upload", "download", "mv", "rm", "trash", "restore", "purge", "search", "export", "home", "register", "forget", "delete-all", "mcp", "principal", "key"];
|
|
50
66
|
var UsageError = class extends Error {
|
|
51
67
|
};
|
|
52
68
|
var options = {
|
|
53
69
|
dir: { type: "string" },
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
70
|
+
token: { type: "string" },
|
|
71
|
+
of: { type: "string" },
|
|
72
|
+
scope: { type: "string", multiple: true },
|
|
73
|
+
label: { type: "string" },
|
|
57
74
|
"on-conflict": { type: "string" },
|
|
58
75
|
parents: { type: "boolean", short: "p" },
|
|
59
76
|
mime: { type: "string" },
|
|
@@ -87,12 +104,21 @@ async function runCli(argv, io) {
|
|
|
87
104
|
};
|
|
88
105
|
const env = io.env;
|
|
89
106
|
const dir = resolve(values.dir ?? env.AGENTIC_DRIVE_DIR ?? ".agentic-drive");
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
107
|
+
if (!COMMANDS.includes(command)) throw new UsageError(`unknown command "${command}"`);
|
|
108
|
+
const admin = command === "principal" || command === "key";
|
|
109
|
+
const token = values.token ?? env.AGENTIC_DRIVE_TOKEN;
|
|
110
|
+
if (!admin && !token) throw new AuthError(NO_TOKEN);
|
|
111
|
+
const { drive, store } = openLocal(dir);
|
|
112
|
+
if (admin || command === "key") {
|
|
113
|
+
const [sub, target] = args;
|
|
114
|
+
const out = (value) => io.out(JSON.stringify(value));
|
|
115
|
+
if (command === "principal" && sub === "add" && target) return out(await addPrincipal(store, target, { parent: values.of, scopes: values.scope })), 0;
|
|
116
|
+
if (command === "principal" && sub === "revoke" && target) return await revokePrincipal(store, target), out({ id: target, revoked: true }), 0;
|
|
117
|
+
if (command === "key" && sub === "issue" && target) return out({ principal: target, token: await issueKey(store, target, values.label) }), 0;
|
|
118
|
+
if (command === "key" && sub === "revoke" && target) return await revokeKey(store, target), out({ revoked: true }), 0;
|
|
119
|
+
throw new UsageError("usage: agentic-drive principal add <id> [--of <person>] [--scope <s>] | principal revoke <id> | key issue <principal> | key revoke <key>");
|
|
120
|
+
}
|
|
121
|
+
const ctx = await resolveToken(store, token);
|
|
96
122
|
const onConflict = values["on-conflict"];
|
|
97
123
|
if (onConflict && !["fail", "rename", "replace"].includes(onConflict)) throw new UsageError("--on-conflict needs fail, rename, or replace");
|
|
98
124
|
const print = (value) => io.out(JSON.stringify(value));
|
|
@@ -199,6 +225,10 @@ async function runCli(argv, io) {
|
|
|
199
225
|
io.err(JSON.stringify({ error: { code: "USAGE", message: error.message } }));
|
|
200
226
|
return 2;
|
|
201
227
|
}
|
|
228
|
+
if (error instanceof AuthError) {
|
|
229
|
+
io.err(JSON.stringify({ error: { code: error.code, message: error.message } }));
|
|
230
|
+
return 3;
|
|
231
|
+
}
|
|
202
232
|
if (error instanceof DriveError) {
|
|
203
233
|
io.err(JSON.stringify({ error: { code: error.code, message: error.message } }));
|
|
204
234
|
return 1;
|
package/dist/client.d.ts
CHANGED
package/dist/client.js
CHANGED
|
@@ -43,10 +43,10 @@ var record = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
|
43
43
|
var text = (v) => typeof v === "string";
|
|
44
44
|
var orNull = (test) => (v) => v === null || test(v);
|
|
45
45
|
var num = (v) => typeof v === "number";
|
|
46
|
-
var ACTOR_KINDS =
|
|
46
|
+
var ACTOR_KINDS = { human: true, agent_of_human: true, agent_of_agent: true, agent: true, user_token: true };
|
|
47
47
|
var ROLES = ["viewer", "commenter", "editor", "owner"];
|
|
48
48
|
var oneOf = (list) => (v) => typeof v === "string" && list.includes(v);
|
|
49
|
-
var actor = (v) => record(v) && text(v.id) && oneOf(ACTOR_KINDS)(v.kind);
|
|
49
|
+
var actor = (v) => record(v) && text(v.id) && oneOf(Object.keys(ACTOR_KINDS))(v.kind);
|
|
50
50
|
var isItem = (v) => record(v) && text(v.id) && text(v.owner) && text(v.parentId) && text(v.name) && (v.kind === "folder" || v.kind === "file" || v.kind === "ref") && num(v.size) && orNull(text)(v.mime) && orNull(text)(v.hash) && orNull(text)(v.ref) && num(v.createdAt) && num(v.updatedAt) && actor(v.createdBy) && actor(v.updatedBy) && text(v.createdById) && text(v.updatedById) && typeof v.trashed === "boolean" && orNull(num)(v.trashedAt);
|
|
51
51
|
var isPathItem = (v) => isItem(v) && typeof v.path === "string";
|
|
52
52
|
var listOf = (check) => (v) => Array.isArray(v) && v.every(check);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as agentic_identity from 'agentic-identity';
|
|
2
|
-
import { PrincipalContext, Store, Blobs, createSharing, Subject, Role, SubjectKind } from 'agentic-identity';
|
|
2
|
+
import { PrincipalKind, PrincipalContext, Store, Blobs, createSharing, Subject, Role, SubjectKind } from 'agentic-identity';
|
|
3
3
|
|
|
4
4
|
declare const ROOT_ID = "root";
|
|
5
5
|
declare const ACTOR_KINDS: readonly ["human", "agent_of_human", "agent_of_agent", "agent", "user_token"];
|
|
6
|
-
type ActorKind =
|
|
6
|
+
type ActorKind = PrincipalKind;
|
|
7
7
|
type Actor = {
|
|
8
8
|
id: string;
|
|
9
9
|
kind: ActorKind;
|
|
@@ -331,4 +331,4 @@ declare function createDrive(deps: DriveDeps): {
|
|
|
331
331
|
}>;
|
|
332
332
|
};
|
|
333
333
|
|
|
334
|
-
export { type
|
|
334
|
+
export { type Actor 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, ACTOR_KINDS as a, type Action as b, type ActorKind as c, type Can as d, type DriveDeps as e, DriveError as f, createDrive as g, parseSubject as h, parseName as p, subjectKindOf as s };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { O as OnConflict, D as Drive, C as Ctx, N as Node, A as
|
|
2
|
-
export {
|
|
3
|
-
import { AdapterOptions, Blobs, Store } from 'agentic-identity';
|
|
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';
|
|
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
|
|
|
6
6
|
declare const VERSION: string;
|
|
@@ -51,20 +51,30 @@ declare function writeZip(entries: ZipEntry[], at: number): Uint8Array;
|
|
|
51
51
|
/** Reads a zip made by `writeZip`. It checks every CRC. */
|
|
52
52
|
declare function readZip(bytes: Uint8Array): Map<string, Uint8Array>;
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
dir: string;
|
|
57
|
-
/** Whose drive this is. */
|
|
58
|
-
owner: string;
|
|
59
|
-
/** Who acts. Defaults to the owner. */
|
|
60
|
-
actor?: string;
|
|
61
|
-
/** What kind of actor. Defaults to `human`. Set an agent kind when an agent runs the command. */
|
|
62
|
-
kind?: ActorKind;
|
|
63
|
-
}
|
|
64
|
-
declare function openLocal(options: LocalOptions): {
|
|
54
|
+
/** The drive and the store behind it, both kept in `dir`. The store holds the principals and keys too. */
|
|
55
|
+
declare function openLocal(dir: string): {
|
|
65
56
|
drive: Drive;
|
|
66
|
-
|
|
57
|
+
store: Store;
|
|
67
58
|
};
|
|
59
|
+
/** Why no caller could be named. The CLI turns it into exit code 3. */
|
|
60
|
+
declare class AuthError extends Error {
|
|
61
|
+
readonly code = "UNAUTHENTICATED";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The caller behind `token`, resolved through agentic-identity like the Worker does. The owner is the root
|
|
65
|
+
* of the delegation chain, and the actor is the principal itself, so an agent key shows as a machine.
|
|
66
|
+
*/
|
|
67
|
+
declare function resolveToken(store: Store, token: string | undefined): Promise<Ctx>;
|
|
68
|
+
/** Make a principal. One with a parent is an agent of that human. One without is a human. Local data is the trust boundary. */
|
|
69
|
+
declare function addPrincipal(store: Store, id: string, opts: {
|
|
70
|
+
parent?: string;
|
|
71
|
+
scopes?: string[];
|
|
72
|
+
}): Promise<Doc>;
|
|
73
|
+
/** Stop a principal. Every agent below it stops too, because its chain is no longer live. */
|
|
74
|
+
declare function revokePrincipal(store: Store, id: string): Promise<void>;
|
|
75
|
+
/** Issue an API key for a principal. The token is shown once. */
|
|
76
|
+
declare function issueKey(store: Store, principalId: string, label?: string): Promise<string>;
|
|
77
|
+
declare function revokeKey(store: Store, token: string): Promise<void>;
|
|
68
78
|
/** A node as the CLI and MCP print it: no storage keys. */
|
|
69
79
|
declare function publicNode(node: Node, path?: string): {
|
|
70
80
|
id: string;
|
|
@@ -105,4 +115,4 @@ declare function publicNode(node: Node, path?: string): {
|
|
|
105
115
|
path: string;
|
|
106
116
|
};
|
|
107
117
|
|
|
108
|
-
export { Actor,
|
|
118
|
+
export { Actor, AuthError, Ctx, DEFAULT_MAX_BYTES, Drive, Node, OnConflict, type UploadOptions, VERSION, addPrincipal, buildExport, createFileBlobs, createFileStore, issueKey, openLocal, publicNode, readZip, resolveToken, revokeKey, revokePrincipal, sniffMime, upload, writeZip };
|
package/dist/index.js
CHANGED
|
@@ -1,46 +1,58 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DRACF66Y.js";
|
|
4
4
|
import {
|
|
5
5
|
backfillAuthorIds,
|
|
6
6
|
backfillResources
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-L7GASKBQ.js";
|
|
8
8
|
import {
|
|
9
9
|
ACTOR_KINDS,
|
|
10
|
+
AuthError,
|
|
10
11
|
DEFAULT_MAX_BYTES,
|
|
11
12
|
DriveError,
|
|
12
13
|
ROOT_ID,
|
|
14
|
+
addPrincipal,
|
|
13
15
|
buildExport,
|
|
14
16
|
createDrive,
|
|
15
17
|
createFileBlobs,
|
|
16
18
|
createFileStore,
|
|
19
|
+
issueKey,
|
|
17
20
|
openLocal,
|
|
18
21
|
parseName,
|
|
19
22
|
parseSubject,
|
|
20
23
|
publicNode,
|
|
21
24
|
readZip,
|
|
25
|
+
resolveToken,
|
|
26
|
+
revokeKey,
|
|
27
|
+
revokePrincipal,
|
|
22
28
|
sniffMime,
|
|
23
29
|
subjectKindOf,
|
|
24
30
|
upload,
|
|
25
31
|
writeZip
|
|
26
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-ATJDMCRV.js";
|
|
27
33
|
export {
|
|
28
34
|
ACTOR_KINDS,
|
|
35
|
+
AuthError,
|
|
29
36
|
DEFAULT_MAX_BYTES,
|
|
30
37
|
DriveError,
|
|
31
38
|
ROOT_ID,
|
|
32
39
|
VERSION,
|
|
40
|
+
addPrincipal,
|
|
33
41
|
backfillAuthorIds,
|
|
34
42
|
backfillResources,
|
|
35
43
|
buildExport,
|
|
36
44
|
createDrive,
|
|
37
45
|
createFileBlobs,
|
|
38
46
|
createFileStore,
|
|
47
|
+
issueKey,
|
|
39
48
|
openLocal,
|
|
40
49
|
parseName,
|
|
41
50
|
parseSubject,
|
|
42
51
|
publicNode,
|
|
43
52
|
readZip,
|
|
53
|
+
resolveToken,
|
|
54
|
+
revokeKey,
|
|
55
|
+
revokePrincipal,
|
|
44
56
|
sniffMime,
|
|
45
57
|
subjectKindOf,
|
|
46
58
|
upload,
|
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-
|
|
2
|
+
import { D as Drive, C as Ctx } from './drive-Cn3mpuDW.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-
|
|
3
|
+
} from "./chunk-DRACF66Y.js";
|
|
4
4
|
import {
|
|
5
5
|
ACTOR_KINDS,
|
|
6
6
|
DriveError,
|
|
7
7
|
buildExport,
|
|
8
8
|
publicNode,
|
|
9
9
|
upload
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-ATJDMCRV.js";
|
|
11
11
|
|
|
12
12
|
// src/mcp.ts
|
|
13
13
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
package/dist/worker.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import { D1Like, R2Like } from 'agentic-identity/cloudflare';
|
|
3
3
|
import { Store } from 'agentic-identity';
|
|
4
|
-
import { C as Ctx, D as Drive } from './drive-
|
|
4
|
+
import { C as Ctx, D as Drive } from './drive-Cn3mpuDW.js';
|
|
5
5
|
export { B as BackfillOptions, a as BackfillResult, b as backfillAuthorIds, c as backfillResources } from './migrate-B79gRPMu.js';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* The caller behind a bearer token, resolved by the store-backed Auth of agentic-identity: the credential
|
|
9
|
+
* names a principal, and the whole delegation chain must be live. Only a bearer token counts. A session
|
|
10
|
+
* cookie is refused, because no check of the request origin stands behind it here. Any problem gives null.
|
|
11
|
+
*/
|
|
12
|
+
declare function authenticate(request: Request, store: Store, now?: () => number): Promise<Ctx | null>;
|
|
13
|
+
|
|
7
14
|
interface WorkerEnv {
|
|
8
15
|
DB: D1Like;
|
|
9
16
|
FILES: R2Like;
|
|
@@ -22,12 +29,6 @@ type App = {
|
|
|
22
29
|
ctx: Ctx;
|
|
23
30
|
};
|
|
24
31
|
};
|
|
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
32
|
declare function createApp(): Hono<App>;
|
|
32
33
|
declare const _default: {
|
|
33
34
|
fetch: (request: Request, env: WorkerEnv, ctx: unknown) => Response | Promise<Response>;
|
package/dist/worker.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
backfillAuthorIds,
|
|
3
3
|
backfillResources
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-L7GASKBQ.js";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_MAX_BYTES,
|
|
7
7
|
DEFAULT_URL_SECONDS,
|
|
8
8
|
DriveError,
|
|
9
|
+
authenticate,
|
|
9
10
|
buildExport,
|
|
10
11
|
createDrive,
|
|
11
12
|
publicNode,
|
|
12
13
|
safeMime,
|
|
13
14
|
upload
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-ATJDMCRV.js";
|
|
15
16
|
|
|
16
17
|
// src/worker.ts
|
|
17
18
|
import { Hono } from "hono";
|
|
18
19
|
import { D1_SCHEMA, createD1Store, createR2Blobs, verifySignedBlobUrl } from "agentic-identity/cloudflare";
|
|
19
|
-
import { createSharing,
|
|
20
|
+
import { createSharing, parseRegion } from "agentic-identity";
|
|
20
21
|
var STATUS = {
|
|
21
22
|
CROSS_DRIVE: 409,
|
|
22
23
|
INVALID_SHARE: 400,
|
|
@@ -42,11 +43,6 @@ var STATUS = {
|
|
|
42
43
|
TOO_LARGE: 413
|
|
43
44
|
};
|
|
44
45
|
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
46
|
var schemas = /* @__PURE__ */ new WeakMap();
|
|
51
47
|
function settings(env) {
|
|
52
48
|
let publicUrl;
|