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 +107 -0
- package/README.md +16 -0
- package/dist/{chunk-LNR5GXPG.js → chunk-FOA4UIDE.js} +19 -5
- package/dist/{chunk-WNLRMSDB.js → chunk-YVOO3PTV.js} +9 -6
- package/dist/cli.js +1490 -119
- package/dist/proxy.js +46 -6
- package/package.json +1 -1
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-
|
|
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
|
|
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(
|
|
144
|
+
heading: (text) => paint(INK, text.toUpperCase()),
|
|
133
145
|
accent: (text) => paint(ACCENT, text),
|
|
134
|
-
strong: (text) => paint(
|
|
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-
|
|
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
|
-
|
|
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-
|
|
151
|
+
//# sourceMappingURL=chunk-YVOO3PTV.js.map
|