@wirecat/drive-cli 0.14.1 → 0.15.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/README.md +8 -4
- package/dist/{catalog-commands.d.ts → commands/catalog.d.ts} +2 -2
- package/dist/commands/catalog.d.ts.map +1 -0
- package/dist/{catalog-commands.js → commands/catalog.js} +19 -22
- package/dist/commands/catalog.js.map +1 -0
- package/dist/commands/config.d.ts +5 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +81 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/context.d.ts +38 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +14 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/discovery.d.ts +5 -0
- package/dist/commands/discovery.d.ts.map +1 -0
- package/dist/commands/discovery.js +27 -0
- package/dist/commands/discovery.js.map +1 -0
- package/dist/commands/documents.d.ts +10 -0
- package/dist/commands/documents.d.ts.map +1 -0
- package/dist/commands/documents.js +89 -0
- package/dist/commands/documents.js.map +1 -0
- package/dist/commands/folders.d.ts +4 -0
- package/dist/commands/folders.d.ts.map +1 -0
- package/dist/commands/folders.js +44 -0
- package/dist/commands/folders.js.map +1 -0
- package/dist/{knowledge-commands.d.ts → commands/knowledge.d.ts} +4 -4
- package/dist/commands/knowledge.d.ts.map +1 -0
- package/dist/{knowledge-commands.js → commands/knowledge.js} +36 -54
- package/dist/commands/knowledge.js.map +1 -0
- package/dist/commands/mcp.d.ts +4 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +18 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/{remote-commands.d.ts → commands/remote.d.ts} +1 -1
- package/dist/commands/remote.d.ts.map +1 -0
- package/dist/{remote-commands.js → commands/remote.js} +42 -61
- package/dist/commands/remote.js.map +1 -0
- package/dist/commands/search.d.ts +4 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +20 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/session.d.ts +4 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +59 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/sync.d.ts +4 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +50 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/config-upgrade.d.ts.map +1 -1
- package/dist/config-upgrade.js +10 -7
- package/dist/config-upgrade.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +48 -11
- package/dist/config.js.map +1 -1
- package/dist/failure.d.ts.map +1 -1
- package/dist/failure.js +12 -1
- package/dist/failure.js.map +1 -1
- package/dist/google.js +4 -3
- package/dist/google.js.map +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +8 -6
- package/dist/mcp.js.map +1 -1
- package/dist/model.d.ts +10 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/program.d.ts +2 -19
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +66 -437
- package/dist/program.js.map +1 -1
- package/dist/registry.d.ts +4 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +3 -0
- package/dist/registry.js.map +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +4 -1
- package/dist/state.js.map +1 -1
- package/dist/store-transition.d.ts +3 -0
- package/dist/store-transition.d.ts.map +1 -0
- package/dist/store-transition.js +99 -0
- package/dist/store-transition.js.map +1 -0
- package/dist/store.d.ts +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +21 -6
- package/dist/store.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +25 -2
- package/dist/sync.js.map +1 -1
- package/package.json +2 -2
- package/dist/catalog-commands.d.ts.map +0 -1
- package/dist/catalog-commands.js.map +0 -1
- package/dist/knowledge-commands.d.ts.map +0 -1
- package/dist/knowledge-commands.js.map +0 -1
- package/dist/remote-commands.d.ts.map +0 -1
- package/dist/remote-commands.js.map +0 -1
package/dist/model.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACb,YAAY,EACZ,aAAa,EACb,UAAU,EACV,UAAU,EACV,WAAW,EACX,UAAU,EACX,MAAM,6BAA6B,CAAA;AAEpC,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAA;AACzE,eAAO,MAAM,MAAM,uCAAuC,CAAA;AAC1D,eAAO,MAAM,GAAG,yCAAyC,CAAA;AACzD,eAAO,MAAM,KAAK,4CAA4C,CAAA;AAC9D,MAAM,WAAW,UAAU;IACzB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AACD,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,UAAU,EAAE,CAAA;IACrB,cAAc,CAAC,EAAE,UAAU,CAAA;CAC5B;AACD,+EAA+E;AAC/E,eAAO,MAAM,YAAY,+FAQtB,YAAY;;;;;;;;;;aAZJ,UAAU,EAAE;;;;;;;CAoBrB,CAAA;AAEF,eAAO,MAAM,oBAAoB,UAmBsD,CAAA;AACvF,eAAO,MAAM,QAAQ,iDAAkD,IAAI;;;;;;;;;;;;;iBA1ChE,UAAU,EAAE;;;;;;;;;;CA+CrB,CAAA;AACF,MAAM,MAAM,eAAe,GAAG,aAAa,CAAC,YAAY,CAAC,CAAA;AACzD,MAAM,MAAM,MAAM,GAAG,YAAY,CAAC,YAAY,CAAC,CAAA;AAC/C,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC,YAAY,CAAC,CAAA;AACvD,MAAM,MAAM,eAAe,GAAG,aAAa,CAAC,YAAY,CAAC,CAAA;AACzD,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC,YAAY,CAAC,CAAA;AAC/C,MAAM,MAAM,IAAI,GAAG,UAAU,CAAC,YAAY,CAAC,CAAA;AAC3C,MAAM,MAAM,SAAS,GAAG,WAAW,CAAC,YAAY,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
|
package/dist/program.d.ts
CHANGED
|
@@ -1,22 +1,5 @@
|
|
|
1
|
-
import { type KeyringStore, type Streams } from "@wirecat/cli-base";
|
|
2
1
|
import { Command } from "commander";
|
|
3
|
-
import { type
|
|
4
|
-
|
|
5
|
-
import { type DocumentIndex, type GoogleSource } from "./model.js";
|
|
6
|
-
import { type UploadRequest } from "./upload.js";
|
|
7
|
-
export interface ProgramOptions {
|
|
8
|
-
env?: NodeJS.ProcessEnv;
|
|
9
|
-
streams?: Streams;
|
|
10
|
-
keyring?: KeyringStore;
|
|
11
|
-
openBrowser?: (url: string) => Promise<void>;
|
|
12
|
-
source?: (profile: string) => Promise<GoogleSource>;
|
|
13
|
-
index?: () => Promise<DocumentIndex>;
|
|
14
|
-
signal?: AbortSignal;
|
|
15
|
-
api?: GoogleApi;
|
|
16
|
-
complete?: Complete;
|
|
17
|
-
ask?: (key: string) => Promise<boolean>;
|
|
18
|
-
readBody?: (path: string | number) => string;
|
|
19
|
-
uploadRequest?: UploadRequest;
|
|
20
|
-
}
|
|
2
|
+
import { type ProgramOptions } from "./commands/context.js";
|
|
3
|
+
export type { ProgramOptions };
|
|
21
4
|
export declare const createProgram: (options?: ProgramOptions) => Command;
|
|
22
5
|
//# sourceMappingURL=program.d.ts.map
|
package/dist/program.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAE,OAAO,EAAQ,MAAM,WAAW,CAAA;AAIzC,OAAO,EAA+B,KAAK,cAAc,EAAY,MAAM,uBAAuB,CAAA;AA2BlG,YAAY,EAAE,cAAc,EAAE,CAAA;AAc9B,eAAO,MAAM,aAAa,aAAa,cAAc,KAAQ,OAmV5D,CAAA"}
|
package/dist/program.js
CHANGED
|
@@ -1,48 +1,38 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { readFileSync
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
3
|
import { CliError, createDeadline, createOutputBuffer, createRenderer, fieldsOf, processStreams, projectFields, } from "@wirecat/cli-base";
|
|
4
|
-
import { annotate,
|
|
5
|
-
import { formatSuggestions, suggest } from "@wirecat/cli-base/completion";
|
|
4
|
+
import { annotate, flatten, metaOf } from "@wirecat/cli-base/commands";
|
|
6
5
|
import { writes } from "@wirecat/cli-base/standards";
|
|
7
|
-
import { commandWords, itemOf, liftJson, liftProfile,
|
|
8
|
-
import {
|
|
9
|
-
import { Command, Help, InvalidArgumentError, Option } from "commander";
|
|
6
|
+
import { commandWords, itemOf, liftJson, liftProfile, parseDuration, refuseCommandName, renderAnswer, } from "@wirecat/cli-core/cli";
|
|
7
|
+
import { Command, Help } from "commander";
|
|
10
8
|
import { validateRequest } from "./api-contract.js";
|
|
11
|
-
import { installCatalogCommands } from "./catalog
|
|
12
|
-
import {
|
|
9
|
+
import { installCatalogCommands } from "./commands/catalog.js";
|
|
10
|
+
import { installConfigCommands, installModelCommands } from "./commands/config.js";
|
|
11
|
+
import { offset, positive } from "./commands/context.js";
|
|
12
|
+
import { installCommandsCommand, installCompleteCommand } from "./commands/discovery.js";
|
|
13
|
+
import { importedDocument, installDocumentCommands } from "./commands/documents.js";
|
|
14
|
+
import { installFolderCommands } from "./commands/folders.js";
|
|
15
|
+
import { installKnowledgeCommands } from "./commands/knowledge.js";
|
|
16
|
+
import { installMcpCommand } from "./commands/mcp.js";
|
|
17
|
+
import { installRemoteCommands } from "./commands/remote.js";
|
|
18
|
+
import { installSearchCommand } from "./commands/search.js";
|
|
19
|
+
import { installSessionCommands } from "./commands/session.js";
|
|
20
|
+
import { installSyncCommands } from "./commands/sync.js";
|
|
21
|
+
import { activeProfile, fileId, profileName, readConfig } from "./config.js";
|
|
13
22
|
import { googleError } from "./errors.js";
|
|
14
23
|
import { memoryModel } from "./extraction.js";
|
|
15
|
-
import { googleSource
|
|
24
|
+
import { googleSource } from "./google.js";
|
|
16
25
|
import { apiMethod, googleApi, googleUploadRequest } from "./google-api.js";
|
|
17
|
-
import {
|
|
18
|
-
import { login } from "./login.js";
|
|
19
|
-
import { FOLDER, metadataJson, skipJson } from "./model.js";
|
|
20
|
-
import { allOf, snakeKeys } from "./output.js";
|
|
26
|
+
import { snakeKeys } from "./output.js";
|
|
21
27
|
import { checkPolicy, guardedGoogleWrite, methodPolicyKeys } from "./policy.js";
|
|
22
28
|
import { lockProfile } from "./profile-lock.js";
|
|
23
29
|
import { checkProviderJson, checkResultFields, providerJsonFields } from "./provider-json.js";
|
|
24
|
-
import { ACCOUNT_FREE_COMMANDS,
|
|
25
|
-
import {
|
|
26
|
-
import { profileDir, readSnapshot, readState } from "./state.js";
|
|
30
|
+
import { ACCOUNT_FREE_COMMANDS, describeDriveProgram } from "./registry.js";
|
|
31
|
+
import { profileDir } from "./state.js";
|
|
27
32
|
import { openIndex } from "./store.js";
|
|
28
|
-
import {
|
|
29
|
-
import { authorizedClient, checkAuthorization, credentials } from "./tokens.js";
|
|
33
|
+
import { authorizedClient, checkAuthorization } from "./tokens.js";
|
|
30
34
|
import { uploadFile } from "./upload.js";
|
|
31
35
|
import { VERSION } from "./version.js";
|
|
32
|
-
const NETWORK_GROUPS = ["files", "comments", "permissions", "revisions", "shared-drives"];
|
|
33
|
-
const NETWORK_COMMANDS = ["folders add", "documents get", "documents history", "sync"];
|
|
34
|
-
const positive = (input) => {
|
|
35
|
-
const n = Number(input);
|
|
36
|
-
if (!Number.isSafeInteger(n) || n < 1)
|
|
37
|
-
throw new InvalidArgumentError("use a positive integer");
|
|
38
|
-
return n;
|
|
39
|
-
};
|
|
40
|
-
const offset = (input) => {
|
|
41
|
-
const n = Number(input);
|
|
42
|
-
if (!Number.isSafeInteger(n) || n < 0)
|
|
43
|
-
throw new InvalidArgumentError("use a nonnegative integer");
|
|
44
|
-
return n;
|
|
45
|
-
};
|
|
46
36
|
const eachItem = (body, change) => "items" in body ? { ...body, items: body.items.map(change) } : { ...body, item: change(body.item) };
|
|
47
37
|
const commandIn = (root, words) => {
|
|
48
38
|
let at = root;
|
|
@@ -57,14 +47,6 @@ const commandIn = (root, words) => {
|
|
|
57
47
|
};
|
|
58
48
|
class Preview extends Error {
|
|
59
49
|
}
|
|
60
|
-
const GRANTS = ["write", "activity", "apps"];
|
|
61
|
-
const grants = (raw) => {
|
|
62
|
-
const values = raw.split(",").map((value) => value.trim());
|
|
63
|
-
const unknown = values.filter((value) => !GRANTS.includes(value));
|
|
64
|
-
if (unknown.length)
|
|
65
|
-
throw new CliError("validation_error", `--grant takes ${GRANTS.join(", ")}; got ${unknown.join(", ")}`);
|
|
66
|
-
return new Set(values);
|
|
67
|
-
};
|
|
68
50
|
export const createProgram = (options = {}) => {
|
|
69
51
|
const startedAt = Date.now();
|
|
70
52
|
const env = options.env ?? process.env;
|
|
@@ -105,6 +87,7 @@ export const createProgram = (options = {}) => {
|
|
|
105
87
|
return profileName(name);
|
|
106
88
|
};
|
|
107
89
|
let currentAction;
|
|
90
|
+
let checkedMethod;
|
|
108
91
|
let fields;
|
|
109
92
|
let activeSignal = options.signal;
|
|
110
93
|
let deadline;
|
|
@@ -159,7 +142,7 @@ export const createProgram = (options = {}) => {
|
|
|
159
142
|
return googleSource(client);
|
|
160
143
|
};
|
|
161
144
|
const withIndex = async (fn) => {
|
|
162
|
-
const index = await (options.index?.() ?? openIndex(env));
|
|
145
|
+
const index = await (options.index?.() ?? openIndex(env, selected(), currentAction?.opts().rescan === true));
|
|
163
146
|
try {
|
|
164
147
|
return await fn(index);
|
|
165
148
|
}
|
|
@@ -167,237 +150,47 @@ export const createProgram = (options = {}) => {
|
|
|
167
150
|
await index.close();
|
|
168
151
|
}
|
|
169
152
|
};
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
env,
|
|
182
|
-
profile: selected(),
|
|
183
|
-
application: application(env, args.clientFile),
|
|
184
|
-
storage: args.credentialStorage,
|
|
185
|
-
...Object.fromEntries([...(args.grant ?? [])].map((grant) => [grant, true])),
|
|
186
|
-
...(options.keyring ? { keyring: options.keyring } : {}),
|
|
187
|
-
...(activeSignal ? { signal: activeSignal } : {}),
|
|
188
|
-
openBrowser: args.browser ? (options.openBrowser ?? (async () => { })) : async () => { },
|
|
189
|
-
showUrl: (url) => streams.diagnostic(`Open Google sign-in: ${url}\n`),
|
|
190
|
-
})));
|
|
191
|
-
});
|
|
192
|
-
examples(session.command("list").description("list the configured profiles; credentials are never printed"), [
|
|
193
|
-
"drive personal session list",
|
|
194
|
-
]).action(() => output(itemOf({
|
|
195
|
-
scope: "all-configured-profiles",
|
|
196
|
-
items: Object.entries(readConfig(env).profiles).map(([name, p]) => ({
|
|
197
|
-
profile: name,
|
|
198
|
-
selected_folders: p.roots.length,
|
|
199
|
-
storage: p.storage,
|
|
200
|
-
})),
|
|
201
|
-
})));
|
|
202
|
-
examples(session.command("end").description("sign this profile out; imported documents stay"), [
|
|
203
|
-
"drive personal session end",
|
|
204
|
-
]).action(() => {
|
|
205
|
-
const name = selected();
|
|
206
|
-
const config = readConfig(env);
|
|
207
|
-
const profile = activeProfile(env, name);
|
|
208
|
-
credentials(env, profile.storage, options.keyring).remove(name);
|
|
209
|
-
// Preserve the principal and roots so reauthorization resumes the same source identities.
|
|
210
|
-
writeConfig(env, config);
|
|
211
|
-
output(itemOf({ profile: name, signed_out: true, cached_data: "retained" }));
|
|
212
|
-
});
|
|
213
|
-
const folders = program.command("folders").description("choose the Google Drive folders that sync imports");
|
|
214
|
-
const folderArgument = "folder id or Google Drive URL";
|
|
215
|
-
examples(folders
|
|
216
|
-
.command("add")
|
|
217
|
-
.argument("<folder>", folderArgument)
|
|
218
|
-
.description("import a folder and everything under it on the next sync"), ["drive personal folders add FOLDER_ID"]).action(async (input) => {
|
|
219
|
-
const id = fileId(input);
|
|
220
|
-
const config = readConfig(env);
|
|
221
|
-
const name = selected();
|
|
222
|
-
const profile = activeProfile(env, name);
|
|
223
|
-
const metadata = await (await source()).metadata(id);
|
|
224
|
-
if (metadata.mime !== FOLDER || metadata.trashed)
|
|
225
|
-
throw new CliError("validation_error", "source is not an accessible Google folder");
|
|
226
|
-
if (!profile.roots.includes(metadata.id))
|
|
227
|
-
profile.roots.push(metadata.id);
|
|
228
|
-
config.profiles[name] = profile;
|
|
229
|
-
writeConfig(env, config);
|
|
230
|
-
output(itemOf({ id: metadata.id, name: metadata.name, added: true }));
|
|
231
|
-
});
|
|
232
|
-
examples(folders.command("list").description("list the folder ids sync imports"), [
|
|
233
|
-
"drive personal folders list",
|
|
234
|
-
]).action(() => output(allOf(activeProfile(env, selected()).roots.map((id) => ({ id })))));
|
|
235
|
-
examples(folders
|
|
236
|
-
.command("remove")
|
|
237
|
-
.argument("<folder>", folderArgument)
|
|
238
|
-
.description("stop importing a folder; the next sync drops its documents"), ["drive personal folders remove FOLDER_ID"]).action((input) => {
|
|
239
|
-
const id = fileId(input);
|
|
240
|
-
const config = readConfig(env);
|
|
241
|
-
const name = selected();
|
|
242
|
-
const profile = activeProfile(env, name);
|
|
243
|
-
profile.roots = profile.roots.filter((root) => root !== id);
|
|
244
|
-
config.profiles[name] = profile;
|
|
245
|
-
writeConfig(env, config);
|
|
246
|
-
output(itemOf({ id, removed: true }));
|
|
247
|
-
});
|
|
248
|
-
examples(program
|
|
249
|
-
.command("sync")
|
|
250
|
-
.summary("import what changed in the chosen folders, and finish an unfinished sync")
|
|
251
|
-
.description("import what changed in the chosen folders, and finish an unfinished sync; " +
|
|
252
|
-
`one sync reads at most ${MAX_ENTRIES} entries under the chosen folders, and as many changes`), ["drive personal sync", "drive personal sync --rescan --limit 500"])
|
|
253
|
-
.option("--limit <n>", "how many documents to read from Google in this run", positive, 200)
|
|
254
|
-
.option("--rescan", "read every chosen folder again instead of only what changed")
|
|
255
|
-
.option("--embed", "also index pending text for meaning search, with a model already downloaded")
|
|
256
|
-
.action(async (args) => {
|
|
257
|
-
const remote = activeProfile(env, selected()).roots.length ? await source() : undefined;
|
|
258
|
-
const result = await withIndex(async (index) => ({
|
|
259
|
-
...(await sync(remote, index, env, selected(), args.limit, activeSignal, args.rescan)),
|
|
260
|
-
...(args.embed ? { embeddings: await index.embed(600) } : {}),
|
|
261
|
-
}));
|
|
262
|
-
output(itemOf(result));
|
|
263
|
-
if (result.errors.length || result.pending)
|
|
264
|
-
streams.diagnostic(`${result.pending} source operations remain; run drive ${selected()} sync again\n`);
|
|
265
|
-
if (result.errors.length)
|
|
266
|
-
throw new CliError("provider_error", "sync completed partially; failed operations remain pending", {
|
|
267
|
-
reason: "sync.partial",
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
examples(program.command("status").description("show the last sync, imported documents and pending work"), [
|
|
271
|
-
"drive personal status",
|
|
272
|
-
]).action(() => {
|
|
273
|
-
const name = selected();
|
|
274
|
-
const profile = activeProfile(env, name);
|
|
275
|
-
const state = readState(env, name, profile.principal);
|
|
276
|
-
output(itemOf({
|
|
277
|
-
profile: name,
|
|
278
|
-
selected_folders: profile.roots,
|
|
279
|
-
documents: Object.keys(state.documents).length,
|
|
280
|
-
pending: state.pending?.jobs.length ?? 0,
|
|
281
|
-
skipped: state.skipped.map(skipJson),
|
|
282
|
-
last_sync: state.lastSync ?? null,
|
|
283
|
-
coverage: "Google Docs/Sheets, PDF/DOCX/XLSX and Markdown/TXT/CSV/TSV; native export layout, tab fidelity and assets are not verified",
|
|
284
|
-
remote_checked: false,
|
|
285
|
-
}));
|
|
286
|
-
});
|
|
287
|
-
const documents = program.command("documents").description("read imported documents and Google Docs");
|
|
288
|
-
const documentArgument = "document id or Google Docs URL";
|
|
289
|
-
annotate(withQuery(examples(documents.command("list").description("list imported documents with their Google URLs"), [
|
|
290
|
-
"drive personal documents list",
|
|
291
|
-
"drive personal documents list tag:budget",
|
|
292
|
-
"drive personal documents list person:PERSON_UID --limit 10",
|
|
293
|
-
]), IMPORTED_DOCUMENT_QUERY), { output: { entity: "drive_document" } })
|
|
294
|
-
.option("--limit <n>", "how many documents to return", positive, 50)
|
|
295
|
-
.option("--page <n>", "which page to return", positive, 1)
|
|
296
|
-
.action(async (words, { limit, page }) => {
|
|
297
|
-
const profile = activeProfile(env, selected());
|
|
298
|
-
const state = readState(env, selected(), profile.principal);
|
|
299
|
-
const query = typedQuery(words);
|
|
300
|
-
const ast = listQuery(query, IMPORTED_DOCUMENT_QUERY, "documents list");
|
|
301
|
-
const found = await withIndex(async (index) => {
|
|
302
|
-
if (!index.knowledge)
|
|
303
|
-
throw new CliError("configuration_error", "index has no shared knowledge adapter");
|
|
304
|
-
return index.knowledge.documents(profile.principal, ast, { limit, offset: (page - 1) * limit });
|
|
305
|
-
});
|
|
306
|
-
output(listOf("documents list", {
|
|
307
|
-
items: found.items.map((note) => {
|
|
308
|
-
const id = (note.path ?? "").replace(/\.md$/, "");
|
|
309
|
-
const doc = state.documents[id];
|
|
310
|
-
return doc
|
|
311
|
-
? { ...metadataJson(doc.metadata), reference: doc.reference, tabs: doc.tabs }
|
|
312
|
-
: { id, name: note.title, reference: note.ref, tabs: 0 };
|
|
313
|
-
}),
|
|
314
|
-
limit,
|
|
315
|
-
has_more: found.hasMore,
|
|
316
|
-
page,
|
|
317
|
-
args: { query: query || undefined },
|
|
318
|
-
}));
|
|
319
|
-
});
|
|
320
|
-
const imported = (input) => {
|
|
321
|
-
const id = fileId(input);
|
|
322
|
-
const profile = activeProfile(env, selected());
|
|
323
|
-
const state = readState(env, selected(), profile.principal);
|
|
324
|
-
if (!state.documents[id])
|
|
325
|
-
throw new CliError("not_found", "this source is not imported in the selected profile", {
|
|
326
|
-
reason: "document.not-imported",
|
|
327
|
-
});
|
|
328
|
-
const doc = state.documents[id];
|
|
329
|
-
return { id, ref: doc.reference, hash: doc.hash, snapshot: readSnapshot(env, selected(), id) };
|
|
153
|
+
const context = {
|
|
154
|
+
env,
|
|
155
|
+
streams,
|
|
156
|
+
options,
|
|
157
|
+
selected,
|
|
158
|
+
signal: () => activeSignal,
|
|
159
|
+
output,
|
|
160
|
+
print,
|
|
161
|
+
source,
|
|
162
|
+
withIndex,
|
|
163
|
+
policyFlags,
|
|
330
164
|
};
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
metadata: metadataJson(snapshot.metadata),
|
|
338
|
-
text: snapshot.text,
|
|
339
|
-
tabs: snapshot.tabs,
|
|
340
|
-
...(snapshot.notRead ? { not_read: snapshot.notRead } : {}),
|
|
341
|
-
}));
|
|
342
|
-
});
|
|
343
|
-
examples(documents
|
|
344
|
-
.command("export")
|
|
345
|
-
.argument("<document>", documentArgument)
|
|
346
|
-
.description("write an imported document to a new Markdown file"), ["drive personal documents export DOC_ID --output notes.md"])
|
|
347
|
-
.requiredOption("--output <file>", "path of the Markdown file to write; an existing file is refused")
|
|
348
|
-
.action((input, args) => {
|
|
349
|
-
const { id, snapshot } = imported(input);
|
|
350
|
-
try {
|
|
351
|
-
writeFileSync(args.output, snapshot.markdown, { flag: "wx", mode: 0o600 });
|
|
352
|
-
}
|
|
353
|
-
catch (error) {
|
|
354
|
-
if (error.code === "EEXIST")
|
|
355
|
-
throw new CliError("validation_error", "the output file already exists; choose another --output", {
|
|
356
|
-
reason: "export.exists",
|
|
357
|
-
param: "--output",
|
|
358
|
-
});
|
|
359
|
-
throw error;
|
|
360
|
-
}
|
|
361
|
-
output(itemOf({ id, path: args.output, format: "markdown" }));
|
|
362
|
-
});
|
|
363
|
-
examples(program
|
|
364
|
-
.command("search")
|
|
365
|
-
.argument("<query>", "words to look for")
|
|
366
|
-
.description("search imported documents by words, and by meaning when indexed"), ["drive personal search budget", `drive personal search "quarterly plan" --exact --limit 5`])
|
|
367
|
-
.option("--limit <n>", "how many results to return", positive, 20)
|
|
368
|
-
.option("--page <n>", "which page to return", positive, 1)
|
|
369
|
-
.option("--exact", "match the words exactly: no word forms, no meaning")
|
|
370
|
-
.action(async (query, { page, ...args }) => output(itemOf(await withIndex((index) => index.search(activeProfile(env, selected()).principal, query, {
|
|
371
|
-
...args,
|
|
372
|
-
offset: (page - 1) * args.limit,
|
|
373
|
-
exact: args.exact ?? false,
|
|
374
|
-
...(pictureSearch(env, selected()) ? { pictures: true } : {}),
|
|
375
|
-
})))));
|
|
165
|
+
const imported = importedDocument(context);
|
|
166
|
+
installSessionCommands(program, context);
|
|
167
|
+
installFolderCommands(program, context);
|
|
168
|
+
installSyncCommands(program, context);
|
|
169
|
+
installDocumentCommands(program, context);
|
|
170
|
+
installSearchCommand(program, context);
|
|
376
171
|
installCatalogCommands(program, { env, selected, output, positive, offset, withIndex });
|
|
377
172
|
installKnowledgeCommands(program, {
|
|
378
173
|
document: imported,
|
|
379
174
|
output,
|
|
380
175
|
positive,
|
|
381
176
|
complete: () => options.complete ?? memoryModel(env, selected(), activeSignal),
|
|
382
|
-
run: async (input, fn) => {
|
|
177
|
+
run: async (input, fn) => withIndex(async (index) => {
|
|
383
178
|
const lock = await lockProfile(env, selected());
|
|
384
179
|
try {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return result;
|
|
395
|
-
});
|
|
180
|
+
lock.assertHeld();
|
|
181
|
+
if (!index.knowledge)
|
|
182
|
+
throw new CliError("configuration_error", "index has no shared knowledge adapter");
|
|
183
|
+
const principal = activeProfile(env, selected()).principal;
|
|
184
|
+
const found = await index.catalog?.get(principal, fileId(input));
|
|
185
|
+
const ref = found?.ref ?? imported(input).ref;
|
|
186
|
+
const result = await fn(index.knowledge, principal, ref);
|
|
187
|
+
lock.assertHeld();
|
|
188
|
+
return result;
|
|
396
189
|
}
|
|
397
190
|
finally {
|
|
398
191
|
await lock.release();
|
|
399
192
|
}
|
|
400
|
-
},
|
|
193
|
+
}),
|
|
401
194
|
});
|
|
402
195
|
installRemoteCommands(program, {
|
|
403
196
|
output,
|
|
@@ -418,12 +211,10 @@ export const createProgram = (options = {}) => {
|
|
|
418
211
|
const descriptor = apiMethod(method);
|
|
419
212
|
validateRequest(method, params, body);
|
|
420
213
|
const flags = policyFlags();
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
reason: "offline.refused",
|
|
426
|
-
});
|
|
214
|
+
// An api command's own method was checked before its parameters were read; checking again changes nothing.
|
|
215
|
+
if (method !== checkedMethod)
|
|
216
|
+
for (const key of methodPolicyKeys(method))
|
|
217
|
+
await checkPolicy(env, selected(), key, descriptor.mutates, flags, askOnce);
|
|
427
218
|
if (flags.dryRun) {
|
|
428
219
|
output(itemOf({
|
|
429
220
|
dry_run: true,
|
|
@@ -473,178 +264,16 @@ export const createProgram = (options = {}) => {
|
|
|
473
264
|
return guardedGoogleWrite(env, selected(), `api.${method}`, act, flags, askOnce);
|
|
474
265
|
},
|
|
475
266
|
});
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
"drive personal config set read_only true",
|
|
482
|
-
`drive personal config set permissions.files '"readonly"' --defaults`,
|
|
483
|
-
])
|
|
484
|
-
.argument("<key>", "read_only, max_text_chars, picture_search, shared AI settings, permissions.<key> or models.<purpose>.<field>")
|
|
485
|
-
.argument("<json-value>", "the new value, as JSON")
|
|
486
|
-
.option("--defaults", "change the defaults instead of this profile")
|
|
487
|
-
.action((rawKey, raw, args) => {
|
|
488
|
-
const key = configKey(rawKey);
|
|
489
|
-
const value = readConfig(env);
|
|
490
|
-
if (args.defaults)
|
|
491
|
-
value.defaults ??= {};
|
|
492
|
-
const target = args.defaults
|
|
493
|
-
? value.defaults
|
|
494
|
-
: activeProfile(env, selected());
|
|
495
|
-
if (!args.defaults)
|
|
496
|
-
value.profiles[selected()] = target;
|
|
497
|
-
const [head, ...parts] = key.split(".");
|
|
498
|
-
if (!SETTING_NAMES.has(head ?? "") ||
|
|
499
|
-
parts.some((part) => ["__proto__", "prototype", "constructor"].includes(part)))
|
|
500
|
-
throw new CliError("validation_error", "config set accepts profile settings and shared AI settings");
|
|
501
|
-
let parsed;
|
|
502
|
-
try {
|
|
503
|
-
parsed = JSON.parse(raw);
|
|
504
|
-
}
|
|
505
|
-
catch {
|
|
506
|
-
throw new CliError("validation_error", "setting value must be JSON");
|
|
507
|
-
}
|
|
508
|
-
if (head === "picture_search") {
|
|
509
|
-
if (args.defaults || parts.length || typeof parsed !== "boolean")
|
|
510
|
-
throw new CliError("validation_error", "picture_search is true or false, set per profile");
|
|
511
|
-
target.picture_search = parsed;
|
|
512
|
-
}
|
|
513
|
-
else if (head === "max_text_chars" && !parts.length)
|
|
514
|
-
target.max_text_chars = checkTextLimit(parsed);
|
|
515
|
-
else if (!parts.length)
|
|
516
|
-
Object.assign(target, { [key]: parsed });
|
|
517
|
-
else if (head === "permissions") {
|
|
518
|
-
target.permissions ??= {};
|
|
519
|
-
Object.assign(target.permissions, { [parts.join(".")]: parsed });
|
|
520
|
-
}
|
|
521
|
-
else if (head === "models" && parts.length === 2 && parts[0] && parts[1]) {
|
|
522
|
-
target.models ??= {};
|
|
523
|
-
target.models[parts[0]] ??= {};
|
|
524
|
-
Object.assign(target.models[parts[0]], { [parts[1]]: parsed });
|
|
525
|
-
}
|
|
526
|
-
else
|
|
527
|
-
throw new CliError("validation_error", "unknown setting path");
|
|
528
|
-
writeConfig(env, value);
|
|
529
|
-
output(itemOf({ key, updated: true, scope: args.defaults ? "defaults" : selected() }));
|
|
530
|
-
});
|
|
531
|
-
examples(program.command("complete").description("print a shell completion script, or complete words"), [
|
|
532
|
-
"drive complete zsh",
|
|
533
|
-
])
|
|
534
|
-
.argument("[words...]", "a shell name, or after -- the words typed so far")
|
|
535
|
-
.allowUnknownOption()
|
|
536
|
-
.helpOption(false)
|
|
537
|
-
.action(async (words) => {
|
|
538
|
-
if (!program.rawArgs.includes("--")) {
|
|
539
|
-
if (!["bash", "zsh", "fish", "powershell"].includes(words[0] ?? ""))
|
|
540
|
-
throw new CliError("validation_error", "choose a supported shell");
|
|
541
|
-
const { script } = await import("@bomb.sh/tab");
|
|
542
|
-
script(words[0], "drive", "drive");
|
|
543
|
-
}
|
|
544
|
-
else
|
|
545
|
-
streams.data(formatSuggestions(suggest({ commands: describeDriveProgram(program), globalOptions: describeOptions(program), words })));
|
|
546
|
-
});
|
|
547
|
-
examples(program
|
|
548
|
-
.command("models")
|
|
549
|
-
.description("choose the model that proposes memories; keys stay out of config")
|
|
550
|
-
.command("set")
|
|
551
|
-
.description("set the provider and model for one use"), ["drive personal models set --provider openai --model MODEL_ID", "drive personal models set --provider off"])
|
|
552
|
-
.option("--purpose <purpose>", "what the model is used for", "memories")
|
|
553
|
-
.addOption(new Option("--provider <provider>", "who runs the model, or off to use none")
|
|
554
|
-
.choices(["openai", "anthropic", "off"])
|
|
555
|
-
.makeOptionMandatory())
|
|
556
|
-
.option("--model <model>", "model id at the provider")
|
|
557
|
-
.option("--base-url <url>", "HTTP or HTTPS address of the model API, without credentials in it")
|
|
558
|
-
.action((args) => {
|
|
559
|
-
const config = readConfig(env);
|
|
560
|
-
config.defaults ??= {};
|
|
561
|
-
config.defaults.models ??= {};
|
|
562
|
-
config.defaults.models[args.purpose] = {
|
|
563
|
-
provider: args.provider,
|
|
564
|
-
...(args.model ? { model: args.model } : {}),
|
|
565
|
-
...(args.baseUrl ? { base_url: args.baseUrl } : {}),
|
|
566
|
-
};
|
|
567
|
-
writeConfig(env, config);
|
|
568
|
-
output(itemOf({ purpose: args.purpose, configured: true }));
|
|
569
|
-
});
|
|
570
|
-
const mcp = examples(program.command("mcp").description("serve drive's commands to an agent over MCP on stdio"), [
|
|
571
|
-
"drive personal mcp",
|
|
572
|
-
"drive personal mcp --tools",
|
|
573
|
-
]);
|
|
574
|
-
mcp
|
|
575
|
-
.option("--tools", "list the tools offered instead of starting the server")
|
|
576
|
-
.action(async (args) => {
|
|
577
|
-
const { mcpTools, serveMcp } = await import("./mcp.js");
|
|
578
|
-
if (args.tools)
|
|
579
|
-
output(allOf(mcpTools(createProgram, { ...options, env }, selected(), policyFlags()).map(({ name, schema, command }) => ({ name, input_schema: schema, effects: command.effects }))));
|
|
580
|
-
else
|
|
581
|
-
await serveMcp(createProgram, { ...options, env, ...(activeSignal ? { signal: activeSignal } : {}) }, selected(), policyFlags());
|
|
582
|
-
});
|
|
583
|
-
examples(program.command("commands").description("print the command tree and its options as data"), [
|
|
584
|
-
"drive commands --json",
|
|
585
|
-
"drive commands files copy --json",
|
|
586
|
-
])
|
|
587
|
-
.argument("[path...]", "only this command or group, as words")
|
|
588
|
-
.action((path) => print((renderer) => renderer.result(commandsDocument(program, path))));
|
|
267
|
+
installConfigCommands(program, context);
|
|
268
|
+
installCompleteCommand(program, context);
|
|
269
|
+
installModelCommands(program, context);
|
|
270
|
+
installMcpCommand(program, context, createProgram);
|
|
271
|
+
installCommandsCommand(program, context);
|
|
589
272
|
for (const info of flatten(describeDriveProgram(program))) {
|
|
590
273
|
let command = program;
|
|
591
274
|
for (const part of info.path)
|
|
592
275
|
command = command.commands.find((child) => child.name() === part) ?? command;
|
|
593
|
-
|
|
594
|
-
"session start",
|
|
595
|
-
"session end",
|
|
596
|
-
"folders add",
|
|
597
|
-
"folders remove",
|
|
598
|
-
"sync",
|
|
599
|
-
"documents export",
|
|
600
|
-
"tags add",
|
|
601
|
-
"tags remove",
|
|
602
|
-
"relationships add",
|
|
603
|
-
"relationships remove",
|
|
604
|
-
"notes add",
|
|
605
|
-
"notes edit",
|
|
606
|
-
"notes remove",
|
|
607
|
-
"memories add",
|
|
608
|
-
"memories confirm",
|
|
609
|
-
"memories stale",
|
|
610
|
-
"memories extract",
|
|
611
|
-
"facts add",
|
|
612
|
-
"facts confirm",
|
|
613
|
-
"facts reverse",
|
|
614
|
-
"identities link",
|
|
615
|
-
"models set",
|
|
616
|
-
"config set",
|
|
617
|
-
];
|
|
618
|
-
const remoteWrites = [
|
|
619
|
-
"api call",
|
|
620
|
-
"files copy",
|
|
621
|
-
"files move",
|
|
622
|
-
"files update",
|
|
623
|
-
"files trash",
|
|
624
|
-
"files restore",
|
|
625
|
-
"files mkdir",
|
|
626
|
-
"files upload",
|
|
627
|
-
"documents create",
|
|
628
|
-
"documents update",
|
|
629
|
-
];
|
|
630
|
-
const name = info.path.join(" ");
|
|
631
|
-
const remote = remoteWrites.includes(name) || name === "session start" || name === "memories extract"
|
|
632
|
-
? "write"
|
|
633
|
-
: NETWORK_GROUPS.includes(info.path[0] ?? "") || NETWORK_COMMANDS.includes(name)
|
|
634
|
-
? "read"
|
|
635
|
-
: "none";
|
|
636
|
-
annotate(command, {
|
|
637
|
-
origin: metaOf(command).origin ?? "handwritten",
|
|
638
|
-
effects: metaOf(command).effects ?? {
|
|
639
|
-
remote,
|
|
640
|
-
local: localWrites.includes(name)
|
|
641
|
-
? "write"
|
|
642
|
-
: remote !== "none" || ACCOUNT_FREE_COMMANDS.has(name)
|
|
643
|
-
? "none"
|
|
644
|
-
: "read",
|
|
645
|
-
},
|
|
646
|
-
state: "beta",
|
|
647
|
-
});
|
|
276
|
+
annotate(command, { origin: metaOf(command).origin ?? "handwritten", state: "beta" });
|
|
648
277
|
}
|
|
649
278
|
const pathOf = (command) => {
|
|
650
279
|
const path = [];
|
|
@@ -689,14 +318,14 @@ export const createProgram = (options = {}) => {
|
|
|
689
318
|
const key = method ? `api.${method}` : path.join(".");
|
|
690
319
|
for (const permission of method ? methodPolicyKeys(method) : [key])
|
|
691
320
|
await checkPolicy(env, selected(), permission, mutates, flags, askOnce);
|
|
321
|
+
checkedMethod = method;
|
|
692
322
|
if (flags.offline && (method || metadata.effects?.remote !== "none"))
|
|
693
323
|
throw new CliError("permission_error", "this command may require network access; --offline was requested", {
|
|
694
324
|
reason: "offline.refused",
|
|
695
325
|
});
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
!["documents get", "documents create", "documents update", "documents history"].includes(words)) {
|
|
326
|
+
// A command that only calls Google previews the request itself, once its parameters are known.
|
|
327
|
+
const googleOnly = metadata.effects?.local === "none" && metadata.effects.remote !== "none";
|
|
328
|
+
if (flags.dryRun && !googleOnly) {
|
|
700
329
|
output(itemOf({ dry_run: true, command: words, effects: metadata.effects }));
|
|
701
330
|
throw new Preview();
|
|
702
331
|
}
|