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