synartesis 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  toPayload,
25
25
  verifyAgainstServers,
26
26
  wasRefused
27
- } from "./chunk-GD6PRYZZ.js";
27
+ } from "./chunk-FIIKQECN.js";
28
28
  import {
29
29
  DriftConflict,
30
30
  ManifestError,
@@ -35,8 +35,9 @@ import {
35
35
  } from "./chunk-K3QIPVBY.js";
36
36
 
37
37
  // src/cli.ts
38
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
39
- import { dirname, resolve } from "path";
38
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, statSync, writeFileSync } from "fs";
39
+ import { dirname, join, resolve } from "path";
40
+ import { fileURLToPath as fileURLToPath2 } from "url";
40
41
 
41
42
  // src/init/draft.ts
42
43
  import { z } from "zod";
@@ -1255,6 +1256,7 @@ var COMMANDS = `
1255
1256
  synartesis show <runId> [--journal <path>]
1256
1257
  synartesis gates [--journal <path>]
1257
1258
  synartesis close [runId] [--journal <path>]
1259
+ synartesis prune [--older-than <days>] [--dry-run] [--journal <path>]
1258
1260
  synartesis proxy --manifest <path> [--journal <path>] what your agent runs
1259
1261
  [--http <port> --token <secret>] for a client that
1260
1262
  cannot start one
@@ -1267,6 +1269,10 @@ var COMMANDS = `
1267
1269
  close ends a run left active by a proxy that was killed; nothing guesses at
1268
1270
  that, since several proxies can share one journal.
1269
1271
 
1272
+ prune reclaims space. Putting a file back means keeping what was in it, so a
1273
+ journal grows at several times what an agent writes and never shrinks by
1274
+ itself. Nothing still active or still waiting on a person is ever pruned.
1275
+
1270
1276
  Ids may be shortened to any unambiguous prefix. show and undo default to the
1271
1277
  most recent run; approve and deny default to the only request waiting. init
1272
1278
  adds to an existing manifest rather than replacing it.
@@ -1284,6 +1290,8 @@ and a and d answer it without a second terminal or an id to copy.
1284
1290
  --dry-run read current state and print the plan without changing anything
1285
1291
  --replan rebuild each undo from the current manifest, for a run recorded
1286
1292
  under a policy that turned out to be wrong
1293
+ --older-than days of history prune keeps; defaults to 30
1294
+ --version print the version and exit
1287
1295
 
1288
1296
  Neither path usually needs giving. A policy that belongs to a project sits in
1289
1297
  it and is found from any directory inside it, the way a version control tool
@@ -1306,7 +1314,7 @@ function flag(argv, name) {
1306
1314
  return value;
1307
1315
  }
1308
1316
  function positional(argv) {
1309
- const skip = /* @__PURE__ */ new Set(["--manifest", "--journal", "--to", "--by", "--reason", "--gate-timeout"]);
1317
+ const skip = /* @__PURE__ */ new Set(["--manifest", "--journal", "--to", "--by", "--reason", "--gate-timeout", "--older-than"]);
1310
1318
  const values = [];
1311
1319
  const end = argv.indexOf("--");
1312
1320
  const ours = end === -1 ? argv : argv.slice(0, end);
@@ -1386,7 +1394,7 @@ async function runInit(argv) {
1386
1394
  ...present ? { existing: readFileSync2(path, "utf8") } : {}
1387
1395
  });
1388
1396
  parseManifest(draft.yaml, path);
1389
- mkdirSync(dirname(resolve(path)), { recursive: true });
1397
+ mkdirSync(dirname(resolve(path)), { recursive: true, mode: 448 });
1390
1398
  writeFileSync(path, draft.yaml);
1391
1399
  out("");
1392
1400
  out(` ${style.label(present ? "extended" : "wrote")} ${style.strong(path)}`);
@@ -1450,7 +1458,7 @@ function out(line2) {
1450
1458
  process.stdout.write(`${line2}
1451
1459
  `);
1452
1460
  }
1453
- function runList(journal, asJson) {
1461
+ function runList(journal, asJson, journalPath) {
1454
1462
  const runs = [...journal.listRuns()].reverse();
1455
1463
  if (asJson) {
1456
1464
  out(JSON.stringify(runs.map((run) => ({ ...run, actions: journal.getActions(run.id).length }))));
@@ -1483,6 +1491,10 @@ function runList(journal, asJson) {
1483
1491
  );
1484
1492
  }
1485
1493
  out("");
1494
+ if (bytesOf(journalPath) > PRUNE_NAG_BYTES) {
1495
+ out(` ${style.quiet(`This journal is ${sizeOf(journalPath)}; synartesis prune reclaims what is old enough to lose.`)}`);
1496
+ out("");
1497
+ }
1486
1498
  return 0;
1487
1499
  }
1488
1500
  function runShow(argv, journal, asJson) {
@@ -1589,6 +1601,74 @@ function summarise2(actions) {
1589
1601
  return `${String(actions.length)} actions: ${parts.join(", ")} | ${String(undoable)} with a recorded undo`;
1590
1602
  }
1591
1603
  var journalArg = "";
1604
+ var PRUNE_NAG_BYTES = 100 * 1024 * 1024;
1605
+ function bytesOf(path) {
1606
+ try {
1607
+ return statSync(path).size;
1608
+ } catch {
1609
+ return 0;
1610
+ }
1611
+ }
1612
+ function sizeOf(path) {
1613
+ try {
1614
+ const bytes = bytesOf(path);
1615
+ if (bytes < 1024 * 1024) {
1616
+ return `${String(Math.max(1, Math.round(bytes / 1024)))} kB`;
1617
+ }
1618
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1619
+ } catch {
1620
+ return "unknown";
1621
+ }
1622
+ }
1623
+ var PRUNE_DEFAULT_DAYS = 30;
1624
+ function runPrune(argv, journal, journalPath) {
1625
+ const given = flag(argv, "--older-than");
1626
+ const days = given === void 0 ? PRUNE_DEFAULT_DAYS : Number(given);
1627
+ if (!Number.isFinite(days) || days < 0) {
1628
+ throw new UsageError(`--older-than takes a number of days, not ${given ?? ""}`);
1629
+ }
1630
+ const before = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
1631
+ const stale = journal.prunableRuns(before);
1632
+ const planned = argv.includes("--dry-run");
1633
+ const sizeBefore = sizeOf(journalPath);
1634
+ out("");
1635
+ out(` ${style.label(planned ? "would prune" : "prune")} ${style.quiet(`older than ${String(days)} days`)}`);
1636
+ out(` ${rule(54)}`);
1637
+ out("");
1638
+ out(` ${style.quiet(journalPath)}`);
1639
+ out("");
1640
+ if (stale.length === 0) {
1641
+ out(` ${style.quiet(`Nothing is older than ${String(days)} days and finished with.`)}`);
1642
+ out("");
1643
+ out(` ${style.quiet(`The journal is ${sizeBefore}. Runs still active, and any`)}`);
1644
+ out(` ${style.quiet("holding a call that is waiting or in flight, are never pruned.")}`);
1645
+ out("");
1646
+ return 0;
1647
+ }
1648
+ let actions = 0;
1649
+ for (const run of stale) {
1650
+ actions += run.actions;
1651
+ out(
1652
+ ` ${style.strong((run.label ?? "an agent").padEnd(24))} ${style.quiet(run.at.slice(0, 19).replace("T", " "))} ${style.quiet(run.status.padEnd(11))} ${style.quiet(`${String(run.actions)} actions`)}`
1653
+ );
1654
+ }
1655
+ out("");
1656
+ if (planned) {
1657
+ out(
1658
+ ` ${style.accent(`${String(stale.length)} runs`)} ${style.quiet(`and ${String(actions)} actions would go. Nothing was changed.`)}`
1659
+ );
1660
+ out("");
1661
+ return 0;
1662
+ }
1663
+ const removed = journal.deleteRuns(stale.map((run) => run.id));
1664
+ journal.vacuum();
1665
+ out(
1666
+ ` ${style.accent(`${String(removed.runs)} runs`)} ${style.quiet(`and ${String(removed.actions)} actions removed.`)}`
1667
+ );
1668
+ out(` ${style.quiet(`Journal ${sizeBefore} \u2192 ${sizeOf(journalPath)}.`)}`);
1669
+ out("");
1670
+ return 0;
1671
+ }
1592
1672
  function runClose(argv, journal) {
1593
1673
  const active = journal.listRuns().filter((candidate) => candidate.status === "active");
1594
1674
  const run = pick([...active].reverse(), positional(argv)[1], RUN, true);
@@ -1783,9 +1863,22 @@ var FLAGS = /* @__PURE__ */ new Set([
1783
1863
  "--replan",
1784
1864
  "--reason",
1785
1865
  "--force",
1866
+ "--older-than",
1786
1867
  "--help",
1787
- "-h"
1868
+ "-h",
1869
+ "--version",
1870
+ "-V"
1788
1871
  ]);
1872
+ function version() {
1873
+ try {
1874
+ const root = dirname(fileURLToPath2(import.meta.url));
1875
+ const parsed = JSON.parse(readFileSync2(join(root, "..", "package.json"), "utf8"));
1876
+ const found = typeof parsed === "object" && parsed !== null ? parsed.version : void 0;
1877
+ return typeof found === "string" ? found : "unknown";
1878
+ } catch {
1879
+ return "unknown";
1880
+ }
1881
+ }
1789
1882
  function rejectUnknownFlags(argv) {
1790
1883
  for (const token of argv) {
1791
1884
  if (token === "--") {
@@ -1815,6 +1908,11 @@ async function main(argv) {
1815
1908
  ${COMMANDS}`);
1816
1909
  return 0;
1817
1910
  }
1911
+ if (argv.includes("--version") || argv.includes("-V")) {
1912
+ process.stdout.write(`${version()}
1913
+ `);
1914
+ return 0;
1915
+ }
1818
1916
  rejectUnknownFlags(argv);
1819
1917
  if (command === void 0) {
1820
1918
  const manifestPath = findManifest(flag(argv, "--manifest"));
@@ -1874,11 +1972,13 @@ ${COMMANDS}`);
1874
1972
  try {
1875
1973
  switch (command) {
1876
1974
  case "list":
1877
- return runList(journal, asJson);
1975
+ return runList(journal, asJson, journalPath);
1878
1976
  case "show":
1879
1977
  return runShow(argv, journal, asJson);
1880
1978
  case "close":
1881
1979
  return runClose(argv, journal);
1980
+ case "prune":
1981
+ return runPrune(argv, journal, journalPath);
1882
1982
  case "gates":
1883
1983
  return runGates(journal, asJson);
1884
1984
  case "approve":