agentic-drive 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +88 -15
- package/dist/{chunk-B7E2Q3RB.js → chunk-5Z3SFBUK.js} +1 -1
- package/dist/{chunk-T6C5Y2WA.js → chunk-EAKQK7ST.js} +209 -201
- package/dist/{chunk-WQHJNHAR.js → chunk-H6JK62SP.js} +1 -1
- package/dist/chunk-S4AGCKXW.js +241 -0
- package/dist/cli.js +2 -2
- package/dist/client.d.ts +1 -1
- package/dist/client.js +4 -236
- package/dist/{drive-Cn3mpuDW.d.ts → drive-BtRyexUP.d.ts} +1 -1
- package/dist/embed.css +154 -0
- package/dist/embed.d.ts +21 -0
- package/dist/embed.js +412 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +2 -2
- package/dist/worker.d.ts +23 -4
- package/dist/worker.js +48 -23
- package/llms.txt +5 -0
- package/package.json +24 -4
- package/ui-dist/assets/{index-xUEd8lQS.css → index-B9nizMSQ.css} +1 -1
- package/ui-dist/assets/index-DQzn7NQP.js +49 -0
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/index-BiU4TGuq.js +0 -49
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.1
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Bearer tokens are checked by the agentic-identity HTTP helper.** The Worker and the local key check now call `requirePrincipal` from `agentic-identity/http` (agentic-identity 0.6). The package no longer has its own copy of the bearer-token check in `src/auth.ts` and `src/local.ts`. A request without an `authorization` header still gets no caller, so a session cookie does not sign in. A bad key still gives 401, and `resolveToken` still throws `AuthError` with the code `UNAUTHENTICATED`.
|
|
8
|
+
- **The dependency is agentic-identity 0.6.** The package now needs `agentic-identity` `^0.6.0`.
|
|
9
|
+
|
|
10
|
+
## 0.6.0
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`createApp` takes an injected store, blobs, and auth.** `createApp({ store, blobs, auth })` from `agentic-drive/worker` lets a host serve the drive routes with its own `Store`, `Blobs`, and sign-in callback. Each part may be a function of the Worker env. With no options, the Worker works as before. A Worker with no `DB` or `FILES` binding and no injected store or blobs now answers 503 `NOT_CONFIGURED`. The package also exports the `AppOptions` type.
|
|
15
|
+
- **Mountable UI at `agentic-drive/ui`.** `mount(element, { baseUrl, getToken })` renders the drive UI in any page and returns `{ update, unmount }`. `getToken` runs before each request. The styles are in `agentic-drive/ui.css`. The embed keeps its folder in memory, so it does not change the page address. `react` and `react-dom` 19 are optional peer dependencies, and `agentic-design` is now a dependency.
|
|
16
|
+
|
|
3
17
|
## 0.5.0
|
|
4
18
|
|
|
5
19
|
Read the breaking changes before you upgrade or deploy.
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
Store, sort, and share files and folders.
|
|
9
9
|
|
|
10
|
-
> Status: 0.
|
|
10
|
+
> Status: 0.6.1, early. The library, CLI, MCP server, and HTTP API work, with sharing in the SDK and the HTTP API.
|
|
11
11
|
> A small web UI works. See [UI](#ui).
|
|
12
12
|
|
|
13
13
|
## Live demo
|
|
@@ -47,18 +47,7 @@ await drive.trash(me, "/reports/2026");
|
|
|
47
47
|
|
|
48
48
|
## CLI and MCP
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
npx agentic-drive mkdir -p /reports
|
|
52
|
-
npx agentic-drive upload ./q1.pdf /reports/
|
|
53
|
-
npx agentic-drive search --name q1
|
|
54
|
-
npx agentic-drive export ./everything.zip
|
|
55
|
-
npx agentic-drive mcp
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Every command prints JSON. A failure prints `{"error":{"code","message"}}` on stderr and exits 1.
|
|
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
|
|
50
|
+
Every command except `help`, `--version`, `principal`, and `key` needs a principal. Pass an API key with
|
|
62
51
|
`--token <key>` or `AGENTIC_DRIVE_TOKEN`. Prefer the variable: a flag shows in `ps`.
|
|
63
52
|
`agentic-identity` resolves the key. The drive belongs to the person at the top of the
|
|
64
53
|
principal's chain. An agent key marks each item it makes as made by a machine. A revoked,
|
|
@@ -73,10 +62,21 @@ npx agentic-drive key revoke <key>
|
|
|
73
62
|
npx agentic-drive principal revoke helper # stops its keys, and its agents
|
|
74
63
|
```
|
|
75
64
|
|
|
76
|
-
`principal` and `key` need no key: anyone who can
|
|
65
|
+
`principal` and `key` need no key: anyone who can write to the `--dir` folder can run them.
|
|
77
66
|
Set `--dir` (or `AGENTIC_DRIVE_DIR`) to choose the data folder.
|
|
78
67
|
The local drive checks the owner only. It does not apply the scopes of an agent key.
|
|
79
68
|
|
|
69
|
+
```sh
|
|
70
|
+
npx agentic-drive mkdir -p /reports
|
|
71
|
+
npx agentic-drive upload ./q1.pdf /reports/
|
|
72
|
+
npx agentic-drive search --name q1
|
|
73
|
+
npx agentic-drive export ./everything.zip
|
|
74
|
+
npx agentic-drive mcp
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Every command prints JSON, except `help`, `--version`, `mcp`, and `download` to `-`. A failure prints `{"error":{"code","message"}}` on stderr and exits 1.
|
|
78
|
+
Exit code 2 means bad usage. Exit code 3 means no principal, or a key that is not valid.
|
|
79
|
+
|
|
80
80
|
## HTTP API
|
|
81
81
|
|
|
82
82
|
A Cloudflare Worker (Hono) serves `/api/v1/drive` and mirrors the CLI. It uses D1 and R2
|
|
@@ -133,6 +133,35 @@ try {
|
|
|
133
133
|
- A browser page needs the Worker on its own origin for now: the Worker sends no CORS headers.
|
|
134
134
|
- Share with a subject: `share("/pub", { kind: "human", id: "u2" }, "viewer")`.
|
|
135
135
|
|
|
136
|
+
### Other exports
|
|
137
|
+
|
|
138
|
+
The root export, `agentic-drive`, also exports these names. The package typings give the type of each one.
|
|
139
|
+
|
|
140
|
+
- `VERSION` is the package version. `ROOT_ID` is the id of a drive's root folder, `"root"`. `ACTOR_KINDS` lists
|
|
141
|
+
the five principal kinds that `agentic-identity` names.
|
|
142
|
+
- `DEFAULT_MAX_BYTES` is the upload cap: 25 MiB (26,214,400 bytes). `sniffMime(head)` returns the type that
|
|
143
|
+
the first bytes name: PNG, JPEG, GIF, PDF, ZIP, gzip, or WebP. It returns `null` for any other bytes.
|
|
144
|
+
- `parseName(raw)` returns `{ name, key }`. It normalizes the name to NFC, and `key` is the lowercase name.
|
|
145
|
+
It throws a `DriveError` with the code `INVALID_NAME` for a blank name, `.`, `..`, a lone surrogate, a `/`,
|
|
146
|
+
a control character (U+0000 to U+001F, or U+007F), or a name over 255 bytes in UTF-8. `parseSubject(input)` returns `{ kind, id }` for a valid subject, and throws
|
|
147
|
+
`INVALID_SHARE` for any other input. `subjectKindOf(kind)` returns `human` or `agent`, and throws
|
|
148
|
+
`INVALID_SHARE` for any other kind.
|
|
149
|
+
- A `DriveError` has a `code` from `ErrorCode`, such as `NOT_FOUND` or `NAME_TAKEN`.
|
|
150
|
+
- `readZip(bytes)` reads a zip that `writeZip` made, with stored (uncompressed) entries only. It returns a `Map`
|
|
151
|
+
from each entry name to its bytes. It throws for a compressed entry. `writeZip(entries, at)` writes a zip with
|
|
152
|
+
one `{ name, data }` entry for each file, and `at` sets the time in milliseconds. It throws for an unsafe
|
|
153
|
+
name, such as one with `..` or a leading `/`.
|
|
154
|
+
- The local drive has the functions behind the CLI. `openLocal(dir)` returns `{ drive, store }` for a folder.
|
|
155
|
+
`addPrincipal(store, id, { parent?, scopes? })` makes a principal. `issueKey(store, principalId, label?)`
|
|
156
|
+
returns the key. Keep it: the store keeps no plain copy. `resolveToken(store, token)` returns the caller, or throws `AuthError`
|
|
157
|
+
with the code `UNAUTHENTICATED`. `revokeKey(store, token)` stops one key. `revokePrincipal(store, id)` stops a
|
|
158
|
+
principal, its keys, and every agent below it.
|
|
159
|
+
`publicNode(node, path?)` returns an item without its storage keys and its `purging` flag, and adds `machineMade`. When `path` is
|
|
160
|
+
given, it also adds `path`.
|
|
161
|
+
|
|
162
|
+
The local functions trust their caller. Local data is the trust boundary: anyone who can write the folder
|
|
163
|
+
can act as any principal in it.
|
|
164
|
+
|
|
136
165
|
## UI
|
|
137
166
|
|
|
138
167
|
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.
|
|
@@ -155,6 +184,26 @@ Run `npm run build:ui` before `npm run test:e2e` or `wrangler deploy`, because t
|
|
|
155
184
|
|
|
156
185
|
Set `DRIVE_PUBLIC_URL` to the origin that serves the UI. A download link on another origin is refused.
|
|
157
186
|
|
|
187
|
+
### Mount the UI in your app
|
|
188
|
+
|
|
189
|
+
`agentic-drive/ui` renders the same UI inside your own page. It needs no framework in your app. You give it an element, the address of a drive server, and a function that returns the bearer token. `mount` returns a handle.
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import "agentic-design/styles.css"; // tokens, fonts, and base styles for body, headings, and links
|
|
193
|
+
import "agentic-drive/ui.css"; // the drive styles
|
|
194
|
+
import { mount } from "agentic-drive/ui";
|
|
195
|
+
|
|
196
|
+
const drive = mount(document.getElementById("drive")!, {
|
|
197
|
+
baseUrl: "https://drive.example.com",
|
|
198
|
+
getToken: () => session.token, // runs before every request, so it can refresh
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
drive.update({ baseUrl, getToken }); // use another server or callback
|
|
202
|
+
drive.unmount(); // remove the UI
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`getToken` returns 1 to 512 letters, digits, `-` or `_`, or `undefined` to send no `authorization` header. A bad `baseUrl` or a `getToken` that is not a function makes `mount` throw a `TypeError`. The server still checks every request against the token. The embed shows files and trash with upload, rename, move, share, and the "Created by agent" label. It has no sign-in form, because your app owns sign-in. It does not change the page address, and it has no theme switch. `agentic-design/styles.css` sets base styles for the whole page, and it follows the dark mode of the system unless your page sets `data-theme`. Add `agentic-design` to your own dependencies, because your page imports its files. The Worker sends no CORS headers, so the server must be on the origin of your page. Set `baseUrl` to the same address as `DRIVE_PUBLIC_URL`, or the UI refuses its download links. `unmount` and `update` abort the requests that run and stop an upload that has more files to send. The UI keeps the list or grid choice in `localStorage` under `agentic-drive-view`, and it has its own heading for each view. Dialogs, menus, and messages render in `document.body`, outside your element. A click with Ctrl, Cmd, Shift, or the middle button on a folder link does not open the folder. The class names start with `drive-`. `DriveBrowser` is the same UI as a React component. It closes its requests when it unmounts. Wrap it in `TooltipProvider` and `ToastProvider` from `agentic-design/react`. `react` and `react-dom` (version 19 or later) are optional peer dependencies. Install them in your project when you use `agentic-drive/ui`. The web UI that the Worker serves uses the same views.
|
|
206
|
+
|
|
158
207
|
### Serve the UI from the package
|
|
159
208
|
|
|
160
209
|
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`:
|
|
@@ -190,6 +239,30 @@ The [seed script](https://github.com/pooriaarab/agentic-drive/blob/main/e2e/serv
|
|
|
190
239
|
|
|
191
240
|
The UI does not make snapshot links yet. The address of a share link shows once, when you create it.
|
|
192
241
|
|
|
242
|
+
### Inject a store, blobs, and sign-in
|
|
243
|
+
|
|
244
|
+
A host that has its own Worker can serve the drive routes from it. `createApp` from `agentic-drive/worker` takes the parts to use. Each part is optional. A part may be a function of the Worker env, which runs on each request.
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import { createApp } from "agentic-drive/worker";
|
|
248
|
+
|
|
249
|
+
const app = createApp({
|
|
250
|
+
store: (env) => myStore(env), // a Store of agentic-identity
|
|
251
|
+
blobs: (env) => myBlobs(env), // a Blobs of agentic-identity
|
|
252
|
+
// Return a Ctx for the caller, or null for a 401.
|
|
253
|
+
auth: async (request, store, env) => {
|
|
254
|
+
const principal = await mySignIn(request, env);
|
|
255
|
+
return principal && { owner: principal.chain[0].id, actor: { id: principal.principalId, kind: principal.kind }, principal };
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
export default { fetch: app.fetch };
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- With `store`, the Worker reads no `DB` binding. It does not create the table either, so the host owns the schema.
|
|
262
|
+
- With `blobs`, the Worker reads no `FILES` binding and needs no `DRIVE_PUBLIC_URL`. The blobs must sign download URLs that point to `/api/v1/drive/blob` on this Worker. That route checks the signature with `DRIVE_URL_SECRET` before it reads the store or the blobs, so set that secret.
|
|
263
|
+
- With `auth`, the Worker does not read bearer tokens from the store. The callback decides who the caller is. Return a whole `Ctx`: a `principal`, and an `owner` and `actor` that match it. Any other value gets 401. Make sure `principal.chain` and `principal.scopes` are real, because the drive checks them. A callback that reads a session cookie must check the request origin itself. Every write route is open to a cross-site form until it does.
|
|
264
|
+
- With no options, `createApp()` works as before: D1, R2, and bearer tokens from the store. `export { default } from "agentic-drive/worker"` is the same.
|
|
265
|
+
|
|
193
266
|
## Features
|
|
194
267
|
|
|
195
268
|
- Folders, files, and typed references to documents in other apps (`docs:<id>`).
|
|
@@ -217,7 +290,7 @@ The UI does not make snapshot links yet. The address of a share link shows once,
|
|
|
217
290
|
- `registerDocument` makes one item per document in a drive. Two calls at the same moment can make two.
|
|
218
291
|
|
|
219
292
|
- Drive reads the `store_docs` table in D1 and `store.jsonl` in the CLI folder. It does not read older layouts. There is no import and no claim backfill.
|
|
220
|
-
- Uploads buffer the stream in memory up to the cap. The default cap is 25 MiB.
|
|
293
|
+
- Uploads buffer the stream in memory up to the cap. The default cap is 25 MiB (`DEFAULT_MAX_BYTES`).
|
|
221
294
|
- Local file blobs return a `file:` URL that nothing serves or checks.
|
|
222
295
|
- Drive writes each new blob only when its key is free (`ifNoneMatch`). A write that finds the key taken fails with `BLOB_EXISTS`.
|
|
223
296
|
- Only `upload()` enforces the size cap. A caller of `putFile()` must check size first.
|
|
@@ -94,7 +94,8 @@ function parseSubject(input) {
|
|
|
94
94
|
var SHARE_ERROR = /^(invalid|give an|expiresAt|expiresInMs|resource exists|the last owner)/;
|
|
95
95
|
function createDrive(deps) {
|
|
96
96
|
const { store, blobs } = deps;
|
|
97
|
-
|
|
97
|
+
let last = 0;
|
|
98
|
+
const now = deps.now ?? (() => last = Math.max(Date.now(), last + 1));
|
|
98
99
|
const newId = deps.newId ?? (() => globalThis.crypto.randomUUID());
|
|
99
100
|
const maxDepth = deps.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
100
101
|
const onOrphan = deps.onOrphan ?? ((key, error) => console.error(`orphan blob ${key}: ${error instanceof Error ? error.name : "delete failed"}`));
|
|
@@ -314,9 +315,9 @@ function createDrive(deps) {
|
|
|
314
315
|
}
|
|
315
316
|
async function parentAndName(ops, w, ctx, path, parents) {
|
|
316
317
|
const { start, segs } = await startOf(ops, ctx, path);
|
|
317
|
-
const
|
|
318
|
-
if (!
|
|
319
|
-
return { parent: await folderAt(ops, w, ctx, start, segs, parents), name:
|
|
318
|
+
const last2 = segs.pop();
|
|
319
|
+
if (!last2) throw start.id === ROOT_ID ? new DriveError("IS_ROOT", "the root already exists") : new DriveError("INVALID_PATH", "name an item inside the folder");
|
|
320
|
+
return { parent: await folderAt(ops, w, ctx, start, segs, parents), name: last2.name };
|
|
320
321
|
}
|
|
321
322
|
async function relocate(ops, w, ctx, node, destLoc, opts) {
|
|
322
323
|
if (node.id === ROOT_ID) throw new DriveError("IS_ROOT", "cannot change the root");
|
|
@@ -991,137 +992,6 @@ function createDrive(deps) {
|
|
|
991
992
|
return Object.fromEntries(Object.entries(api).map(([name, call]) => [name, open.has(name) ? call : guarded(call)]));
|
|
992
993
|
}
|
|
993
994
|
|
|
994
|
-
// src/zip.ts
|
|
995
|
-
var CRC_TABLE = Array.from({ length: 256 }, (_, n) => {
|
|
996
|
-
let c = n;
|
|
997
|
-
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
998
|
-
return c >>> 0;
|
|
999
|
-
});
|
|
1000
|
-
function crc32(data) {
|
|
1001
|
-
let crc = 4294967295;
|
|
1002
|
-
for (const byte of data) crc = CRC_TABLE[(crc ^ byte) & 255] ^ crc >>> 8;
|
|
1003
|
-
return (crc ^ 4294967295) >>> 0;
|
|
1004
|
-
}
|
|
1005
|
-
function safeEntryName(name) {
|
|
1006
|
-
if (name === "" || /[\\\u0000]/.test(name) || name.startsWith("/") || /^[a-zA-Z]:/.test(name)) return false;
|
|
1007
|
-
return name.split("/").every((part) => part !== "" && part !== "." && part !== "..");
|
|
1008
|
-
}
|
|
1009
|
-
function writeZip(entries, at) {
|
|
1010
|
-
for (const { name } of entries) if (!safeEntryName(name)) throw new Error(`zip: unsafe entry name ${JSON.stringify(name)}`);
|
|
1011
|
-
const d = new Date(at);
|
|
1012
|
-
const time = d.getUTCHours() << 11 | d.getUTCMinutes() << 5 | d.getUTCSeconds() >> 1;
|
|
1013
|
-
const date = Math.max(d.getUTCFullYear(), 1980) - 1980 << 9 | d.getUTCMonth() + 1 << 5 | d.getUTCDate();
|
|
1014
|
-
const parts = [];
|
|
1015
|
-
const central = [];
|
|
1016
|
-
let offset = 0;
|
|
1017
|
-
const u16 = (v, o, n) => v.setUint16(o, n, true);
|
|
1018
|
-
const u32 = (v, o, n) => v.setUint32(o, n, true);
|
|
1019
|
-
for (const entry of entries) {
|
|
1020
|
-
const name = new TextEncoder().encode(entry.name);
|
|
1021
|
-
const crc = crc32(entry.data);
|
|
1022
|
-
if (entry.data.byteLength >= 4294967295 || offset >= 4294967295 || entries.length >= 65535) throw new Error("zip: export is too large for a plain zip");
|
|
1023
|
-
const local = new DataView(new ArrayBuffer(30));
|
|
1024
|
-
u32(local, 0, 67324752);
|
|
1025
|
-
u16(local, 4, 20);
|
|
1026
|
-
u16(local, 6, 2048);
|
|
1027
|
-
u16(local, 8, 0);
|
|
1028
|
-
u16(local, 10, time);
|
|
1029
|
-
u16(local, 12, date);
|
|
1030
|
-
u32(local, 14, crc);
|
|
1031
|
-
u32(local, 18, entry.data.byteLength);
|
|
1032
|
-
u32(local, 22, entry.data.byteLength);
|
|
1033
|
-
u16(local, 26, name.length);
|
|
1034
|
-
u16(local, 28, 0);
|
|
1035
|
-
const head = new DataView(new ArrayBuffer(46));
|
|
1036
|
-
u32(head, 0, 33639248);
|
|
1037
|
-
u16(head, 4, 20);
|
|
1038
|
-
u16(head, 6, 20);
|
|
1039
|
-
u16(head, 8, 2048);
|
|
1040
|
-
u16(head, 10, 0);
|
|
1041
|
-
u16(head, 12, time);
|
|
1042
|
-
u16(head, 14, date);
|
|
1043
|
-
u32(head, 16, crc);
|
|
1044
|
-
u32(head, 20, entry.data.byteLength);
|
|
1045
|
-
u32(head, 24, entry.data.byteLength);
|
|
1046
|
-
u16(head, 28, name.length);
|
|
1047
|
-
u32(head, 42, offset);
|
|
1048
|
-
parts.push(new Uint8Array(local.buffer), name, entry.data);
|
|
1049
|
-
central.push(new Uint8Array(head.buffer), name);
|
|
1050
|
-
offset += 30 + name.length + entry.data.byteLength;
|
|
1051
|
-
}
|
|
1052
|
-
const size = central.reduce((n, p) => n + p.byteLength, 0);
|
|
1053
|
-
const end = new DataView(new ArrayBuffer(22));
|
|
1054
|
-
u32(end, 0, 101010256);
|
|
1055
|
-
u16(end, 8, entries.length);
|
|
1056
|
-
u16(end, 10, entries.length);
|
|
1057
|
-
u32(end, 12, size);
|
|
1058
|
-
u32(end, 16, offset);
|
|
1059
|
-
const all = [...parts, ...central, new Uint8Array(end.buffer)];
|
|
1060
|
-
const out = new Uint8Array(all.reduce((n, p) => n + p.byteLength, 0));
|
|
1061
|
-
let at2 = 0;
|
|
1062
|
-
for (const p of all) {
|
|
1063
|
-
out.set(p, at2);
|
|
1064
|
-
at2 += p.byteLength;
|
|
1065
|
-
}
|
|
1066
|
-
return out;
|
|
1067
|
-
}
|
|
1068
|
-
function readZip(bytes) {
|
|
1069
|
-
const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1070
|
-
let end = bytes.byteLength - 22;
|
|
1071
|
-
while (end >= 0 && v.getUint32(end, true) !== 101010256) end--;
|
|
1072
|
-
if (end < 0) throw new Error("zip: no end signature");
|
|
1073
|
-
const count = v.getUint16(end + 10, true);
|
|
1074
|
-
const out = /* @__PURE__ */ new Map();
|
|
1075
|
-
for (let i = 0, at = v.getUint32(end + 16, true); i < count; i++) {
|
|
1076
|
-
if (v.getUint32(at, true) !== 33639248) throw new Error("zip: bad central signature");
|
|
1077
|
-
if (v.getUint16(at + 10, true) !== 0) throw new Error("zip: compressed entries are not supported");
|
|
1078
|
-
const crc = v.getUint32(at + 16, true);
|
|
1079
|
-
const size = v.getUint32(at + 24, true);
|
|
1080
|
-
const nameLen = v.getUint16(at + 28, true);
|
|
1081
|
-
const local = v.getUint32(at + 42, true);
|
|
1082
|
-
const name = new TextDecoder().decode(bytes.subarray(at + 46, at + 46 + nameLen));
|
|
1083
|
-
if (v.getUint32(local, true) !== 67324752) throw new Error("zip: bad local signature");
|
|
1084
|
-
const start = local + 30 + v.getUint16(local + 26, true) + v.getUint16(local + 28, true);
|
|
1085
|
-
const data = bytes.slice(start, start + size);
|
|
1086
|
-
if (crc32(data) !== crc) throw new Error(`zip: crc mismatch in ${name}`);
|
|
1087
|
-
out.set(name, data);
|
|
1088
|
-
at += 46 + nameLen + v.getUint16(at + 30, true) + v.getUint16(at + 32, true);
|
|
1089
|
-
}
|
|
1090
|
-
return out;
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// src/export.ts
|
|
1094
|
-
async function buildExport(drive, ctx, now = Date.now) {
|
|
1095
|
-
const entries = [];
|
|
1096
|
-
const items = (await drive.exportData(ctx)).map(({ node, path, data, missing }) => {
|
|
1097
|
-
const file = data ? `files/${node.id}` : null;
|
|
1098
|
-
if (data && file) entries.push({ name: file, data });
|
|
1099
|
-
return {
|
|
1100
|
-
id: node.id,
|
|
1101
|
-
owner: node.owner,
|
|
1102
|
-
path,
|
|
1103
|
-
parentId: node.parentId,
|
|
1104
|
-
kind: node.kind,
|
|
1105
|
-
name: node.name,
|
|
1106
|
-
size: node.size,
|
|
1107
|
-
mime: node.mime,
|
|
1108
|
-
hash: node.hash,
|
|
1109
|
-
ref: node.ref,
|
|
1110
|
-
createdAt: node.createdAt,
|
|
1111
|
-
updatedAt: node.updatedAt,
|
|
1112
|
-
createdBy: node.createdBy,
|
|
1113
|
-
updatedBy: node.updatedBy,
|
|
1114
|
-
trashed: node.trashed,
|
|
1115
|
-
trashedAt: node.trashedAt,
|
|
1116
|
-
file,
|
|
1117
|
-
...missing ? { missing: true } : {}
|
|
1118
|
-
};
|
|
1119
|
-
});
|
|
1120
|
-
const person = await drive.exportPerson(ctx);
|
|
1121
|
-
const manifest = { format: "agentic-drive-export", version: 2, exportedAt: now(), owner: ctx.owner, subject: person.subject, partial: person.partial, items, authored: person.authored, agents: person.agents, shares: person.shares, audit: person.audit };
|
|
1122
|
-
return writeZip([{ name: "manifest.json", data: new TextEncoder().encode(JSON.stringify(manifest, null, 2)) }, ...entries], now());
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
995
|
// src/adapters/fs.ts
|
|
1126
996
|
import { createHash } from "crypto";
|
|
1127
997
|
import { appendFileSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, truncateSync, writeFileSync, writeSync } from "fs";
|
|
@@ -1283,66 +1153,6 @@ function createFileBlobs(dir, options = {}) {
|
|
|
1283
1153
|
};
|
|
1284
1154
|
}
|
|
1285
1155
|
|
|
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
|
-
|
|
1294
|
-
// src/local.ts
|
|
1295
|
-
import { createHash as createHash2 } from "crypto";
|
|
1296
|
-
import { join as join2 } from "path";
|
|
1297
|
-
import { isMachineMade, 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());
|
|
1339
|
-
}
|
|
1340
|
-
function publicNode(node, path) {
|
|
1341
|
-
const { nameKey: _nameKey, blobKey: _blobKey, purging: _purging, ...rest } = node;
|
|
1342
|
-
const shown = { ...rest, machineMade: isMachineMade(node.createdBy.kind) };
|
|
1343
|
-
return path === void 0 ? shown : { path, ...shown };
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
1156
|
// src/upload.ts
|
|
1347
1157
|
var DEFAULT_MAX_BYTES = 25 * 1024 * 1024;
|
|
1348
1158
|
var SIGNATURES = [
|
|
@@ -1403,6 +1213,204 @@ async function upload(drive, ctx, path, source, opts = {}) {
|
|
|
1403
1213
|
return drive.putFile(ctx, path, data, { mime, parents: opts.parents, onConflict: opts.onConflict });
|
|
1404
1214
|
}
|
|
1405
1215
|
|
|
1216
|
+
// src/zip.ts
|
|
1217
|
+
var CRC_TABLE = Array.from({ length: 256 }, (_, n) => {
|
|
1218
|
+
let c = n;
|
|
1219
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
1220
|
+
return c >>> 0;
|
|
1221
|
+
});
|
|
1222
|
+
function crc32(data) {
|
|
1223
|
+
let crc = 4294967295;
|
|
1224
|
+
for (const byte of data) crc = CRC_TABLE[(crc ^ byte) & 255] ^ crc >>> 8;
|
|
1225
|
+
return (crc ^ 4294967295) >>> 0;
|
|
1226
|
+
}
|
|
1227
|
+
function safeEntryName(name) {
|
|
1228
|
+
if (name === "" || /[\\\u0000]/.test(name) || name.startsWith("/") || /^[a-zA-Z]:/.test(name)) return false;
|
|
1229
|
+
return name.split("/").every((part) => part !== "" && part !== "." && part !== "..");
|
|
1230
|
+
}
|
|
1231
|
+
function writeZip(entries, at) {
|
|
1232
|
+
for (const { name } of entries) if (!safeEntryName(name)) throw new Error(`zip: unsafe entry name ${JSON.stringify(name)}`);
|
|
1233
|
+
const d = new Date(at);
|
|
1234
|
+
const time = d.getUTCHours() << 11 | d.getUTCMinutes() << 5 | d.getUTCSeconds() >> 1;
|
|
1235
|
+
const date = Math.max(d.getUTCFullYear(), 1980) - 1980 << 9 | d.getUTCMonth() + 1 << 5 | d.getUTCDate();
|
|
1236
|
+
const parts = [];
|
|
1237
|
+
const central = [];
|
|
1238
|
+
let offset = 0;
|
|
1239
|
+
const u16 = (v, o, n) => v.setUint16(o, n, true);
|
|
1240
|
+
const u32 = (v, o, n) => v.setUint32(o, n, true);
|
|
1241
|
+
for (const entry of entries) {
|
|
1242
|
+
const name = new TextEncoder().encode(entry.name);
|
|
1243
|
+
const crc = crc32(entry.data);
|
|
1244
|
+
if (entry.data.byteLength >= 4294967295 || offset >= 4294967295 || entries.length >= 65535) throw new Error("zip: export is too large for a plain zip");
|
|
1245
|
+
const local = new DataView(new ArrayBuffer(30));
|
|
1246
|
+
u32(local, 0, 67324752);
|
|
1247
|
+
u16(local, 4, 20);
|
|
1248
|
+
u16(local, 6, 2048);
|
|
1249
|
+
u16(local, 8, 0);
|
|
1250
|
+
u16(local, 10, time);
|
|
1251
|
+
u16(local, 12, date);
|
|
1252
|
+
u32(local, 14, crc);
|
|
1253
|
+
u32(local, 18, entry.data.byteLength);
|
|
1254
|
+
u32(local, 22, entry.data.byteLength);
|
|
1255
|
+
u16(local, 26, name.length);
|
|
1256
|
+
u16(local, 28, 0);
|
|
1257
|
+
const head = new DataView(new ArrayBuffer(46));
|
|
1258
|
+
u32(head, 0, 33639248);
|
|
1259
|
+
u16(head, 4, 20);
|
|
1260
|
+
u16(head, 6, 20);
|
|
1261
|
+
u16(head, 8, 2048);
|
|
1262
|
+
u16(head, 10, 0);
|
|
1263
|
+
u16(head, 12, time);
|
|
1264
|
+
u16(head, 14, date);
|
|
1265
|
+
u32(head, 16, crc);
|
|
1266
|
+
u32(head, 20, entry.data.byteLength);
|
|
1267
|
+
u32(head, 24, entry.data.byteLength);
|
|
1268
|
+
u16(head, 28, name.length);
|
|
1269
|
+
u32(head, 42, offset);
|
|
1270
|
+
parts.push(new Uint8Array(local.buffer), name, entry.data);
|
|
1271
|
+
central.push(new Uint8Array(head.buffer), name);
|
|
1272
|
+
offset += 30 + name.length + entry.data.byteLength;
|
|
1273
|
+
}
|
|
1274
|
+
const size = central.reduce((n, p) => n + p.byteLength, 0);
|
|
1275
|
+
const end = new DataView(new ArrayBuffer(22));
|
|
1276
|
+
u32(end, 0, 101010256);
|
|
1277
|
+
u16(end, 8, entries.length);
|
|
1278
|
+
u16(end, 10, entries.length);
|
|
1279
|
+
u32(end, 12, size);
|
|
1280
|
+
u32(end, 16, offset);
|
|
1281
|
+
const all = [...parts, ...central, new Uint8Array(end.buffer)];
|
|
1282
|
+
const out = new Uint8Array(all.reduce((n, p) => n + p.byteLength, 0));
|
|
1283
|
+
let at2 = 0;
|
|
1284
|
+
for (const p of all) {
|
|
1285
|
+
out.set(p, at2);
|
|
1286
|
+
at2 += p.byteLength;
|
|
1287
|
+
}
|
|
1288
|
+
return out;
|
|
1289
|
+
}
|
|
1290
|
+
function readZip(bytes) {
|
|
1291
|
+
const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1292
|
+
let end = bytes.byteLength - 22;
|
|
1293
|
+
while (end >= 0 && v.getUint32(end, true) !== 101010256) end--;
|
|
1294
|
+
if (end < 0) throw new Error("zip: no end signature");
|
|
1295
|
+
const count = v.getUint16(end + 10, true);
|
|
1296
|
+
const out = /* @__PURE__ */ new Map();
|
|
1297
|
+
for (let i = 0, at = v.getUint32(end + 16, true); i < count; i++) {
|
|
1298
|
+
if (v.getUint32(at, true) !== 33639248) throw new Error("zip: bad central signature");
|
|
1299
|
+
if (v.getUint16(at + 10, true) !== 0) throw new Error("zip: compressed entries are not supported");
|
|
1300
|
+
const crc = v.getUint32(at + 16, true);
|
|
1301
|
+
const size = v.getUint32(at + 24, true);
|
|
1302
|
+
const nameLen = v.getUint16(at + 28, true);
|
|
1303
|
+
const local = v.getUint32(at + 42, true);
|
|
1304
|
+
const name = new TextDecoder().decode(bytes.subarray(at + 46, at + 46 + nameLen));
|
|
1305
|
+
if (v.getUint32(local, true) !== 67324752) throw new Error("zip: bad local signature");
|
|
1306
|
+
const start = local + 30 + v.getUint16(local + 26, true) + v.getUint16(local + 28, true);
|
|
1307
|
+
const data = bytes.slice(start, start + size);
|
|
1308
|
+
if (crc32(data) !== crc) throw new Error(`zip: crc mismatch in ${name}`);
|
|
1309
|
+
out.set(name, data);
|
|
1310
|
+
at += 46 + nameLen + v.getUint16(at + 30, true) + v.getUint16(at + 32, true);
|
|
1311
|
+
}
|
|
1312
|
+
return out;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// src/export.ts
|
|
1316
|
+
async function buildExport(drive, ctx, now = Date.now) {
|
|
1317
|
+
const entries = [];
|
|
1318
|
+
const items = (await drive.exportData(ctx)).map(({ node, path, data, missing }) => {
|
|
1319
|
+
const file = data ? `files/${node.id}` : null;
|
|
1320
|
+
if (data && file) entries.push({ name: file, data });
|
|
1321
|
+
return {
|
|
1322
|
+
id: node.id,
|
|
1323
|
+
owner: node.owner,
|
|
1324
|
+
path,
|
|
1325
|
+
parentId: node.parentId,
|
|
1326
|
+
kind: node.kind,
|
|
1327
|
+
name: node.name,
|
|
1328
|
+
size: node.size,
|
|
1329
|
+
mime: node.mime,
|
|
1330
|
+
hash: node.hash,
|
|
1331
|
+
ref: node.ref,
|
|
1332
|
+
createdAt: node.createdAt,
|
|
1333
|
+
updatedAt: node.updatedAt,
|
|
1334
|
+
createdBy: node.createdBy,
|
|
1335
|
+
updatedBy: node.updatedBy,
|
|
1336
|
+
trashed: node.trashed,
|
|
1337
|
+
trashedAt: node.trashedAt,
|
|
1338
|
+
file,
|
|
1339
|
+
...missing ? { missing: true } : {}
|
|
1340
|
+
};
|
|
1341
|
+
});
|
|
1342
|
+
const person = await drive.exportPerson(ctx);
|
|
1343
|
+
const manifest = { format: "agentic-drive-export", version: 2, exportedAt: now(), owner: ctx.owner, subject: person.subject, partial: person.partial, items, authored: person.authored, agents: person.agents, shares: person.shares, audit: person.audit };
|
|
1344
|
+
return writeZip([{ name: "manifest.json", data: new TextEncoder().encode(JSON.stringify(manifest, null, 2)) }, ...entries], now());
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// src/auth.ts
|
|
1348
|
+
import { createStoreAuth } from "agentic-identity";
|
|
1349
|
+
import { requirePrincipal } from "agentic-identity/http";
|
|
1350
|
+
async function authenticate(request, store, now) {
|
|
1351
|
+
if (!request.headers.has("authorization")) return null;
|
|
1352
|
+
const principal = await requirePrincipal(request, createStoreAuth(store, now ? { now } : {}));
|
|
1353
|
+
if (principal instanceof Response) return null;
|
|
1354
|
+
return { owner: principal.chain[0].id, actor: { id: principal.principalId, kind: principal.kind }, principal };
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// src/local.ts
|
|
1358
|
+
import { createHash as createHash2 } from "crypto";
|
|
1359
|
+
import { join as join2 } from "path";
|
|
1360
|
+
import { isMachineMade, isScope, issueCredential, revokeCredential } from "agentic-identity";
|
|
1361
|
+
function openLocal(dir) {
|
|
1362
|
+
const store = createFileStore(join2(dir, "db"));
|
|
1363
|
+
return { store, drive: createDrive({ store, blobs: createFileBlobs(join2(dir, "blobs")) }) };
|
|
1364
|
+
}
|
|
1365
|
+
var AuthError = class extends Error {
|
|
1366
|
+
code = "UNAUTHENTICATED";
|
|
1367
|
+
};
|
|
1368
|
+
var NO_TOKEN = "no principal: pass --token <key> or set AGENTIC_DRIVE_TOKEN to an API key";
|
|
1369
|
+
var BAD_TOKEN = "the key is not valid: it is unknown, revoked, or expired, or its principal is revoked";
|
|
1370
|
+
async function resolveToken(store, token) {
|
|
1371
|
+
if (!token) throw new AuthError(NO_TOKEN);
|
|
1372
|
+
let request;
|
|
1373
|
+
try {
|
|
1374
|
+
request = new Request("http://local", { headers: { authorization: `Bearer ${token}` } });
|
|
1375
|
+
} catch {
|
|
1376
|
+
throw new AuthError(BAD_TOKEN);
|
|
1377
|
+
}
|
|
1378
|
+
const ctx = await authenticate(request, store);
|
|
1379
|
+
if (!ctx) throw new AuthError(BAD_TOKEN);
|
|
1380
|
+
return ctx;
|
|
1381
|
+
}
|
|
1382
|
+
async function addPrincipal(store, id, opts) {
|
|
1383
|
+
if (!/^[A-Za-z0-9_.@-]{1,128}$/.test(id)) throw new Error(`invalid principal id "${id}"`);
|
|
1384
|
+
const scopes = opts.scopes?.length ? opts.scopes : [opts.parent ? "drive:write" : "drive:admin"];
|
|
1385
|
+
const bad = scopes.find((s) => !isScope(s));
|
|
1386
|
+
if (bad !== void 0) throw new Error(`invalid scope "${bad}"`);
|
|
1387
|
+
if (await store.get("principals", id)) throw new Error(`principal "${id}" exists`);
|
|
1388
|
+
if (opts.parent && !await store.get("principals", opts.parent)) throw new Error(`parent "${opts.parent}" does not exist`);
|
|
1389
|
+
const parent = opts.parent ? await store.get("principals", opts.parent) : null;
|
|
1390
|
+
if (parent && parent.kind !== "human") throw new Error(`parent "${opts.parent}" is not a person`);
|
|
1391
|
+
const doc = opts.parent ? { id, kind: "agent_of_human", parentId: opts.parent, scopes } : { id, kind: "human", scopes };
|
|
1392
|
+
await store.put("principals", id, doc);
|
|
1393
|
+
return doc;
|
|
1394
|
+
}
|
|
1395
|
+
async function revokePrincipal(store, id) {
|
|
1396
|
+
const doc = await store.get("principals", id);
|
|
1397
|
+
if (!doc) throw new DriveError("NOT_FOUND", `principal "${id}" does not exist`);
|
|
1398
|
+
await store.put("principals", id, { ...doc, revokedAt: Date.now() });
|
|
1399
|
+
}
|
|
1400
|
+
async function issueKey(store, principalId, label) {
|
|
1401
|
+
if (!await store.get("principals", principalId)) throw new Error(`principal "${principalId}" does not exist`);
|
|
1402
|
+
return issueCredential(store, { principalId, kind: "api_key", ...label ? { label } : {} });
|
|
1403
|
+
}
|
|
1404
|
+
async function revokeKey(store, token) {
|
|
1405
|
+
if (!await store.get("credentials", createHash2("sha256").update(token).digest("hex"))) throw new DriveError("NOT_FOUND", "no such key");
|
|
1406
|
+
await revokeCredential(store, token, Date.now());
|
|
1407
|
+
}
|
|
1408
|
+
function publicNode(node, path) {
|
|
1409
|
+
const { nameKey: _nameKey, blobKey: _blobKey, purging: _purging, ...rest } = node;
|
|
1410
|
+
const shown = { ...rest, machineMade: isMachineMade(node.createdBy.kind) };
|
|
1411
|
+
return path === void 0 ? shown : { path, ...shown };
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1406
1414
|
export {
|
|
1407
1415
|
ROOT_ID,
|
|
1408
1416
|
ACTOR_KINDS,
|
|
@@ -1414,11 +1422,14 @@ export {
|
|
|
1414
1422
|
subjectKindOf,
|
|
1415
1423
|
parseSubject,
|
|
1416
1424
|
createDrive,
|
|
1425
|
+
createFileStore,
|
|
1426
|
+
createFileBlobs,
|
|
1427
|
+
DEFAULT_MAX_BYTES,
|
|
1428
|
+
sniffMime,
|
|
1429
|
+
upload,
|
|
1417
1430
|
writeZip,
|
|
1418
1431
|
readZip,
|
|
1419
1432
|
buildExport,
|
|
1420
|
-
createFileStore,
|
|
1421
|
-
createFileBlobs,
|
|
1422
1433
|
authenticate,
|
|
1423
1434
|
openLocal,
|
|
1424
1435
|
AuthError,
|
|
@@ -1428,8 +1439,5 @@ export {
|
|
|
1428
1439
|
revokePrincipal,
|
|
1429
1440
|
issueKey,
|
|
1430
1441
|
revokeKey,
|
|
1431
|
-
publicNode
|
|
1432
|
-
DEFAULT_MAX_BYTES,
|
|
1433
|
-
sniffMime,
|
|
1434
|
-
upload
|
|
1442
|
+
publicNode
|
|
1435
1443
|
};
|