@persistmemory/cli 0.4.0 → 0.6.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/bin.js +307 -64
- package/dist/bin.js.map +4 -4
- package/dist/index.js +307 -64
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1404,7 +1404,7 @@ function shortDate(iso) {
|
|
|
1404
1404
|
}
|
|
1405
1405
|
|
|
1406
1406
|
// src/help.ts
|
|
1407
|
-
var VERSION = true ? "0.
|
|
1407
|
+
var VERSION = true ? "0.6.0" : versionFromManifest();
|
|
1408
1408
|
var PACKAGE = "@persistmemory/cli";
|
|
1409
1409
|
var HELP = `
|
|
1410
1410
|
pm \u2014 PersistMemory from your terminal
|
|
@@ -1446,14 +1446,18 @@ var HELP = `
|
|
|
1446
1446
|
remember - capture whatever is piped in
|
|
1447
1447
|
remember --file <path> capture a file's contents
|
|
1448
1448
|
|
|
1449
|
-
agent answer file requests from this
|
|
1450
|
-
Reads
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1449
|
+
agent answer file and command requests from this
|
|
1450
|
+
machine. Reads anywhere on it \u2014 you
|
|
1451
|
+
approve every request first, and see the
|
|
1452
|
+
exact path or command before you do.
|
|
1453
|
+
Stays in the foreground; background it
|
|
1454
|
+
and stop it later with:
|
|
1454
1455
|
nohup pm agent > ~/agent.log 2>&1 &
|
|
1455
1456
|
pkill -f "pm agent"
|
|
1456
|
-
agent --root <dir> [--root ...]
|
|
1457
|
+
agent --root <dir> [--root ...] narrow it to these folders, for this run
|
|
1458
|
+
|
|
1459
|
+
A command that reaches the network, or that runs a language, is refused by
|
|
1460
|
+
this machine whatever anybody approves.
|
|
1457
1461
|
|
|
1458
1462
|
search <query> search your memory
|
|
1459
1463
|
list memories the most recent memories
|
|
@@ -2067,8 +2071,8 @@ async function readStdin() {
|
|
|
2067
2071
|
// src/commands/agent.ts
|
|
2068
2072
|
import { hostname } from "node:os";
|
|
2069
2073
|
import { homedir as homedir2 } from "node:os";
|
|
2070
|
-
import { basename, join as
|
|
2071
|
-
import { existsSync as
|
|
2074
|
+
import { basename, join as join5, resolve as resolve3 } from "node:path";
|
|
2075
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync, statSync as statSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2072
2076
|
|
|
2073
2077
|
// src/files.ts
|
|
2074
2078
|
import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync, statSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
@@ -2176,6 +2180,250 @@ ${head.join("\n")}`;
|
|
|
2176
2180
|
].join("\n");
|
|
2177
2181
|
}
|
|
2178
2182
|
|
|
2183
|
+
// src/commands/run-command.ts
|
|
2184
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2185
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
|
|
2186
|
+
import { isAbsolute as isAbsolute2, join as join4, resolve as resolvePath } from "node:path";
|
|
2187
|
+
var CLASSES = new Map([
|
|
2188
|
+
// Reads. Report on the filesystem and change nothing.
|
|
2189
|
+
...[
|
|
2190
|
+
"ls",
|
|
2191
|
+
"cat",
|
|
2192
|
+
"head",
|
|
2193
|
+
"tail",
|
|
2194
|
+
"wc",
|
|
2195
|
+
"file",
|
|
2196
|
+
"stat",
|
|
2197
|
+
"du",
|
|
2198
|
+
"df",
|
|
2199
|
+
"find",
|
|
2200
|
+
"grep",
|
|
2201
|
+
"rg",
|
|
2202
|
+
"fd",
|
|
2203
|
+
"tree",
|
|
2204
|
+
"pwd",
|
|
2205
|
+
"date",
|
|
2206
|
+
"uname",
|
|
2207
|
+
"which",
|
|
2208
|
+
"echo",
|
|
2209
|
+
"sort",
|
|
2210
|
+
"uniq",
|
|
2211
|
+
"diff",
|
|
2212
|
+
"basename",
|
|
2213
|
+
"dirname",
|
|
2214
|
+
"realpath",
|
|
2215
|
+
"ps",
|
|
2216
|
+
"env"
|
|
2217
|
+
].map((name) => [name, "read"]),
|
|
2218
|
+
// Writes. Recoverable or not, they change the machine.
|
|
2219
|
+
...[
|
|
2220
|
+
"rm",
|
|
2221
|
+
"mv",
|
|
2222
|
+
"cp",
|
|
2223
|
+
"mkdir",
|
|
2224
|
+
"rmdir",
|
|
2225
|
+
"touch",
|
|
2226
|
+
"chmod",
|
|
2227
|
+
"chown",
|
|
2228
|
+
"ln",
|
|
2229
|
+
"tee",
|
|
2230
|
+
"truncate",
|
|
2231
|
+
"dd",
|
|
2232
|
+
"kill",
|
|
2233
|
+
"killall",
|
|
2234
|
+
"make",
|
|
2235
|
+
"cargo",
|
|
2236
|
+
"swift",
|
|
2237
|
+
"xcodebuild",
|
|
2238
|
+
"gradle",
|
|
2239
|
+
"docker",
|
|
2240
|
+
"brew",
|
|
2241
|
+
"apt",
|
|
2242
|
+
"yum"
|
|
2243
|
+
].map((name) => [name, "write"]),
|
|
2244
|
+
// A way out. See the trifecta note above.
|
|
2245
|
+
...["curl", "wget", "nc", "ncat", "telnet", "ssh", "scp", "rsync", "ftp", "http", "httpie"].map((name) => [name, "network"]),
|
|
2246
|
+
// Every command at once, wearing one name.
|
|
2247
|
+
...["sh", "bash", "zsh", "fish", "python", "python3", "node", "ruby", "perl", "osascript", "eval"].map((name) => [name, "interpreter"])
|
|
2248
|
+
]);
|
|
2249
|
+
var SUBCOMMAND_READS = /* @__PURE__ */ new Map([
|
|
2250
|
+
[
|
|
2251
|
+
"git",
|
|
2252
|
+
/* @__PURE__ */ new Set([
|
|
2253
|
+
"status",
|
|
2254
|
+
"log",
|
|
2255
|
+
"diff",
|
|
2256
|
+
"show",
|
|
2257
|
+
"branch",
|
|
2258
|
+
"remote",
|
|
2259
|
+
"describe",
|
|
2260
|
+
"blame",
|
|
2261
|
+
"shortlog",
|
|
2262
|
+
"ls-files",
|
|
2263
|
+
"rev-parse",
|
|
2264
|
+
"config"
|
|
2265
|
+
])
|
|
2266
|
+
],
|
|
2267
|
+
["npm", /* @__PURE__ */ new Set(["ls", "list", "view", "outdated", "why", "config"])],
|
|
2268
|
+
["yarn", /* @__PURE__ */ new Set(["list", "why", "info", "config"])],
|
|
2269
|
+
["docker", /* @__PURE__ */ new Set(["ps", "images", "logs", "inspect"])]
|
|
2270
|
+
]);
|
|
2271
|
+
var MAX_OUTPUT_BYTES = 256 * 1024;
|
|
2272
|
+
var TIMEOUT_MS = 2e4;
|
|
2273
|
+
function describe2(argv) {
|
|
2274
|
+
return argv.join(" ");
|
|
2275
|
+
}
|
|
2276
|
+
function programOf(argv) {
|
|
2277
|
+
const first = argv[0] ?? "";
|
|
2278
|
+
return first.split("/").pop() ?? first;
|
|
2279
|
+
}
|
|
2280
|
+
function classify(argv, policy) {
|
|
2281
|
+
const program = programOf(argv);
|
|
2282
|
+
if (policy.allow.includes(program)) return "read";
|
|
2283
|
+
const reads = SUBCOMMAND_READS.get(program);
|
|
2284
|
+
if (reads) {
|
|
2285
|
+
const sub = argv.slice(1).find((one) => !one.startsWith("-"));
|
|
2286
|
+
return sub && reads.has(sub) ? "read" : "write";
|
|
2287
|
+
}
|
|
2288
|
+
return CLASSES.get(program) ?? "unknown";
|
|
2289
|
+
}
|
|
2290
|
+
function judge(argv, policy) {
|
|
2291
|
+
const program = programOf(argv);
|
|
2292
|
+
if (!argv[0]) {
|
|
2293
|
+
return { commandClass: "unknown", automatic: false, refusal: "No command was given.", reason: "empty" };
|
|
2294
|
+
}
|
|
2295
|
+
if (policy.deny.includes(program)) {
|
|
2296
|
+
return {
|
|
2297
|
+
commandClass: "unknown",
|
|
2298
|
+
automatic: false,
|
|
2299
|
+
refusal: `This machine refuses "${program}".`,
|
|
2300
|
+
reason: "on this machine's deny list"
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
const commandClass = classify(argv, policy);
|
|
2304
|
+
if (commandClass === "network") {
|
|
2305
|
+
return {
|
|
2306
|
+
commandClass,
|
|
2307
|
+
automatic: false,
|
|
2308
|
+
refusal: `"${program}" can reach the network. This machine will not run it, and no approval enables it: a command that reads private files and can also send them is the one combination nobody can review by looking at it.`,
|
|
2309
|
+
reason: "reads private data and has a way out"
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
if (commandClass === "interpreter") {
|
|
2313
|
+
return {
|
|
2314
|
+
commandClass,
|
|
2315
|
+
automatic: false,
|
|
2316
|
+
refusal: `"${program}" runs a language, which is every command at once. Ask for the specific command instead.`,
|
|
2317
|
+
reason: "an interpreter is not one command"
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2320
|
+
const outside = pathOutsideRoots(argv, policy.roots);
|
|
2321
|
+
if (outside) {
|
|
2322
|
+
return {
|
|
2323
|
+
commandClass,
|
|
2324
|
+
automatic: false,
|
|
2325
|
+
refusal: `${outside} is outside the folders this machine may read.`,
|
|
2326
|
+
reason: "outside the roots"
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
if (policy.mode === "plan") {
|
|
2330
|
+
return {
|
|
2331
|
+
commandClass,
|
|
2332
|
+
automatic: false,
|
|
2333
|
+
reason: "this machine is in plan mode and runs nothing"
|
|
2334
|
+
};
|
|
2335
|
+
}
|
|
2336
|
+
return {
|
|
2337
|
+
commandClass,
|
|
2338
|
+
automatic: policy.mode === "auto-read" && commandClass === "read",
|
|
2339
|
+
reason: commandClass === "read" ? "reads and changes nothing" : commandClass === "write" ? "changes this machine" : "not a command this machine recognises"
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
function pathOutsideRoots(argv, roots) {
|
|
2343
|
+
for (const argument of argv.slice(1)) {
|
|
2344
|
+
if (argument.startsWith("-")) continue;
|
|
2345
|
+
if (!argument.startsWith("/") && !argument.startsWith("~") && !argument.startsWith(".") && !argument.includes("/")) {
|
|
2346
|
+
continue;
|
|
2347
|
+
}
|
|
2348
|
+
const resolved = expand(argument, roots[0] ?? process.cwd());
|
|
2349
|
+
if (!roots.some((root) => resolved === root || resolved.startsWith(`${root}/`))) {
|
|
2350
|
+
return argument;
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
return void 0;
|
|
2354
|
+
}
|
|
2355
|
+
function expand(argument, base) {
|
|
2356
|
+
const home = process.env["HOME"] ?? "";
|
|
2357
|
+
const withHome = argument.startsWith("~") ? join4(home, argument.slice(1)) : argument;
|
|
2358
|
+
return isAbsolute2(withHome) ? resolvePath(withHome) : resolvePath(base, withHome);
|
|
2359
|
+
}
|
|
2360
|
+
async function runCommand(argv, policy) {
|
|
2361
|
+
const verdict = judge(argv, policy);
|
|
2362
|
+
if (verdict.refusal) return { ok: false, text: verdict.refusal };
|
|
2363
|
+
if (policy.mode === "plan") {
|
|
2364
|
+
return { ok: false, text: `Plan mode: this machine did not run \`${describe2(argv)}\`.` };
|
|
2365
|
+
}
|
|
2366
|
+
const cwd = policy.roots[0];
|
|
2367
|
+
if (!cwd) return { ok: false, text: "This machine has no folders it may read." };
|
|
2368
|
+
try {
|
|
2369
|
+
if (!statSync2(cwd).isDirectory()) return { ok: false, text: `${cwd} is not a folder.` };
|
|
2370
|
+
} catch {
|
|
2371
|
+
return { ok: false, text: `${cwd} does not exist.` };
|
|
2372
|
+
}
|
|
2373
|
+
return new Promise((resolve8) => {
|
|
2374
|
+
const child = spawn2(argv[0], argv.slice(1), {
|
|
2375
|
+
cwd,
|
|
2376
|
+
// NO shell. With one, every character a model can produce is a character
|
|
2377
|
+
// the shell can act on, and the argument list stops meaning anything.
|
|
2378
|
+
shell: false,
|
|
2379
|
+
/*
|
|
2380
|
+
A bare environment.
|
|
2381
|
+
|
|
2382
|
+
This process holds the credential that authorises the machine, and
|
|
2383
|
+
handing it to a subprocess a model chose would mean `env` — or anything
|
|
2384
|
+
that prints its environment — returning that key.
|
|
2385
|
+
*/
|
|
2386
|
+
env: {
|
|
2387
|
+
PATH: process.env["PATH"] ?? "/usr/bin:/bin",
|
|
2388
|
+
HOME: process.env["HOME"] ?? "",
|
|
2389
|
+
LANG: process.env["LANG"] ?? "C"
|
|
2390
|
+
}
|
|
2391
|
+
});
|
|
2392
|
+
let output = "";
|
|
2393
|
+
let truncated = false;
|
|
2394
|
+
const collect = (chunk) => {
|
|
2395
|
+
if (truncated) return;
|
|
2396
|
+
output += chunk.toString("utf8");
|
|
2397
|
+
if (output.length > MAX_OUTPUT_BYTES) {
|
|
2398
|
+
output = output.slice(0, MAX_OUTPUT_BYTES);
|
|
2399
|
+
truncated = true;
|
|
2400
|
+
child.kill("SIGKILL");
|
|
2401
|
+
}
|
|
2402
|
+
};
|
|
2403
|
+
child.stdout.on("data", collect);
|
|
2404
|
+
child.stderr.on("data", collect);
|
|
2405
|
+
const timer = setTimeout(() => child.kill("SIGKILL"), TIMEOUT_MS);
|
|
2406
|
+
child.on("error", (error) => {
|
|
2407
|
+
clearTimeout(timer);
|
|
2408
|
+
resolve8({ ok: false, text: `Could not run it: ${error.message}` });
|
|
2409
|
+
});
|
|
2410
|
+
child.on("close", (code) => {
|
|
2411
|
+
clearTimeout(timer);
|
|
2412
|
+
const notes = [
|
|
2413
|
+
truncated ? `
|
|
2414
|
+
|
|
2415
|
+
[output cut off at ${MAX_OUTPUT_BYTES} bytes]` : "",
|
|
2416
|
+
code !== 0 && code !== null ? `
|
|
2417
|
+
|
|
2418
|
+
[exit code ${code}]` : ""
|
|
2419
|
+
].join("");
|
|
2420
|
+
resolve8({ ok: code === 0, text: `$ ${describe2(argv)}
|
|
2421
|
+
|
|
2422
|
+
${output}${notes}` });
|
|
2423
|
+
});
|
|
2424
|
+
});
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2179
2427
|
// src/commands/agent.ts
|
|
2180
2428
|
async function said(response, fallback) {
|
|
2181
2429
|
const body = await response.json().catch(() => void 0);
|
|
@@ -2199,18 +2447,32 @@ function locate(roots, requested) {
|
|
|
2199
2447
|
throw new Error(`${requested} is not inside any allowed folder (${roots.join(", ")})`);
|
|
2200
2448
|
}
|
|
2201
2449
|
function uncontested(target) {
|
|
2202
|
-
if (!
|
|
2450
|
+
if (!existsSync5(target)) return target;
|
|
2203
2451
|
const dot = target.lastIndexOf(".");
|
|
2204
2452
|
const stem = dot > target.lastIndexOf("/") && dot !== -1 ? target.slice(0, dot) : target;
|
|
2205
2453
|
const extension = stem === target ? "" : target.slice(dot);
|
|
2206
2454
|
for (let n = 1; n < 1e3; n += 1) {
|
|
2207
2455
|
const candidate = `${stem} (${n})${extension}`;
|
|
2208
|
-
if (!
|
|
2456
|
+
if (!existsSync5(candidate)) return candidate;
|
|
2209
2457
|
}
|
|
2210
2458
|
throw new Error(`${target} and a thousand names beside it are taken.`);
|
|
2211
2459
|
}
|
|
2212
2460
|
async function answer(context, apiUrl, token, roots, request) {
|
|
2213
2461
|
let located;
|
|
2462
|
+
if (request.kind === "run_command") {
|
|
2463
|
+
if (!request.argv || request.argv.length === 0) {
|
|
2464
|
+
return { ok: false, error: "No command was given." };
|
|
2465
|
+
}
|
|
2466
|
+
const policy = { mode: "ask", allow: [], deny: [], roots };
|
|
2467
|
+
const outcome = await runCommand(request.argv, policy);
|
|
2468
|
+
if (!outcome.ok) return { ok: false, error: outcome.text };
|
|
2469
|
+
return upload(
|
|
2470
|
+
apiUrl,
|
|
2471
|
+
token,
|
|
2472
|
+
`${(request.argv[0] ?? "output").split("/").pop()}.txt`,
|
|
2473
|
+
Buffer.from(outcome.text, "utf8")
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2214
2476
|
try {
|
|
2215
2477
|
located = locate(roots, request.path);
|
|
2216
2478
|
} catch (error) {
|
|
@@ -2231,10 +2493,10 @@ async function answer(context, apiUrl, token, roots, request) {
|
|
|
2231
2493
|
}
|
|
2232
2494
|
try {
|
|
2233
2495
|
let target = located;
|
|
2234
|
-
if (
|
|
2496
|
+
if (existsSync5(located) && statSync3(located).isDirectory()) {
|
|
2235
2497
|
const disposition = fetched.headers.get("content-disposition") ?? "";
|
|
2236
2498
|
const named = /filename="([^"]+)"/.exec(disposition)?.[1];
|
|
2237
|
-
target =
|
|
2499
|
+
target = join5(located, basename(named ?? "file"));
|
|
2238
2500
|
}
|
|
2239
2501
|
target = uncontested(target);
|
|
2240
2502
|
writeFileSync4(target, downloaded, { flag: "wx" });
|
|
@@ -2257,12 +2519,12 @@ ${downloaded.length} bytes
|
|
|
2257
2519
|
let filename = request.path.split("/").pop() ?? "file";
|
|
2258
2520
|
if (request.kind === "list_dir") {
|
|
2259
2521
|
try {
|
|
2260
|
-
const stats =
|
|
2522
|
+
const stats = statSync3(located);
|
|
2261
2523
|
if (!stats.isDirectory()) return { ok: false, error: `${request.path} is not a folder.` };
|
|
2262
2524
|
const entries = readdirSync(located, { withFileTypes: true }).filter((entry) => !entry.name.startsWith(".")).slice(0, MAX_LISTED).map((entry) => {
|
|
2263
2525
|
if (entry.isDirectory()) return `${entry.name}/`;
|
|
2264
2526
|
try {
|
|
2265
|
-
return `${entry.name} ${sizeOf(
|
|
2527
|
+
return `${entry.name} ${sizeOf(join5(located, entry.name))}`;
|
|
2266
2528
|
} catch {
|
|
2267
2529
|
return entry.name;
|
|
2268
2530
|
}
|
|
@@ -2280,9 +2542,9 @@ ${listing}
|
|
|
2280
2542
|
}
|
|
2281
2543
|
}
|
|
2282
2544
|
try {
|
|
2283
|
-
const stats =
|
|
2545
|
+
const stats = statSync3(located);
|
|
2284
2546
|
if (!stats.isFile()) return { ok: false, error: `${request.path} is not a file.` };
|
|
2285
|
-
bytes =
|
|
2547
|
+
bytes = readFileSync5(located);
|
|
2286
2548
|
} catch (error) {
|
|
2287
2549
|
return { ok: false, error: error instanceof Error ? error.message : "could not read it" };
|
|
2288
2550
|
}
|
|
@@ -2322,18 +2584,9 @@ async function upload(apiUrl, token, filename, bytes) {
|
|
|
2322
2584
|
if (!stored.attachToken) return { ok: false, error: "the upload returned no reference" };
|
|
2323
2585
|
return { ok: true, attachToken: stored.attachToken, bytes: bytes.length };
|
|
2324
2586
|
}
|
|
2325
|
-
function defaultRoots(home = homedir2(), isDirectory = (path) => {
|
|
2326
|
-
try {
|
|
2327
|
-
return statSync2(path).isDirectory();
|
|
2328
|
-
} catch {
|
|
2329
|
-
return false;
|
|
2330
|
-
}
|
|
2331
|
-
}) {
|
|
2332
|
-
return ["Desktop", "Documents", "Downloads"].map((name) => join4(home, name)).filter(isDirectory);
|
|
2333
|
-
}
|
|
2334
2587
|
var MAX_LISTED = 200;
|
|
2335
2588
|
function sizeOf(path) {
|
|
2336
|
-
const size =
|
|
2589
|
+
const size = statSync3(path).size;
|
|
2337
2590
|
if (size < 1024) return `${size} B`;
|
|
2338
2591
|
if (size < 1024 * 1024) return `${Math.round(size / 1024)} KB`;
|
|
2339
2592
|
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
@@ -2347,24 +2600,14 @@ async function agentCommand(context) {
|
|
|
2347
2600
|
const roots = (listFlag(context.args, "root") ?? []).map(
|
|
2348
2601
|
(one) => resolve3(one.startsWith("~") ? resolve3(homedir2(), one.slice(1).replace(/^[/\\]/, "")) : one)
|
|
2349
2602
|
);
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
);
|
|
2356
|
-
context.error("");
|
|
2357
|
-
context.error(" pm agent --root ~/projects");
|
|
2358
|
-
return 1;
|
|
2359
|
-
}
|
|
2360
|
-
roots.push(...usual);
|
|
2361
|
-
context.print(
|
|
2362
|
-
`Reading from ${usual.map((path) => path.replace(homedir2(), "~")).join(", ")} \u2014 nothing outside them. Use --root to choose your own.`
|
|
2363
|
-
);
|
|
2364
|
-
}
|
|
2603
|
+
const everywhere = roots.length === 0;
|
|
2604
|
+
if (everywhere) roots.push("/");
|
|
2605
|
+
context.print(
|
|
2606
|
+
everywhere ? "Reading anywhere on this machine. You approve every request first, and see the exact path or command before you do. Narrow it with --root if you want to." : `Reading ${roots.map((path) => path.replace(homedir2(), "~")).join(", ")} \u2014 nothing outside them.`
|
|
2607
|
+
);
|
|
2365
2608
|
for (const root of roots) {
|
|
2366
2609
|
try {
|
|
2367
|
-
if (!
|
|
2610
|
+
if (!statSync3(root).isDirectory()) {
|
|
2368
2611
|
context.error(`${root} is not a folder.`);
|
|
2369
2612
|
return 1;
|
|
2370
2613
|
}
|
|
@@ -2461,7 +2704,7 @@ async function agentCommand(context) {
|
|
|
2461
2704
|
// src/commands/google.ts
|
|
2462
2705
|
import { writeFileSync as writeFileSync5 } from "node:fs";
|
|
2463
2706
|
import { basename as basename2, resolve as resolve4 } from "node:path";
|
|
2464
|
-
import { readFileSync as
|
|
2707
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
2465
2708
|
async function callApi(context, path, init = {}) {
|
|
2466
2709
|
const credential = context.resolved.credential;
|
|
2467
2710
|
if (!credential) {
|
|
@@ -2538,7 +2781,7 @@ async function drivePut(context, path) {
|
|
|
2538
2781
|
}
|
|
2539
2782
|
let bytes;
|
|
2540
2783
|
try {
|
|
2541
|
-
bytes =
|
|
2784
|
+
bytes = readFileSync6(resolve4(path));
|
|
2542
2785
|
} catch {
|
|
2543
2786
|
context.error(`Cannot read ${path}.`);
|
|
2544
2787
|
return 1;
|
|
@@ -2622,7 +2865,7 @@ async function mailCommand(context) {
|
|
|
2622
2865
|
}
|
|
2623
2866
|
|
|
2624
2867
|
// src/commands/requests.ts
|
|
2625
|
-
import { existsSync as
|
|
2868
|
+
import { existsSync as existsSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2626
2869
|
import { resolve as resolve5 } from "node:path";
|
|
2627
2870
|
async function requestsCommand(context) {
|
|
2628
2871
|
if (context.args.words[1] === "get") return collectCommand(context);
|
|
@@ -2692,7 +2935,7 @@ async function collectCommand(context) {
|
|
|
2692
2935
|
}
|
|
2693
2936
|
const name = stringFlag(context.args, "output", "o") ?? filename;
|
|
2694
2937
|
const target = resolve5(name);
|
|
2695
|
-
if (
|
|
2938
|
+
if (existsSync6(target)) {
|
|
2696
2939
|
context.error(`${target} already exists. Pass --output to write somewhere else.`);
|
|
2697
2940
|
return 1;
|
|
2698
2941
|
}
|
|
@@ -2702,14 +2945,14 @@ async function collectCommand(context) {
|
|
|
2702
2945
|
}
|
|
2703
2946
|
|
|
2704
2947
|
// src/workspace.ts
|
|
2705
|
-
import { existsSync as
|
|
2706
|
-
import { dirname as dirname3, join as
|
|
2948
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
2949
|
+
import { dirname as dirname3, join as join6, resolve as resolvePath2 } from "node:path";
|
|
2707
2950
|
var WORKSPACE_FILE = ".persistmemory.json";
|
|
2708
2951
|
function findWorkspace(from = process.cwd()) {
|
|
2709
|
-
let dir =
|
|
2952
|
+
let dir = resolvePath2(from);
|
|
2710
2953
|
for (; ; ) {
|
|
2711
|
-
const file =
|
|
2712
|
-
if (
|
|
2954
|
+
const file = join6(dir, WORKSPACE_FILE);
|
|
2955
|
+
if (existsSync7(file)) {
|
|
2713
2956
|
const config = readWorkspace(file);
|
|
2714
2957
|
if (config) return { file, dir, config };
|
|
2715
2958
|
}
|
|
@@ -2720,7 +2963,7 @@ function findWorkspace(from = process.cwd()) {
|
|
|
2720
2963
|
}
|
|
2721
2964
|
function readWorkspace(file) {
|
|
2722
2965
|
try {
|
|
2723
|
-
const parsed = JSON.parse(
|
|
2966
|
+
const parsed = JSON.parse(readFileSync7(file, "utf8"));
|
|
2724
2967
|
const space = parsed.space;
|
|
2725
2968
|
if (space && typeof space === "object" && typeof space.id === "string" && space.id !== "" && typeof space.name === "string") {
|
|
2726
2969
|
return { space: { id: space.id, name: space.name } };
|
|
@@ -2731,7 +2974,7 @@ function readWorkspace(file) {
|
|
|
2731
2974
|
}
|
|
2732
2975
|
}
|
|
2733
2976
|
function writeWorkspace(dir, config) {
|
|
2734
|
-
const file =
|
|
2977
|
+
const file = join6(dir, WORKSPACE_FILE);
|
|
2735
2978
|
writeFileSync7(file, `${JSON.stringify(config, null, 2)}
|
|
2736
2979
|
`, "utf8");
|
|
2737
2980
|
return file;
|
|
@@ -3001,7 +3244,7 @@ function message(error) {
|
|
|
3001
3244
|
}
|
|
3002
3245
|
|
|
3003
3246
|
// src/commands/maintain.ts
|
|
3004
|
-
import { existsSync as
|
|
3247
|
+
import { existsSync as existsSync8, rmSync } from "node:fs";
|
|
3005
3248
|
import { spawnSync } from "node:child_process";
|
|
3006
3249
|
import { dirname as dirname4, resolve as resolve7 } from "node:path";
|
|
3007
3250
|
import { fileURLToPath } from "node:url";
|
|
@@ -3067,7 +3310,7 @@ Delete the file it runs from: ${processPath()}`
|
|
|
3067
3310
|
return 0;
|
|
3068
3311
|
}
|
|
3069
3312
|
async function deleteCommand(context) {
|
|
3070
|
-
if (!
|
|
3313
|
+
if (!existsSync8(context.paths.dir)) {
|
|
3071
3314
|
context.print(`Nothing to delete: ${context.paths.dir} does not exist.`);
|
|
3072
3315
|
return 0;
|
|
3073
3316
|
}
|
|
@@ -3120,12 +3363,12 @@ import { randomUUID } from "node:crypto";
|
|
|
3120
3363
|
import { relative as relative2 } from "node:path";
|
|
3121
3364
|
|
|
3122
3365
|
// src/events.ts
|
|
3123
|
-
import { appendFileSync, existsSync as
|
|
3124
|
-
import { join as
|
|
3366
|
+
import { appendFileSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync8 } from "node:fs";
|
|
3367
|
+
import { join as join7 } from "node:path";
|
|
3125
3368
|
function openSessionLog(paths, id) {
|
|
3126
|
-
const directory =
|
|
3369
|
+
const directory = join7(paths.dir, "sessions");
|
|
3127
3370
|
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
3128
|
-
const path =
|
|
3371
|
+
const path = join7(directory, `${id}.jsonl`);
|
|
3129
3372
|
return {
|
|
3130
3373
|
id,
|
|
3131
3374
|
path,
|
|
@@ -3137,8 +3380,8 @@ function openSessionLog(paths, id) {
|
|
|
3137
3380
|
}
|
|
3138
3381
|
},
|
|
3139
3382
|
read() {
|
|
3140
|
-
if (!
|
|
3141
|
-
return
|
|
3383
|
+
if (!existsSync9(path)) return [];
|
|
3384
|
+
return readFileSync8(path, "utf8").split("\n").filter((line) => line.trim() !== "").flatMap((line) => {
|
|
3142
3385
|
try {
|
|
3143
3386
|
return [JSON.parse(line)];
|
|
3144
3387
|
} catch {
|
|
@@ -3401,7 +3644,7 @@ async function write2(args) {
|
|
|
3401
3644
|
}
|
|
3402
3645
|
|
|
3403
3646
|
// src/commands/memory.ts
|
|
3404
|
-
import { readFileSync as
|
|
3647
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
3405
3648
|
|
|
3406
3649
|
// src/spaces.ts
|
|
3407
3650
|
async function spacesFor(context, client, env = process.env) {
|
|
@@ -3465,7 +3708,7 @@ async function rememberCommand(context) {
|
|
|
3465
3708
|
let text;
|
|
3466
3709
|
if (file) {
|
|
3467
3710
|
try {
|
|
3468
|
-
text =
|
|
3711
|
+
text = readFileSync9(file, "utf8");
|
|
3469
3712
|
} catch {
|
|
3470
3713
|
context.error(`Could not read ${file}.`);
|
|
3471
3714
|
return 1;
|