agentic-drive 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  VERSION
4
- } from "./chunk-4QE7E5UQ.js";
4
+ } from "./chunk-GMA5I5L6.js";
5
5
  import {
6
6
  DriveError,
7
7
  buildExport,
8
8
  openLocal,
9
9
  publicNode,
10
10
  upload
11
- } from "./chunk-QRGKXPLX.js";
11
+ } from "./chunk-VSVIJBMJ.js";
12
12
 
13
13
  // src/cli.ts
14
14
  import { createReadStream, realpathSync, writeFileSync } from "fs";
@@ -33,7 +33,10 @@ Commands:
33
33
  purge <id> Delete a trashed item for good.
34
34
  search Find items. Flags: --name --mime --kind --min-size --max-size --created-by-kind --limit
35
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.
36
+ home [--app <name>] List files and app documents, newest change first.
37
+ register <ref> <name> Put an app document in the drive. Flag: --parent <folder>
38
+ forget <ref> Move the references to an app document to the trash.
39
+ delete-all --yes Erase you for good: your drive, and what you shared or held.
37
40
  mcp Start the MCP server on stdio.
38
41
 
39
42
  Flags:
@@ -62,6 +65,7 @@ var options = {
62
65
  "max-size": { type: "string" },
63
66
  "created-by-kind": { type: "string" },
64
67
  limit: { type: "string" },
68
+ app: { type: "string" },
65
69
  yes: { type: "boolean" },
66
70
  help: { type: "boolean", short: "h" },
67
71
  version: { type: "boolean", short: "V" }
@@ -164,9 +168,22 @@ async function runCli(argv, io) {
164
168
  print({ saved: resolve(args[0]), size: zip.byteLength });
165
169
  break;
166
170
  }
171
+ case "home": {
172
+ const home = await drive.home(ctx, { app: values.app, limit: num(values.limit, "limit") });
173
+ print({ apps: home.apps, files: home.files, items: home.items.map((i) => ({ ...publicNode(i.node, i.path), app: i.app })) });
174
+ break;
175
+ }
176
+ case "register":
177
+ need(2, "register <ref> <name>");
178
+ print(publicNode(await drive.registerDocument(ctx, { ref: args[0], name: args[1], folder: values.parent })));
179
+ break;
180
+ case "forget":
181
+ need(1, "forget <ref>");
182
+ print(await drive.forgetDocument(ctx, args[0]));
183
+ break;
167
184
  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));
185
+ if (!values.yes) throw new UsageError("delete-all erases you for good. Add --yes to confirm.");
186
+ print(await drive.erasePerson(ctx));
170
187
  break;
171
188
  case "mcp": {
172
189
  const { startMcp } = await import("./mcp.js");
@@ -0,0 +1,173 @@
1
+ import { E as ErrorCode, N as Node, S as SearchQuery } from './drive-Dlzwukjq.js';
2
+ import 'agentic-identity';
3
+
4
+ type Role = "viewer" | "commenter" | "editor" | "owner";
5
+ type OnConflict = "fail" | "rename" | "replace";
6
+ type SubjectKind = "human" | "agent";
7
+ type Subject = {
8
+ kind: SubjectKind;
9
+ id: string;
10
+ };
11
+ /** An item as the API prints it: no storage keys. */
12
+ type Item = Omit<Node, "nameKey" | "blobKey" | "purging">;
13
+ /** A search hit, or a home item: an item with its path. */
14
+ type PathItem = Item & {
15
+ path: string;
16
+ };
17
+ /** The codes of the drive, and the ones that only the Worker raises. */
18
+ type ApiErrorCode = ErrorCode | "UNAUTHORIZED" | "NOT_CONFIGURED" | "INVALID_LINK" | "INTERNAL";
19
+ /** `NETWORK_ERROR` means no answer came (status 0). `BAD_RESPONSE` means an answer that is not the API's. */
20
+ type ClientErrorCode = ApiErrorCode | "NETWORK_ERROR" | "BAD_RESPONSE";
21
+ /** The error of every failed call. `code` is the code of the API, and `status` is the HTTP status. */
22
+ declare class DriveApiError extends Error {
23
+ readonly code: ClientErrorCode;
24
+ readonly status: number;
25
+ constructor(code: ClientErrorCode, status: number, message: string);
26
+ }
27
+ interface ClientOptions {
28
+ /** The origin of the Worker, such as `https://drive.example.com`. */
29
+ baseUrl: string;
30
+ /** A bearer token. It is optional only for the link routes, `openShared`, `listShared`, and `readShared`. */
31
+ token?: string;
32
+ /** Defaults to the global `fetch`. */
33
+ fetch?: typeof fetch;
34
+ /** How often a read (GET) call tries again after a network error or a 502, 503, or 504. The default is 0. A write is never tried again. */
35
+ retries?: number;
36
+ }
37
+ interface CallOptions {
38
+ signal?: AbortSignal;
39
+ }
40
+ type UploadBody = string | Uint8Array | ArrayBuffer | Blob | ReadableStream<Uint8Array>;
41
+ interface Downloaded {
42
+ data: Uint8Array;
43
+ contentType: string;
44
+ sha256: string;
45
+ }
46
+ declare function createClient(options: ClientOptions): {
47
+ /** The live items of a folder. */
48
+ list: (path?: string, o?: CallOptions) => Promise<Item[]>;
49
+ stat: (path: string, o?: CallOptions) => Promise<Item>;
50
+ mkdir: (path: string, opts?: {
51
+ parents?: boolean;
52
+ }, o?: CallOptions) => Promise<Item>;
53
+ /** Writes a file. A `ReadableStream` body streams in runtimes that allow it. */
54
+ upload: (path: string, body: UploadBody, opts?: {
55
+ mime?: string;
56
+ parents?: boolean;
57
+ onConflict?: OnConflict;
58
+ }, o?: CallOptions) => Promise<Item>;
59
+ /** The bytes. They are checked against the hash that the server sends. */
60
+ download: (path: string, o?: CallOptions) => Promise<Downloaded>;
61
+ /** A signed link that works without a token, for `expires` seconds. You fetch the link yourself. */
62
+ url: (path: string, expires?: number, o?: CallOptions) => Promise<{
63
+ url: string;
64
+ expiresInSeconds: number;
65
+ }>;
66
+ move: (src: string, folder: string, opts?: {
67
+ name?: string;
68
+ onConflict?: OnConflict;
69
+ }, o?: CallOptions) => Promise<Item>;
70
+ trash: (path: string, o?: CallOptions) => Promise<Item>;
71
+ listTrash: (o?: CallOptions) => Promise<Item[]>;
72
+ restore: (itemId: string, opts?: {
73
+ parentId?: string;
74
+ onConflict?: OnConflict;
75
+ }, o?: CallOptions) => Promise<Item>;
76
+ /** Deletes a trashed item for good. */
77
+ purge: (itemId: string, o?: CallOptions) => Promise<{
78
+ removed: number;
79
+ }>;
80
+ search: (q?: Pick<SearchQuery, "name" | "mime" | "kind" | "minSize" | "maxSize" | "createdByKind" | "app" | "limit">, o?: CallOptions) => Promise<PathItem[]>;
81
+ /** Everything the caller has, as a zip. */
82
+ export: (o?: CallOptions) => Promise<Uint8Array<ArrayBufferLike>>;
83
+ /** Erases the caller for good. The client sends `confirm=yes`. */
84
+ deleteAccount: (o?: CallOptions) => Promise<{
85
+ removed: number;
86
+ grants: number;
87
+ invites: number;
88
+ anonymized: number;
89
+ }>;
90
+ home: (q?: {
91
+ app?: string;
92
+ limit?: number;
93
+ }, o?: CallOptions) => Promise<{
94
+ apps: Record<string, number>;
95
+ files: number;
96
+ items: (PathItem & {
97
+ app: string | null;
98
+ })[];
99
+ }>;
100
+ registerDocument: (input: {
101
+ ref: string;
102
+ name: string;
103
+ folder?: string;
104
+ }, o?: CallOptions) => Promise<Item>;
105
+ forgetDocument: (ref: string, o?: CallOptions) => Promise<{
106
+ trashed: number;
107
+ }>;
108
+ shares: (path: string, o?: CallOptions) => Promise<{
109
+ grants: {
110
+ subjectKind: SubjectKind;
111
+ subjectId: string;
112
+ role: Role;
113
+ }[];
114
+ links: {
115
+ id: string;
116
+ role: Role;
117
+ signedInOnly: boolean;
118
+ expiresAt: number | null;
119
+ revoked: boolean;
120
+ }[];
121
+ invites: {
122
+ id: string;
123
+ role: Role;
124
+ email: string | null;
125
+ handle: string | null;
126
+ expiresAt: number | null;
127
+ accepted: boolean;
128
+ }[];
129
+ }>;
130
+ share: (path: string, subject: Subject, role: Role, o?: CallOptions) => Promise<{
131
+ ok: true;
132
+ }>;
133
+ unshare: (path: string, subject: Subject, o?: CallOptions) => Promise<{
134
+ ok: true;
135
+ }>;
136
+ /** Invite by `email` (a person) or `handle` (an agent). It sends no message. */
137
+ invite: (path: string, input: {
138
+ role: Role;
139
+ email?: string;
140
+ handle?: string;
141
+ expiresInMs?: number;
142
+ }, o?: CallOptions) => Promise<{
143
+ id: string;
144
+ }>;
145
+ acceptInvite: (inviteId: string, o?: CallOptions) => Promise<{
146
+ ok: true;
147
+ }>;
148
+ /** The token of a link comes back once. */
149
+ createLink: (path: string, input: {
150
+ role: Role;
151
+ expiresInMs?: number;
152
+ signedInOnly?: boolean;
153
+ }, o?: CallOptions) => Promise<{
154
+ id: string;
155
+ token: string;
156
+ }>;
157
+ revokeLink: (linkId: string, o?: CallOptions) => Promise<{
158
+ ok: true;
159
+ }>;
160
+ /** The next three read a shared item by id. Pass a link token, a bearer token, or both. */
161
+ openShared: (itemId: string, opts?: {
162
+ linkToken?: string;
163
+ } & CallOptions) => Promise<Item>;
164
+ listShared: (itemId: string, opts?: {
165
+ linkToken?: string;
166
+ } & CallOptions) => Promise<Item[]>;
167
+ readShared: (itemId: string, opts?: {
168
+ linkToken?: string;
169
+ } & CallOptions) => Promise<Omit<Downloaded, "sha256">>;
170
+ };
171
+ type DriveClient = ReturnType<typeof createClient>;
172
+
173
+ export { type ApiErrorCode, type CallOptions, type ClientErrorCode, type ClientOptions, type Downloaded, DriveApiError, type DriveClient, type Item, type OnConflict, type PathItem, type Role, type Subject, type SubjectKind, type UploadBody, createClient };
package/dist/client.js ADDED
@@ -0,0 +1,240 @@
1
+ // src/client.ts
2
+ var DriveApiError = class extends Error {
3
+ constructor(code, status, message) {
4
+ super(message);
5
+ this.code = code;
6
+ this.status = status;
7
+ this.name = "DriveApiError";
8
+ }
9
+ code;
10
+ status;
11
+ };
12
+ var BASE = "/api/v1/drive";
13
+ var CODES = {
14
+ NOT_FOUND: true,
15
+ NAME_TAKEN: true,
16
+ BLOB_EXISTS: true,
17
+ INVALID_NAME: true,
18
+ INVALID_PATH: true,
19
+ INVALID_REF: true,
20
+ NOT_A_FOLDER: true,
21
+ NOT_A_FILE: true,
22
+ MOVE_INTO_SELF: true,
23
+ TOO_DEEP: true,
24
+ PARENT_GONE: true,
25
+ TRASHED: true,
26
+ NOT_TRASHED: true,
27
+ IS_ROOT: true,
28
+ FORBIDDEN: true,
29
+ BLOB_MISSING: true,
30
+ TOO_LARGE: true,
31
+ INVALID_EXPIRY: true,
32
+ BUSY: true,
33
+ CROSS_DRIVE: true,
34
+ INVALID_SHARE: true,
35
+ TOO_MANY_ITEMS: true,
36
+ UNAUTHORIZED: true,
37
+ NOT_CONFIGURED: true,
38
+ INVALID_LINK: true,
39
+ INTERNAL: true
40
+ };
41
+ var KNOWN_CODES = new Set(Object.keys(CODES));
42
+ var record = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
43
+ var text = (v) => typeof v === "string";
44
+ var orNull = (test) => (v) => v === null || test(v);
45
+ var num = (v) => typeof v === "number";
46
+ var ACTOR_KINDS = ["human", "agent_of_human", "agent_of_agent", "agent", "user_token"];
47
+ var ROLES = ["viewer", "commenter", "editor", "owner"];
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);
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
+ var isPathItem = (v) => isItem(v) && typeof v.path === "string";
52
+ var listOf = (check) => (v) => Array.isArray(v) && v.every(check);
53
+ var shape = (test) => (v) => record(v) && test(v);
54
+ var numbers = (...keys) => shape((v) => keys.every((k) => typeof v[k] === "number"));
55
+ var isOk = shape((v) => v.ok === true);
56
+ var RETRY_STATUS = /* @__PURE__ */ new Set([502, 503, 504]);
57
+ function createClient(options) {
58
+ let origin;
59
+ try {
60
+ const url = new URL(options.baseUrl);
61
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("not http");
62
+ origin = url.origin;
63
+ } catch {
64
+ throw new TypeError("baseUrl must be an http or https URL");
65
+ }
66
+ const { token } = options;
67
+ if (token !== void 0 && (typeof token !== "string" || !/^[A-Za-z0-9_-]{1,512}$/.test(token))) throw new TypeError("token must be 1 to 512 letters, digits, - or _");
68
+ const retries = options.retries ?? 0;
69
+ if (!Number.isInteger(retries) || retries < 0 || retries > 5) throw new RangeError("retries must be an integer from 0 to 5");
70
+ const doFetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
71
+ const query = (params) => {
72
+ const search = new URLSearchParams();
73
+ for (const [key, value] of Object.entries(params)) if (value !== void 0) search.set(key, String(value));
74
+ const text2 = search.toString();
75
+ return text2 === "" ? "" : `?${text2}`;
76
+ };
77
+ async function send(method, path, init) {
78
+ if (init.auth !== false && token === void 0) throw new TypeError("this call needs a token");
79
+ const headers = { ...token === void 0 ? {} : { authorization: `Bearer ${token}` }, ...init.headers };
80
+ let body = init.body;
81
+ if (init.json !== void 0) {
82
+ headers["content-type"] = "application/json";
83
+ body = JSON.stringify(init.json);
84
+ }
85
+ const attempts = method === "GET" ? retries + 1 : 1;
86
+ for (let attempt = 1; ; attempt++) {
87
+ let response;
88
+ try {
89
+ response = await doFetch(`${origin}${BASE}${path}`, { method, headers, body, signal: init.signal, redirect: "manual", ...body instanceof ReadableStream ? { duplex: "half" } : {} });
90
+ } catch (error) {
91
+ if (init.signal?.aborted || error instanceof Error && error.name === "AbortError") throw error;
92
+ if (attempt < attempts) {
93
+ await pause(attempt, init.signal);
94
+ continue;
95
+ }
96
+ const refused = body instanceof ReadableStream ? " (a stream body may be refused by this runtime: send bytes)" : "";
97
+ throw new DriveApiError("NETWORK_ERROR", 0, `${error instanceof Error ? `no answer: ${error.message}` : "no answer"}${refused}`);
98
+ }
99
+ if (attempt < attempts && RETRY_STATUS.has(response.status)) {
100
+ await response.body?.cancel();
101
+ await pause(attempt, init.signal);
102
+ continue;
103
+ }
104
+ if (response.type === "opaqueredirect" || response.status >= 300 && response.status < 400) {
105
+ await response.body?.cancel();
106
+ throw new DriveApiError("BAD_RESPONSE", response.status, "the server redirected. Set baseUrl to the final URL.");
107
+ }
108
+ if (response.ok) return response;
109
+ throw await errorOf(response, init.signal);
110
+ }
111
+ }
112
+ async function pause(attempt, signal) {
113
+ signal?.throwIfAborted();
114
+ await new Promise((resolve, reject) => {
115
+ const timer = setTimeout(() => {
116
+ signal?.removeEventListener("abort", stop);
117
+ resolve();
118
+ }, Math.min(100 * 2 ** (attempt - 1), 1e3));
119
+ const stop = () => {
120
+ clearTimeout(timer);
121
+ reject(signal?.reason);
122
+ };
123
+ signal?.addEventListener("abort", stop, { once: true });
124
+ });
125
+ }
126
+ async function errorOf(response, signal) {
127
+ let code = "BAD_RESPONSE";
128
+ let message = `the server answered ${response.status}`;
129
+ try {
130
+ const parsed = await response.json();
131
+ if (typeof parsed.error?.code === "string" && KNOWN_CODES.has(parsed.error.code)) code = parsed.error.code;
132
+ else if (typeof parsed.error?.code === "string") message = `the server answered ${response.status} with an unknown code, ${JSON.stringify(parsed.error.code.slice(0, 40))}`;
133
+ if (code !== "BAD_RESPONSE" && typeof parsed.error?.message === "string") message = parsed.error.message;
134
+ } catch (error) {
135
+ if (signal?.aborted) throw error;
136
+ }
137
+ return new DriveApiError(code, response.status, message);
138
+ }
139
+ async function json(method, path, init, check) {
140
+ const response = await send(method, path, init);
141
+ let value;
142
+ try {
143
+ value = await response.json();
144
+ } catch (error) {
145
+ if (init.signal?.aborted) throw error;
146
+ if (!(error instanceof SyntaxError)) throw new DriveApiError("NETWORK_ERROR", 0, "the connection dropped while the body came in");
147
+ throw new DriveApiError("BAD_RESPONSE", response.status, "the answer is not JSON");
148
+ }
149
+ if (!check(value)) throw new DriveApiError("BAD_RESPONSE", response.status, "the answer is not the shape that the API sends");
150
+ return value;
151
+ }
152
+ async function bytes(path, init, verify = false) {
153
+ const response = await send("GET", path, init);
154
+ let data;
155
+ try {
156
+ data = new Uint8Array(await response.arrayBuffer());
157
+ } catch (error) {
158
+ if (init.signal?.aborted) throw error;
159
+ throw new DriveApiError("NETWORK_ERROR", 0, "the connection dropped while the body came in");
160
+ }
161
+ const sha256 = response.headers.get("x-content-sha256") ?? "";
162
+ if (verify) {
163
+ if (!/^[0-9a-f]{64}$/.test(sha256)) throw new DriveApiError("BAD_RESPONSE", response.status, "the answer has no valid x-content-sha256");
164
+ if (typeof crypto === "undefined" || !crypto.subtle) throw new DriveApiError("BAD_RESPONSE", response.status, "this runtime has no WebCrypto, so the bytes cannot be checked. Use https.");
165
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", data));
166
+ if (Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("") !== sha256) throw new DriveApiError("BAD_RESPONSE", response.status, "the bytes do not match their hash");
167
+ }
168
+ return { data, contentType: response.headers.get("content-type") ?? "application/octet-stream", sha256 };
169
+ }
170
+ const link = (token2) => {
171
+ if (token2 === void 0) return void 0;
172
+ if (!/^[A-Za-z0-9_-]{1,512}$/.test(token2)) throw new TypeError("a link token has 1 to 512 letters, digits, - or _");
173
+ return { "x-link-token": token2 };
174
+ };
175
+ const id = (value) => {
176
+ if (typeof value !== "string" || !/^[A-Za-z0-9_-][A-Za-z0-9_.-]*$/.test(value) || value === "." || value === "..") throw new TypeError("an item id has letters, digits, -, _ and . (not first), and is not . or ..");
177
+ return value;
178
+ };
179
+ const isUrl = shape((v) => typeof v.url === "string" && /^https?:\/\//.test(v.url) && URL.canParse(v.url) && typeof v.expiresInSeconds === "number");
180
+ const isShares = shape(
181
+ (v) => listOf(shape((g) => (g.subjectKind === "human" || g.subjectKind === "agent") && text(g.subjectId) && oneOf(ROLES)(g.role)))(v.grants) && listOf(shape((l) => text(l.id) && oneOf(ROLES)(l.role) && typeof l.signedInOnly === "boolean" && orNull(num)(l.expiresAt) && typeof l.revoked === "boolean"))(v.links) && listOf(shape((i) => text(i.id) && oneOf(ROLES)(i.role) && orNull(text)(i.email) && orNull(text)(i.handle) && orNull(num)(i.expiresAt) && typeof i.accepted === "boolean"))(v.invites)
182
+ );
183
+ const isHome = shape((v) => record(v.apps) && Object.values(v.apps).every(num) && num(v.files) && listOf(shape((i) => isPathItem(i) && orNull(text)(i.app)))(v.items));
184
+ const isId = shape((v) => typeof v.id === "string");
185
+ const isLink = shape((v) => typeof v.id === "string" && typeof v.token === "string");
186
+ return {
187
+ /** The live items of a folder. */
188
+ list: (path = "/", o = {}) => json("GET", `/items${query({ path })}`, o, listOf(isItem)),
189
+ stat: (path, o = {}) => json("GET", `/stat${query({ path })}`, o, isItem),
190
+ mkdir: (path, opts = {}, o = {}) => json("POST", "/folders", { json: { path, parents: opts.parents === true }, ...o }, isItem),
191
+ /** Writes a file. A `ReadableStream` body streams in runtimes that allow it. */
192
+ upload: (path, body, opts = {}, o = {}) => json("PUT", `/files${query({ path, mime: opts.mime, parents: opts.parents === true ? true : void 0, onConflict: opts.onConflict })}`, {
193
+ body: typeof body === "string" ? new TextEncoder().encode(body) : body,
194
+ headers: { "content-type": "application/octet-stream" },
195
+ ...o
196
+ }, isItem),
197
+ /** The bytes. They are checked against the hash that the server sends. */
198
+ download: (path, o = {}) => bytes(`/files${query({ path })}`, o, true),
199
+ /** A signed link that works without a token, for `expires` seconds. You fetch the link yourself. */
200
+ url: (path, expires, o = {}) => json("GET", `/url${query({ path, expires })}`, o, isUrl),
201
+ move: (src, folder, opts = {}, o = {}) => json("POST", "/move", { json: { src, folder, ...opts }, ...o }, isItem),
202
+ trash: (path, o = {}) => json("POST", "/trash", { json: { path }, ...o }, isItem),
203
+ listTrash: (o = {}) => json("GET", "/trash", o, listOf(isItem)),
204
+ restore: (itemId, opts = {}, o = {}) => json("POST", "/restore", { json: { id: itemId, ...opts }, ...o }, isItem),
205
+ /** Deletes a trashed item for good. */
206
+ purge: (itemId, o = {}) => json("POST", "/purge", { json: { id: itemId }, ...o }, numbers("removed")),
207
+ search: (q = {}, o = {}) => json("GET", `/search${query(q)}`, o, listOf(isPathItem)),
208
+ /** Everything the caller has, as a zip. */
209
+ export: async (o = {}) => {
210
+ const { data } = await bytes("/export", o);
211
+ if (data[0] !== 80 || data[1] !== 75) throw new DriveApiError("BAD_RESPONSE", 200, "the answer is not a zip");
212
+ return data;
213
+ },
214
+ /** Erases the caller for good. The client sends `confirm=yes`. */
215
+ deleteAccount: (o = {}) => json("DELETE", "/account?confirm=yes", o, numbers("removed", "grants", "invites", "anonymized")),
216
+ home: (q = {}, o = {}) => json("GET", `/home${query(q)}`, o, isHome),
217
+ registerDocument: (input, o = {}) => json("POST", "/documents", { json: input, ...o }, isItem),
218
+ forgetDocument: (ref, o = {}) => json("DELETE", `/documents${query({ ref })}`, o, numbers("trashed")),
219
+ shares: (path, o = {}) => json("GET", `/shares${query({ path })}`, o, isShares),
220
+ share: (path, subject, role, o = {}) => json("POST", "/shares", { json: { path, subjectId: subject.id, subjectKind: subject.kind, role }, ...o }, isOk),
221
+ unshare: (path, subject, o = {}) => json("DELETE", `/shares${query({ path, subjectId: subject.id, subjectKind: subject.kind })}`, o, isOk),
222
+ /** Invite by `email` (a person) or `handle` (an agent). It sends no message. */
223
+ invite: (path, input, o = {}) => json("POST", "/invites", { json: { path, ...input }, ...o }, isId),
224
+ acceptInvite: (inviteId, o = {}) => json("POST", "/invites/accept", { json: { id: inviteId }, ...o }, isOk),
225
+ /** The token of a link comes back once. */
226
+ createLink: (path, input, o = {}) => json("POST", "/links", { json: { path, ...input }, ...o }, isLink),
227
+ revokeLink: (linkId, o = {}) => json("DELETE", `/links${query({ id: linkId })}`, o, isOk),
228
+ /** The next three read a shared item by id. Pass a link token, a bearer token, or both. */
229
+ openShared: async (itemId, opts = {}) => json("GET", `/shared/${id(itemId)}`, { auth: false, headers: link(opts.linkToken), signal: opts.signal }, isItem),
230
+ listShared: async (itemId, opts = {}) => json("GET", `/shared/${id(itemId)}/items`, { auth: false, headers: link(opts.linkToken), signal: opts.signal }, listOf(isItem)),
231
+ readShared: async (itemId, opts = {}) => {
232
+ const { data, contentType } = await bytes(`/shared/${id(itemId)}/content`, { auth: false, headers: link(opts.linkToken), signal: opts.signal });
233
+ return { data, contentType };
234
+ }
235
+ };
236
+ }
237
+ export {
238
+ DriveApiError,
239
+ createClient
240
+ };