synartesis 0.3.4 → 0.4.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/CHANGELOG.md CHANGED
@@ -2,6 +2,113 @@
2
2
 
3
3
  What changed, and why it mattered. Dates are release dates.
4
4
 
5
+ ## 0.4.0 — 2026-09-08
6
+
7
+ ### Added
8
+
9
+ - **`synartesis install`.** One command wraps every server your MCP client
10
+ already lists. It knows Claude Code, Claude Desktop, Cursor and Codex, finds
11
+ their config files, writes a policy covering everything it finds — adopting
12
+ the bundled policies where they fit — and points each entry at the proxy.
13
+ `uninstall` puts the configs back; `status` says what is covered.
14
+
15
+ Setting a single server up used to mean reading your client's JSON, retyping
16
+ the command into `init`, hand-editing the policy, then editing the JSON back.
17
+ Two hand edits across two files, per server, which is most of the reason
18
+ anyone gave up before seeing an undo work.
19
+
20
+ Your config is copied aside first, the write lands by rename rather than in
21
+ place, a file that does not parse is refused rather than repaired, and every
22
+ key we do not recognise is carried through. Codex's TOML is edited by line so
23
+ its comments, ordering and env subtables survive.
24
+
25
+ - **A connections screen**, on `c` in `synartesis`. Every AI on the machine,
26
+ whether its config points here, and when anything last actually came through
27
+ it — read from the journal, which records the server on every action, rather
28
+ than guessed from processes. `enter` connects one, `a` connects everything
29
+ uncovered, `r` rescans. Nothing is written without a keypress.
30
+
31
+ - **`--server <name>` on the proxy**, so one policy can back several client
32
+ entries. A proxy carrying two servers has to qualify tool names to tell them
33
+ apart, which renames every tool the agent already knows; one entry per server
34
+ keeps the names.
35
+
36
+ - **`synartesis show <session> --full`**, and `f` in the screen: every argument,
37
+ the captured snapshot and the inverse, pretty-printed with nothing elided.
38
+
39
+ ### Fixed
40
+
41
+ - **A tool a client would not call at all.** The official filesystem server
42
+ declares its `outputSchema` as JSON Schema draft-07, and a client whose
43
+ validator only knows 2020-12 refuses to call the tool — nothing reaches the
44
+ proxy, and there is nothing in the journal to explain the silence. The
45
+ dialect is now dropped from advertised output schemas. `inputSchema` is
46
+ untouched, so the tool list is byte-identical wherever a server declares no
47
+ output schema.
48
+
49
+ - **Times are shown where you are standing.** The journal stores UTC, which is
50
+ right, and every view printed that UTC string unchanged — so somebody in
51
+ Kolkata watching an agent work saw 10:05 while their own clock said 15:35.
52
+
53
+ ### Changed
54
+
55
+ - **Actions read as sentences.** `10:36:56 reversible rolled_back
56
+ sim.edit_file`, twelve times over, is a class and a status and never once
57
+ which file. Rows now say the server, the tool, what it acted on and what it
58
+ means: `15:36:56 sim edit_file loadtest.mjs undone`. Arguments are summarised
59
+ rather than truncated JSON; `--full` shows everything.
60
+
61
+ - **`synchronous = NORMAL` under WAL.** Measured on the journal alone: writes
62
+ 0.188 → 0.125 ms per action, rollback marks 0.079 → 0.039 ms. A crash of the
63
+ process still loses nothing; only the machine going down can cost the tail of
64
+ the WAL, and what is there is the record of a call, never the call.
65
+
66
+ - Headings and names are a softer ink. Bold near-white smears at terminal
67
+ sizes, and the palette only works if one thing is bright.
68
+
69
+ - **A halt says what it refused, not only that it stopped.** `halted: halted
70
+ here on an earlier attempt` explains nothing; the sentence worth reading is
71
+ the one saying somebody edited the resource since. And a dry run now re-reads
72
+ the world rather than quoting the message from the last attempt — that halt
73
+ exists so a retry cannot silently repeat what a person stopped, and a dry run
74
+ writes nothing, so it is not a retry.
75
+
76
+ - **Undo says which session, before acting on it.** Without an id it takes the
77
+ most recent, which is not necessarily the one on your screen — somebody undid
78
+ a session they were not looking at and read the result as the tool acting on
79
+ its own. It now names its pick first, and where that session is empty it says
80
+ so and names the one that did something.
81
+
82
+ ### Fixed (undo, from using it)
83
+
84
+ - **`u` in the screen appeared to do nothing.** A client that connects and calls
85
+ nothing still opens a session, so the newest one is regularly empty while the
86
+ one you mean is a line below it. Pressing undo on it confirmed, reverted zero
87
+ and said nothing about why. `u` and `p` now count what could actually be put
88
+ back first, and answer with the reason and the id of the session that has
89
+ something.
90
+
91
+ - **The screen prints the command for what `u` would do**, for the session under
92
+ the cursor: `synartesis undo <id>`. A screen that only answers keystrokes
93
+ gives you nothing to carry to another window and nothing to check when a key
94
+ seems to have done nothing.
95
+
96
+ ### Added (drift)
97
+
98
+ - **`undo --force`**, for a resource somebody changed after the run. Asked for
99
+ twice: on its own it reads the world, prints the lines it would write over,
100
+ and writes nothing; `--force --yes` goes ahead. Two flags rather than a
101
+ prompt, so it reads the same in a terminal and in a script.
102
+
103
+ A refusal with no way past it is half an answer. The halt now carries what
104
+ undoing would overwrite — not only what changed since the run, which is
105
+ history, but which of *your* lines would go — and prints the three ways on as
106
+ three commands: leave it, put the resource back and `--replan`, or `--force`.
107
+
108
+ In the screen, a conflicted session says so and names the command. `u` shows
109
+ the overwrite diff, and only a second `u` will offer to do it, with `y` still
110
+ required after that.
111
+
5
112
  ## 0.3.4 — 2026-09-03
6
113
 
7
114
  ### Changed
package/README.md CHANGED
@@ -83,6 +83,22 @@ clone on macOS run `xcode-select --install`; on Debian or Ubuntu,
83
83
  npm install -g synartesis
84
84
  ```
85
85
 
86
+ Then, from anywhere:
87
+
88
+ ```bash
89
+ synartesis install
90
+ ```
91
+
92
+ That finds what Claude Code, Claude Desktop, Cursor or Codex already list, writes one
93
+ policy covering all of it, and points each entry at the proxy. Servers it
94
+ recognises get the policy that ships for them and work immediately; the rest
95
+ are drafted with every tool held until you say how to undo it. Your config is
96
+ copied aside first, `synartesis uninstall` puts it back, and `synartesis
97
+ status` says what is covered.
98
+
99
+ Each server keeps its own entry and its own proxy, selected with `--server`,
100
+ so **no tool is renamed** — the agent sees exactly the names it saw before.
101
+
86
102
  That is for you: the screen, `undo`, `watch`, `approve`. **Your agent needs
87
103
  nothing installed** — the config block below fetches the proxy on demand.
88
104
 
@@ -4,9 +4,10 @@ import {
4
4
  SnapshotError,
5
5
  UpstreamError,
6
6
  describe
7
- } from "./chunk-WNLRMSDB.js";
7
+ } from "./chunk-YVOO3PTV.js";
8
8
 
9
9
  // src/invocation.ts
10
+ import { spawnSync } from "child_process";
10
11
  import { accessSync, constants } from "fs";
11
12
  import { basename, delimiter, join } from "path";
12
13
  import { fileURLToPath } from "url";
@@ -57,6 +58,17 @@ function proxyCommand() {
57
58
  }
58
59
  return `node ${fileURLToPath(new URL("cli.js", import.meta.url))} proxy`;
59
60
  }
61
+ function pathBinaryMatches(ourVersion) {
62
+ if (!onPath("synartesis")) {
63
+ return false;
64
+ }
65
+ try {
66
+ const result = spawnSync("synartesis", ["--version"], { encoding: "utf8", timeout: 1e4 });
67
+ return result.status === 0 && result.stdout.trim() === ourVersion;
68
+ } catch {
69
+ return false;
70
+ }
71
+ }
60
72
 
61
73
  // src/locate.ts
62
74
  import { existsSync } from "fs";
@@ -115,7 +127,7 @@ function findJournal(given, manifest) {
115
127
  var ESC = "\x1B[";
116
128
  var ACCENT = `${ESC}38;2;226;134;118m`;
117
129
  var ON_ACCENT = `${ESC}48;2;94;20;32m${ESC}38;2;246;233;229m`;
118
- var BRIGHT = `${ESC}38;2;246;233;229m`;
130
+ var INK = `${ESC}38;2;214;201;197m`;
119
131
  var DIM = `${ESC}2m`;
120
132
  var BOLD = `${ESC}1m`;
121
133
  var RESET = `${ESC}0m`;
@@ -129,9 +141,9 @@ function spaced(text) {
129
141
  var style = {
130
142
  /** A section label: small, capital, spaced out. */
131
143
  label: (text) => paint(ACCENT + DIM, spaced(text.toUpperCase())),
132
- heading: (text) => paint(BRIGHT + BOLD, text.toUpperCase()),
144
+ heading: (text) => paint(INK, text.toUpperCase()),
133
145
  accent: (text) => paint(ACCENT, text),
134
- strong: (text) => paint(BOLD, text),
146
+ strong: (text) => paint(INK, text),
135
147
  quiet: (text) => paint(DIM, text),
136
148
  /** Off-white on oxblood, the way the wordmark is set. */
137
149
  plate: (text) => paint(ON_ACCENT + BOLD, ` ${text} `)
@@ -327,6 +339,7 @@ function openDatabase(path) {
327
339
  db.pragma("journal_mode = WAL");
328
340
  db.pragma("foreign_keys = ON");
329
341
  db.pragma("busy_timeout = 5000");
342
+ db.pragma("synchronous = NORMAL");
330
343
  if (path !== ":memory:") {
331
344
  restrictToOwner(path);
332
345
  }
@@ -1451,6 +1464,7 @@ export {
1451
1464
  cliCommand,
1452
1465
  cliCommandFrom,
1453
1466
  proxyCommand,
1467
+ pathBinaryMatches,
1454
1468
  findManifest,
1455
1469
  findJournal,
1456
1470
  style,
@@ -1479,4 +1493,4 @@ export {
1479
1493
  observeState,
1480
1494
  connectStdioUpstream
1481
1495
  };
1482
- //# sourceMappingURL=chunk-LNR5GXPG.js.map
1496
+ //# sourceMappingURL=chunk-FOA4UIDE.js.map
@@ -95,15 +95,17 @@ function brief(value) {
95
95
  return "(a value too deeply nested to print)";
96
96
  }
97
97
  }
98
+ function changedLines(expected, actual) {
99
+ const before = longestString(expected);
100
+ const after = longestString(actual);
101
+ return before !== "" && after !== "" && before !== after ? lineDiff(before, after) : ` expected: ${brief(expected)}
102
+ actual: ${brief(actual)}`;
103
+ }
98
104
  var DriftConflict = class extends SynartesisError {
99
105
  constructor(seq, expected, actual) {
100
- const before = longestString(expected);
101
- const after = longestString(actual);
102
- const body = before !== "" && after !== "" && before !== after ? lineDiff(before, after) : ` expected: ${brief(expected)}
103
- actual: ${brief(actual)}`;
104
106
  super(
105
107
  `drift at sequence ${String(seq)}: the resource is not in the state this run left it in.
106
- ${body}`
108
+ ` + changedLines(expected, actual)
107
109
  );
108
110
  this.seq = seq;
109
111
  this.expected = expected;
@@ -140,9 +142,10 @@ export {
140
142
  ManifestError,
141
143
  UpstreamError,
142
144
  SnapshotError,
145
+ changedLines,
143
146
  DriftConflict,
144
147
  RollbackHalted,
145
148
  JournalError,
146
149
  describe
147
150
  };
148
- //# sourceMappingURL=chunk-WNLRMSDB.js.map
151
+ //# sourceMappingURL=chunk-YVOO3PTV.js.map