@persistmemory/cli 0.1.1 → 0.1.2
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 +215 -49
- package/dist/bin.js.map +4 -4
- package/dist/index.js +215 -49
- package/dist/index.js.map +4 -4
- package/package.json +2 -2
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((resolve7, 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
|
+
resolve7(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((resolve7, 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
|
+
resolve7();
|
|
373
373
|
}, ms);
|
|
374
374
|
function onAbort() {
|
|
375
375
|
clearTimeout(timer);
|
|
@@ -556,6 +556,58 @@ var Spaces = class {
|
|
|
556
556
|
async create(params, options) {
|
|
557
557
|
return this.#http.post("/api/v1/spaces", params, options);
|
|
558
558
|
}
|
|
559
|
+
/**
|
|
560
|
+
* Deletes a Space. You must say what happens to what is in it.
|
|
561
|
+
*
|
|
562
|
+
* There is no default, here or in the API, and that is deliberate: "delete
|
|
563
|
+
* this Space" means the label to some people and everything inside it to
|
|
564
|
+
* others, and a client that guessed would destroy or keep somebody's
|
|
565
|
+
* material without being asked.
|
|
566
|
+
*
|
|
567
|
+
* `delete` never destroys a memory that is filed in another Space as well —
|
|
568
|
+
* that one is detached and left alone. `deleted` and `kept` come back so you
|
|
569
|
+
* can say what actually happened.
|
|
570
|
+
*/
|
|
571
|
+
async delete(id, params, options) {
|
|
572
|
+
return this.#http.delete(
|
|
573
|
+
`/api/v1/spaces/${encodeURIComponent(id)}`,
|
|
574
|
+
params,
|
|
575
|
+
options
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Merges Spaces into a NEW one, leaving every source exactly as it was.
|
|
580
|
+
*
|
|
581
|
+
* Additive, not destructive: a memory ends up in the sources AND the result,
|
|
582
|
+
* every existing search over a source returns what it did before, and
|
|
583
|
+
* undoing it is deleting the Space this returns. A memory in two sources is
|
|
584
|
+
* filed once.
|
|
585
|
+
*/
|
|
586
|
+
async merge(params, options) {
|
|
587
|
+
return this.#http.post(
|
|
588
|
+
"/api/v1/spaces/merge",
|
|
589
|
+
params,
|
|
590
|
+
options
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* The Space this account files into when a capture names none.
|
|
595
|
+
*
|
|
596
|
+
* `{}` — an object with no `space` — means there is no default, which is the
|
|
597
|
+
* normal state rather than a gap. It is also what comes back after the Space
|
|
598
|
+
* somebody chose has been deleted.
|
|
599
|
+
*/
|
|
600
|
+
async getDefault(options) {
|
|
601
|
+
return this.#http.get("/api/v1/spaces/default", void 0, options);
|
|
602
|
+
}
|
|
603
|
+
/** `null` clears it. Not the same as omitting it, which is why the type says so. */
|
|
604
|
+
async setDefault(spaceId, options) {
|
|
605
|
+
return this.#http.patch(
|
|
606
|
+
"/api/v1/spaces/default",
|
|
607
|
+
{ spaceId },
|
|
608
|
+
options
|
|
609
|
+
);
|
|
610
|
+
}
|
|
559
611
|
/** Renaming, retention, and archiving - `archived` is a field, not a verb. */
|
|
560
612
|
async update(id, params, options) {
|
|
561
613
|
return this.#http.patch(`/api/v1/spaces/${encodeURIComponent(id)}`, params, options);
|
|
@@ -918,6 +970,42 @@ var Health = class {
|
|
|
918
970
|
return this.#http.get("/health/ready", void 0, options);
|
|
919
971
|
}
|
|
920
972
|
};
|
|
973
|
+
var Agent = class {
|
|
974
|
+
#http;
|
|
975
|
+
constructor(http) {
|
|
976
|
+
this.#http = http;
|
|
977
|
+
}
|
|
978
|
+
/** The row, including whether it finished and how large the result is. */
|
|
979
|
+
async request(id, options) {
|
|
980
|
+
return this.#http.get(
|
|
981
|
+
`/api/v1/agent/request/${encodeURIComponent(id)}`,
|
|
982
|
+
void 0,
|
|
983
|
+
options
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* A short-lived link to the bytes of a finished request.
|
|
988
|
+
*
|
|
989
|
+
* Returns the URL rather than the file, and that is a deliberate limit of
|
|
990
|
+
* this package rather than an oversight. The transport under every other
|
|
991
|
+
* method parses JSON, retries, and attaches the API key; none of those is
|
|
992
|
+
* right for a hundred-megabyte binary body, and building a second request
|
|
993
|
+
* path inside the SDK to serve one method is how a client ends up with two
|
|
994
|
+
* retry policies that differ only during an outage. Fetch the URL with
|
|
995
|
+
* whatever already streams in your runtime - it needs no credential, which
|
|
996
|
+
* is the whole reason it is signed.
|
|
997
|
+
*
|
|
998
|
+
* Treat the URL as the file. It is a bearer credential for exactly one
|
|
999
|
+
* object, it expires in minutes, and it should not be logged or stored.
|
|
1000
|
+
*/
|
|
1001
|
+
async downloadLink(id, options) {
|
|
1002
|
+
return this.#http.get(
|
|
1003
|
+
`/api/v1/agent/request/${encodeURIComponent(id)}/download`,
|
|
1004
|
+
void 0,
|
|
1005
|
+
options
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
921
1009
|
var PersistMemory = class {
|
|
922
1010
|
memories;
|
|
923
1011
|
search;
|
|
@@ -931,6 +1019,7 @@ var PersistMemory = class {
|
|
|
931
1019
|
conversations;
|
|
932
1020
|
integrations;
|
|
933
1021
|
health;
|
|
1022
|
+
agent;
|
|
934
1023
|
#http;
|
|
935
1024
|
constructor(options) {
|
|
936
1025
|
this.#http = new HttpClient(options);
|
|
@@ -946,6 +1035,7 @@ var PersistMemory = class {
|
|
|
946
1035
|
this.conversations = new Conversations(this.#http);
|
|
947
1036
|
this.integrations = new Integrations(this.#http);
|
|
948
1037
|
this.health = new Health(this.#http);
|
|
1038
|
+
this.agent = new Agent(this.#http);
|
|
949
1039
|
}
|
|
950
1040
|
/**
|
|
951
1041
|
* An escape hatch for an endpoint this package has not caught up with.
|
|
@@ -1287,7 +1377,7 @@ function shortDate(iso) {
|
|
|
1287
1377
|
}
|
|
1288
1378
|
|
|
1289
1379
|
// src/help.ts
|
|
1290
|
-
var VERSION = "0.1.
|
|
1380
|
+
var VERSION = "0.1.2";
|
|
1291
1381
|
var PACKAGE = "@persistmemory/cli";
|
|
1292
1382
|
var HELP = `
|
|
1293
1383
|
pm \u2014 PersistMemory from your terminal
|
|
@@ -1334,6 +1424,7 @@ var HELP = `
|
|
|
1334
1424
|
|
|
1335
1425
|
status is the service healthy
|
|
1336
1426
|
requests file requests waiting for you to approve
|
|
1427
|
+
requests get <id> write a finished one to a file here
|
|
1337
1428
|
|
|
1338
1429
|
update install the newest version
|
|
1339
1430
|
uninstall remove pm from this machine
|
|
@@ -1477,8 +1568,8 @@ async function startLoopback(options = {}) {
|
|
|
1477
1568
|
const timeoutMs = options.timeoutMs ?? 5 * 60 * 1e3;
|
|
1478
1569
|
let resolveCallback;
|
|
1479
1570
|
let rejectCallback;
|
|
1480
|
-
const received = new Promise((
|
|
1481
|
-
resolveCallback =
|
|
1571
|
+
const received = new Promise((resolve7, reject) => {
|
|
1572
|
+
resolveCallback = resolve7;
|
|
1482
1573
|
rejectCallback = reject;
|
|
1483
1574
|
});
|
|
1484
1575
|
const server = createServer((request, response) => {
|
|
@@ -1504,9 +1595,9 @@ async function startLoopback(options = {}) {
|
|
|
1504
1595
|
response.end(donePage(callback));
|
|
1505
1596
|
resolveCallback?.(callback);
|
|
1506
1597
|
});
|
|
1507
|
-
await new Promise((
|
|
1598
|
+
await new Promise((resolve7, reject) => {
|
|
1508
1599
|
server.once("error", reject);
|
|
1509
|
-
server.listen(0, "127.0.0.1",
|
|
1600
|
+
server.listen(0, "127.0.0.1", resolve7);
|
|
1510
1601
|
});
|
|
1511
1602
|
const address = server.address();
|
|
1512
1603
|
if (address === null || typeof address === "string") {
|
|
@@ -1768,7 +1859,7 @@ function safeEqual(a, b) {
|
|
|
1768
1859
|
}
|
|
1769
1860
|
async function openBrowser(url) {
|
|
1770
1861
|
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
1771
|
-
await new Promise((
|
|
1862
|
+
await new Promise((resolve7, reject) => {
|
|
1772
1863
|
const child = spawn(command, args, {
|
|
1773
1864
|
stdio: "ignore",
|
|
1774
1865
|
// Detached so closing the terminal does not close the browser, and so
|
|
@@ -1777,7 +1868,7 @@ async function openBrowser(url) {
|
|
|
1777
1868
|
});
|
|
1778
1869
|
child.once("error", reject);
|
|
1779
1870
|
child.unref();
|
|
1780
|
-
|
|
1871
|
+
resolve7();
|
|
1781
1872
|
});
|
|
1782
1873
|
}
|
|
1783
1874
|
async function describe(response) {
|
|
@@ -1839,13 +1930,13 @@ async function currentCredential(resolved, deps) {
|
|
|
1839
1930
|
// src/context.ts
|
|
1840
1931
|
import { createInterface } from "node:readline";
|
|
1841
1932
|
async function askOnTty(prompt) {
|
|
1842
|
-
return new Promise((
|
|
1933
|
+
return new Promise((resolve7) => {
|
|
1843
1934
|
const readline = createInterface({ input: process.stdin, output: process.stdout });
|
|
1844
1935
|
readline.question(prompt, (answer3) => {
|
|
1845
1936
|
readline.close();
|
|
1846
|
-
|
|
1937
|
+
resolve7(answer3.trim());
|
|
1847
1938
|
});
|
|
1848
|
-
readline.once("close", () =>
|
|
1939
|
+
readline.once("close", () => resolve7(""));
|
|
1849
1940
|
});
|
|
1850
1941
|
}
|
|
1851
1942
|
var ETX = "";
|
|
@@ -1854,13 +1945,13 @@ var BACKSPACE = "\b";
|
|
|
1854
1945
|
async function readSecretFromTty(prompt) {
|
|
1855
1946
|
const input = process.stdin;
|
|
1856
1947
|
if (!input.isTTY) {
|
|
1857
|
-
return new Promise((
|
|
1948
|
+
return new Promise((resolve7) => {
|
|
1858
1949
|
const readline = createInterface({ input });
|
|
1859
1950
|
readline.once("line", (line) => {
|
|
1860
1951
|
readline.close();
|
|
1861
|
-
|
|
1952
|
+
resolve7(line.trim());
|
|
1862
1953
|
});
|
|
1863
|
-
readline.once("close", () =>
|
|
1954
|
+
readline.once("close", () => resolve7(""));
|
|
1864
1955
|
});
|
|
1865
1956
|
}
|
|
1866
1957
|
process.stdout.write(prompt);
|
|
@@ -1868,14 +1959,14 @@ async function readSecretFromTty(prompt) {
|
|
|
1868
1959
|
input.setRawMode?.(true);
|
|
1869
1960
|
input.resume();
|
|
1870
1961
|
input.setEncoding("utf8");
|
|
1871
|
-
return new Promise((
|
|
1962
|
+
return new Promise((resolve7) => {
|
|
1872
1963
|
let value = "";
|
|
1873
1964
|
const finish2 = () => {
|
|
1874
1965
|
input.removeListener("data", onData);
|
|
1875
1966
|
input.setRawMode?.(previouslyRaw);
|
|
1876
1967
|
input.pause();
|
|
1877
1968
|
process.stdout.write("\n");
|
|
1878
|
-
|
|
1969
|
+
resolve7(value.trim());
|
|
1879
1970
|
};
|
|
1880
1971
|
const onData = (chunk) => {
|
|
1881
1972
|
for (const character of chunk) {
|
|
@@ -1912,8 +2003,8 @@ async function readStdin() {
|
|
|
1912
2003
|
// src/commands/agent.ts
|
|
1913
2004
|
import { hostname } from "node:os";
|
|
1914
2005
|
import { homedir as homedir2 } from "node:os";
|
|
1915
|
-
import { resolve as resolve3 } from "node:path";
|
|
1916
|
-
import { readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
|
|
2006
|
+
import { join as join4, resolve as resolve3 } from "node:path";
|
|
2007
|
+
import { readFileSync as readFileSync4, readdirSync, statSync as statSync2 } from "node:fs";
|
|
1917
2008
|
|
|
1918
2009
|
// src/files.ts
|
|
1919
2010
|
import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync, statSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
@@ -1926,11 +2017,13 @@ var OutsideWorkspace = class extends Error {
|
|
|
1926
2017
|
};
|
|
1927
2018
|
var TooLarge = class extends Error {
|
|
1928
2019
|
constructor(path, bytes, limit) {
|
|
1929
|
-
|
|
2020
|
+
const say = (value) => value >= 1024 * 1024 ? `${(value / (1024 * 1024)).toFixed(1)} MB` : `${Math.round(value / 1024)} KB`;
|
|
2021
|
+
super(`${path} is ${say(bytes)}, over the ${say(limit)} limit.`);
|
|
1930
2022
|
this.name = "TooLarge";
|
|
1931
2023
|
}
|
|
1932
2024
|
};
|
|
1933
2025
|
var MAX_READ_BYTES = 512 * 1024;
|
|
2026
|
+
var MAX_TRANSFER_BYTES = 100 * 1024 * 1024;
|
|
1934
2027
|
function realLocation(absolute) {
|
|
1935
2028
|
let existing = absolute;
|
|
1936
2029
|
const trailing = [];
|
|
@@ -2044,19 +2137,41 @@ async function answer(context, apiUrl, token, roots, request) {
|
|
|
2044
2137
|
return { ok: false, error: error instanceof Error ? error.message : "refused" };
|
|
2045
2138
|
}
|
|
2046
2139
|
let bytes;
|
|
2140
|
+
let filename = request.path.split("/").pop() ?? "file";
|
|
2141
|
+
if (request.kind === "list_dir") {
|
|
2142
|
+
try {
|
|
2143
|
+
const stats = statSync2(located);
|
|
2144
|
+
if (!stats.isDirectory()) return { ok: false, error: `${request.path} is not a folder.` };
|
|
2145
|
+
const entries = readdirSync(located, { withFileTypes: true }).filter((entry) => !entry.name.startsWith(".")).slice(0, MAX_LISTED).map((entry) => {
|
|
2146
|
+
if (entry.isDirectory()) return `${entry.name}/`;
|
|
2147
|
+
try {
|
|
2148
|
+
return `${entry.name} ${sizeOf(join4(located, entry.name))}`;
|
|
2149
|
+
} catch {
|
|
2150
|
+
return entry.name;
|
|
2151
|
+
}
|
|
2152
|
+
}).sort();
|
|
2153
|
+
const listing = entries.length > 0 ? entries.join("\n") : "(empty)";
|
|
2154
|
+
bytes = Buffer.from(`${request.path}
|
|
2155
|
+
|
|
2156
|
+
${listing}
|
|
2157
|
+
`, "utf8");
|
|
2158
|
+
filename = `${request.path.split("/").filter(Boolean).pop() ?? "listing"}.txt`;
|
|
2159
|
+
const grant = await upload(apiUrl, token, filename, bytes);
|
|
2160
|
+
return grant;
|
|
2161
|
+
} catch (error) {
|
|
2162
|
+
return { ok: false, error: error instanceof Error ? error.message : "could not list it" };
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2047
2165
|
try {
|
|
2048
2166
|
const stats = statSync2(located);
|
|
2049
2167
|
if (!stats.isFile()) return { ok: false, error: `${request.path} is not a file.` };
|
|
2050
|
-
if (stats.size > MAX_READ_BYTES) {
|
|
2051
|
-
return {
|
|
2052
|
-
ok: false,
|
|
2053
|
-
error: new TooLarge(request.path, stats.size, MAX_READ_BYTES).message
|
|
2054
|
-
};
|
|
2055
|
-
}
|
|
2056
2168
|
bytes = readFileSync4(located);
|
|
2057
2169
|
} catch (error) {
|
|
2058
2170
|
return { ok: false, error: error instanceof Error ? error.message : "could not read it" };
|
|
2059
2171
|
}
|
|
2172
|
+
return upload(apiUrl, token, filename, bytes);
|
|
2173
|
+
}
|
|
2174
|
+
async function upload(apiUrl, token, filename, bytes) {
|
|
2060
2175
|
const grant = await fetch(`${apiUrl}/api/v1/agent/upload-url`, {
|
|
2061
2176
|
method: "POST",
|
|
2062
2177
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
@@ -2068,13 +2183,17 @@ async function answer(context, apiUrl, token, roots, request) {
|
|
|
2068
2183
|
// No content type is sent: this machine has a path, not a declaration.
|
|
2069
2184
|
// The server resolves it from the name against the one table that knows
|
|
2070
2185
|
// which types it can read, and tells us below what it decided.
|
|
2071
|
-
filename
|
|
2186
|
+
filename
|
|
2072
2187
|
})
|
|
2073
2188
|
});
|
|
2074
2189
|
if (!grant.ok) {
|
|
2075
2190
|
return { ok: false, error: await said(grant, "could not get an upload url") };
|
|
2076
2191
|
}
|
|
2077
|
-
const { uploadUrl, contentType } = await grant.json();
|
|
2192
|
+
const { uploadUrl, contentType, maxBytes } = await grant.json();
|
|
2193
|
+
const limit = maxBytes ?? MAX_TRANSFER_BYTES;
|
|
2194
|
+
if (bytes.length > limit) {
|
|
2195
|
+
return { ok: false, error: new TooLarge(filename, bytes.length, limit).message };
|
|
2196
|
+
}
|
|
2078
2197
|
const put = await fetch(uploadUrl, {
|
|
2079
2198
|
method: "PUT",
|
|
2080
2199
|
// The type the grant was signed for. Anything else is refused.
|
|
@@ -2086,6 +2205,13 @@ async function answer(context, apiUrl, token, roots, request) {
|
|
|
2086
2205
|
if (!stored.attachToken) return { ok: false, error: "the upload returned no reference" };
|
|
2087
2206
|
return { ok: true, attachToken: stored.attachToken, bytes: bytes.length };
|
|
2088
2207
|
}
|
|
2208
|
+
var MAX_LISTED = 200;
|
|
2209
|
+
function sizeOf(path) {
|
|
2210
|
+
const size = statSync2(path).size;
|
|
2211
|
+
if (size < 1024) return `${size} B`;
|
|
2212
|
+
if (size < 1024 * 1024) return `${Math.round(size / 1024)} KB`;
|
|
2213
|
+
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
2214
|
+
}
|
|
2089
2215
|
async function agentCommand(context) {
|
|
2090
2216
|
const credential = context.resolved.credential;
|
|
2091
2217
|
if (!credential) {
|
|
@@ -2195,7 +2321,10 @@ async function agentCommand(context) {
|
|
|
2195
2321
|
}
|
|
2196
2322
|
|
|
2197
2323
|
// src/commands/requests.ts
|
|
2324
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2325
|
+
import { resolve as resolve4 } from "node:path";
|
|
2198
2326
|
async function requestsCommand(context) {
|
|
2327
|
+
if (context.args.words[1] === "get") return collectCommand(context);
|
|
2199
2328
|
const credential = context.resolved.credential;
|
|
2200
2329
|
if (!credential) {
|
|
2201
2330
|
context.error("Sign in first: pm auth login");
|
|
@@ -2233,16 +2362,53 @@ async function requestsCommand(context) {
|
|
|
2233
2362
|
context.print("They cannot be approved from here \u2014 see `pm help requests`.");
|
|
2234
2363
|
return 0;
|
|
2235
2364
|
}
|
|
2365
|
+
async function collectCommand(context) {
|
|
2366
|
+
const credential = context.resolved.credential;
|
|
2367
|
+
if (!credential) {
|
|
2368
|
+
context.error("Sign in first: pm auth login");
|
|
2369
|
+
return 1;
|
|
2370
|
+
}
|
|
2371
|
+
const id = context.args.words[2];
|
|
2372
|
+
if (!id) {
|
|
2373
|
+
context.error("Which request? `pm requests` lists them with their ids.");
|
|
2374
|
+
return 2;
|
|
2375
|
+
}
|
|
2376
|
+
const apiUrl = context.resolved.apiUrl.replace(/\/+$/, "");
|
|
2377
|
+
const link = await fetch(
|
|
2378
|
+
`${apiUrl}/api/v1/agent/request/${encodeURIComponent(id)}/download`,
|
|
2379
|
+
{ headers: { authorization: `Bearer ${credential.token}` } }
|
|
2380
|
+
);
|
|
2381
|
+
if (!link.ok) {
|
|
2382
|
+
const body = await link.json().catch(() => void 0);
|
|
2383
|
+
context.error(body?.error?.message ?? `Could not prepare that file (${link.status}).`);
|
|
2384
|
+
return 1;
|
|
2385
|
+
}
|
|
2386
|
+
const { downloadUrl, filename } = await link.json();
|
|
2387
|
+
const file = await fetch(downloadUrl);
|
|
2388
|
+
if (!file.ok) {
|
|
2389
|
+
context.error(`The download refused it (${file.status}). Links expire in minutes \u2014 try again.`);
|
|
2390
|
+
return 1;
|
|
2391
|
+
}
|
|
2392
|
+
const name = stringFlag(context.args, "output", "o") ?? filename;
|
|
2393
|
+
const target = resolve4(name);
|
|
2394
|
+
if (existsSync4(target)) {
|
|
2395
|
+
context.error(`${target} already exists. Pass --output to write somewhere else.`);
|
|
2396
|
+
return 1;
|
|
2397
|
+
}
|
|
2398
|
+
writeFileSync4(target, new Uint8Array(await file.arrayBuffer()));
|
|
2399
|
+
context.print(`Wrote ${target}`);
|
|
2400
|
+
return 0;
|
|
2401
|
+
}
|
|
2236
2402
|
|
|
2237
2403
|
// src/workspace.ts
|
|
2238
|
-
import { existsSync as
|
|
2239
|
-
import { dirname as dirname3, join as
|
|
2404
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2405
|
+
import { dirname as dirname3, join as join5, resolve as resolvePath } from "node:path";
|
|
2240
2406
|
var WORKSPACE_FILE = ".persistmemory.json";
|
|
2241
2407
|
function findWorkspace(from = process.cwd()) {
|
|
2242
2408
|
let dir = resolvePath(from);
|
|
2243
2409
|
for (; ; ) {
|
|
2244
|
-
const file =
|
|
2245
|
-
if (
|
|
2410
|
+
const file = join5(dir, WORKSPACE_FILE);
|
|
2411
|
+
if (existsSync5(file)) {
|
|
2246
2412
|
const config = readWorkspace(file);
|
|
2247
2413
|
if (config) return { file, dir, config };
|
|
2248
2414
|
}
|
|
@@ -2264,8 +2430,8 @@ function readWorkspace(file) {
|
|
|
2264
2430
|
}
|
|
2265
2431
|
}
|
|
2266
2432
|
function writeWorkspace(dir, config) {
|
|
2267
|
-
const file =
|
|
2268
|
-
|
|
2433
|
+
const file = join5(dir, WORKSPACE_FILE);
|
|
2434
|
+
writeFileSync5(file, `${JSON.stringify(config, null, 2)}
|
|
2269
2435
|
`, "utf8");
|
|
2270
2436
|
return file;
|
|
2271
2437
|
}
|
|
@@ -2534,9 +2700,9 @@ function message(error) {
|
|
|
2534
2700
|
}
|
|
2535
2701
|
|
|
2536
2702
|
// src/commands/maintain.ts
|
|
2537
|
-
import { existsSync as
|
|
2703
|
+
import { existsSync as existsSync6, rmSync } from "node:fs";
|
|
2538
2704
|
import { spawnSync } from "node:child_process";
|
|
2539
|
-
import { dirname as dirname4, resolve as
|
|
2705
|
+
import { dirname as dirname4, resolve as resolve6 } from "node:path";
|
|
2540
2706
|
import { fileURLToPath } from "node:url";
|
|
2541
2707
|
async function updateCommand(context) {
|
|
2542
2708
|
const manager = installer();
|
|
@@ -2578,7 +2744,7 @@ Delete the file it runs from: ${processPath()}`
|
|
|
2578
2744
|
return 0;
|
|
2579
2745
|
}
|
|
2580
2746
|
async function deleteCommand(context) {
|
|
2581
|
-
if (!
|
|
2747
|
+
if (!existsSync6(context.paths.dir)) {
|
|
2582
2748
|
context.print(`Nothing to delete: ${context.paths.dir} does not exist.`);
|
|
2583
2749
|
return 0;
|
|
2584
2750
|
}
|
|
@@ -2607,7 +2773,7 @@ async function deleteCommand(context) {
|
|
|
2607
2773
|
return 0;
|
|
2608
2774
|
}
|
|
2609
2775
|
function removeEverything(context) {
|
|
2610
|
-
const dir =
|
|
2776
|
+
const dir = resolve6(context.paths.dir);
|
|
2611
2777
|
if (dir === "/" || dir.split("/").filter(Boolean).length < 2) {
|
|
2612
2778
|
context.error(`Refusing to delete ${dir}: that does not look like a data directory.`);
|
|
2613
2779
|
return;
|
|
@@ -2619,7 +2785,7 @@ function installer() {
|
|
|
2619
2785
|
}
|
|
2620
2786
|
function processPath() {
|
|
2621
2787
|
try {
|
|
2622
|
-
return
|
|
2788
|
+
return resolve6(dirname4(fileURLToPath(import.meta.url)));
|
|
2623
2789
|
} catch {
|
|
2624
2790
|
return process.argv[1] ?? "";
|
|
2625
2791
|
}
|
|
@@ -2631,12 +2797,12 @@ import { randomUUID } from "node:crypto";
|
|
|
2631
2797
|
import { relative as relative2 } from "node:path";
|
|
2632
2798
|
|
|
2633
2799
|
// src/events.ts
|
|
2634
|
-
import { appendFileSync, existsSync as
|
|
2635
|
-
import { join as
|
|
2800
|
+
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "node:fs";
|
|
2801
|
+
import { join as join6 } from "node:path";
|
|
2636
2802
|
function openSessionLog(paths, id) {
|
|
2637
|
-
const directory =
|
|
2803
|
+
const directory = join6(paths.dir, "sessions");
|
|
2638
2804
|
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
2639
|
-
const path =
|
|
2805
|
+
const path = join6(directory, `${id}.jsonl`);
|
|
2640
2806
|
return {
|
|
2641
2807
|
id,
|
|
2642
2808
|
path,
|
|
@@ -2648,7 +2814,7 @@ function openSessionLog(paths, id) {
|
|
|
2648
2814
|
}
|
|
2649
2815
|
},
|
|
2650
2816
|
read() {
|
|
2651
|
-
if (!
|
|
2817
|
+
if (!existsSync7(path)) return [];
|
|
2652
2818
|
return readFileSync6(path, "utf8").split("\n").filter((line) => line.trim() !== "").flatMap((line) => {
|
|
2653
2819
|
try {
|
|
2654
2820
|
return [JSON.parse(line)];
|
|
@@ -2726,9 +2892,9 @@ async function sessionCommand(context) {
|
|
|
2726
2892
|
context.print(` Ask anything. /help for commands, /exit to leave.
|
|
2727
2893
|
`);
|
|
2728
2894
|
const readline = createInterface2({ input: process.stdin, output: process.stdout });
|
|
2729
|
-
const ask = (prompt) => new Promise((
|
|
2730
|
-
readline.question(prompt,
|
|
2731
|
-
readline.once("close", () =>
|
|
2895
|
+
const ask = (prompt) => new Promise((resolve7) => {
|
|
2896
|
+
readline.question(prompt, resolve7);
|
|
2897
|
+
readline.once("close", () => resolve7(void 0));
|
|
2732
2898
|
});
|
|
2733
2899
|
const root = process.cwd();
|
|
2734
2900
|
let running = true;
|