@persistmemory/cli 0.1.0 → 0.1.1
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 +555 -58
- package/dist/bin.js.map +4 -4
- package/dist/index.js +555 -58
- 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((resolve6, 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
|
+
resolve6(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((resolve6, 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
|
+
resolve6();
|
|
373
373
|
}, ms);
|
|
374
374
|
function onAbort() {
|
|
375
375
|
clearTimeout(timer);
|
|
@@ -1287,7 +1287,8 @@ function shortDate(iso) {
|
|
|
1287
1287
|
}
|
|
1288
1288
|
|
|
1289
1289
|
// src/help.ts
|
|
1290
|
-
var VERSION = "0.1.
|
|
1290
|
+
var VERSION = "0.1.1";
|
|
1291
|
+
var PACKAGE = "@persistmemory/cli";
|
|
1291
1292
|
var HELP = `
|
|
1292
1293
|
pm \u2014 PersistMemory from your terminal
|
|
1293
1294
|
|
|
@@ -1295,7 +1296,9 @@ var HELP = `
|
|
|
1295
1296
|
|
|
1296
1297
|
GETTING STARTED
|
|
1297
1298
|
|
|
1299
|
+
pm setup sign in, then pick a Space for this folder
|
|
1298
1300
|
pm auth login sign in with your browser
|
|
1301
|
+
pm auth logout sign out on this machine
|
|
1299
1302
|
pm start a session and just ask
|
|
1300
1303
|
pm remember "we chose Postgres" capture something
|
|
1301
1304
|
pm search "what did we choose" ask for it back
|
|
@@ -1313,6 +1316,13 @@ var HELP = `
|
|
|
1313
1316
|
chat start an interactive session
|
|
1314
1317
|
chat --resume <id> pick up an earlier session
|
|
1315
1318
|
|
|
1319
|
+
setup sign in and choose this folder's Space
|
|
1320
|
+
setup --space "Acme" choose one without being asked
|
|
1321
|
+
setup --new-space "Acme" create one and use it
|
|
1322
|
+
|
|
1323
|
+
spaces list your Spaces
|
|
1324
|
+
spaces create <name> make a new one
|
|
1325
|
+
|
|
1316
1326
|
remember <text> capture text
|
|
1317
1327
|
remember - capture whatever is piped in
|
|
1318
1328
|
remember --file <path> capture a file's contents
|
|
@@ -1325,6 +1335,10 @@ var HELP = `
|
|
|
1325
1335
|
status is the service healthy
|
|
1326
1336
|
requests file requests waiting for you to approve
|
|
1327
1337
|
|
|
1338
|
+
update install the newest version
|
|
1339
|
+
uninstall remove pm from this machine
|
|
1340
|
+
delete delete every file pm has written here
|
|
1341
|
+
|
|
1328
1342
|
FLAGS
|
|
1329
1343
|
|
|
1330
1344
|
--output table|json|yaml|csv|tsv how to print (default: table on a
|
|
@@ -1332,7 +1346,7 @@ var HELP = `
|
|
|
1332
1346
|
--profile <name> use a named account
|
|
1333
1347
|
--api-url <url> talk to a different server
|
|
1334
1348
|
--limit <n> how many results
|
|
1335
|
-
--space <a,b> restrict to Spaces
|
|
1349
|
+
--space <a,b> restrict to Spaces, by name or id
|
|
1336
1350
|
--quiet suppress notices
|
|
1337
1351
|
--version print the version
|
|
1338
1352
|
--help print this
|
|
@@ -1343,14 +1357,96 @@ var HELP = `
|
|
|
1343
1357
|
login. This is what CI should set.
|
|
1344
1358
|
PERSISTMEMORY_API_URL the server to talk to
|
|
1345
1359
|
PERSISTMEMORY_PROFILE which stored profile to use
|
|
1360
|
+
PERSISTMEMORY_SPACE Spaces to use when --space is not given
|
|
1346
1361
|
PERSISTMEMORY_HOME where config, credentials and session
|
|
1347
1362
|
transcripts live (default: ~/.persistmemory)
|
|
1348
1363
|
PERSISTMEMORY_CLIENT_ID override the OAuth client id, for a
|
|
1349
1364
|
self-hosted deployment
|
|
1365
|
+
PERSISTMEMORY_AUTO_UPDATE update without asking when a newer
|
|
1366
|
+
version is published
|
|
1367
|
+
PERSISTMEMORY_NO_UPDATE never check for updates
|
|
1350
1368
|
|
|
1351
1369
|
Docs: https://persistmemory.com/docs/cli
|
|
1352
1370
|
`;
|
|
1353
1371
|
|
|
1372
|
+
// src/update.ts
|
|
1373
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1374
|
+
import { dirname, join as join2 } from "node:path";
|
|
1375
|
+
var REGISTRY = "https://registry.npmjs.org/@persistmemory/cli/latest";
|
|
1376
|
+
var EVERY_MS = 24 * 60 * 60 * 1e3;
|
|
1377
|
+
function updateNotice(deps) {
|
|
1378
|
+
if (!wanted(deps)) return void 0;
|
|
1379
|
+
const cached = read(deps.file);
|
|
1380
|
+
if (!cached) return void 0;
|
|
1381
|
+
if (!isNewer(cached.latest, deps.current)) return void 0;
|
|
1382
|
+
return [
|
|
1383
|
+
`A newer PersistMemory CLI is available: ${deps.current} \u2192 ${cached.latest}`,
|
|
1384
|
+
`Run \`pm update\` to install it.`
|
|
1385
|
+
].join("\n");
|
|
1386
|
+
}
|
|
1387
|
+
async function refreshUpdateCache(deps) {
|
|
1388
|
+
if (!wanted(deps)) return;
|
|
1389
|
+
const now = (deps.now ?? Date.now)();
|
|
1390
|
+
const cached = read(deps.file);
|
|
1391
|
+
if (cached && now - cached.checkedAt < EVERY_MS) return;
|
|
1392
|
+
const call = deps.fetch ?? globalThis.fetch;
|
|
1393
|
+
try {
|
|
1394
|
+
const controller = new AbortController();
|
|
1395
|
+
const timer = setTimeout(() => controller.abort(), 1500);
|
|
1396
|
+
const response = await call(REGISTRY, {
|
|
1397
|
+
signal: controller.signal,
|
|
1398
|
+
headers: { accept: "application/vnd.npm.install-v1+json" }
|
|
1399
|
+
}).finally(() => clearTimeout(timer));
|
|
1400
|
+
if (!response.ok) return;
|
|
1401
|
+
const body = await response.json();
|
|
1402
|
+
if (typeof body.version !== "string") return;
|
|
1403
|
+
write(deps.file, { checkedAt: now, latest: body.version });
|
|
1404
|
+
} catch {
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
function wanted(deps) {
|
|
1408
|
+
const env = deps.env ?? process.env;
|
|
1409
|
+
if (deps.quiet === true) return false;
|
|
1410
|
+
if (deps.isTty === false) return false;
|
|
1411
|
+
if (env["PERSISTMEMORY_NO_UPDATE"]) return false;
|
|
1412
|
+
if (env["CI"]) return false;
|
|
1413
|
+
return true;
|
|
1414
|
+
}
|
|
1415
|
+
function read(file) {
|
|
1416
|
+
try {
|
|
1417
|
+
if (!existsSync2(file)) return void 0;
|
|
1418
|
+
const parsed = JSON.parse(readFileSync2(file, "utf8"));
|
|
1419
|
+
if (typeof parsed.checkedAt !== "number" || typeof parsed.latest !== "string") {
|
|
1420
|
+
return void 0;
|
|
1421
|
+
}
|
|
1422
|
+
return { checkedAt: parsed.checkedAt, latest: parsed.latest };
|
|
1423
|
+
} catch {
|
|
1424
|
+
return void 0;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
function write(file, value) {
|
|
1428
|
+
try {
|
|
1429
|
+
mkdirSync2(dirname(file), { recursive: true });
|
|
1430
|
+
writeFileSync2(file, JSON.stringify(value), "utf8");
|
|
1431
|
+
} catch {
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
function isNewer(candidate, current) {
|
|
1435
|
+
if (candidate.includes("-") || current.includes("-")) return false;
|
|
1436
|
+
const a = candidate.split(".").map(Number);
|
|
1437
|
+
const b = current.split(".").map(Number);
|
|
1438
|
+
if (a.some(Number.isNaN) || b.some(Number.isNaN)) return false;
|
|
1439
|
+
for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
|
|
1440
|
+
const left = a[index] ?? 0;
|
|
1441
|
+
const right = b[index] ?? 0;
|
|
1442
|
+
if (left !== right) return left > right;
|
|
1443
|
+
}
|
|
1444
|
+
return false;
|
|
1445
|
+
}
|
|
1446
|
+
function updateCacheFile(home) {
|
|
1447
|
+
return join2(home, "update-check.json");
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1354
1450
|
// src/auth/oauth.ts
|
|
1355
1451
|
import { spawn } from "node:child_process";
|
|
1356
1452
|
import { timingSafeEqual } from "node:crypto";
|
|
@@ -1381,8 +1477,8 @@ async function startLoopback(options = {}) {
|
|
|
1381
1477
|
const timeoutMs = options.timeoutMs ?? 5 * 60 * 1e3;
|
|
1382
1478
|
let resolveCallback;
|
|
1383
1479
|
let rejectCallback;
|
|
1384
|
-
const received = new Promise((
|
|
1385
|
-
resolveCallback =
|
|
1480
|
+
const received = new Promise((resolve6, reject) => {
|
|
1481
|
+
resolveCallback = resolve6;
|
|
1386
1482
|
rejectCallback = reject;
|
|
1387
1483
|
});
|
|
1388
1484
|
const server = createServer((request, response) => {
|
|
@@ -1408,9 +1504,9 @@ async function startLoopback(options = {}) {
|
|
|
1408
1504
|
response.end(donePage(callback));
|
|
1409
1505
|
resolveCallback?.(callback);
|
|
1410
1506
|
});
|
|
1411
|
-
await new Promise((
|
|
1507
|
+
await new Promise((resolve6, reject) => {
|
|
1412
1508
|
server.once("error", reject);
|
|
1413
|
-
server.listen(0, "127.0.0.1",
|
|
1509
|
+
server.listen(0, "127.0.0.1", resolve6);
|
|
1414
1510
|
});
|
|
1415
1511
|
const address = server.address();
|
|
1416
1512
|
if (address === null || typeof address === "string") {
|
|
@@ -1672,7 +1768,7 @@ function safeEqual(a, b) {
|
|
|
1672
1768
|
}
|
|
1673
1769
|
async function openBrowser(url) {
|
|
1674
1770
|
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
1675
|
-
await new Promise((
|
|
1771
|
+
await new Promise((resolve6, reject) => {
|
|
1676
1772
|
const child = spawn(command, args, {
|
|
1677
1773
|
stdio: "ignore",
|
|
1678
1774
|
// Detached so closing the terminal does not close the browser, and so
|
|
@@ -1681,7 +1777,7 @@ async function openBrowser(url) {
|
|
|
1681
1777
|
});
|
|
1682
1778
|
child.once("error", reject);
|
|
1683
1779
|
child.unref();
|
|
1684
|
-
|
|
1780
|
+
resolve6();
|
|
1685
1781
|
});
|
|
1686
1782
|
}
|
|
1687
1783
|
async function describe(response) {
|
|
@@ -1742,19 +1838,29 @@ async function currentCredential(resolved, deps) {
|
|
|
1742
1838
|
|
|
1743
1839
|
// src/context.ts
|
|
1744
1840
|
import { createInterface } from "node:readline";
|
|
1841
|
+
async function askOnTty(prompt) {
|
|
1842
|
+
return new Promise((resolve6) => {
|
|
1843
|
+
const readline = createInterface({ input: process.stdin, output: process.stdout });
|
|
1844
|
+
readline.question(prompt, (answer3) => {
|
|
1845
|
+
readline.close();
|
|
1846
|
+
resolve6(answer3.trim());
|
|
1847
|
+
});
|
|
1848
|
+
readline.once("close", () => resolve6(""));
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1745
1851
|
var ETX = "";
|
|
1746
1852
|
var DELETE = "\x7F";
|
|
1747
1853
|
var BACKSPACE = "\b";
|
|
1748
1854
|
async function readSecretFromTty(prompt) {
|
|
1749
1855
|
const input = process.stdin;
|
|
1750
1856
|
if (!input.isTTY) {
|
|
1751
|
-
return new Promise((
|
|
1857
|
+
return new Promise((resolve6) => {
|
|
1752
1858
|
const readline = createInterface({ input });
|
|
1753
1859
|
readline.once("line", (line) => {
|
|
1754
1860
|
readline.close();
|
|
1755
|
-
|
|
1861
|
+
resolve6(line.trim());
|
|
1756
1862
|
});
|
|
1757
|
-
readline.once("close", () =>
|
|
1863
|
+
readline.once("close", () => resolve6(""));
|
|
1758
1864
|
});
|
|
1759
1865
|
}
|
|
1760
1866
|
process.stdout.write(prompt);
|
|
@@ -1762,21 +1868,21 @@ async function readSecretFromTty(prompt) {
|
|
|
1762
1868
|
input.setRawMode?.(true);
|
|
1763
1869
|
input.resume();
|
|
1764
1870
|
input.setEncoding("utf8");
|
|
1765
|
-
return new Promise((
|
|
1871
|
+
return new Promise((resolve6) => {
|
|
1766
1872
|
let value = "";
|
|
1767
|
-
const
|
|
1873
|
+
const finish2 = () => {
|
|
1768
1874
|
input.removeListener("data", onData);
|
|
1769
1875
|
input.setRawMode?.(previouslyRaw);
|
|
1770
1876
|
input.pause();
|
|
1771
1877
|
process.stdout.write("\n");
|
|
1772
|
-
|
|
1878
|
+
resolve6(value.trim());
|
|
1773
1879
|
};
|
|
1774
1880
|
const onData = (chunk) => {
|
|
1775
1881
|
for (const character of chunk) {
|
|
1776
1882
|
switch (character) {
|
|
1777
1883
|
case "\r":
|
|
1778
1884
|
case "\n":
|
|
1779
|
-
|
|
1885
|
+
finish2();
|
|
1780
1886
|
return;
|
|
1781
1887
|
case ETX:
|
|
1782
1888
|
input.setRawMode?.(previouslyRaw);
|
|
@@ -1807,11 +1913,11 @@ async function readStdin() {
|
|
|
1807
1913
|
import { hostname } from "node:os";
|
|
1808
1914
|
import { homedir as homedir2 } from "node:os";
|
|
1809
1915
|
import { resolve as resolve3 } from "node:path";
|
|
1810
|
-
import { readFileSync as
|
|
1916
|
+
import { readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
|
|
1811
1917
|
|
|
1812
1918
|
// src/files.ts
|
|
1813
|
-
import { existsSync as
|
|
1814
|
-
import { dirname, isAbsolute, join as
|
|
1919
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync, statSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1920
|
+
import { dirname as dirname2, isAbsolute, join as join3, relative, resolve as resolve2 } from "node:path";
|
|
1815
1921
|
var OutsideWorkspace = class extends Error {
|
|
1816
1922
|
constructor(path) {
|
|
1817
1923
|
super(`${path} is outside the directory this session was started in.`);
|
|
@@ -1828,14 +1934,14 @@ var MAX_READ_BYTES = 512 * 1024;
|
|
|
1828
1934
|
function realLocation(absolute) {
|
|
1829
1935
|
let existing = absolute;
|
|
1830
1936
|
const trailing = [];
|
|
1831
|
-
while (!
|
|
1832
|
-
const parent =
|
|
1937
|
+
while (!existsSync3(existing)) {
|
|
1938
|
+
const parent = dirname2(existing);
|
|
1833
1939
|
if (parent === existing) return absolute;
|
|
1834
1940
|
trailing.unshift(existing.slice(parent.length + 1));
|
|
1835
1941
|
existing = parent;
|
|
1836
1942
|
}
|
|
1837
1943
|
try {
|
|
1838
|
-
return
|
|
1944
|
+
return join3(realpathSync(existing), ...trailing);
|
|
1839
1945
|
} catch {
|
|
1840
1946
|
return absolute;
|
|
1841
1947
|
}
|
|
@@ -1857,7 +1963,7 @@ function readWithin(root, path) {
|
|
|
1857
1963
|
if (stats.size > MAX_READ_BYTES) throw new TooLarge(path, stats.size, MAX_READ_BYTES);
|
|
1858
1964
|
return {
|
|
1859
1965
|
path: absolute,
|
|
1860
|
-
text:
|
|
1966
|
+
text: readFileSync3(absolute, "utf8"),
|
|
1861
1967
|
bytes: stats.size
|
|
1862
1968
|
};
|
|
1863
1969
|
}
|
|
@@ -1867,7 +1973,7 @@ function proposeWrite(root, path, contents) {
|
|
|
1867
1973
|
try {
|
|
1868
1974
|
const stats = statSync(absolute);
|
|
1869
1975
|
if (stats.isFile() && stats.size <= MAX_READ_BYTES) {
|
|
1870
|
-
existing =
|
|
1976
|
+
existing = readFileSync3(absolute, "utf8");
|
|
1871
1977
|
}
|
|
1872
1978
|
} catch {
|
|
1873
1979
|
}
|
|
@@ -1877,21 +1983,21 @@ function proposeWrite(root, path, contents) {
|
|
|
1877
1983
|
...existing !== void 0 ? { existing } : {}
|
|
1878
1984
|
};
|
|
1879
1985
|
}
|
|
1880
|
-
function commitWrite(
|
|
1986
|
+
function commitWrite(write3, confirmed) {
|
|
1881
1987
|
if (!confirmed) throw new Error("refusing to write without confirmation");
|
|
1882
|
-
|
|
1988
|
+
writeFileSync3(write3.path, write3.contents, "utf8");
|
|
1883
1989
|
}
|
|
1884
|
-
function summarise(
|
|
1885
|
-
if (
|
|
1886
|
-
const lines =
|
|
1990
|
+
function summarise(write3, maxLines = 40) {
|
|
1991
|
+
if (write3.existing === void 0) {
|
|
1992
|
+
const lines = write3.contents.split("\n");
|
|
1887
1993
|
const head = lines.slice(0, maxLines).map((line) => `+ ${line}`);
|
|
1888
1994
|
if (lines.length > maxLines) head.push(` \u2026 ${lines.length - maxLines} more lines`);
|
|
1889
|
-
return `create ${
|
|
1995
|
+
return `create ${write3.path} (${lines.length} lines)
|
|
1890
1996
|
${head.join("\n")}`;
|
|
1891
1997
|
}
|
|
1892
|
-
if (
|
|
1893
|
-
const before =
|
|
1894
|
-
const after =
|
|
1998
|
+
if (write3.existing === write3.contents) return `${write3.path} is already exactly this.`;
|
|
1999
|
+
const before = write3.existing.split("\n");
|
|
2000
|
+
const after = write3.contents.split("\n");
|
|
1895
2001
|
const changes = [];
|
|
1896
2002
|
let start = 0;
|
|
1897
2003
|
while (start < before.length && start < after.length && before[start] === after[start]) {
|
|
@@ -1908,7 +2014,7 @@ ${head.join("\n")}`;
|
|
|
1908
2014
|
for (const line of added.slice(0, maxLines)) changes.push(`+ ${line}`);
|
|
1909
2015
|
if (added.length > maxLines) changes.push(` \u2026 ${added.length - maxLines} more added`);
|
|
1910
2016
|
return [
|
|
1911
|
-
`edit ${
|
|
2017
|
+
`edit ${write3.path} (line ${start + 1}: -${removed.length} +${added.length})`,
|
|
1912
2018
|
...changes
|
|
1913
2019
|
].join("\n");
|
|
1914
2020
|
}
|
|
@@ -1947,7 +2053,7 @@ async function answer(context, apiUrl, token, roots, request) {
|
|
|
1947
2053
|
error: new TooLarge(request.path, stats.size, MAX_READ_BYTES).message
|
|
1948
2054
|
};
|
|
1949
2055
|
}
|
|
1950
|
-
bytes =
|
|
2056
|
+
bytes = readFileSync4(located);
|
|
1951
2057
|
} catch (error) {
|
|
1952
2058
|
return { ok: false, error: error instanceof Error ? error.message : "could not read it" };
|
|
1953
2059
|
}
|
|
@@ -2128,6 +2234,181 @@ async function requestsCommand(context) {
|
|
|
2128
2234
|
return 0;
|
|
2129
2235
|
}
|
|
2130
2236
|
|
|
2237
|
+
// src/workspace.ts
|
|
2238
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2239
|
+
import { dirname as dirname3, join as join4, resolve as resolvePath } from "node:path";
|
|
2240
|
+
var WORKSPACE_FILE = ".persistmemory.json";
|
|
2241
|
+
function findWorkspace(from = process.cwd()) {
|
|
2242
|
+
let dir = resolvePath(from);
|
|
2243
|
+
for (; ; ) {
|
|
2244
|
+
const file = join4(dir, WORKSPACE_FILE);
|
|
2245
|
+
if (existsSync4(file)) {
|
|
2246
|
+
const config = readWorkspace(file);
|
|
2247
|
+
if (config) return { file, dir, config };
|
|
2248
|
+
}
|
|
2249
|
+
const parent = dirname3(dir);
|
|
2250
|
+
if (parent === dir) return void 0;
|
|
2251
|
+
dir = parent;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
function readWorkspace(file) {
|
|
2255
|
+
try {
|
|
2256
|
+
const parsed = JSON.parse(readFileSync5(file, "utf8"));
|
|
2257
|
+
const space = parsed.space;
|
|
2258
|
+
if (space && typeof space === "object" && typeof space.id === "string" && space.id !== "" && typeof space.name === "string") {
|
|
2259
|
+
return { space: { id: space.id, name: space.name } };
|
|
2260
|
+
}
|
|
2261
|
+
return {};
|
|
2262
|
+
} catch {
|
|
2263
|
+
return void 0;
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
function writeWorkspace(dir, config) {
|
|
2267
|
+
const file = join4(dir, WORKSPACE_FILE);
|
|
2268
|
+
writeFileSync4(file, `${JSON.stringify(config, null, 2)}
|
|
2269
|
+
`, "utf8");
|
|
2270
|
+
return file;
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
// src/commands/setup.ts
|
|
2274
|
+
async function setupCommand(context) {
|
|
2275
|
+
const { print, error } = context;
|
|
2276
|
+
print("");
|
|
2277
|
+
print(" PersistMemory setup");
|
|
2278
|
+
print("");
|
|
2279
|
+
if (!context.resolved.credential) {
|
|
2280
|
+
print(" You are not signed in yet. Opening your browser.");
|
|
2281
|
+
print("");
|
|
2282
|
+
const code = await login(context);
|
|
2283
|
+
if (code !== 0) {
|
|
2284
|
+
error("Setup stopped: signing in did not finish.");
|
|
2285
|
+
return code;
|
|
2286
|
+
}
|
|
2287
|
+
} else {
|
|
2288
|
+
const how = context.resolved.credential.kind === "api-key" ? "an API key" : "your browser sign-in";
|
|
2289
|
+
print(` Already signed in to ${context.resolved.apiUrl} with ${how}.`);
|
|
2290
|
+
}
|
|
2291
|
+
let client;
|
|
2292
|
+
let spaces;
|
|
2293
|
+
try {
|
|
2294
|
+
client = await context.client();
|
|
2295
|
+
const page = await client.spaces.list({ limit: 200 }).first();
|
|
2296
|
+
spaces = page.data;
|
|
2297
|
+
} catch (caught) {
|
|
2298
|
+
error(
|
|
2299
|
+
`Signed in, but the server would not answer: ${caught instanceof Error ? caught.message : String(caught)}`
|
|
2300
|
+
);
|
|
2301
|
+
error("Run `pm auth login` to sign in again.");
|
|
2302
|
+
return 1;
|
|
2303
|
+
}
|
|
2304
|
+
print(" Signed in and the server answered. \u2713");
|
|
2305
|
+
print("");
|
|
2306
|
+
return chooseSpace(context, client, spaces);
|
|
2307
|
+
}
|
|
2308
|
+
async function chooseSpace(context, client, spaces) {
|
|
2309
|
+
const { print, error } = context;
|
|
2310
|
+
const found = findWorkspace();
|
|
2311
|
+
const current = found?.config.space;
|
|
2312
|
+
const named = stringFlag(context.args, "space");
|
|
2313
|
+
const creating = stringFlag(context.args, "new-space");
|
|
2314
|
+
if (creating !== void 0) {
|
|
2315
|
+
const space = await create(client, creating);
|
|
2316
|
+
return finish(context, space, current);
|
|
2317
|
+
}
|
|
2318
|
+
if (named !== void 0) {
|
|
2319
|
+
const match = byName(spaces, named);
|
|
2320
|
+
if (!match) {
|
|
2321
|
+
error(`No Space called "${named}". Use --new-space to create it.`);
|
|
2322
|
+
return 1;
|
|
2323
|
+
}
|
|
2324
|
+
return finish(context, match, current);
|
|
2325
|
+
}
|
|
2326
|
+
const interactive = context.args.flags["yes"] !== true && context.isTty;
|
|
2327
|
+
if (!interactive) {
|
|
2328
|
+
print(" No Space chosen. Memories will go to your account's default.");
|
|
2329
|
+
print(` Pass --space "<name>" or --new-space "<name>" to pick one.`);
|
|
2330
|
+
return 0;
|
|
2331
|
+
}
|
|
2332
|
+
if (current) {
|
|
2333
|
+
print(` This folder currently uses the Space "${current.name}".`);
|
|
2334
|
+
print("");
|
|
2335
|
+
}
|
|
2336
|
+
print(" Which Space should this folder use?");
|
|
2337
|
+
print("");
|
|
2338
|
+
spaces.forEach((space, index) => {
|
|
2339
|
+
const mark = current?.id === space.id ? " (current)" : "";
|
|
2340
|
+
const count = space.memoryCount === void 0 ? "" : `, ${space.memoryCount} memories`;
|
|
2341
|
+
print(` ${index + 1}. ${space.name}${mark} ${space.kind}${count}`);
|
|
2342
|
+
});
|
|
2343
|
+
const createIndex = spaces.length + 1;
|
|
2344
|
+
const noneIndex = spaces.length + 2;
|
|
2345
|
+
print(` ${createIndex}. Create a new Space`);
|
|
2346
|
+
print(` ${noneIndex}. No Space \u2014 use everything in my account`);
|
|
2347
|
+
print("");
|
|
2348
|
+
const fallback = current ? "keep the current one" : String(createIndex);
|
|
2349
|
+
const answer3 = await context.ask(` Choose 1-${noneIndex} [${fallback}]: `);
|
|
2350
|
+
if (answer3 === "") {
|
|
2351
|
+
if (current) {
|
|
2352
|
+
print("");
|
|
2353
|
+
print(` Keeping "${current.name}".`);
|
|
2354
|
+
return 0;
|
|
2355
|
+
}
|
|
2356
|
+
return finish(context, await askForNewSpace(context, client), current);
|
|
2357
|
+
}
|
|
2358
|
+
const choice = Number(answer3);
|
|
2359
|
+
if (!Number.isInteger(choice) || choice < 1 || choice > noneIndex) {
|
|
2360
|
+
error(`"${answer3}" is not one of the choices. Nothing was changed.`);
|
|
2361
|
+
return 2;
|
|
2362
|
+
}
|
|
2363
|
+
if (choice === noneIndex) {
|
|
2364
|
+
if (found) {
|
|
2365
|
+
writeWorkspace(found.dir, {});
|
|
2366
|
+
print("");
|
|
2367
|
+
print(` Cleared the Space in ${WORKSPACE_FILE}. This folder now uses everything.`);
|
|
2368
|
+
} else {
|
|
2369
|
+
print("");
|
|
2370
|
+
print(" No Space. This folder uses everything in your account.");
|
|
2371
|
+
}
|
|
2372
|
+
return 0;
|
|
2373
|
+
}
|
|
2374
|
+
if (choice === createIndex) {
|
|
2375
|
+
return finish(context, await askForNewSpace(context, client), current);
|
|
2376
|
+
}
|
|
2377
|
+
return finish(context, spaces[choice - 1], current);
|
|
2378
|
+
}
|
|
2379
|
+
async function askForNewSpace(context, client) {
|
|
2380
|
+
for (; ; ) {
|
|
2381
|
+
const name = await context.ask(" Name for the new Space: ");
|
|
2382
|
+
if (name !== "") return create(client, name);
|
|
2383
|
+
context.print(" A Space needs a name.");
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
async function create(client, name) {
|
|
2387
|
+
return client.spaces.create({ name, kind: "project" });
|
|
2388
|
+
}
|
|
2389
|
+
function byName(spaces, name) {
|
|
2390
|
+
const wanted2 = name.trim().toLowerCase();
|
|
2391
|
+
return spaces.find((one) => one.name.trim().toLowerCase() === wanted2) ?? spaces.find((one) => one.id === name);
|
|
2392
|
+
}
|
|
2393
|
+
function finish(context, space, previous) {
|
|
2394
|
+
const file = writeWorkspace(process.cwd(), {
|
|
2395
|
+
space: { id: space.id, name: space.name }
|
|
2396
|
+
});
|
|
2397
|
+
context.print("");
|
|
2398
|
+
context.print(` This folder now uses the Space "${space.name}".`);
|
|
2399
|
+
if (previous && previous.id !== space.id) {
|
|
2400
|
+
context.print(` It used to use "${previous.name}". Existing memories were not moved.`);
|
|
2401
|
+
}
|
|
2402
|
+
context.print(` Saved to ${file}`);
|
|
2403
|
+
context.print("");
|
|
2404
|
+
context.print(" Try it:");
|
|
2405
|
+
context.print("");
|
|
2406
|
+
context.print(' pm remember "we chose Postgres for the ledger"');
|
|
2407
|
+
context.print(' pm search "what did we choose"');
|
|
2408
|
+
context.print("");
|
|
2409
|
+
return 0;
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2131
2412
|
// src/commands/auth.ts
|
|
2132
2413
|
async function authCommand(context) {
|
|
2133
2414
|
const action = context.args.words[1] ?? "status";
|
|
@@ -2179,12 +2460,25 @@ async function login(context) {
|
|
|
2179
2460
|
});
|
|
2180
2461
|
context.print(`
|
|
2181
2462
|
Signed in to ${apiUrl} as profile "${profile}".`);
|
|
2182
|
-
return 0;
|
|
2463
|
+
if (context.args.flags["no-setup"] === true || !context.isTty) return 0;
|
|
2464
|
+
return continueToSpace(context);
|
|
2183
2465
|
} catch (error) {
|
|
2184
2466
|
context.error(message(error));
|
|
2185
2467
|
return 1;
|
|
2186
2468
|
}
|
|
2187
2469
|
}
|
|
2470
|
+
async function continueToSpace(context) {
|
|
2471
|
+
try {
|
|
2472
|
+
const client = await context.client();
|
|
2473
|
+
const { data } = await client.spaces.list({ limit: 200 }).first();
|
|
2474
|
+
return await chooseSpace(context, client, data);
|
|
2475
|
+
} catch (caught) {
|
|
2476
|
+
context.print("");
|
|
2477
|
+
context.print(`Could not load your Spaces: ${message(caught)}`);
|
|
2478
|
+
context.print("You are signed in. Run `pm setup` to choose a Space.");
|
|
2479
|
+
return 0;
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2188
2482
|
function logout(context) {
|
|
2189
2483
|
const profile = context.flags.profile ?? context.resolved.profile;
|
|
2190
2484
|
const forgotten = clearLogin(context.paths, profile);
|
|
@@ -2239,18 +2533,110 @@ function message(error) {
|
|
|
2239
2533
|
return error instanceof Error ? error.message : String(error);
|
|
2240
2534
|
}
|
|
2241
2535
|
|
|
2536
|
+
// src/commands/maintain.ts
|
|
2537
|
+
import { existsSync as existsSync5, rmSync } from "node:fs";
|
|
2538
|
+
import { spawnSync } from "node:child_process";
|
|
2539
|
+
import { dirname as dirname4, resolve as resolve5 } from "node:path";
|
|
2540
|
+
import { fileURLToPath } from "node:url";
|
|
2541
|
+
async function updateCommand(context) {
|
|
2542
|
+
const manager = installer();
|
|
2543
|
+
if (!manager) {
|
|
2544
|
+
context.error(
|
|
2545
|
+
"This copy was not installed by npm, so `pm update` cannot replace it.\nRe-run the installer: curl -fsSL https://persistmemory.com/install.sh | sh"
|
|
2546
|
+
);
|
|
2547
|
+
return 1;
|
|
2548
|
+
}
|
|
2549
|
+
context.print(`Installing the newest ${PACKAGE}\u2026`);
|
|
2550
|
+
const result = spawnSync(manager, ["install", "-g", `${PACKAGE}@latest`], {
|
|
2551
|
+
stdio: "inherit"
|
|
2552
|
+
});
|
|
2553
|
+
if (result.status !== 0) {
|
|
2554
|
+
context.error(
|
|
2555
|
+
"That did not work. If it failed on permissions, do NOT re-run it with sudo \u2014\npoint npm at a directory you own instead:\n npm config set prefix ~/.npm-global\n export PATH=$HOME/.npm-global/bin:$PATH"
|
|
2556
|
+
);
|
|
2557
|
+
return result.status ?? 1;
|
|
2558
|
+
}
|
|
2559
|
+
context.print("Done. `pm --version` will show the new one.");
|
|
2560
|
+
return 0;
|
|
2561
|
+
}
|
|
2562
|
+
async function uninstallCommand(context) {
|
|
2563
|
+
const manager = installer();
|
|
2564
|
+
if (!manager) {
|
|
2565
|
+
context.error(
|
|
2566
|
+
`This copy was not installed by npm, so it cannot uninstall itself.
|
|
2567
|
+
Delete the file it runs from: ${processPath()}`
|
|
2568
|
+
);
|
|
2569
|
+
return 1;
|
|
2570
|
+
}
|
|
2571
|
+
const alsoData = context.args.flags["purge"] === true || (context.isTty ? /^y(es)?$/i.test(
|
|
2572
|
+
await context.ask(`Also delete your credentials and settings in ${context.paths.dir}? [y/N] `)
|
|
2573
|
+
) : false);
|
|
2574
|
+
const result = spawnSync(manager, ["uninstall", "-g", PACKAGE], { stdio: "inherit" });
|
|
2575
|
+
if (result.status !== 0) return result.status ?? 1;
|
|
2576
|
+
if (alsoData) removeEverything(context);
|
|
2577
|
+
context.print("Removed. Your memories are untouched \u2014 this only removed the program.");
|
|
2578
|
+
return 0;
|
|
2579
|
+
}
|
|
2580
|
+
async function deleteCommand(context) {
|
|
2581
|
+
if (!existsSync5(context.paths.dir)) {
|
|
2582
|
+
context.print(`Nothing to delete: ${context.paths.dir} does not exist.`);
|
|
2583
|
+
return 0;
|
|
2584
|
+
}
|
|
2585
|
+
if (context.args.flags["yes"] !== true) {
|
|
2586
|
+
if (!context.isTty) {
|
|
2587
|
+
context.error("Refusing to delete without confirmation. Pass --yes.");
|
|
2588
|
+
return 2;
|
|
2589
|
+
}
|
|
2590
|
+
context.print("");
|
|
2591
|
+
context.print(`This deletes everything in ${context.paths.dir}:`);
|
|
2592
|
+
context.print(" \u2022 the credential you signed in with");
|
|
2593
|
+
context.print(" \u2022 your profiles and settings");
|
|
2594
|
+
context.print(" \u2022 transcripts of your `pm` sessions");
|
|
2595
|
+
context.print("");
|
|
2596
|
+
context.print("Your account and your memories are NOT touched. To delete those,");
|
|
2597
|
+
context.print("go to https://persistmemory.com/settings.");
|
|
2598
|
+
context.print("");
|
|
2599
|
+
const answer3 = await context.ask("Type 'delete' to confirm: ");
|
|
2600
|
+
if (answer3.trim().toLowerCase() !== "delete") {
|
|
2601
|
+
context.print("Nothing was deleted.");
|
|
2602
|
+
return 1;
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
removeEverything(context);
|
|
2606
|
+
context.print(`Deleted ${context.paths.dir}.`);
|
|
2607
|
+
return 0;
|
|
2608
|
+
}
|
|
2609
|
+
function removeEverything(context) {
|
|
2610
|
+
const dir = resolve5(context.paths.dir);
|
|
2611
|
+
if (dir === "/" || dir.split("/").filter(Boolean).length < 2) {
|
|
2612
|
+
context.error(`Refusing to delete ${dir}: that does not look like a data directory.`);
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
rmSync(dir, { recursive: true, force: true });
|
|
2616
|
+
}
|
|
2617
|
+
function installer() {
|
|
2618
|
+
return processPath().includes(`node_modules`) ? "npm" : void 0;
|
|
2619
|
+
}
|
|
2620
|
+
function processPath() {
|
|
2621
|
+
try {
|
|
2622
|
+
return resolve5(dirname4(fileURLToPath(import.meta.url)));
|
|
2623
|
+
} catch {
|
|
2624
|
+
return process.argv[1] ?? "";
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2242
2628
|
// src/commands/session.ts
|
|
2243
2629
|
import { createInterface as createInterface2 } from "node:readline";
|
|
2244
2630
|
import { randomUUID } from "node:crypto";
|
|
2245
2631
|
import { relative as relative2 } from "node:path";
|
|
2246
2632
|
|
|
2247
2633
|
// src/events.ts
|
|
2248
|
-
import { appendFileSync, existsSync as
|
|
2249
|
-
import { join as
|
|
2634
|
+
import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "node:fs";
|
|
2635
|
+
import { join as join5 } from "node:path";
|
|
2250
2636
|
function openSessionLog(paths, id) {
|
|
2251
|
-
const directory =
|
|
2252
|
-
|
|
2253
|
-
const path =
|
|
2637
|
+
const directory = join5(paths.dir, "sessions");
|
|
2638
|
+
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
2639
|
+
const path = join5(directory, `${id}.jsonl`);
|
|
2254
2640
|
return {
|
|
2255
2641
|
id,
|
|
2256
2642
|
path,
|
|
@@ -2262,8 +2648,8 @@ function openSessionLog(paths, id) {
|
|
|
2262
2648
|
}
|
|
2263
2649
|
},
|
|
2264
2650
|
read() {
|
|
2265
|
-
if (!
|
|
2266
|
-
return
|
|
2651
|
+
if (!existsSync6(path)) return [];
|
|
2652
|
+
return readFileSync6(path, "utf8").split("\n").filter((line) => line.trim() !== "").flatMap((line) => {
|
|
2267
2653
|
try {
|
|
2268
2654
|
return [JSON.parse(line)];
|
|
2269
2655
|
} catch {
|
|
@@ -2340,9 +2726,9 @@ async function sessionCommand(context) {
|
|
|
2340
2726
|
context.print(` Ask anything. /help for commands, /exit to leave.
|
|
2341
2727
|
`);
|
|
2342
2728
|
const readline = createInterface2({ input: process.stdin, output: process.stdout });
|
|
2343
|
-
const ask = (prompt) => new Promise((
|
|
2344
|
-
readline.question(prompt,
|
|
2345
|
-
readline.once("close", () =>
|
|
2729
|
+
const ask = (prompt) => new Promise((resolve6) => {
|
|
2730
|
+
readline.question(prompt, resolve6);
|
|
2731
|
+
readline.once("close", () => resolve6(void 0));
|
|
2346
2732
|
});
|
|
2347
2733
|
const root = process.cwd();
|
|
2348
2734
|
let running = true;
|
|
@@ -2442,7 +2828,7 @@ async function handleInput(args) {
|
|
|
2442
2828
|
return true;
|
|
2443
2829
|
}
|
|
2444
2830
|
case "write":
|
|
2445
|
-
await
|
|
2831
|
+
await write2({ ...args, path: argument });
|
|
2446
2832
|
return true;
|
|
2447
2833
|
default:
|
|
2448
2834
|
context.error(` Unknown command /${command ?? ""}. Try /help.`);
|
|
@@ -2493,7 +2879,7 @@ ${reply}
|
|
|
2493
2879
|
context.error(" Note: answered without the semantic index, so this may be narrower than usual.");
|
|
2494
2880
|
}
|
|
2495
2881
|
}
|
|
2496
|
-
async function
|
|
2882
|
+
async function write2(args) {
|
|
2497
2883
|
const { context, state, log, root } = args;
|
|
2498
2884
|
if (!args.path) {
|
|
2499
2885
|
context.error(" /write needs a path.");
|
|
@@ -2526,7 +2912,43 @@ async function write(args) {
|
|
|
2526
2912
|
}
|
|
2527
2913
|
|
|
2528
2914
|
// src/commands/memory.ts
|
|
2529
|
-
import { readFileSync as
|
|
2915
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
2916
|
+
|
|
2917
|
+
// src/spaces.ts
|
|
2918
|
+
async function spacesFor(context, client, env = process.env) {
|
|
2919
|
+
const asked = listFlag(context.args, "space", "spaces") ?? splitList(env["PERSISTMEMORY_SPACE"]) ?? workspaceSpace();
|
|
2920
|
+
if (!asked || asked.length === 0) return void 0;
|
|
2921
|
+
if (asked.every(looksLikeId)) return asked;
|
|
2922
|
+
const { data } = await client.spaces.list({ limit: 200 }).first();
|
|
2923
|
+
return asked.map((one) => looksLikeId(one) ? one : resolveName(one, data));
|
|
2924
|
+
}
|
|
2925
|
+
function workspaceSpace() {
|
|
2926
|
+
const found = findWorkspace();
|
|
2927
|
+
return found?.config.space ? [found.config.space.id] : void 0;
|
|
2928
|
+
}
|
|
2929
|
+
function splitList(raw) {
|
|
2930
|
+
if (!raw) return void 0;
|
|
2931
|
+
const items = raw.split(",").map((one) => one.trim()).filter((one) => one.length > 0);
|
|
2932
|
+
return items.length > 0 ? items : void 0;
|
|
2933
|
+
}
|
|
2934
|
+
function looksLikeId(value) {
|
|
2935
|
+
return value.startsWith("space_");
|
|
2936
|
+
}
|
|
2937
|
+
function resolveName(name, spaces) {
|
|
2938
|
+
const wanted2 = name.trim().toLowerCase();
|
|
2939
|
+
const matches = spaces.filter((one) => one.name.trim().toLowerCase() === wanted2);
|
|
2940
|
+
if (matches.length === 1) return matches[0].id;
|
|
2941
|
+
if (matches.length === 0) {
|
|
2942
|
+
throw new Error(
|
|
2943
|
+
`No Space called "${name}". Run \`pm list spaces\` to see yours, or \`pm spaces create "${name}"\` to make it.`
|
|
2944
|
+
);
|
|
2945
|
+
}
|
|
2946
|
+
throw new Error(
|
|
2947
|
+
`More than one Space is called "${name}": ${matches.map((one) => one.id).join(", ")}. Name it by id.`
|
|
2948
|
+
);
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
// src/commands/memory.ts
|
|
2530
2952
|
var memoryColumns = [
|
|
2531
2953
|
{ header: "id", value: (m) => m.id },
|
|
2532
2954
|
{ header: "type", value: (m) => m.type },
|
|
@@ -2554,7 +2976,7 @@ async function rememberCommand(context) {
|
|
|
2554
2976
|
let text;
|
|
2555
2977
|
if (file) {
|
|
2556
2978
|
try {
|
|
2557
|
-
text =
|
|
2979
|
+
text = readFileSync7(file, "utf8");
|
|
2558
2980
|
} catch {
|
|
2559
2981
|
context.error(`Could not read ${file}.`);
|
|
2560
2982
|
return 1;
|
|
@@ -2569,7 +2991,7 @@ async function rememberCommand(context) {
|
|
|
2569
2991
|
return 2;
|
|
2570
2992
|
}
|
|
2571
2993
|
const client = await context.client();
|
|
2572
|
-
const spaceIds =
|
|
2994
|
+
const spaceIds = await spacesFor(context, client);
|
|
2573
2995
|
const title = stringFlag(context.args, "title");
|
|
2574
2996
|
const result = await client.memories.remember(
|
|
2575
2997
|
{
|
|
@@ -2613,7 +3035,7 @@ async function searchCommand(context) {
|
|
|
2613
3035
|
return 2;
|
|
2614
3036
|
}
|
|
2615
3037
|
const client = await context.client();
|
|
2616
|
-
const spaceIds =
|
|
3038
|
+
const spaceIds = await spacesFor(context, client);
|
|
2617
3039
|
const response = await client.search.query({
|
|
2618
3040
|
query,
|
|
2619
3041
|
...limit !== void 0 ? { limit } : {},
|
|
@@ -2650,10 +3072,11 @@ async function listMemoriesCommand(context) {
|
|
|
2650
3072
|
return 2;
|
|
2651
3073
|
}
|
|
2652
3074
|
const client = await context.client();
|
|
3075
|
+
const spaceIds = await spacesFor(context, client);
|
|
2653
3076
|
const page = client.memories.list({
|
|
2654
3077
|
...limit !== void 0 ? { limit } : {},
|
|
2655
3078
|
...listFlag(context.args, "type") ? { type: listFlag(context.args, "type") } : {},
|
|
2656
|
-
...
|
|
3079
|
+
...spaceIds ? { spaceIds: [...spaceIds] } : {}
|
|
2657
3080
|
});
|
|
2658
3081
|
const rows = context.args.flags["all"] ? await page.all(limit ?? 1e3) : (await page.first()).data;
|
|
2659
3082
|
if (rows.length === 0) {
|
|
@@ -2709,6 +3132,36 @@ function hash(text) {
|
|
|
2709
3132
|
}
|
|
2710
3133
|
return value.toString(16).padStart(8, "0");
|
|
2711
3134
|
}
|
|
3135
|
+
async function createSpaceCommand(context) {
|
|
3136
|
+
const name = context.args.words.slice(2).join(" ").trim();
|
|
3137
|
+
if (name === "") {
|
|
3138
|
+
context.error('A Space needs a name. Try `pm spaces create "Acme"`.');
|
|
3139
|
+
return 2;
|
|
3140
|
+
}
|
|
3141
|
+
const kind = stringFlag(context.args, "kind") ?? "project";
|
|
3142
|
+
const description = stringFlag(context.args, "description");
|
|
3143
|
+
const client = await context.client();
|
|
3144
|
+
const space = await client.spaces.create({
|
|
3145
|
+
name,
|
|
3146
|
+
kind,
|
|
3147
|
+
...description ? { description } : {}
|
|
3148
|
+
});
|
|
3149
|
+
if (context.flags.output !== "table") {
|
|
3150
|
+
context.print(renderOne(space, spaceFields, { format: context.flags.output }));
|
|
3151
|
+
return 0;
|
|
3152
|
+
}
|
|
3153
|
+
context.print(`Created "${space.name}".`);
|
|
3154
|
+
context.print(` id ${space.id}`);
|
|
3155
|
+
context.print(` kind ${space.kind}`);
|
|
3156
|
+
context.print("");
|
|
3157
|
+
context.print(`Use it here with: pm setup --space "${space.name}"`);
|
|
3158
|
+
return 0;
|
|
3159
|
+
}
|
|
3160
|
+
var spaceFields = [
|
|
3161
|
+
{ header: "id", value: (s) => s.id },
|
|
3162
|
+
{ header: "name", value: (s) => s.name },
|
|
3163
|
+
{ header: "kind", value: (s) => s.kind }
|
|
3164
|
+
];
|
|
2712
3165
|
|
|
2713
3166
|
// src/index.ts
|
|
2714
3167
|
async function run(deps) {
|
|
@@ -2760,7 +3213,9 @@ async function run(deps) {
|
|
|
2760
3213
|
client: () => clientFor(resolved, session),
|
|
2761
3214
|
print,
|
|
2762
3215
|
error,
|
|
2763
|
-
readSecret: deps.readSecret ?? readSecretFromTty
|
|
3216
|
+
readSecret: deps.readSecret ?? readSecretFromTty,
|
|
3217
|
+
ask: deps.ask ?? askOnTty,
|
|
3218
|
+
isTty: deps.isTty ?? process.stdin.isTTY ?? false
|
|
2764
3219
|
};
|
|
2765
3220
|
if (args.words.length === 0) {
|
|
2766
3221
|
if (!(deps.isTty ?? process.stdin.isTTY ?? false)) {
|
|
@@ -2770,16 +3225,52 @@ async function run(deps) {
|
|
|
2770
3225
|
return sessionCommand(context);
|
|
2771
3226
|
}
|
|
2772
3227
|
try {
|
|
2773
|
-
|
|
3228
|
+
await offerUpdate(context, deps);
|
|
3229
|
+
const code = await dispatch(context);
|
|
3230
|
+
void refreshUpdateCache(updateDeps(context, deps));
|
|
3231
|
+
return code;
|
|
2774
3232
|
} catch (caught) {
|
|
2775
3233
|
return report(caught, error);
|
|
2776
3234
|
}
|
|
2777
3235
|
}
|
|
3236
|
+
async function offerUpdate(context, deps) {
|
|
3237
|
+
const verb = context.args.words[0];
|
|
3238
|
+
if (verb === "update" || verb === "upgrade" || verb === "uninstall") return;
|
|
3239
|
+
const notice = updateNotice(updateDeps(context, deps));
|
|
3240
|
+
if (!notice) return;
|
|
3241
|
+
const env = deps.env ?? process.env;
|
|
3242
|
+
if (env["PERSISTMEMORY_AUTO_UPDATE"]) {
|
|
3243
|
+
context.print(notice.split("\n")[0] ?? "");
|
|
3244
|
+
await updateCommand(context);
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
context.error(notice);
|
|
3248
|
+
}
|
|
3249
|
+
function updateDeps(context, deps) {
|
|
3250
|
+
return {
|
|
3251
|
+
file: updateCacheFile(context.paths.dir),
|
|
3252
|
+
current: VERSION,
|
|
3253
|
+
...deps.fetch ? { fetch: deps.fetch } : {},
|
|
3254
|
+
...deps.env ? { env: deps.env } : {},
|
|
3255
|
+
isTty: context.isTty,
|
|
3256
|
+
quiet: context.flags.quiet
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
2778
3259
|
async function dispatch(context) {
|
|
2779
3260
|
const [verb, noun] = context.args.words;
|
|
2780
3261
|
switch (verb) {
|
|
2781
3262
|
case "auth":
|
|
2782
3263
|
return authCommand(context);
|
|
3264
|
+
case "setup":
|
|
3265
|
+
case "init":
|
|
3266
|
+
return setupCommand(context);
|
|
3267
|
+
case "update":
|
|
3268
|
+
case "upgrade":
|
|
3269
|
+
return updateCommand(context);
|
|
3270
|
+
case "uninstall":
|
|
3271
|
+
return uninstallCommand(context);
|
|
3272
|
+
case "delete":
|
|
3273
|
+
return deleteCommand(context);
|
|
2783
3274
|
case "agent":
|
|
2784
3275
|
return agentCommand(context);
|
|
2785
3276
|
case "chat":
|
|
@@ -2801,6 +3292,12 @@ async function dispatch(context) {
|
|
|
2801
3292
|
* a consistent grammar is what lets a tool grow past the handful of
|
|
2802
3293
|
* commands anybody can memorise.
|
|
2803
3294
|
*/
|
|
3295
|
+
case "spaces":
|
|
3296
|
+
case "space":
|
|
3297
|
+
if (noun === "create" || noun === "new") return createSpaceCommand(context);
|
|
3298
|
+
if (noun === void 0 || noun === "list") return listSpacesCommand(context);
|
|
3299
|
+
context.error(`Cannot "pm spaces ${noun}". Try list or create.`);
|
|
3300
|
+
return 2;
|
|
2804
3301
|
case "list":
|
|
2805
3302
|
if (noun === "memories" || noun === "memory") return listMemoriesCommand(context);
|
|
2806
3303
|
if (noun === "spaces" || noun === "space") return listSpacesCommand(context);
|