@persistmemory/cli 0.4.0 → 0.5.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 +461 -96
- package/dist/bin.js.map +4 -4
- package/dist/index.js +461 -96
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -344,7 +344,7 @@ var SignalFired = class extends Error {
|
|
|
344
344
|
};
|
|
345
345
|
function untilAborted(work, signal) {
|
|
346
346
|
work.catch(() => void 0);
|
|
347
|
-
return new Promise((
|
|
347
|
+
return new Promise((resolve9, reject) => {
|
|
348
348
|
if (signal.aborted) {
|
|
349
349
|
reject(new SignalFired());
|
|
350
350
|
return;
|
|
@@ -354,7 +354,7 @@ function untilAborted(work, signal) {
|
|
|
354
354
|
work.then(
|
|
355
355
|
(value) => {
|
|
356
356
|
signal.removeEventListener("abort", onAbort);
|
|
357
|
-
|
|
357
|
+
resolve9(value);
|
|
358
358
|
},
|
|
359
359
|
(error) => {
|
|
360
360
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -364,14 +364,14 @@ function untilAborted(work, signal) {
|
|
|
364
364
|
});
|
|
365
365
|
}
|
|
366
366
|
function defaultSleep(ms, signal) {
|
|
367
|
-
return new Promise((
|
|
367
|
+
return new Promise((resolve9, reject) => {
|
|
368
368
|
if (signal?.aborted) {
|
|
369
369
|
reject(new AbortError());
|
|
370
370
|
return;
|
|
371
371
|
}
|
|
372
372
|
const timer = setTimeout(() => {
|
|
373
373
|
signal?.removeEventListener("abort", onAbort);
|
|
374
|
-
|
|
374
|
+
resolve9();
|
|
375
375
|
}, ms);
|
|
376
376
|
function onAbort() {
|
|
377
377
|
clearTimeout(timer);
|
|
@@ -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.5.0" : versionFromManifest();
|
|
1408
1408
|
var PACKAGE = "@persistmemory/cli";
|
|
1409
1409
|
var HELP = `
|
|
1410
1410
|
pm \u2014 PersistMemory from your terminal
|
|
@@ -1446,14 +1446,24 @@ 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
|
-
outside them. It
|
|
1453
|
-
|
|
1449
|
+
agent answer file and command requests from this
|
|
1450
|
+
machine. Reads the folders in your home
|
|
1451
|
+
directory \u2014 never anything hidden \u2014 and
|
|
1452
|
+
nothing outside them. It says which ones
|
|
1453
|
+
when it starts. Stays in the foreground;
|
|
1454
|
+
background it 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 ...] choose the folders
|
|
1457
|
+
agent --root <dir> [--root ...] choose the folders for this run
|
|
1458
|
+
|
|
1459
|
+
Settings live in ~/.persistmemory/agent.json, and apply every time:
|
|
1460
|
+
|
|
1461
|
+
{
|
|
1462
|
+
"roots": ["~/code", "~/Documents"],
|
|
1463
|
+
"mode": "ask", ask | auto-read | plan
|
|
1464
|
+
"allow": ["swift"], programs this machine treats as reads
|
|
1465
|
+
"deny": ["rm"] programs it refuses, whatever is approved
|
|
1466
|
+
}
|
|
1457
1467
|
|
|
1458
1468
|
search <query> search your memory
|
|
1459
1469
|
list memories the most recent memories
|
|
@@ -1612,8 +1622,8 @@ async function startLoopback(options = {}) {
|
|
|
1612
1622
|
const timeoutMs = options.timeoutMs ?? 5 * 60 * 1e3;
|
|
1613
1623
|
let resolveCallback;
|
|
1614
1624
|
let rejectCallback;
|
|
1615
|
-
const received = new Promise((
|
|
1616
|
-
resolveCallback =
|
|
1625
|
+
const received = new Promise((resolve9, reject) => {
|
|
1626
|
+
resolveCallback = resolve9;
|
|
1617
1627
|
rejectCallback = reject;
|
|
1618
1628
|
});
|
|
1619
1629
|
const server = createServer((request, response) => {
|
|
@@ -1649,9 +1659,9 @@ async function startLoopback(options = {}) {
|
|
|
1649
1659
|
response.end(donePage(callback));
|
|
1650
1660
|
resolveCallback?.(callback);
|
|
1651
1661
|
});
|
|
1652
|
-
await new Promise((
|
|
1662
|
+
await new Promise((resolve9, reject) => {
|
|
1653
1663
|
server.once("error", reject);
|
|
1654
|
-
server.listen(0, "127.0.0.1",
|
|
1664
|
+
server.listen(0, "127.0.0.1", resolve9);
|
|
1655
1665
|
});
|
|
1656
1666
|
const address = server.address();
|
|
1657
1667
|
if (address === null || typeof address === "string") {
|
|
@@ -1915,7 +1925,7 @@ function safeEqual(a, b) {
|
|
|
1915
1925
|
}
|
|
1916
1926
|
async function openBrowser(url) {
|
|
1917
1927
|
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
1918
|
-
await new Promise((
|
|
1928
|
+
await new Promise((resolve9, reject) => {
|
|
1919
1929
|
const child = spawn(command, args, {
|
|
1920
1930
|
stdio: "ignore",
|
|
1921
1931
|
// Detached so closing the terminal does not close the browser, and so
|
|
@@ -1924,7 +1934,7 @@ async function openBrowser(url) {
|
|
|
1924
1934
|
});
|
|
1925
1935
|
child.once("error", reject);
|
|
1926
1936
|
child.unref();
|
|
1927
|
-
|
|
1937
|
+
resolve9();
|
|
1928
1938
|
});
|
|
1929
1939
|
}
|
|
1930
1940
|
async function describe(response) {
|
|
@@ -1986,16 +1996,16 @@ async function currentCredential(resolved, deps) {
|
|
|
1986
1996
|
// src/context.ts
|
|
1987
1997
|
import { createInterface } from "node:readline";
|
|
1988
1998
|
async function askOnTty(prompt, input = process.stdin, output = process.stdout) {
|
|
1989
|
-
return new Promise((
|
|
1999
|
+
return new Promise((resolve9) => {
|
|
1990
2000
|
const readline = createInterface({ input, output });
|
|
1991
2001
|
let answered = false;
|
|
1992
2002
|
readline.question(prompt, (answer3) => {
|
|
1993
2003
|
answered = true;
|
|
1994
|
-
|
|
2004
|
+
resolve9(answer3.trim());
|
|
1995
2005
|
readline.close();
|
|
1996
2006
|
});
|
|
1997
2007
|
readline.once("close", () => {
|
|
1998
|
-
if (!answered)
|
|
2008
|
+
if (!answered) resolve9("");
|
|
1999
2009
|
});
|
|
2000
2010
|
});
|
|
2001
2011
|
}
|
|
@@ -2005,16 +2015,16 @@ var BACKSPACE = "\b";
|
|
|
2005
2015
|
async function readSecretFromTty(prompt) {
|
|
2006
2016
|
const input = process.stdin;
|
|
2007
2017
|
if (!input.isTTY) {
|
|
2008
|
-
return new Promise((
|
|
2018
|
+
return new Promise((resolve9) => {
|
|
2009
2019
|
const readline = createInterface({ input });
|
|
2010
2020
|
let answered = false;
|
|
2011
2021
|
readline.once("line", (line) => {
|
|
2012
2022
|
answered = true;
|
|
2013
|
-
|
|
2023
|
+
resolve9(line.trim());
|
|
2014
2024
|
readline.close();
|
|
2015
2025
|
});
|
|
2016
2026
|
readline.once("close", () => {
|
|
2017
|
-
if (!answered)
|
|
2027
|
+
if (!answered) resolve9("");
|
|
2018
2028
|
});
|
|
2019
2029
|
});
|
|
2020
2030
|
}
|
|
@@ -2023,14 +2033,14 @@ async function readSecretFromTty(prompt) {
|
|
|
2023
2033
|
input.setRawMode?.(true);
|
|
2024
2034
|
input.resume();
|
|
2025
2035
|
input.setEncoding("utf8");
|
|
2026
|
-
return new Promise((
|
|
2036
|
+
return new Promise((resolve9) => {
|
|
2027
2037
|
let value = "";
|
|
2028
2038
|
const finish2 = () => {
|
|
2029
2039
|
input.removeListener("data", onData);
|
|
2030
2040
|
input.setRawMode?.(previouslyRaw);
|
|
2031
2041
|
input.pause();
|
|
2032
2042
|
process.stdout.write("\n");
|
|
2033
|
-
|
|
2043
|
+
resolve9(value.trim());
|
|
2034
2044
|
};
|
|
2035
2045
|
const onData = (chunk) => {
|
|
2036
2046
|
for (const character of chunk) {
|
|
@@ -2066,9 +2076,9 @@ async function readStdin() {
|
|
|
2066
2076
|
|
|
2067
2077
|
// src/commands/agent.ts
|
|
2068
2078
|
import { hostname } from "node:os";
|
|
2069
|
-
import { homedir as
|
|
2070
|
-
import { basename, join as
|
|
2071
|
-
import { existsSync as
|
|
2079
|
+
import { homedir as homedir3 } from "node:os";
|
|
2080
|
+
import { basename, join as join6, resolve as resolve4 } from "node:path";
|
|
2081
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2072
2082
|
|
|
2073
2083
|
// src/files.ts
|
|
2074
2084
|
import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync, statSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
@@ -2176,6 +2186,335 @@ ${head.join("\n")}`;
|
|
|
2176
2186
|
].join("\n");
|
|
2177
2187
|
}
|
|
2178
2188
|
|
|
2189
|
+
// src/commands/run-command.ts
|
|
2190
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2191
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
|
|
2192
|
+
import { isAbsolute as isAbsolute2, join as join4, resolve as resolvePath } from "node:path";
|
|
2193
|
+
var CLASSES = new Map([
|
|
2194
|
+
// Reads. Report on the filesystem and change nothing.
|
|
2195
|
+
...[
|
|
2196
|
+
"ls",
|
|
2197
|
+
"cat",
|
|
2198
|
+
"head",
|
|
2199
|
+
"tail",
|
|
2200
|
+
"wc",
|
|
2201
|
+
"file",
|
|
2202
|
+
"stat",
|
|
2203
|
+
"du",
|
|
2204
|
+
"df",
|
|
2205
|
+
"find",
|
|
2206
|
+
"grep",
|
|
2207
|
+
"rg",
|
|
2208
|
+
"fd",
|
|
2209
|
+
"tree",
|
|
2210
|
+
"pwd",
|
|
2211
|
+
"date",
|
|
2212
|
+
"uname",
|
|
2213
|
+
"which",
|
|
2214
|
+
"echo",
|
|
2215
|
+
"sort",
|
|
2216
|
+
"uniq",
|
|
2217
|
+
"diff",
|
|
2218
|
+
"basename",
|
|
2219
|
+
"dirname",
|
|
2220
|
+
"realpath",
|
|
2221
|
+
"ps",
|
|
2222
|
+
"env"
|
|
2223
|
+
].map((name) => [name, "read"]),
|
|
2224
|
+
// Writes. Recoverable or not, they change the machine.
|
|
2225
|
+
...[
|
|
2226
|
+
"rm",
|
|
2227
|
+
"mv",
|
|
2228
|
+
"cp",
|
|
2229
|
+
"mkdir",
|
|
2230
|
+
"rmdir",
|
|
2231
|
+
"touch",
|
|
2232
|
+
"chmod",
|
|
2233
|
+
"chown",
|
|
2234
|
+
"ln",
|
|
2235
|
+
"tee",
|
|
2236
|
+
"truncate",
|
|
2237
|
+
"dd",
|
|
2238
|
+
"kill",
|
|
2239
|
+
"killall",
|
|
2240
|
+
"make",
|
|
2241
|
+
"cargo",
|
|
2242
|
+
"swift",
|
|
2243
|
+
"xcodebuild",
|
|
2244
|
+
"gradle",
|
|
2245
|
+
"docker",
|
|
2246
|
+
"brew",
|
|
2247
|
+
"apt",
|
|
2248
|
+
"yum"
|
|
2249
|
+
].map((name) => [name, "write"]),
|
|
2250
|
+
// A way out. See the trifecta note above.
|
|
2251
|
+
...["curl", "wget", "nc", "ncat", "telnet", "ssh", "scp", "rsync", "ftp", "http", "httpie"].map((name) => [name, "network"]),
|
|
2252
|
+
// Every command at once, wearing one name.
|
|
2253
|
+
...["sh", "bash", "zsh", "fish", "python", "python3", "node", "ruby", "perl", "osascript", "eval"].map((name) => [name, "interpreter"])
|
|
2254
|
+
]);
|
|
2255
|
+
var SUBCOMMAND_READS = /* @__PURE__ */ new Map([
|
|
2256
|
+
[
|
|
2257
|
+
"git",
|
|
2258
|
+
/* @__PURE__ */ new Set([
|
|
2259
|
+
"status",
|
|
2260
|
+
"log",
|
|
2261
|
+
"diff",
|
|
2262
|
+
"show",
|
|
2263
|
+
"branch",
|
|
2264
|
+
"remote",
|
|
2265
|
+
"describe",
|
|
2266
|
+
"blame",
|
|
2267
|
+
"shortlog",
|
|
2268
|
+
"ls-files",
|
|
2269
|
+
"rev-parse",
|
|
2270
|
+
"config"
|
|
2271
|
+
])
|
|
2272
|
+
],
|
|
2273
|
+
["npm", /* @__PURE__ */ new Set(["ls", "list", "view", "outdated", "why", "config"])],
|
|
2274
|
+
["yarn", /* @__PURE__ */ new Set(["list", "why", "info", "config"])],
|
|
2275
|
+
["docker", /* @__PURE__ */ new Set(["ps", "images", "logs", "inspect"])]
|
|
2276
|
+
]);
|
|
2277
|
+
var MAX_OUTPUT_BYTES = 256 * 1024;
|
|
2278
|
+
var TIMEOUT_MS = 2e4;
|
|
2279
|
+
function describe2(argv) {
|
|
2280
|
+
return argv.join(" ");
|
|
2281
|
+
}
|
|
2282
|
+
function programOf(argv) {
|
|
2283
|
+
const first = argv[0] ?? "";
|
|
2284
|
+
return first.split("/").pop() ?? first;
|
|
2285
|
+
}
|
|
2286
|
+
function classify(argv, policy) {
|
|
2287
|
+
const program = programOf(argv);
|
|
2288
|
+
if (policy.allow.includes(program)) return "read";
|
|
2289
|
+
const reads = SUBCOMMAND_READS.get(program);
|
|
2290
|
+
if (reads) {
|
|
2291
|
+
const sub = argv.slice(1).find((one) => !one.startsWith("-"));
|
|
2292
|
+
return sub && reads.has(sub) ? "read" : "write";
|
|
2293
|
+
}
|
|
2294
|
+
return CLASSES.get(program) ?? "unknown";
|
|
2295
|
+
}
|
|
2296
|
+
function judge(argv, policy) {
|
|
2297
|
+
const program = programOf(argv);
|
|
2298
|
+
if (!argv[0]) {
|
|
2299
|
+
return { commandClass: "unknown", automatic: false, refusal: "No command was given.", reason: "empty" };
|
|
2300
|
+
}
|
|
2301
|
+
if (policy.deny.includes(program)) {
|
|
2302
|
+
return {
|
|
2303
|
+
commandClass: "unknown",
|
|
2304
|
+
automatic: false,
|
|
2305
|
+
refusal: `This machine refuses "${program}".`,
|
|
2306
|
+
reason: "on this machine's deny list"
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
const commandClass = classify(argv, policy);
|
|
2310
|
+
if (commandClass === "network") {
|
|
2311
|
+
return {
|
|
2312
|
+
commandClass,
|
|
2313
|
+
automatic: false,
|
|
2314
|
+
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.`,
|
|
2315
|
+
reason: "reads private data and has a way out"
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
if (commandClass === "interpreter") {
|
|
2319
|
+
return {
|
|
2320
|
+
commandClass,
|
|
2321
|
+
automatic: false,
|
|
2322
|
+
refusal: `"${program}" runs a language, which is every command at once. Ask for the specific command instead.`,
|
|
2323
|
+
reason: "an interpreter is not one command"
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
const outside = pathOutsideRoots(argv, policy.roots);
|
|
2327
|
+
if (outside) {
|
|
2328
|
+
return {
|
|
2329
|
+
commandClass,
|
|
2330
|
+
automatic: false,
|
|
2331
|
+
refusal: `${outside} is outside the folders this machine may read.`,
|
|
2332
|
+
reason: "outside the roots"
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
if (policy.mode === "plan") {
|
|
2336
|
+
return {
|
|
2337
|
+
commandClass,
|
|
2338
|
+
automatic: false,
|
|
2339
|
+
reason: "this machine is in plan mode and runs nothing"
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
return {
|
|
2343
|
+
commandClass,
|
|
2344
|
+
automatic: policy.mode === "auto-read" && commandClass === "read",
|
|
2345
|
+
reason: commandClass === "read" ? "reads and changes nothing" : commandClass === "write" ? "changes this machine" : "not a command this machine recognises"
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
function pathOutsideRoots(argv, roots) {
|
|
2349
|
+
for (const argument of argv.slice(1)) {
|
|
2350
|
+
if (argument.startsWith("-")) continue;
|
|
2351
|
+
if (!argument.startsWith("/") && !argument.startsWith("~") && !argument.startsWith(".") && !argument.includes("/")) {
|
|
2352
|
+
continue;
|
|
2353
|
+
}
|
|
2354
|
+
const resolved = expand(argument, roots[0] ?? process.cwd());
|
|
2355
|
+
if (!roots.some((root) => resolved === root || resolved.startsWith(`${root}/`))) {
|
|
2356
|
+
return argument;
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
return void 0;
|
|
2360
|
+
}
|
|
2361
|
+
function expand(argument, base) {
|
|
2362
|
+
const home = process.env["HOME"] ?? "";
|
|
2363
|
+
const withHome = argument.startsWith("~") ? join4(home, argument.slice(1)) : argument;
|
|
2364
|
+
return isAbsolute2(withHome) ? resolvePath(withHome) : resolvePath(base, withHome);
|
|
2365
|
+
}
|
|
2366
|
+
async function runCommand(argv, policy) {
|
|
2367
|
+
const verdict = judge(argv, policy);
|
|
2368
|
+
if (verdict.refusal) return { ok: false, text: verdict.refusal };
|
|
2369
|
+
if (policy.mode === "plan") {
|
|
2370
|
+
return { ok: false, text: `Plan mode: this machine did not run \`${describe2(argv)}\`.` };
|
|
2371
|
+
}
|
|
2372
|
+
const cwd = policy.roots[0];
|
|
2373
|
+
if (!cwd) return { ok: false, text: "This machine has no folders it may read." };
|
|
2374
|
+
try {
|
|
2375
|
+
if (!statSync2(cwd).isDirectory()) return { ok: false, text: `${cwd} is not a folder.` };
|
|
2376
|
+
} catch {
|
|
2377
|
+
return { ok: false, text: `${cwd} does not exist.` };
|
|
2378
|
+
}
|
|
2379
|
+
return new Promise((resolve9) => {
|
|
2380
|
+
const child = spawn2(argv[0], argv.slice(1), {
|
|
2381
|
+
cwd,
|
|
2382
|
+
// NO shell. With one, every character a model can produce is a character
|
|
2383
|
+
// the shell can act on, and the argument list stops meaning anything.
|
|
2384
|
+
shell: false,
|
|
2385
|
+
/*
|
|
2386
|
+
A bare environment.
|
|
2387
|
+
|
|
2388
|
+
This process holds the credential that authorises the machine, and
|
|
2389
|
+
handing it to a subprocess a model chose would mean `env` — or anything
|
|
2390
|
+
that prints its environment — returning that key.
|
|
2391
|
+
*/
|
|
2392
|
+
env: {
|
|
2393
|
+
PATH: process.env["PATH"] ?? "/usr/bin:/bin",
|
|
2394
|
+
HOME: process.env["HOME"] ?? "",
|
|
2395
|
+
LANG: process.env["LANG"] ?? "C"
|
|
2396
|
+
}
|
|
2397
|
+
});
|
|
2398
|
+
let output = "";
|
|
2399
|
+
let truncated = false;
|
|
2400
|
+
const collect = (chunk) => {
|
|
2401
|
+
if (truncated) return;
|
|
2402
|
+
output += chunk.toString("utf8");
|
|
2403
|
+
if (output.length > MAX_OUTPUT_BYTES) {
|
|
2404
|
+
output = output.slice(0, MAX_OUTPUT_BYTES);
|
|
2405
|
+
truncated = true;
|
|
2406
|
+
child.kill("SIGKILL");
|
|
2407
|
+
}
|
|
2408
|
+
};
|
|
2409
|
+
child.stdout.on("data", collect);
|
|
2410
|
+
child.stderr.on("data", collect);
|
|
2411
|
+
const timer = setTimeout(() => child.kill("SIGKILL"), TIMEOUT_MS);
|
|
2412
|
+
child.on("error", (error) => {
|
|
2413
|
+
clearTimeout(timer);
|
|
2414
|
+
resolve9({ ok: false, text: `Could not run it: ${error.message}` });
|
|
2415
|
+
});
|
|
2416
|
+
child.on("close", (code) => {
|
|
2417
|
+
clearTimeout(timer);
|
|
2418
|
+
const notes = [
|
|
2419
|
+
truncated ? `
|
|
2420
|
+
|
|
2421
|
+
[output cut off at ${MAX_OUTPUT_BYTES} bytes]` : "",
|
|
2422
|
+
code !== 0 && code !== null ? `
|
|
2423
|
+
|
|
2424
|
+
[exit code ${code}]` : ""
|
|
2425
|
+
].join("");
|
|
2426
|
+
resolve9({ ok: code === 0, text: `$ ${describe2(argv)}
|
|
2427
|
+
|
|
2428
|
+
${output}${notes}` });
|
|
2429
|
+
});
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
// src/commands/agent-config.ts
|
|
2434
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync, statSync as statSync3 } from "node:fs";
|
|
2435
|
+
import { homedir as homedir2 } from "node:os";
|
|
2436
|
+
import { join as join5, resolve as resolve3 } from "node:path";
|
|
2437
|
+
var NOT_MATERIAL = /* @__PURE__ */ new Set([
|
|
2438
|
+
"Library",
|
|
2439
|
+
"Applications",
|
|
2440
|
+
"System",
|
|
2441
|
+
"Public",
|
|
2442
|
+
"node_modules",
|
|
2443
|
+
"go",
|
|
2444
|
+
"Parallels",
|
|
2445
|
+
"VirtualBox VMs"
|
|
2446
|
+
]);
|
|
2447
|
+
function discoverRoots(home = homedir2(), list = (path) => {
|
|
2448
|
+
try {
|
|
2449
|
+
return readdirSync(path);
|
|
2450
|
+
} catch {
|
|
2451
|
+
return [];
|
|
2452
|
+
}
|
|
2453
|
+
}, isDirectory = (path) => {
|
|
2454
|
+
try {
|
|
2455
|
+
return statSync3(path).isDirectory();
|
|
2456
|
+
} catch {
|
|
2457
|
+
return false;
|
|
2458
|
+
}
|
|
2459
|
+
}) {
|
|
2460
|
+
return list(home).filter((name) => !name.startsWith(".")).filter((name) => !NOT_MATERIAL.has(name)).map((name) => join5(home, name)).filter(isDirectory).sort();
|
|
2461
|
+
}
|
|
2462
|
+
function expandHome(path, home = homedir2()) {
|
|
2463
|
+
return path.startsWith("~") ? resolve3(home, path.slice(1).replace(/^[/\\]/, "")) : resolve3(path);
|
|
2464
|
+
}
|
|
2465
|
+
var MODES = /* @__PURE__ */ new Set(["ask", "auto-read", "plan"]);
|
|
2466
|
+
function readAgentConfig(path, read2 = (at) => readFileSync5(at, "utf8"), exists = (at) => existsSync5(at), home = homedir2()) {
|
|
2467
|
+
if (!exists(path)) return {};
|
|
2468
|
+
let parsed;
|
|
2469
|
+
try {
|
|
2470
|
+
parsed = JSON.parse(read2(path));
|
|
2471
|
+
} catch (error) {
|
|
2472
|
+
return { error: `${path} is not valid JSON: ${error instanceof Error ? error.message : ""}` };
|
|
2473
|
+
}
|
|
2474
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2475
|
+
return { error: `${path} should hold an object, like {"roots": ["~/code"]}.` };
|
|
2476
|
+
}
|
|
2477
|
+
const held = parsed;
|
|
2478
|
+
const config = {};
|
|
2479
|
+
if (held["roots"] !== void 0) {
|
|
2480
|
+
if (!Array.isArray(held["roots"]) || held["roots"].some((one) => typeof one !== "string")) {
|
|
2481
|
+
return { error: `${path}: "roots" should be a list of folders.` };
|
|
2482
|
+
}
|
|
2483
|
+
config.roots = held["roots"].map((one) => expandHome(one, home));
|
|
2484
|
+
}
|
|
2485
|
+
if (held["mode"] !== void 0) {
|
|
2486
|
+
if (typeof held["mode"] !== "string" || !MODES.has(held["mode"])) {
|
|
2487
|
+
return { error: `${path}: "mode" should be "ask", "auto-read" or "plan".` };
|
|
2488
|
+
}
|
|
2489
|
+
config.mode = held["mode"];
|
|
2490
|
+
}
|
|
2491
|
+
for (const key of ["allow", "deny"]) {
|
|
2492
|
+
if (held[key] === void 0) continue;
|
|
2493
|
+
if (!Array.isArray(held[key]) || held[key].some((one) => typeof one !== "string")) {
|
|
2494
|
+
return { error: `${path}: "${key}" should be a list of program names.` };
|
|
2495
|
+
}
|
|
2496
|
+
config[key] = held[key];
|
|
2497
|
+
}
|
|
2498
|
+
return { config };
|
|
2499
|
+
}
|
|
2500
|
+
function resolveAgentConfig(args) {
|
|
2501
|
+
const roots = args.flagRoots && args.flagRoots.length > 0 ? args.flagRoots : args.stored?.roots && args.stored.roots.length > 0 ? args.stored.roots : args.discovered ?? [];
|
|
2502
|
+
return {
|
|
2503
|
+
roots,
|
|
2504
|
+
/*
|
|
2505
|
+
`ask` unless the owner said otherwise.
|
|
2506
|
+
|
|
2507
|
+
The mode decides how much happens with nobody watching, so the default
|
|
2508
|
+
is the one that does least. `auto-read` is a reasonable thing to choose
|
|
2509
|
+
and an unreasonable thing to be given.
|
|
2510
|
+
*/
|
|
2511
|
+
mode: args.stored?.mode ?? "ask",
|
|
2512
|
+
allow: args.stored?.allow ?? [],
|
|
2513
|
+
deny: args.stored?.deny ?? [],
|
|
2514
|
+
source: args.flagRoots && args.flagRoots.length > 0 ? "flags" : args.stored?.roots && args.stored.roots.length > 0 ? "config" : "discovered"
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2179
2518
|
// src/commands/agent.ts
|
|
2180
2519
|
async function said(response, fallback) {
|
|
2181
2520
|
const body = await response.json().catch(() => void 0);
|
|
@@ -2183,7 +2522,7 @@ async function said(response, fallback) {
|
|
|
2183
2522
|
return message2 ?? `${fallback} (${response.status})`;
|
|
2184
2523
|
}
|
|
2185
2524
|
function locate(roots, requested) {
|
|
2186
|
-
const expanded = requested.startsWith("~") ?
|
|
2525
|
+
const expanded = requested.startsWith("~") ? resolve4(homedir3(), requested.slice(1).replace(/^[/\\]/, "")) : requested;
|
|
2187
2526
|
if (!expanded.includes("/") && !expanded.includes("\\")) {
|
|
2188
2527
|
const wanted2 = expanded.trim().toLowerCase();
|
|
2189
2528
|
const named = roots.find((root) => basename(root).toLowerCase() === wanted2);
|
|
@@ -2199,18 +2538,38 @@ function locate(roots, requested) {
|
|
|
2199
2538
|
throw new Error(`${requested} is not inside any allowed folder (${roots.join(", ")})`);
|
|
2200
2539
|
}
|
|
2201
2540
|
function uncontested(target) {
|
|
2202
|
-
if (!
|
|
2541
|
+
if (!existsSync6(target)) return target;
|
|
2203
2542
|
const dot = target.lastIndexOf(".");
|
|
2204
2543
|
const stem = dot > target.lastIndexOf("/") && dot !== -1 ? target.slice(0, dot) : target;
|
|
2205
2544
|
const extension = stem === target ? "" : target.slice(dot);
|
|
2206
2545
|
for (let n = 1; n < 1e3; n += 1) {
|
|
2207
2546
|
const candidate = `${stem} (${n})${extension}`;
|
|
2208
|
-
if (!
|
|
2547
|
+
if (!existsSync6(candidate)) return candidate;
|
|
2209
2548
|
}
|
|
2210
2549
|
throw new Error(`${target} and a thousand names beside it are taken.`);
|
|
2211
2550
|
}
|
|
2212
|
-
async function answer(context, apiUrl, token,
|
|
2551
|
+
async function answer(context, apiUrl, token, settings, request) {
|
|
2552
|
+
const roots = settings.roots;
|
|
2213
2553
|
let located;
|
|
2554
|
+
if (request.kind === "run_command") {
|
|
2555
|
+
if (!request.argv || request.argv.length === 0) {
|
|
2556
|
+
return { ok: false, error: "No command was given." };
|
|
2557
|
+
}
|
|
2558
|
+
const policy = {
|
|
2559
|
+
mode: settings.mode,
|
|
2560
|
+
allow: settings.allow,
|
|
2561
|
+
deny: settings.deny,
|
|
2562
|
+
roots
|
|
2563
|
+
};
|
|
2564
|
+
const outcome = await runCommand(request.argv, policy);
|
|
2565
|
+
if (!outcome.ok) return { ok: false, error: outcome.text };
|
|
2566
|
+
return upload(
|
|
2567
|
+
apiUrl,
|
|
2568
|
+
token,
|
|
2569
|
+
`${(request.argv[0] ?? "output").split("/").pop()}.txt`,
|
|
2570
|
+
Buffer.from(outcome.text, "utf8")
|
|
2571
|
+
);
|
|
2572
|
+
}
|
|
2214
2573
|
try {
|
|
2215
2574
|
located = locate(roots, request.path);
|
|
2216
2575
|
} catch (error) {
|
|
@@ -2231,10 +2590,10 @@ async function answer(context, apiUrl, token, roots, request) {
|
|
|
2231
2590
|
}
|
|
2232
2591
|
try {
|
|
2233
2592
|
let target = located;
|
|
2234
|
-
if (
|
|
2593
|
+
if (existsSync6(located) && statSync4(located).isDirectory()) {
|
|
2235
2594
|
const disposition = fetched.headers.get("content-disposition") ?? "";
|
|
2236
2595
|
const named = /filename="([^"]+)"/.exec(disposition)?.[1];
|
|
2237
|
-
target =
|
|
2596
|
+
target = join6(located, basename(named ?? "file"));
|
|
2238
2597
|
}
|
|
2239
2598
|
target = uncontested(target);
|
|
2240
2599
|
writeFileSync4(target, downloaded, { flag: "wx" });
|
|
@@ -2257,12 +2616,12 @@ ${downloaded.length} bytes
|
|
|
2257
2616
|
let filename = request.path.split("/").pop() ?? "file";
|
|
2258
2617
|
if (request.kind === "list_dir") {
|
|
2259
2618
|
try {
|
|
2260
|
-
const stats =
|
|
2619
|
+
const stats = statSync4(located);
|
|
2261
2620
|
if (!stats.isDirectory()) return { ok: false, error: `${request.path} is not a folder.` };
|
|
2262
|
-
const entries =
|
|
2621
|
+
const entries = readdirSync2(located, { withFileTypes: true }).filter((entry) => !entry.name.startsWith(".")).slice(0, MAX_LISTED).map((entry) => {
|
|
2263
2622
|
if (entry.isDirectory()) return `${entry.name}/`;
|
|
2264
2623
|
try {
|
|
2265
|
-
return `${entry.name} ${sizeOf(
|
|
2624
|
+
return `${entry.name} ${sizeOf(join6(located, entry.name))}`;
|
|
2266
2625
|
} catch {
|
|
2267
2626
|
return entry.name;
|
|
2268
2627
|
}
|
|
@@ -2280,9 +2639,9 @@ ${listing}
|
|
|
2280
2639
|
}
|
|
2281
2640
|
}
|
|
2282
2641
|
try {
|
|
2283
|
-
const stats =
|
|
2642
|
+
const stats = statSync4(located);
|
|
2284
2643
|
if (!stats.isFile()) return { ok: false, error: `${request.path} is not a file.` };
|
|
2285
|
-
bytes =
|
|
2644
|
+
bytes = readFileSync6(located);
|
|
2286
2645
|
} catch (error) {
|
|
2287
2646
|
return { ok: false, error: error instanceof Error ? error.message : "could not read it" };
|
|
2288
2647
|
}
|
|
@@ -2322,18 +2681,9 @@ async function upload(apiUrl, token, filename, bytes) {
|
|
|
2322
2681
|
if (!stored.attachToken) return { ok: false, error: "the upload returned no reference" };
|
|
2323
2682
|
return { ok: true, attachToken: stored.attachToken, bytes: bytes.length };
|
|
2324
2683
|
}
|
|
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
2684
|
var MAX_LISTED = 200;
|
|
2335
2685
|
function sizeOf(path) {
|
|
2336
|
-
const size =
|
|
2686
|
+
const size = statSync4(path).size;
|
|
2337
2687
|
if (size < 1024) return `${size} B`;
|
|
2338
2688
|
if (size < 1024 * 1024) return `${Math.round(size / 1024)} KB`;
|
|
2339
2689
|
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
@@ -2345,26 +2695,41 @@ async function agentCommand(context) {
|
|
|
2345
2695
|
return 1;
|
|
2346
2696
|
}
|
|
2347
2697
|
const roots = (listFlag(context.args, "root") ?? []).map(
|
|
2348
|
-
(one) =>
|
|
2698
|
+
(one) => resolve4(one.startsWith("~") ? resolve4(homedir3(), one.slice(1).replace(/^[/\\]/, "")) : one)
|
|
2349
2699
|
);
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2700
|
+
const configPath = join6(context.paths.dir, "agent.json");
|
|
2701
|
+
const stored = readAgentConfig(configPath);
|
|
2702
|
+
if (stored.error) {
|
|
2703
|
+
context.error(stored.error);
|
|
2704
|
+
return 1;
|
|
2705
|
+
}
|
|
2706
|
+
const settings = resolveAgentConfig({
|
|
2707
|
+
flagRoots: roots,
|
|
2708
|
+
...stored.config ? { stored: stored.config } : {},
|
|
2709
|
+
discovered: discoverRoots()
|
|
2710
|
+
});
|
|
2711
|
+
if (settings.roots.length === 0) {
|
|
2712
|
+
context.error(
|
|
2713
|
+
"There is nothing in your home directory this machine would offer, so say which folders it may read from:"
|
|
2363
2714
|
);
|
|
2715
|
+
context.error("");
|
|
2716
|
+
context.error(" pm agent --root ~/projects");
|
|
2717
|
+
context.error("");
|
|
2718
|
+
context.error(`Or write them once in ${configPath}:`);
|
|
2719
|
+
context.error("");
|
|
2720
|
+
context.error(' { "roots": ["~/projects"], "mode": "ask" }');
|
|
2721
|
+
return 1;
|
|
2364
2722
|
}
|
|
2723
|
+
roots.length = 0;
|
|
2724
|
+
roots.push(...settings.roots);
|
|
2725
|
+
const shown = settings.roots.map((path) => path.replace(homedir3(), "~"));
|
|
2726
|
+
const where = settings.source === "flags" ? "" : settings.source === "config" ? ` (from ${configPath})` : " (found in your home directory)";
|
|
2727
|
+
context.print(
|
|
2728
|
+
`Reading ${shown.join(", ")} \u2014 nothing outside them${where}. Mode: ${settings.mode}.`
|
|
2729
|
+
);
|
|
2365
2730
|
for (const root of roots) {
|
|
2366
2731
|
try {
|
|
2367
|
-
if (!
|
|
2732
|
+
if (!statSync4(root).isDirectory()) {
|
|
2368
2733
|
context.error(`${root} is not a folder.`);
|
|
2369
2734
|
return 1;
|
|
2370
2735
|
}
|
|
@@ -2438,7 +2803,7 @@ async function agentCommand(context) {
|
|
|
2438
2803
|
const { items } = await claimed.json();
|
|
2439
2804
|
for (const request of items) {
|
|
2440
2805
|
context.print(`Reading ${request.path}`);
|
|
2441
|
-
const outcome = await answer(context, apiUrl, await authorization(),
|
|
2806
|
+
const outcome = await answer(context, apiUrl, await authorization(), settings, request);
|
|
2442
2807
|
const done = await call(
|
|
2443
2808
|
`complete/${encodeURIComponent(request.id)}`,
|
|
2444
2809
|
outcome.ok ? { result: { attachToken: outcome.attachToken } } : { error: outcome.error }
|
|
@@ -2460,8 +2825,8 @@ async function agentCommand(context) {
|
|
|
2460
2825
|
|
|
2461
2826
|
// src/commands/google.ts
|
|
2462
2827
|
import { writeFileSync as writeFileSync5 } from "node:fs";
|
|
2463
|
-
import { basename as basename2, resolve as
|
|
2464
|
-
import { readFileSync as
|
|
2828
|
+
import { basename as basename2, resolve as resolve5 } from "node:path";
|
|
2829
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
2465
2830
|
async function callApi(context, path, init = {}) {
|
|
2466
2831
|
const credential = context.resolved.credential;
|
|
2467
2832
|
if (!credential) {
|
|
@@ -2526,7 +2891,7 @@ async function driveGet(context, fileId) {
|
|
|
2526
2891
|
const disposition = response.headers.get("content-disposition") ?? "";
|
|
2527
2892
|
const named = /filename="([^"]+)"/.exec(disposition)?.[1];
|
|
2528
2893
|
const out = stringFlag(context.args, "out");
|
|
2529
|
-
const target =
|
|
2894
|
+
const target = resolve5(out ?? basename2(named ?? fileId));
|
|
2530
2895
|
writeFileSync5(target, Buffer.from(await response.arrayBuffer()));
|
|
2531
2896
|
context.print(target);
|
|
2532
2897
|
return 0;
|
|
@@ -2538,7 +2903,7 @@ async function drivePut(context, path) {
|
|
|
2538
2903
|
}
|
|
2539
2904
|
let bytes;
|
|
2540
2905
|
try {
|
|
2541
|
-
bytes =
|
|
2906
|
+
bytes = readFileSync7(resolve5(path));
|
|
2542
2907
|
} catch {
|
|
2543
2908
|
context.error(`Cannot read ${path}.`);
|
|
2544
2909
|
return 1;
|
|
@@ -2622,8 +2987,8 @@ async function mailCommand(context) {
|
|
|
2622
2987
|
}
|
|
2623
2988
|
|
|
2624
2989
|
// src/commands/requests.ts
|
|
2625
|
-
import { existsSync as
|
|
2626
|
-
import { resolve as
|
|
2990
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2991
|
+
import { resolve as resolve6 } from "node:path";
|
|
2627
2992
|
async function requestsCommand(context) {
|
|
2628
2993
|
if (context.args.words[1] === "get") return collectCommand(context);
|
|
2629
2994
|
const credential = context.resolved.credential;
|
|
@@ -2691,8 +3056,8 @@ async function collectCommand(context) {
|
|
|
2691
3056
|
return 1;
|
|
2692
3057
|
}
|
|
2693
3058
|
const name = stringFlag(context.args, "output", "o") ?? filename;
|
|
2694
|
-
const target =
|
|
2695
|
-
if (
|
|
3059
|
+
const target = resolve6(name);
|
|
3060
|
+
if (existsSync7(target)) {
|
|
2696
3061
|
context.error(`${target} already exists. Pass --output to write somewhere else.`);
|
|
2697
3062
|
return 1;
|
|
2698
3063
|
}
|
|
@@ -2702,14 +3067,14 @@ async function collectCommand(context) {
|
|
|
2702
3067
|
}
|
|
2703
3068
|
|
|
2704
3069
|
// src/workspace.ts
|
|
2705
|
-
import { existsSync as
|
|
2706
|
-
import { dirname as dirname3, join as
|
|
3070
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
3071
|
+
import { dirname as dirname3, join as join7, resolve as resolvePath2 } from "node:path";
|
|
2707
3072
|
var WORKSPACE_FILE = ".persistmemory.json";
|
|
2708
3073
|
function findWorkspace(from = process.cwd()) {
|
|
2709
|
-
let dir =
|
|
3074
|
+
let dir = resolvePath2(from);
|
|
2710
3075
|
for (; ; ) {
|
|
2711
|
-
const file =
|
|
2712
|
-
if (
|
|
3076
|
+
const file = join7(dir, WORKSPACE_FILE);
|
|
3077
|
+
if (existsSync8(file)) {
|
|
2713
3078
|
const config = readWorkspace(file);
|
|
2714
3079
|
if (config) return { file, dir, config };
|
|
2715
3080
|
}
|
|
@@ -2720,7 +3085,7 @@ function findWorkspace(from = process.cwd()) {
|
|
|
2720
3085
|
}
|
|
2721
3086
|
function readWorkspace(file) {
|
|
2722
3087
|
try {
|
|
2723
|
-
const parsed = JSON.parse(
|
|
3088
|
+
const parsed = JSON.parse(readFileSync8(file, "utf8"));
|
|
2724
3089
|
const space = parsed.space;
|
|
2725
3090
|
if (space && typeof space === "object" && typeof space.id === "string" && space.id !== "" && typeof space.name === "string") {
|
|
2726
3091
|
return { space: { id: space.id, name: space.name } };
|
|
@@ -2731,7 +3096,7 @@ function readWorkspace(file) {
|
|
|
2731
3096
|
}
|
|
2732
3097
|
}
|
|
2733
3098
|
function writeWorkspace(dir, config) {
|
|
2734
|
-
const file =
|
|
3099
|
+
const file = join7(dir, WORKSPACE_FILE);
|
|
2735
3100
|
writeFileSync7(file, `${JSON.stringify(config, null, 2)}
|
|
2736
3101
|
`, "utf8");
|
|
2737
3102
|
return file;
|
|
@@ -3001,9 +3366,9 @@ function message(error) {
|
|
|
3001
3366
|
}
|
|
3002
3367
|
|
|
3003
3368
|
// src/commands/maintain.ts
|
|
3004
|
-
import { existsSync as
|
|
3369
|
+
import { existsSync as existsSync9, rmSync } from "node:fs";
|
|
3005
3370
|
import { spawnSync } from "node:child_process";
|
|
3006
|
-
import { dirname as dirname4, resolve as
|
|
3371
|
+
import { dirname as dirname4, resolve as resolve8 } from "node:path";
|
|
3007
3372
|
import { fileURLToPath } from "node:url";
|
|
3008
3373
|
function installArgs(pkg = PACKAGE) {
|
|
3009
3374
|
return ["install", "-g", "--prefer-online", `${pkg}@latest`];
|
|
@@ -3067,7 +3432,7 @@ Delete the file it runs from: ${processPath()}`
|
|
|
3067
3432
|
return 0;
|
|
3068
3433
|
}
|
|
3069
3434
|
async function deleteCommand(context) {
|
|
3070
|
-
if (!
|
|
3435
|
+
if (!existsSync9(context.paths.dir)) {
|
|
3071
3436
|
context.print(`Nothing to delete: ${context.paths.dir} does not exist.`);
|
|
3072
3437
|
return 0;
|
|
3073
3438
|
}
|
|
@@ -3096,7 +3461,7 @@ async function deleteCommand(context) {
|
|
|
3096
3461
|
return 0;
|
|
3097
3462
|
}
|
|
3098
3463
|
function removeEverything(context) {
|
|
3099
|
-
const dir =
|
|
3464
|
+
const dir = resolve8(context.paths.dir);
|
|
3100
3465
|
if (dir === "/" || dir.split("/").filter(Boolean).length < 2) {
|
|
3101
3466
|
context.error(`Refusing to delete ${dir}: that does not look like a data directory.`);
|
|
3102
3467
|
return;
|
|
@@ -3108,7 +3473,7 @@ function installer() {
|
|
|
3108
3473
|
}
|
|
3109
3474
|
function processPath() {
|
|
3110
3475
|
try {
|
|
3111
|
-
return
|
|
3476
|
+
return resolve8(dirname4(fileURLToPath(import.meta.url)));
|
|
3112
3477
|
} catch {
|
|
3113
3478
|
return process.argv[1] ?? "";
|
|
3114
3479
|
}
|
|
@@ -3120,12 +3485,12 @@ import { randomUUID } from "node:crypto";
|
|
|
3120
3485
|
import { relative as relative2 } from "node:path";
|
|
3121
3486
|
|
|
3122
3487
|
// src/events.ts
|
|
3123
|
-
import { appendFileSync, existsSync as
|
|
3124
|
-
import { join as
|
|
3488
|
+
import { appendFileSync, existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync9 } from "node:fs";
|
|
3489
|
+
import { join as join8 } from "node:path";
|
|
3125
3490
|
function openSessionLog(paths, id) {
|
|
3126
|
-
const directory =
|
|
3491
|
+
const directory = join8(paths.dir, "sessions");
|
|
3127
3492
|
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
3128
|
-
const path =
|
|
3493
|
+
const path = join8(directory, `${id}.jsonl`);
|
|
3129
3494
|
return {
|
|
3130
3495
|
id,
|
|
3131
3496
|
path,
|
|
@@ -3137,8 +3502,8 @@ function openSessionLog(paths, id) {
|
|
|
3137
3502
|
}
|
|
3138
3503
|
},
|
|
3139
3504
|
read() {
|
|
3140
|
-
if (!
|
|
3141
|
-
return
|
|
3505
|
+
if (!existsSync10(path)) return [];
|
|
3506
|
+
return readFileSync9(path, "utf8").split("\n").filter((line) => line.trim() !== "").flatMap((line) => {
|
|
3142
3507
|
try {
|
|
3143
3508
|
return [JSON.parse(line)];
|
|
3144
3509
|
} catch {
|
|
@@ -3215,9 +3580,9 @@ async function sessionCommand(context) {
|
|
|
3215
3580
|
context.print(` Ask anything. /help for commands, /exit to leave.
|
|
3216
3581
|
`);
|
|
3217
3582
|
const readline = createInterface2({ input: process.stdin, output: process.stdout });
|
|
3218
|
-
const ask = (prompt) => new Promise((
|
|
3219
|
-
readline.question(prompt,
|
|
3220
|
-
readline.once("close", () =>
|
|
3583
|
+
const ask = (prompt) => new Promise((resolve9) => {
|
|
3584
|
+
readline.question(prompt, resolve9);
|
|
3585
|
+
readline.once("close", () => resolve9(void 0));
|
|
3221
3586
|
});
|
|
3222
3587
|
const root = process.cwd();
|
|
3223
3588
|
let running = true;
|
|
@@ -3401,7 +3766,7 @@ async function write2(args) {
|
|
|
3401
3766
|
}
|
|
3402
3767
|
|
|
3403
3768
|
// src/commands/memory.ts
|
|
3404
|
-
import { readFileSync as
|
|
3769
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
3405
3770
|
|
|
3406
3771
|
// src/spaces.ts
|
|
3407
3772
|
async function spacesFor(context, client, env = process.env) {
|
|
@@ -3465,7 +3830,7 @@ async function rememberCommand(context) {
|
|
|
3465
3830
|
let text;
|
|
3466
3831
|
if (file) {
|
|
3467
3832
|
try {
|
|
3468
|
-
text =
|
|
3833
|
+
text = readFileSync10(file, "utf8");
|
|
3469
3834
|
} catch {
|
|
3470
3835
|
context.error(`Could not read ${file}.`);
|
|
3471
3836
|
return 1;
|