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