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