synartesis 0.3.3 → 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 +124 -0
- package/README.md +45 -4
- 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/dist/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
observeState,
|
|
16
16
|
openJournal,
|
|
17
17
|
parseManifest,
|
|
18
|
+
pathBinaryMatches,
|
|
18
19
|
planInverse,
|
|
19
20
|
planRead,
|
|
20
21
|
proxyCommand,
|
|
@@ -24,19 +25,20 @@ import {
|
|
|
24
25
|
toPayload,
|
|
25
26
|
verifyAgainstServers,
|
|
26
27
|
wasRefused
|
|
27
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-FOA4UIDE.js";
|
|
28
29
|
import {
|
|
29
30
|
DriftConflict,
|
|
30
31
|
ManifestError,
|
|
31
32
|
RollbackHalted,
|
|
32
33
|
SynartesisError,
|
|
33
34
|
UpstreamError,
|
|
35
|
+
changedLines,
|
|
34
36
|
describe
|
|
35
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-YVOO3PTV.js";
|
|
36
38
|
|
|
37
39
|
// src/cli.ts
|
|
38
|
-
import { existsSync as
|
|
39
|
-
import { dirname, join, resolve } from "path";
|
|
40
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
41
|
+
import { dirname as dirname3, join as join2, resolve as resolve3 } from "path";
|
|
40
42
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
41
43
|
|
|
42
44
|
// src/init/draft.ts
|
|
@@ -285,9 +287,9 @@ async function draftManifest(options) {
|
|
|
285
287
|
return adopted === void 0 || known === void 0 ? { yaml: merged } : { yaml: merged, adopted: { server: known.name, tools: adopted.covered } };
|
|
286
288
|
}
|
|
287
289
|
function mergeInto(existing, server, policies, name) {
|
|
288
|
-
const
|
|
290
|
+
const serversAt2 = existing.indexOf("\nservers:");
|
|
289
291
|
const toolsAt = existing.indexOf("\ntools:");
|
|
290
|
-
if (
|
|
292
|
+
if (serversAt2 === -1 || toolsAt === -1 || toolsAt < serversAt2) {
|
|
291
293
|
throw new ManifestError(
|
|
292
294
|
"the existing manifest does not have a servers: block followed by a tools: block, so it cannot be extended automatically"
|
|
293
295
|
);
|
|
@@ -321,7 +323,7 @@ var toolResult = z2.looseObject({ isError: z2.boolean().default(false) });
|
|
|
321
323
|
function sameState(a, b) {
|
|
322
324
|
return canonical(a) === canonical(b);
|
|
323
325
|
}
|
|
324
|
-
function classify(action, replanning) {
|
|
326
|
+
function classify(action, replanning, goAhead) {
|
|
325
327
|
switch (action.status) {
|
|
326
328
|
case "rolled_back":
|
|
327
329
|
return { kind: "already-reverted", reason: "already rolled back", verified: true };
|
|
@@ -342,7 +344,7 @@ function classify(action, replanning) {
|
|
|
342
344
|
if (action.inverse === void 0) {
|
|
343
345
|
return void 0;
|
|
344
346
|
}
|
|
345
|
-
return replanning ? void 0 : { kind: "halt", reason: "halted here on an earlier attempt", verified: false };
|
|
347
|
+
return replanning || goAhead ? void 0 : { kind: "halt", reason: "halted here on an earlier attempt", verified: false };
|
|
346
348
|
case "applied":
|
|
347
349
|
case "rolling_back":
|
|
348
350
|
return void 0;
|
|
@@ -351,6 +353,7 @@ function classify(action, replanning) {
|
|
|
351
353
|
async function rollback(options) {
|
|
352
354
|
const { journal, router, runId } = options;
|
|
353
355
|
const dryRun = options.dryRun ?? false;
|
|
356
|
+
const force = options.force ?? false;
|
|
354
357
|
const signal = options.signal ?? new AbortController().signal;
|
|
355
358
|
const policies = options.replanWith === void 0 ? void 0 : createPolicyResolver(options.replanWith);
|
|
356
359
|
const replan = (action) => {
|
|
@@ -382,13 +385,14 @@ async function rollback(options) {
|
|
|
382
385
|
let halted;
|
|
383
386
|
let leftInPlace = false;
|
|
384
387
|
for (const action of inScope) {
|
|
385
|
-
|
|
388
|
+
let forcedOver;
|
|
389
|
+
const early = classify(action, policies !== void 0, force || dryRun);
|
|
386
390
|
if (early?.kind === "halt") {
|
|
387
391
|
const seen = action.error ?? "";
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
halted = { seq: action.seq, reason: early.reason, detail };
|
|
392
|
+
const conflicted = action.status === "unrecoverable" && seen !== "";
|
|
393
|
+
const detail = conflicted ? `what it saw last time, which may no longer hold:
|
|
394
|
+
${seen}` : seen;
|
|
395
|
+
halted = { seq: action.seq, reason: early.reason, detail, ...conflicted ? { conflict: true } : {} };
|
|
392
396
|
steps.push({ ...describeStep(action), ...early });
|
|
393
397
|
break;
|
|
394
398
|
}
|
|
@@ -440,9 +444,18 @@ Resolve the conflict, then run undo --replan to check it against the world as it
|
|
|
440
444
|
journal.markRolledBack(action.id);
|
|
441
445
|
}
|
|
442
446
|
continue;
|
|
443
|
-
} else {
|
|
447
|
+
} else if (!force) {
|
|
444
448
|
const conflict = new DriftConflict(action.seq, recordedPost.data, current);
|
|
445
|
-
halted = {
|
|
449
|
+
halted = {
|
|
450
|
+
seq: action.seq,
|
|
451
|
+
reason: "drift detected",
|
|
452
|
+
detail: conflict.message,
|
|
453
|
+
conflict: true,
|
|
454
|
+
// What the person deciding actually needs: not only that it changed,
|
|
455
|
+
// but which lines undoing would write over. A halt that shows the
|
|
456
|
+
// first and hides the second leaves them choosing blind.
|
|
457
|
+
overwrites: overwriteText(current, action)
|
|
458
|
+
};
|
|
446
459
|
steps.push({
|
|
447
460
|
...describeStep(action),
|
|
448
461
|
kind: "halt",
|
|
@@ -454,6 +467,8 @@ Resolve the conflict, then run undo --replan to check it against the world as it
|
|
|
454
467
|
journal.markUnrecoverable(action.id, conflict.message);
|
|
455
468
|
}
|
|
456
469
|
break;
|
|
470
|
+
} else {
|
|
471
|
+
forcedOver = "the resource had changed since; that change was overwritten";
|
|
457
472
|
}
|
|
458
473
|
}
|
|
459
474
|
if (!verified && action.status === "rolling_back") {
|
|
@@ -468,7 +483,7 @@ Resolve the conflict, then run undo --replan to check it against the world as it
|
|
|
468
483
|
steps.push({
|
|
469
484
|
...describeStep(action),
|
|
470
485
|
kind: "revert",
|
|
471
|
-
reason: verified ? "state matches; applying inverse" : unverifiedBecause(action),
|
|
486
|
+
reason: verified ? "state matches; applying inverse" : forcedOver ?? unverifiedBecause(action),
|
|
472
487
|
verified,
|
|
473
488
|
plan,
|
|
474
489
|
...rebuilt.inverse === void 0 ? {} : { replanned: true }
|
|
@@ -537,6 +552,13 @@ function unverifiedBecause(action) {
|
|
|
537
552
|
function describeStep(action) {
|
|
538
553
|
return { seq: action.seq, server: action.server, tool: action.tool };
|
|
539
554
|
}
|
|
555
|
+
function overwriteText(current, action) {
|
|
556
|
+
const intended = intendedAfterInverse(action);
|
|
557
|
+
if (intended === void 0) {
|
|
558
|
+
return "";
|
|
559
|
+
}
|
|
560
|
+
return changedLines(current, intended);
|
|
561
|
+
}
|
|
540
562
|
function intendedAfterInverse(action) {
|
|
541
563
|
return action.snapshot === void 0 ? void 0 : { present: true, value: action.snapshot };
|
|
542
564
|
}
|
|
@@ -591,23 +613,159 @@ function keysIn(chunk) {
|
|
|
591
613
|
return keys;
|
|
592
614
|
}
|
|
593
615
|
|
|
616
|
+
// src/clock.ts
|
|
617
|
+
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
618
|
+
var pad = (value) => String(value).padStart(2, "0");
|
|
619
|
+
function isToday(when, now) {
|
|
620
|
+
return when.getFullYear() === now.getFullYear() && when.getMonth() === now.getMonth() && when.getDate() === now.getDate();
|
|
621
|
+
}
|
|
622
|
+
function shortTime(iso, now = /* @__PURE__ */ new Date()) {
|
|
623
|
+
const when = new Date(iso);
|
|
624
|
+
if (Number.isNaN(when.getTime())) {
|
|
625
|
+
return iso.slice(11, 19).padEnd(12);
|
|
626
|
+
}
|
|
627
|
+
const time = `${pad(when.getHours())}:${pad(when.getMinutes())}`;
|
|
628
|
+
if (isToday(when, now)) {
|
|
629
|
+
return `${time}:${pad(when.getSeconds())}`.padEnd(12);
|
|
630
|
+
}
|
|
631
|
+
return `${pad(when.getDate())} ${MONTHS[when.getMonth()] ?? ""} ${time}`.padEnd(12);
|
|
632
|
+
}
|
|
633
|
+
function fullTime(iso, now = /* @__PURE__ */ new Date()) {
|
|
634
|
+
const when = new Date(iso);
|
|
635
|
+
if (Number.isNaN(when.getTime())) {
|
|
636
|
+
return iso;
|
|
637
|
+
}
|
|
638
|
+
const clock = `${pad(when.getHours())}:${pad(when.getMinutes())}:${pad(when.getSeconds())}`;
|
|
639
|
+
if (isToday(when, now)) {
|
|
640
|
+
return `today at ${clock}`;
|
|
641
|
+
}
|
|
642
|
+
return `${pad(when.getDate())} ${MONTHS[when.getMonth()] ?? ""} ${String(when.getFullYear())}, ${clock}`;
|
|
643
|
+
}
|
|
644
|
+
function ago(iso, now = /* @__PURE__ */ new Date()) {
|
|
645
|
+
const when = new Date(iso);
|
|
646
|
+
if (Number.isNaN(when.getTime())) {
|
|
647
|
+
return "";
|
|
648
|
+
}
|
|
649
|
+
const seconds = Math.max(0, Math.round((now.getTime() - when.getTime()) / 1e3));
|
|
650
|
+
if (seconds < 10) {
|
|
651
|
+
return "just now";
|
|
652
|
+
}
|
|
653
|
+
if (seconds < 60) {
|
|
654
|
+
return `${String(seconds)}s ago`;
|
|
655
|
+
}
|
|
656
|
+
const minutes = Math.round(seconds / 60);
|
|
657
|
+
if (minutes < 60) {
|
|
658
|
+
return `${String(minutes)}m ago`;
|
|
659
|
+
}
|
|
660
|
+
const hours = Math.round(minutes / 60);
|
|
661
|
+
if (hours < 24) {
|
|
662
|
+
return `${String(hours)}h ago`;
|
|
663
|
+
}
|
|
664
|
+
return `${String(Math.round(hours / 24))}d ago`;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/describe.ts
|
|
668
|
+
import { basename } from "path";
|
|
669
|
+
var SUBJECT_KEYS = [
|
|
670
|
+
"path",
|
|
671
|
+
"file_path",
|
|
672
|
+
"source",
|
|
673
|
+
"destination",
|
|
674
|
+
"id",
|
|
675
|
+
"name",
|
|
676
|
+
"key",
|
|
677
|
+
"entity",
|
|
678
|
+
"query"
|
|
679
|
+
];
|
|
680
|
+
function isRecord(value) {
|
|
681
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
682
|
+
}
|
|
683
|
+
function subject(args) {
|
|
684
|
+
if (!isRecord(args)) {
|
|
685
|
+
return "";
|
|
686
|
+
}
|
|
687
|
+
const record = args;
|
|
688
|
+
for (const key of SUBJECT_KEYS) {
|
|
689
|
+
const value = record[key];
|
|
690
|
+
if (typeof value === "string" && value !== "") {
|
|
691
|
+
return value.includes("/") ? basename(value) : value;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
for (const value of Object.values(record)) {
|
|
695
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
696
|
+
return `${String(value.length)} items`;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return "";
|
|
700
|
+
}
|
|
701
|
+
function plainly(action) {
|
|
702
|
+
switch (action.status) {
|
|
703
|
+
case "gated":
|
|
704
|
+
return { text: "waiting for you", needs: true };
|
|
705
|
+
case "applied":
|
|
706
|
+
return action.class === "readonly" ? { text: "read", needs: false } : action.inverse === void 0 ? { text: "done, cannot undo", needs: false } : { text: "done, can undo", needs: false };
|
|
707
|
+
case "rolled_back":
|
|
708
|
+
return { text: "undone", needs: false };
|
|
709
|
+
case "rolling_back":
|
|
710
|
+
return { text: "undoing", needs: false };
|
|
711
|
+
case "denied":
|
|
712
|
+
return { text: "refused", needs: false };
|
|
713
|
+
case "failed":
|
|
714
|
+
return { text: "failed", needs: false };
|
|
715
|
+
case "approved":
|
|
716
|
+
return { text: "approved, not yet sent", needs: true };
|
|
717
|
+
case "pending":
|
|
718
|
+
return { text: "sent, outcome unknown", needs: true };
|
|
719
|
+
case "unrecoverable":
|
|
720
|
+
return action.inverse === void 0 ? { text: "cannot be undone", needs: true } : { text: "changed since; not safe to undo", needs: true };
|
|
721
|
+
default:
|
|
722
|
+
return { text: action.status, needs: false };
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function size(bytes) {
|
|
726
|
+
if (bytes < 1024) {
|
|
727
|
+
return `${String(bytes)} B`;
|
|
728
|
+
}
|
|
729
|
+
if (bytes < 1024 * 1024) {
|
|
730
|
+
return `${(bytes / 1024).toFixed(1)} kB`;
|
|
731
|
+
}
|
|
732
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
733
|
+
}
|
|
734
|
+
function summariseArgs(args, limit = 60) {
|
|
735
|
+
if (!isRecord(args)) {
|
|
736
|
+
return args === void 0 ? "" : JSON.stringify(args);
|
|
737
|
+
}
|
|
738
|
+
const parts = [];
|
|
739
|
+
for (const [key, value] of Object.entries(args)) {
|
|
740
|
+
if (typeof value === "string") {
|
|
741
|
+
parts.push(value.length > 48 ? `${key} ${size(Buffer.byteLength(value))}` : `${key} ${value}`);
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
if (Array.isArray(value)) {
|
|
745
|
+
parts.push(`${key} ${String(value.length)} items`);
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (value === null || typeof value !== "object") {
|
|
749
|
+
parts.push(`${key} ${String(value)}`);
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
parts.push(`${key} ${size(Buffer.byteLength(JSON.stringify(value)))}`);
|
|
753
|
+
}
|
|
754
|
+
const line2 = parts.join(" ");
|
|
755
|
+
return line2.length <= limit ? line2 : `${line2.slice(0, limit - 1)}\u2026`;
|
|
756
|
+
}
|
|
757
|
+
|
|
594
758
|
// src/watch.ts
|
|
595
759
|
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
596
|
-
var MARK = {
|
|
597
|
-
readonly: "\xB7",
|
|
598
|
-
reversible: "\u2190",
|
|
599
|
-
compensable: "\u2248",
|
|
600
|
-
irreversible: "!",
|
|
601
|
-
unclassified: "?"
|
|
602
|
-
};
|
|
603
760
|
var NOTICE_TICKS = 26;
|
|
604
|
-
function line(action) {
|
|
605
|
-
const
|
|
606
|
-
const
|
|
607
|
-
const
|
|
608
|
-
const
|
|
609
|
-
const
|
|
610
|
-
|
|
761
|
+
function line(action, now) {
|
|
762
|
+
const when = shortTime(action.ts, now);
|
|
763
|
+
const where = action.server.padEnd(10);
|
|
764
|
+
const what = action.tool.padEnd(20);
|
|
765
|
+
const on = subject(action.args);
|
|
766
|
+
const state = plainly(action);
|
|
767
|
+
const said = state.needs || wasRefused(action) ? style.accent(state.text) : style.quiet(state.text);
|
|
768
|
+
return ` ${style.quiet(when)} ${style.quiet(where)} ${style.strong(what)} ${style.quiet(on.padEnd(20))} ${said}`;
|
|
611
769
|
}
|
|
612
770
|
function waitingForJournal(options, tick) {
|
|
613
771
|
const spinner = options.live ? `${style.accent(FRAMES[tick % FRAMES.length] ?? "")} ` : "";
|
|
@@ -625,6 +783,7 @@ function waitingForJournal(options, tick) {
|
|
|
625
783
|
function render(journal, options, tick, view) {
|
|
626
784
|
const runs = journal.listRuns();
|
|
627
785
|
const recent = journal.recentActions(12);
|
|
786
|
+
const now = /* @__PURE__ */ new Date();
|
|
628
787
|
const waiting = journal.listGated();
|
|
629
788
|
const active = runs.filter((run) => run.status === "active").length;
|
|
630
789
|
const out2 = [];
|
|
@@ -641,7 +800,7 @@ function render(journal, options, tick, view) {
|
|
|
641
800
|
out2.push(` ${style.quiet("No agent has done anything through this journal yet.")}`);
|
|
642
801
|
} else {
|
|
643
802
|
for (const action of recent) {
|
|
644
|
-
out2.push(line(action));
|
|
803
|
+
out2.push(line(action, now));
|
|
645
804
|
}
|
|
646
805
|
}
|
|
647
806
|
if (waiting.length > 0) {
|
|
@@ -789,7 +948,7 @@ async function watch(options) {
|
|
|
789
948
|
if (options.maxTicks !== void 0 && tick + 1 >= options.maxTicks) {
|
|
790
949
|
break;
|
|
791
950
|
}
|
|
792
|
-
await new Promise((
|
|
951
|
+
await new Promise((resolve4) => setTimeout(resolve4, interval));
|
|
793
952
|
}
|
|
794
953
|
return 0;
|
|
795
954
|
} finally {
|
|
@@ -804,14 +963,675 @@ async function watch(options) {
|
|
|
804
963
|
await reader?.return?.(void 0);
|
|
805
964
|
await reading;
|
|
806
965
|
})(),
|
|
807
|
-
new Promise((
|
|
966
|
+
new Promise((resolve4) => setTimeout(resolve4, 50).unref())
|
|
808
967
|
]);
|
|
809
968
|
journal?.close();
|
|
810
969
|
}
|
|
811
970
|
}
|
|
812
971
|
|
|
813
972
|
// src/console.ts
|
|
814
|
-
import { existsSync as
|
|
973
|
+
import { existsSync as existsSync6 } from "fs";
|
|
974
|
+
|
|
975
|
+
// src/install/connections.ts
|
|
976
|
+
import { existsSync as existsSync5 } from "fs";
|
|
977
|
+
|
|
978
|
+
// src/install/clients.ts
|
|
979
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync } from "fs";
|
|
980
|
+
import { homedir, platform } from "os";
|
|
981
|
+
import { basename as basename2, dirname, join, resolve } from "path";
|
|
982
|
+
|
|
983
|
+
// src/install/toml.ts
|
|
984
|
+
var HEADER = /^\s*\[(?!\[)([^[\]]+)\]\s*$/;
|
|
985
|
+
function serverTables(lines) {
|
|
986
|
+
const tables = [];
|
|
987
|
+
let open;
|
|
988
|
+
const close = (at) => {
|
|
989
|
+
if (open !== void 0) {
|
|
990
|
+
tables.push({ name: open.name, start: open.start, end: at });
|
|
991
|
+
open = void 0;
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
lines.forEach((line2, index) => {
|
|
995
|
+
const header2 = HEADER.exec(line2)?.[1];
|
|
996
|
+
if (header2 === void 0) {
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
const parts = header2.split(".");
|
|
1000
|
+
if (parts[0] === "mcp_servers" && parts.length === 2 && parts[1] !== void 0) {
|
|
1001
|
+
close(index);
|
|
1002
|
+
open = { name: unquote(parts[1]), start: index };
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
close(index);
|
|
1006
|
+
});
|
|
1007
|
+
close(lines.length);
|
|
1008
|
+
return tables;
|
|
1009
|
+
}
|
|
1010
|
+
function unquote(text) {
|
|
1011
|
+
const trimmed = text.trim();
|
|
1012
|
+
if (/^'.*'$/s.test(trimmed)) {
|
|
1013
|
+
return trimmed.slice(1, -1);
|
|
1014
|
+
}
|
|
1015
|
+
if (!/^".*"$/s.test(trimmed)) {
|
|
1016
|
+
return trimmed;
|
|
1017
|
+
}
|
|
1018
|
+
return trimmed.slice(1, -1).replace(/\\(["\\])/g, "$1");
|
|
1019
|
+
}
|
|
1020
|
+
function readKey(lines, table, key) {
|
|
1021
|
+
const pattern = new RegExp(`^\\s*${key}\\s*=\\s*(.*)$`);
|
|
1022
|
+
for (let index = table.start + 1; index < table.end; index += 1) {
|
|
1023
|
+
const line2 = lines[index];
|
|
1024
|
+
if (line2 === void 0 || HEADER.test(line2)) {
|
|
1025
|
+
break;
|
|
1026
|
+
}
|
|
1027
|
+
const value = pattern.exec(line2)?.[1];
|
|
1028
|
+
if (value !== void 0) {
|
|
1029
|
+
return value.trim();
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
return void 0;
|
|
1033
|
+
}
|
|
1034
|
+
function splitItems(inner) {
|
|
1035
|
+
const items = [];
|
|
1036
|
+
let current = "";
|
|
1037
|
+
let quote3;
|
|
1038
|
+
let escaped = false;
|
|
1039
|
+
for (const character of inner) {
|
|
1040
|
+
if (escaped) {
|
|
1041
|
+
current += character;
|
|
1042
|
+
escaped = false;
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
if (character === "\\" && quote3 === '"') {
|
|
1046
|
+
current += character;
|
|
1047
|
+
escaped = true;
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
if (quote3 === void 0 && (character === '"' || character === "'")) {
|
|
1051
|
+
quote3 = character;
|
|
1052
|
+
current += character;
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
if (character === quote3) {
|
|
1056
|
+
quote3 = void 0;
|
|
1057
|
+
current += character;
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
if (character === "," && quote3 === void 0) {
|
|
1061
|
+
items.push(current);
|
|
1062
|
+
current = "";
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
current += character;
|
|
1066
|
+
}
|
|
1067
|
+
items.push(current);
|
|
1068
|
+
return items;
|
|
1069
|
+
}
|
|
1070
|
+
function parseArray(value) {
|
|
1071
|
+
if (value === void 0 || !value.startsWith("[")) {
|
|
1072
|
+
return void 0;
|
|
1073
|
+
}
|
|
1074
|
+
if (!value.endsWith("]")) {
|
|
1075
|
+
return void 0;
|
|
1076
|
+
}
|
|
1077
|
+
const inner = value.slice(1, -1).trim();
|
|
1078
|
+
if (inner === "") {
|
|
1079
|
+
return [];
|
|
1080
|
+
}
|
|
1081
|
+
return splitItems(inner).map((item) => item.trim()).filter((item, index, all) => item !== "" || index !== all.length - 1).map(unquote);
|
|
1082
|
+
}
|
|
1083
|
+
function readServers(text) {
|
|
1084
|
+
const lines = text.split("\n");
|
|
1085
|
+
const servers = {};
|
|
1086
|
+
for (const table of serverTables(lines)) {
|
|
1087
|
+
const command = readKey(lines, table, "command");
|
|
1088
|
+
const entry = {};
|
|
1089
|
+
if (command !== void 0) {
|
|
1090
|
+
entry["command"] = unquote(command);
|
|
1091
|
+
}
|
|
1092
|
+
const args = parseArray(readKey(lines, table, "args"));
|
|
1093
|
+
if (args !== void 0) {
|
|
1094
|
+
entry["args"] = args;
|
|
1095
|
+
}
|
|
1096
|
+
const url = readKey(lines, table, "url");
|
|
1097
|
+
if (url !== void 0) {
|
|
1098
|
+
entry["url"] = unquote(url);
|
|
1099
|
+
}
|
|
1100
|
+
const enabled = readKey(lines, table, "enabled");
|
|
1101
|
+
if (enabled !== void 0) {
|
|
1102
|
+
entry["enabled"] = enabled.trim() === "true";
|
|
1103
|
+
}
|
|
1104
|
+
servers[table.name] = entry;
|
|
1105
|
+
}
|
|
1106
|
+
return servers;
|
|
1107
|
+
}
|
|
1108
|
+
var quote2 = (text) => `"${text.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1109
|
+
function writeServers(text, servers) {
|
|
1110
|
+
const lines = text.split("\n");
|
|
1111
|
+
const current = readServers(text);
|
|
1112
|
+
for (const table of serverTables(lines).reverse()) {
|
|
1113
|
+
const wanted = servers[table.name];
|
|
1114
|
+
if (wanted === void 0 || wanted.command === void 0) {
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
const now = current[table.name];
|
|
1118
|
+
if (now?.command === wanted.command && JSON.stringify(now.args ?? []) === JSON.stringify(wanted.args ?? [])) {
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
setKey(lines, table, "command", quote2(wanted.command));
|
|
1122
|
+
setKey(lines, table, "args", `[${(wanted.args ?? []).map(quote2).join(", ")}]`);
|
|
1123
|
+
}
|
|
1124
|
+
return lines.join("\n");
|
|
1125
|
+
}
|
|
1126
|
+
function setKey(lines, table, key, value) {
|
|
1127
|
+
const pattern = new RegExp(`^(\\s*)${key}\\s*=`);
|
|
1128
|
+
for (let index = table.start + 1; index < table.end; index += 1) {
|
|
1129
|
+
const line2 = lines[index];
|
|
1130
|
+
if (line2 === void 0 || HEADER.test(line2)) {
|
|
1131
|
+
break;
|
|
1132
|
+
}
|
|
1133
|
+
const indent = pattern.exec(line2)?.[1];
|
|
1134
|
+
if (indent !== void 0) {
|
|
1135
|
+
lines[index] = `${indent}${key} = ${value}`;
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
lines.splice(table.start + 1, 0, `${key} = ${value}`);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// src/install/clients.ts
|
|
1143
|
+
var LABELS = {
|
|
1144
|
+
"claude-code": "Claude Code",
|
|
1145
|
+
"claude-desktop": "Claude Desktop",
|
|
1146
|
+
cursor: "Cursor",
|
|
1147
|
+
codex: "Codex"
|
|
1148
|
+
};
|
|
1149
|
+
function claudeDesktopPath() {
|
|
1150
|
+
const home = homedir();
|
|
1151
|
+
switch (platform()) {
|
|
1152
|
+
case "darwin":
|
|
1153
|
+
return join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
1154
|
+
case "win32":
|
|
1155
|
+
return join(process.env["APPDATA"] ?? join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
1156
|
+
default:
|
|
1157
|
+
return join(process.env["XDG_CONFIG_HOME"] ?? join(home, ".config"), "Claude", "claude_desktop_config.json");
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
function discover(cwd) {
|
|
1161
|
+
const home = homedir();
|
|
1162
|
+
const sites = [];
|
|
1163
|
+
const claudeCode = join(home, ".claude.json");
|
|
1164
|
+
if (existsSync3(claudeCode)) {
|
|
1165
|
+
const document = readJson(claudeCode);
|
|
1166
|
+
const projects = document?.["projects"];
|
|
1167
|
+
const here = resolve(cwd);
|
|
1168
|
+
if (isRecord2(projects) && Object.prototype.hasOwnProperty.call(projects, here)) {
|
|
1169
|
+
sites.push({
|
|
1170
|
+
client: "claude-code",
|
|
1171
|
+
label: LABELS["claude-code"],
|
|
1172
|
+
format: "json",
|
|
1173
|
+
path: claudeCode,
|
|
1174
|
+
scope: `project ${here}`,
|
|
1175
|
+
at: ["projects", here, "mcpServers"]
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
sites.push({
|
|
1179
|
+
client: "claude-code",
|
|
1180
|
+
label: LABELS["claude-code"],
|
|
1181
|
+
format: "json",
|
|
1182
|
+
path: claudeCode,
|
|
1183
|
+
scope: "global",
|
|
1184
|
+
at: ["mcpServers"]
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
const projectFile = join(resolve(cwd), ".mcp.json");
|
|
1188
|
+
if (existsSync3(projectFile)) {
|
|
1189
|
+
sites.push({
|
|
1190
|
+
client: "claude-code",
|
|
1191
|
+
label: LABELS["claude-code"],
|
|
1192
|
+
format: "json",
|
|
1193
|
+
path: projectFile,
|
|
1194
|
+
scope: "project file",
|
|
1195
|
+
at: ["mcpServers"]
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
const desktop = claudeDesktopPath();
|
|
1199
|
+
if (existsSync3(desktop)) {
|
|
1200
|
+
sites.push({
|
|
1201
|
+
client: "claude-desktop",
|
|
1202
|
+
label: LABELS["claude-desktop"],
|
|
1203
|
+
format: "json",
|
|
1204
|
+
path: desktop,
|
|
1205
|
+
scope: "global",
|
|
1206
|
+
at: ["mcpServers"]
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
const codex = join(process.env["CODEX_HOME"] ?? join(home, ".codex"), "config.toml");
|
|
1210
|
+
if (existsSync3(codex)) {
|
|
1211
|
+
sites.push({
|
|
1212
|
+
client: "codex",
|
|
1213
|
+
label: LABELS.codex,
|
|
1214
|
+
format: "toml",
|
|
1215
|
+
path: codex,
|
|
1216
|
+
scope: "global",
|
|
1217
|
+
at: ["mcp_servers"]
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
for (const [path, scope] of [
|
|
1221
|
+
[join(resolve(cwd), ".cursor", "mcp.json"), "project"],
|
|
1222
|
+
[join(home, ".cursor", "mcp.json"), "global"]
|
|
1223
|
+
]) {
|
|
1224
|
+
if (existsSync3(path)) {
|
|
1225
|
+
sites.push({ client: "cursor", label: LABELS.cursor, format: "json", path, scope, at: ["mcpServers"] });
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
return sites;
|
|
1229
|
+
}
|
|
1230
|
+
function isRecord2(value) {
|
|
1231
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1232
|
+
}
|
|
1233
|
+
function readJson(path) {
|
|
1234
|
+
try {
|
|
1235
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
1236
|
+
return isRecord2(parsed) ? parsed : void 0;
|
|
1237
|
+
} catch {
|
|
1238
|
+
return void 0;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
var ConfigError = class extends Error {
|
|
1242
|
+
};
|
|
1243
|
+
function readDocument(site) {
|
|
1244
|
+
let text;
|
|
1245
|
+
try {
|
|
1246
|
+
text = readFileSync2(site.path, "utf8");
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
throw new ConfigError(`cannot read ${site.path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1249
|
+
}
|
|
1250
|
+
let parsed;
|
|
1251
|
+
try {
|
|
1252
|
+
parsed = JSON.parse(text);
|
|
1253
|
+
} catch (error) {
|
|
1254
|
+
throw new ConfigError(
|
|
1255
|
+
`${site.path} is not valid JSON (${error instanceof Error ? error.message : String(error)}). Fix it or move it aside; synartesis will not rewrite a file it cannot read.`
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1258
|
+
if (!isRecord2(parsed)) {
|
|
1259
|
+
throw new ConfigError(`${site.path} is not a JSON object, so it has no server list to change`);
|
|
1260
|
+
}
|
|
1261
|
+
return parsed;
|
|
1262
|
+
}
|
|
1263
|
+
function readServers2(document, at) {
|
|
1264
|
+
let node = document;
|
|
1265
|
+
for (const key of at) {
|
|
1266
|
+
if (!isRecord2(node)) {
|
|
1267
|
+
return {};
|
|
1268
|
+
}
|
|
1269
|
+
node = node[key];
|
|
1270
|
+
}
|
|
1271
|
+
if (!isRecord2(node)) {
|
|
1272
|
+
return {};
|
|
1273
|
+
}
|
|
1274
|
+
const servers = {};
|
|
1275
|
+
for (const [name, entry] of Object.entries(node)) {
|
|
1276
|
+
if (isRecord2(entry)) {
|
|
1277
|
+
servers[name] = entry;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
return servers;
|
|
1281
|
+
}
|
|
1282
|
+
function withServers(document, at, servers) {
|
|
1283
|
+
const head = at[0];
|
|
1284
|
+
if (head === void 0) {
|
|
1285
|
+
throw new ConfigError("no path to the server list");
|
|
1286
|
+
}
|
|
1287
|
+
const rest = at.slice(1);
|
|
1288
|
+
const below = document[head];
|
|
1289
|
+
const child = rest.length === 0 ? servers : withServers(isRecord2(below) ? below : {}, rest, servers);
|
|
1290
|
+
return { ...document, [head]: child };
|
|
1291
|
+
}
|
|
1292
|
+
function indentOf(path) {
|
|
1293
|
+
try {
|
|
1294
|
+
const line2 = /\n([ \t]+)"/.exec(readFileSync2(path, "utf8"));
|
|
1295
|
+
const found = line2?.[1];
|
|
1296
|
+
if (found === void 0) {
|
|
1297
|
+
return 2;
|
|
1298
|
+
}
|
|
1299
|
+
return found.startsWith(" ") ? " " : found.length;
|
|
1300
|
+
} catch {
|
|
1301
|
+
return 2;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
function backupPathFor(path) {
|
|
1305
|
+
return `${path}.synartesis-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
1306
|
+
}
|
|
1307
|
+
var KEEP_BACKUPS = 5;
|
|
1308
|
+
function pruneBackups(path) {
|
|
1309
|
+
try {
|
|
1310
|
+
const dir = dirname(path);
|
|
1311
|
+
const prefix = `${basename2(path)}.synartesis-backup-`;
|
|
1312
|
+
const ours = readdirSync(dir).filter((name) => name.startsWith(prefix)).sort();
|
|
1313
|
+
for (const name of ours.slice(0, Math.max(0, ours.length - KEEP_BACKUPS))) {
|
|
1314
|
+
rmSync(join(dir, name), { force: true });
|
|
1315
|
+
}
|
|
1316
|
+
} catch {
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
function writeDocument(site, document) {
|
|
1320
|
+
return writeText(site, `${JSON.stringify(document, void 0, indentOf(site.path))}
|
|
1321
|
+
`);
|
|
1322
|
+
}
|
|
1323
|
+
function writeText(site, text) {
|
|
1324
|
+
const backup = backupPathFor(site.path);
|
|
1325
|
+
const original = readFileSync2(site.path);
|
|
1326
|
+
writeFileSync(backup, original);
|
|
1327
|
+
pruneBackups(site.path);
|
|
1328
|
+
const temporary = join(dirname(site.path), `.synartesis-write-${String(process.pid)}.tmp`);
|
|
1329
|
+
try {
|
|
1330
|
+
writeFileSync(temporary, text);
|
|
1331
|
+
renameSync(temporary, site.path);
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
try {
|
|
1334
|
+
unlinkSync(temporary);
|
|
1335
|
+
} catch {
|
|
1336
|
+
}
|
|
1337
|
+
throw new ConfigError(
|
|
1338
|
+
`could not write ${site.path}: ${error instanceof Error ? error.message : String(error)}. The original is untouched, and a copy is at ${backup}.`
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
return backup;
|
|
1342
|
+
}
|
|
1343
|
+
function serversAt(site) {
|
|
1344
|
+
if (site.format === "toml") {
|
|
1345
|
+
try {
|
|
1346
|
+
return readServers(readFileSync2(site.path, "utf8"));
|
|
1347
|
+
} catch (error) {
|
|
1348
|
+
throw new ConfigError(
|
|
1349
|
+
`cannot read ${site.path}: ${error instanceof Error ? error.message : String(error)}`
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
return readServers2(readDocument(site), site.at);
|
|
1354
|
+
}
|
|
1355
|
+
function saveServers(site, servers) {
|
|
1356
|
+
if (site.format === "toml") {
|
|
1357
|
+
const text = readFileSync2(site.path, "utf8");
|
|
1358
|
+
return writeText(site, writeServers(text, servers));
|
|
1359
|
+
}
|
|
1360
|
+
return writeDocument(site, withServers(readDocument(site), site.at, servers));
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// src/install/install.ts
|
|
1364
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1365
|
+
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
1366
|
+
function recordPathFor(manifestPath) {
|
|
1367
|
+
return resolve2(dirname2(manifestPath), "installed.json");
|
|
1368
|
+
}
|
|
1369
|
+
var EMPTY = { version: 1, wrapped: {} };
|
|
1370
|
+
function isRecord3(value) {
|
|
1371
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1372
|
+
}
|
|
1373
|
+
function asRecord(value) {
|
|
1374
|
+
if (!isRecord3(value)) {
|
|
1375
|
+
return void 0;
|
|
1376
|
+
}
|
|
1377
|
+
const wrapped2 = value["wrapped"];
|
|
1378
|
+
if (!isRecord3(wrapped2)) {
|
|
1379
|
+
return void 0;
|
|
1380
|
+
}
|
|
1381
|
+
const kept = {};
|
|
1382
|
+
for (const [key, entry] of Object.entries(wrapped2)) {
|
|
1383
|
+
if (!isRecord3(entry)) {
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
const original = entry["original"];
|
|
1387
|
+
const at = entry["at"];
|
|
1388
|
+
if (!isRecord3(original)) {
|
|
1389
|
+
continue;
|
|
1390
|
+
}
|
|
1391
|
+
if (!Array.isArray(at) || !at.every((step) => typeof step === "string")) {
|
|
1392
|
+
continue;
|
|
1393
|
+
}
|
|
1394
|
+
kept[key] = { original, at };
|
|
1395
|
+
}
|
|
1396
|
+
return { version: 1, wrapped: kept };
|
|
1397
|
+
}
|
|
1398
|
+
function keyFor(site, server) {
|
|
1399
|
+
return [site.path, site.scope, server].join("");
|
|
1400
|
+
}
|
|
1401
|
+
function readRecord(manifestPath) {
|
|
1402
|
+
const path = recordPathFor(manifestPath);
|
|
1403
|
+
if (!existsSync4(path)) {
|
|
1404
|
+
return EMPTY;
|
|
1405
|
+
}
|
|
1406
|
+
try {
|
|
1407
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
1408
|
+
const record = asRecord(parsed);
|
|
1409
|
+
if (record !== void 0) {
|
|
1410
|
+
return record;
|
|
1411
|
+
}
|
|
1412
|
+
} catch {
|
|
1413
|
+
}
|
|
1414
|
+
return EMPTY;
|
|
1415
|
+
}
|
|
1416
|
+
function writeRecord(manifestPath, record) {
|
|
1417
|
+
mkdirSync(dirname2(recordPathFor(manifestPath)), { recursive: true, mode: 448 });
|
|
1418
|
+
writeFileSync2(recordPathFor(manifestPath), `${JSON.stringify(record, void 0, 2)}
|
|
1419
|
+
`);
|
|
1420
|
+
}
|
|
1421
|
+
function proxyEntry(manifestPath, server, original, invoker) {
|
|
1422
|
+
const command = { command: invoker.command, args: [...invoker.args] };
|
|
1423
|
+
return {
|
|
1424
|
+
...command,
|
|
1425
|
+
args: [...command.args, "--manifest", resolve2(manifestPath), "--server", server],
|
|
1426
|
+
// The agent's environment, not ours: the upstream is started by the proxy
|
|
1427
|
+
// from the manifest, but a client that set `env` here meant it for the
|
|
1428
|
+
// server, and the manifest reads `${VAR}` out of exactly this environment.
|
|
1429
|
+
...original.env === void 0 ? {} : { env: original.env },
|
|
1430
|
+
...original.cwd === void 0 ? {} : { cwd: original.cwd }
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
function isWrapped(entry) {
|
|
1434
|
+
const args = entry.args ?? [];
|
|
1435
|
+
return args.includes("proxy") && (entry.command === "synartesis" || entry.command === "synartesis-proxy" || args.includes("synartesis") || args.some((arg) => arg.endsWith("dist/cli.js") || arg.endsWith("dist/proxy.js")));
|
|
1436
|
+
}
|
|
1437
|
+
function invokerFor(ourVersion, cliPath) {
|
|
1438
|
+
if (pathBinaryMatches(ourVersion)) {
|
|
1439
|
+
return { command: "synartesis", args: ["proxy"] };
|
|
1440
|
+
}
|
|
1441
|
+
return {
|
|
1442
|
+
command: process.execPath,
|
|
1443
|
+
args: [cliPath, "proxy"],
|
|
1444
|
+
note: "the synartesis on your PATH is a different build, so the entries name this one directly"
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
async function planInstall(sites, manifestPath, invoker) {
|
|
1448
|
+
let yaml = existsSync4(manifestPath) ? readFileSync3(manifestPath, "utf8") : void 0;
|
|
1449
|
+
const plans = [];
|
|
1450
|
+
const claimed = new Set(
|
|
1451
|
+
yaml === void 0 ? [] : Object.keys(parseManifest(yaml, manifestPath).servers)
|
|
1452
|
+
);
|
|
1453
|
+
for (const site of sites) {
|
|
1454
|
+
const servers = serversAt(site);
|
|
1455
|
+
const planned = [];
|
|
1456
|
+
const skipped = [];
|
|
1457
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
1458
|
+
if (isWrapped(entry)) {
|
|
1459
|
+
skipped.push({ name, why: "already covered" });
|
|
1460
|
+
continue;
|
|
1461
|
+
}
|
|
1462
|
+
if (entry.command === void 0) {
|
|
1463
|
+
skipped.push({ name, why: entry.url === void 0 ? "no command to start" : "remote (http); stdio only today" });
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
if (entry.enabled === false) {
|
|
1467
|
+
skipped.push({ name, why: "switched off in the config" });
|
|
1468
|
+
continue;
|
|
1469
|
+
}
|
|
1470
|
+
const key = claimed.has(name) ? `${name}-${site.client}` : name;
|
|
1471
|
+
if (claimed.has(key)) {
|
|
1472
|
+
skipped.push({ name, why: `already in the policy as ${key}` });
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
let draft;
|
|
1476
|
+
try {
|
|
1477
|
+
draft = await draftManifest({
|
|
1478
|
+
name: key,
|
|
1479
|
+
command: entry.command,
|
|
1480
|
+
args: [...entry.args ?? []],
|
|
1481
|
+
...yaml === void 0 ? {} : { existing: yaml }
|
|
1482
|
+
});
|
|
1483
|
+
} catch (error) {
|
|
1484
|
+
skipped.push({
|
|
1485
|
+
name,
|
|
1486
|
+
why: `will not start: ${(error instanceof Error ? error.message : String(error)).slice(0, 60)}`
|
|
1487
|
+
});
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
1490
|
+
yaml = draft.yaml;
|
|
1491
|
+
claimed.add(key);
|
|
1492
|
+
planned.push({
|
|
1493
|
+
name,
|
|
1494
|
+
original: entry,
|
|
1495
|
+
wrapped: proxyEntry(manifestPath, key, entry, invoker),
|
|
1496
|
+
...draft.adopted === void 0 ? {} : { adopted: draft.adopted.server, tools: draft.adopted.tools }
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
plans.push({ site, servers: planned, skipped });
|
|
1500
|
+
}
|
|
1501
|
+
return { plans, yaml: yaml ?? "" };
|
|
1502
|
+
}
|
|
1503
|
+
function applyInstall(plans, manifestPath, yaml) {
|
|
1504
|
+
if (!plans.some((plan) => plan.servers.length > 0)) {
|
|
1505
|
+
return [];
|
|
1506
|
+
}
|
|
1507
|
+
parseManifest(yaml, manifestPath);
|
|
1508
|
+
mkdirSync(dirname2(resolve2(manifestPath)), { recursive: true, mode: 448 });
|
|
1509
|
+
writeFileSync2(manifestPath, yaml);
|
|
1510
|
+
const record = readRecord(manifestPath);
|
|
1511
|
+
const wrapped2 = { ...record.wrapped };
|
|
1512
|
+
const applied = [];
|
|
1513
|
+
for (const plan of plans) {
|
|
1514
|
+
if (plan.servers.length === 0) {
|
|
1515
|
+
continue;
|
|
1516
|
+
}
|
|
1517
|
+
const servers = { ...serversAt(plan.site) };
|
|
1518
|
+
for (const server of plan.servers) {
|
|
1519
|
+
servers[server.name] = server.wrapped;
|
|
1520
|
+
wrapped2[keyFor(plan.site, server.name)] = { original: server.original, at: plan.site.at };
|
|
1521
|
+
}
|
|
1522
|
+
writeRecord(manifestPath, { version: 1, wrapped: wrapped2 });
|
|
1523
|
+
const backup = saveServers(plan.site, servers);
|
|
1524
|
+
applied.push({ site: plan.site, backup, servers: plan.servers.map((server) => server.name) });
|
|
1525
|
+
}
|
|
1526
|
+
return applied;
|
|
1527
|
+
}
|
|
1528
|
+
function applyUninstall(sites, manifestPath) {
|
|
1529
|
+
const record = readRecord(manifestPath);
|
|
1530
|
+
const restoredKeys = /* @__PURE__ */ new Set();
|
|
1531
|
+
const restored = [];
|
|
1532
|
+
for (const site of sites) {
|
|
1533
|
+
const servers = { ...serversAt(site) };
|
|
1534
|
+
const put = [];
|
|
1535
|
+
const unknown = [];
|
|
1536
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
1537
|
+
if (!isWrapped(entry)) {
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
const known = record.wrapped[keyFor(site, name)];
|
|
1541
|
+
if (known === void 0) {
|
|
1542
|
+
unknown.push(name);
|
|
1543
|
+
continue;
|
|
1544
|
+
}
|
|
1545
|
+
servers[name] = known.original;
|
|
1546
|
+
put.push(name);
|
|
1547
|
+
restoredKeys.add(keyFor(site, name));
|
|
1548
|
+
}
|
|
1549
|
+
if (put.length === 0 && unknown.length === 0) {
|
|
1550
|
+
continue;
|
|
1551
|
+
}
|
|
1552
|
+
const backup = put.length === 0 ? "" : saveServers(site, servers);
|
|
1553
|
+
restored.push({ site, backup, servers: put, unknown });
|
|
1554
|
+
}
|
|
1555
|
+
const remaining = Object.fromEntries(
|
|
1556
|
+
Object.entries(record.wrapped).filter(([key]) => !restoredKeys.has(key))
|
|
1557
|
+
);
|
|
1558
|
+
writeRecord(manifestPath, { version: 1, wrapped: remaining });
|
|
1559
|
+
return restored;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
// src/install/connections.ts
|
|
1563
|
+
var ACTIVE_WITHIN_MS = 2 * 60 * 1e3;
|
|
1564
|
+
function lastSeenByServer(journal) {
|
|
1565
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1566
|
+
for (const action of journal.recentActions(500)) {
|
|
1567
|
+
const known = seen.get(action.server);
|
|
1568
|
+
if (known === void 0 || action.ts > known) {
|
|
1569
|
+
seen.set(action.server, action.ts);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return seen;
|
|
1573
|
+
}
|
|
1574
|
+
function commandMissing(command) {
|
|
1575
|
+
if (command === void 0) {
|
|
1576
|
+
return false;
|
|
1577
|
+
}
|
|
1578
|
+
return (command.includes("/") || command.includes("\\")) && !existsSync5(command);
|
|
1579
|
+
}
|
|
1580
|
+
function scan(journal, cwd) {
|
|
1581
|
+
const seen = journal === void 0 ? /* @__PURE__ */ new Map() : lastSeenByServer(journal);
|
|
1582
|
+
const groups = [];
|
|
1583
|
+
for (const site of discover(cwd)) {
|
|
1584
|
+
let servers;
|
|
1585
|
+
try {
|
|
1586
|
+
servers = serversAt(site);
|
|
1587
|
+
} catch (error) {
|
|
1588
|
+
groups.push({
|
|
1589
|
+
label: site.label,
|
|
1590
|
+
scope: site.scope,
|
|
1591
|
+
path: site.path,
|
|
1592
|
+
connections: [],
|
|
1593
|
+
problem: error instanceof Error ? error.message : String(error)
|
|
1594
|
+
});
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
const connections = Object.entries(servers).map(([server, entry]) => {
|
|
1598
|
+
const covered = isWrapped(entry);
|
|
1599
|
+
const lastSeen = seen.get(server);
|
|
1600
|
+
return {
|
|
1601
|
+
client: site.client,
|
|
1602
|
+
scope: site.scope,
|
|
1603
|
+
path: site.path,
|
|
1604
|
+
server,
|
|
1605
|
+
covered,
|
|
1606
|
+
missing: commandMissing(entry.command),
|
|
1607
|
+
...lastSeen === void 0 ? {} : { lastSeen },
|
|
1608
|
+
site
|
|
1609
|
+
};
|
|
1610
|
+
});
|
|
1611
|
+
groups.push({ label: site.label, scope: site.scope, path: site.path, connections });
|
|
1612
|
+
}
|
|
1613
|
+
return groups;
|
|
1614
|
+
}
|
|
1615
|
+
function stateOf(connection, now = /* @__PURE__ */ new Date()) {
|
|
1616
|
+
if (connection.missing) {
|
|
1617
|
+
return "cannot start; the command is not there";
|
|
1618
|
+
}
|
|
1619
|
+
if (!connection.covered) {
|
|
1620
|
+
return "not covered";
|
|
1621
|
+
}
|
|
1622
|
+
if (connection.lastSeen === void 0) {
|
|
1623
|
+
return "covered, nothing through it yet";
|
|
1624
|
+
}
|
|
1625
|
+
const since = now.getTime() - new Date(connection.lastSeen).getTime();
|
|
1626
|
+
return since <= ACTIVE_WITHIN_MS ? "covered, active now" : `covered, last used ${ago(connection.lastSeen, now)}`;
|
|
1627
|
+
}
|
|
1628
|
+
function needsConnecting(groups) {
|
|
1629
|
+
return groups.flatMap(
|
|
1630
|
+
(group) => group.connections.filter((connection) => !connection.covered && !connection.missing)
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
// src/console.ts
|
|
815
1635
|
var FRAMES2 = [
|
|
816
1636
|
"\u280B",
|
|
817
1637
|
"\u2819",
|
|
@@ -824,17 +1644,11 @@ var FRAMES2 = [
|
|
|
824
1644
|
"\u2807",
|
|
825
1645
|
"\u280F"
|
|
826
1646
|
];
|
|
827
|
-
var MARK2 = {
|
|
828
|
-
readonly: "\xB7",
|
|
829
|
-
reversible: "\u2190",
|
|
830
|
-
compensable: "\u2248",
|
|
831
|
-
irreversible: "!",
|
|
832
|
-
unclassified: "?"
|
|
833
|
-
};
|
|
834
1647
|
var CURSOR = "\u276F";
|
|
835
1648
|
var DOT = "\xB7";
|
|
836
1649
|
var ESC = "\x1B";
|
|
837
1650
|
var NOTICE_TICKS2 = 26;
|
|
1651
|
+
var READING_TICKS = 200;
|
|
838
1652
|
function roomFor(options) {
|
|
839
1653
|
const rows = options.rows ?? rowsOf(process.stdout.rows) ?? 24;
|
|
840
1654
|
return Math.max(3, rows - 11);
|
|
@@ -853,6 +1667,9 @@ function windowed(lines, at, room) {
|
|
|
853
1667
|
...below === 0 ? [] : [` ${style.quiet(`${String(below)} more below`)}`]
|
|
854
1668
|
];
|
|
855
1669
|
}
|
|
1670
|
+
function firstLine(text) {
|
|
1671
|
+
return text.split("\n")[0]?.trim() ?? "";
|
|
1672
|
+
}
|
|
856
1673
|
function truncate2(text, limit) {
|
|
857
1674
|
return text.length <= limit ? text : `${text.slice(0, limit - 3)}...`;
|
|
858
1675
|
}
|
|
@@ -876,6 +1693,8 @@ function modeLabel(screen) {
|
|
|
876
1693
|
return "one run, in the order it happened";
|
|
877
1694
|
case "gates":
|
|
878
1695
|
return "held until a person decides";
|
|
1696
|
+
case "connections":
|
|
1697
|
+
return "every AI on this machine, and whether it goes through Synartesis";
|
|
879
1698
|
}
|
|
880
1699
|
}
|
|
881
1700
|
function header(options, screen, tick) {
|
|
@@ -905,15 +1724,35 @@ function runsView(journal, screen, options) {
|
|
|
905
1724
|
const here = index === at && canPress(options);
|
|
906
1725
|
const name = (run.label ?? "an agent").padEnd(24);
|
|
907
1726
|
const note = held === 0 ? "" : ` ${style.accent(`${String(held)} awaiting approval`)}`;
|
|
908
|
-
return ` ${here ? style.accent(CURSOR) : " "} ${here ? style.accent(name) : style.strong(name)} ${style.quiet(run.startedAt.
|
|
1727
|
+
return ` ${here ? style.accent(CURSOR) : " "} ${here ? style.accent(name) : style.strong(name)} ${style.quiet(shortTime(run.startedAt).trimEnd().padEnd(13))} ${style.quiet(run.status.padEnd(11))} ${style.quiet(`${String(actions.length)} actions`)}${note}`;
|
|
909
1728
|
});
|
|
910
1729
|
}
|
|
911
|
-
function
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1730
|
+
function standing(actions) {
|
|
1731
|
+
let undoable = 0;
|
|
1732
|
+
let conflicted = 0;
|
|
1733
|
+
for (const action of actions) {
|
|
1734
|
+
if (action.inverse === void 0) {
|
|
1735
|
+
continue;
|
|
1736
|
+
}
|
|
1737
|
+
if (action.status === "applied") {
|
|
1738
|
+
undoable += 1;
|
|
1739
|
+
} else if (action.status === "unrecoverable") {
|
|
1740
|
+
conflicted += 1;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
return { undoable, conflicted };
|
|
1744
|
+
}
|
|
1745
|
+
function elsewhere(journal, exceptId) {
|
|
1746
|
+
for (const run of [...journal.listRuns()].reverse()) {
|
|
1747
|
+
if (run.id === exceptId) {
|
|
1748
|
+
continue;
|
|
1749
|
+
}
|
|
1750
|
+
const found = standing(journal.getActions(run.id));
|
|
1751
|
+
if (found.undoable > 0 || found.conflicted > 0) {
|
|
1752
|
+
return run;
|
|
1753
|
+
}
|
|
915
1754
|
}
|
|
916
|
-
return
|
|
1755
|
+
return void 0;
|
|
917
1756
|
}
|
|
918
1757
|
function runView(journal, screen) {
|
|
919
1758
|
const runId = screen.openRun;
|
|
@@ -923,22 +1762,86 @@ function runView(journal, screen) {
|
|
|
923
1762
|
const run = journal.getRun(runId);
|
|
924
1763
|
const actions = journal.getActions(runId);
|
|
925
1764
|
const out2 = [
|
|
926
|
-
|
|
1765
|
+
// "RUN claude 5dce5bda" was read as an instruction to go and run
|
|
1766
|
+
// something: a spaced capital heading followed by two words looks exactly
|
|
1767
|
+
// like a command with two arguments. "Session" is only ever a noun.
|
|
1768
|
+
` ${style.label("session")} ${style.strong(run?.label ?? "an agent")} ${style.quiet(runId.slice(0, 8))}`,
|
|
927
1769
|
""
|
|
928
1770
|
];
|
|
929
1771
|
if (actions.length === 0) {
|
|
930
|
-
out2.push(` ${style.quiet("nothing was recorded in this run")}`);
|
|
1772
|
+
out2.push(` ${style.quiet("nothing was recorded in this run, so there is nothing here to undo")}`);
|
|
1773
|
+
const other = elsewhere(journal, runId);
|
|
1774
|
+
if (other !== void 0) {
|
|
1775
|
+
out2.push("");
|
|
1776
|
+
out2.push(
|
|
1777
|
+
` ${style.quiet("the session that did something:")} ${style.strong(other.label ?? "an agent")} ${style.accent(other.id.slice(0, 8))}`
|
|
1778
|
+
);
|
|
1779
|
+
out2.push(
|
|
1780
|
+
` ${style.quiet("esc, then j/k onto it -- or run:")} ` + style.strong(`${cliCommand()} undo ${other.id.slice(0, 8)}`)
|
|
1781
|
+
);
|
|
1782
|
+
}
|
|
931
1783
|
return out2;
|
|
932
1784
|
}
|
|
933
1785
|
for (const action of actions) {
|
|
934
|
-
const
|
|
1786
|
+
const state = plainly(action);
|
|
1787
|
+
const said = state.needs || wasRefused(action) ? style.accent(state.text) : style.quiet(state.text);
|
|
935
1788
|
out2.push(
|
|
936
|
-
` ${style.quiet(String(action.seq).padStart(3))} ${style.quiet(
|
|
1789
|
+
` ${style.quiet(String(action.seq).padStart(3))} ${style.quiet(action.server.padEnd(10))} ${style.strong(action.tool.padEnd(20))} ${style.quiet(subject(action.args).padEnd(20))} ${said}`
|
|
937
1790
|
);
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1791
|
+
if (screen.expanded) {
|
|
1792
|
+
for (const line2 of JSON.stringify(action.args, void 0, 2).split("\n")) {
|
|
1793
|
+
out2.push(` ${style.quiet(line2)}`);
|
|
1794
|
+
}
|
|
1795
|
+
if (action.inverse !== void 0) {
|
|
1796
|
+
out2.push(` ${style.quiet("undo")}`);
|
|
1797
|
+
for (const line2 of JSON.stringify(action.inverse, void 0, 2).split("\n")) {
|
|
1798
|
+
out2.push(` ${style.quiet(line2)}`);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
} else {
|
|
1802
|
+
out2.push(` ${style.quiet(summariseArgs(action.args, 62))}`);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
return out2;
|
|
1806
|
+
}
|
|
1807
|
+
function connectionRows(screen) {
|
|
1808
|
+
return screen.groups.flatMap((group) => group.connections);
|
|
1809
|
+
}
|
|
1810
|
+
function connectionsView(screen, options) {
|
|
1811
|
+
if (screen.groups.length === 0) {
|
|
1812
|
+
return [
|
|
1813
|
+
` ${style.quiet("No MCP client config was found on this machine.")}`,
|
|
1814
|
+
"",
|
|
1815
|
+
` ${style.quiet("Looked for Claude Code, Claude Desktop, Cursor and Codex.")}`
|
|
1816
|
+
];
|
|
1817
|
+
}
|
|
1818
|
+
const rows = connectionRows(screen);
|
|
1819
|
+
const at = Math.min(screen.cursor, Math.max(0, rows.length - 1));
|
|
1820
|
+
const now = /* @__PURE__ */ new Date();
|
|
1821
|
+
const out2 = [];
|
|
1822
|
+
let index = 0;
|
|
1823
|
+
for (const group of screen.groups) {
|
|
1824
|
+
out2.push(` ${style.strong(group.label)} ${style.quiet(group.scope)}`);
|
|
1825
|
+
if (group.problem !== void 0) {
|
|
1826
|
+
out2.push(` ${style.accent(group.problem)}`);
|
|
1827
|
+
out2.push("");
|
|
1828
|
+
continue;
|
|
1829
|
+
}
|
|
1830
|
+
if (group.connections.length === 0) {
|
|
1831
|
+
out2.push(` ${style.quiet("no servers listed")}`);
|
|
1832
|
+
out2.push("");
|
|
1833
|
+
continue;
|
|
1834
|
+
}
|
|
1835
|
+
for (const connection of group.connections) {
|
|
1836
|
+
const here = index === at && canPress(options);
|
|
1837
|
+
const state = stateOf(connection, now);
|
|
1838
|
+
const shown = connection.covered ? style.quiet(state) : style.accent(state);
|
|
1839
|
+
out2.push(
|
|
1840
|
+
` ${here ? style.accent(CURSOR) : " "} ${here ? style.accent(connection.server.padEnd(20)) : style.strong(connection.server.padEnd(20))} ${shown}`
|
|
1841
|
+
);
|
|
1842
|
+
index += 1;
|
|
941
1843
|
}
|
|
1844
|
+
out2.push("");
|
|
942
1845
|
}
|
|
943
1846
|
return out2;
|
|
944
1847
|
}
|
|
@@ -961,12 +1864,10 @@ function footer(screen, options) {
|
|
|
961
1864
|
return [];
|
|
962
1865
|
}
|
|
963
1866
|
if (screen.confirming !== void 0) {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
` ${style.accent("undo this whole run?")} ${keyHint2("y", "yes")} ${keyHint2("n", "no")}`
|
|
967
|
-
];
|
|
1867
|
+
const what = screen.confirmingForce ? `undo ${screen.confirmingLabel ?? "this session"} anyway, losing that change?` : `undo ${screen.confirmingLabel ?? "this session"}?`;
|
|
1868
|
+
return ["", ` ${style.accent(what)} ${keyHint2("y", "yes")} ${keyHint2("n", "no")}`];
|
|
968
1869
|
}
|
|
969
|
-
const keys = screen.mode === "gates" ? [keyHint2("a", "approve"), keyHint2("d", "deny"), keyHint2("j/k", "move"), keyHint2("r", "runs")] : screen.mode === "run" ? [
|
|
1870
|
+
const keys = screen.mode === "connections" ? [keyHint2("enter", "connect"), keyHint2("a", "connect all"), keyHint2("r", "rescan"), keyHint2("j/k", "move"), keyHint2("h", "back")] : screen.mode === "gates" ? [keyHint2("a", "approve"), keyHint2("d", "deny"), keyHint2("j/k", "move"), keyHint2("r", "runs")] : screen.mode === "run" ? [
|
|
970
1871
|
keyHint2("p", "preview undo"),
|
|
971
1872
|
keyHint2("u", "undo"),
|
|
972
1873
|
keyHint2("esc", "back"),
|
|
@@ -1016,7 +1917,7 @@ async function* terminalKeys2() {
|
|
|
1016
1917
|
async function openConsole(options) {
|
|
1017
1918
|
let journal;
|
|
1018
1919
|
const open = () => {
|
|
1019
|
-
if (journal === void 0 &&
|
|
1920
|
+
if (journal === void 0 && existsSync6(options.journalPath)) {
|
|
1020
1921
|
journal = openJournal(options.journalPath, { mustExist: true });
|
|
1021
1922
|
}
|
|
1022
1923
|
return journal;
|
|
@@ -1027,28 +1928,36 @@ async function openConsole(options) {
|
|
|
1027
1928
|
cursor: 0,
|
|
1028
1929
|
openRun: void 0,
|
|
1029
1930
|
confirming: void 0,
|
|
1931
|
+
confirmingLabel: void 0,
|
|
1932
|
+
confirmingForce: false,
|
|
1933
|
+
warned: void 0,
|
|
1934
|
+
expanded: false,
|
|
1935
|
+
groups: [],
|
|
1030
1936
|
busy: void 0,
|
|
1031
1937
|
notice: "",
|
|
1032
1938
|
noticeUntil: 0
|
|
1033
1939
|
};
|
|
1034
1940
|
let tick = 0;
|
|
1035
1941
|
const stopped = () => screen.stop;
|
|
1036
|
-
const say = (text) => {
|
|
1942
|
+
const say = (text, ticks = NOTICE_TICKS2) => {
|
|
1037
1943
|
screen.notice = text;
|
|
1038
|
-
screen.noticeUntil = tick +
|
|
1944
|
+
screen.noticeUntil = tick + ticks;
|
|
1039
1945
|
};
|
|
1040
1946
|
const frame = () => {
|
|
1041
1947
|
const ready = open();
|
|
1042
1948
|
if (ready === void 0) {
|
|
1043
1949
|
return waitingForJournal2(options, tick);
|
|
1044
1950
|
}
|
|
1045
|
-
const
|
|
1046
|
-
const
|
|
1047
|
-
const
|
|
1951
|
+
const tail = command(ready);
|
|
1952
|
+
const shout = screen.notice === "" ? 0 : screen.notice.split("\n").length;
|
|
1953
|
+
const room = Math.max(3, roomFor(options) - tail.length - shout);
|
|
1954
|
+
const body = screen.mode === "runs" ? windowed(runsView(ready, screen, options), screen.cursor, room) : screen.mode === "run" ? windowed(runView(ready, screen), 0, room) : screen.mode === "connections" ? windowed(connectionsView(screen, options), screen.cursor, room) : windowed(gatesView(ready, screen, options), screen.cursor, room);
|
|
1955
|
+
const notice = screen.notice === "" ? [] : ["", ...screen.notice.split("\n").map((line2) => ` ${style.accent(line2)}`)];
|
|
1048
1956
|
return [
|
|
1049
1957
|
...header(options, screen, tick),
|
|
1050
1958
|
...body,
|
|
1051
1959
|
...notice,
|
|
1960
|
+
...tail,
|
|
1052
1961
|
...footer(screen, options),
|
|
1053
1962
|
""
|
|
1054
1963
|
].join("\n");
|
|
@@ -1060,6 +1969,40 @@ async function openConsole(options) {
|
|
|
1060
1969
|
const runs = [...ready.listRuns()].reverse();
|
|
1061
1970
|
return runs[Math.min(screen.cursor, runs.length - 1)];
|
|
1062
1971
|
};
|
|
1972
|
+
const nothingToUndo = (ready, run) => {
|
|
1973
|
+
const actions = ready.getActions(run.id);
|
|
1974
|
+
const why = actions.length === 0 ? "nothing was recorded in this session" : actions.every((action) => action.status === "rolled_back" || action.class === "readonly") ? "this session has already been undone" : "nothing in this session can be undone";
|
|
1975
|
+
const other = elsewhere(ready, run.id);
|
|
1976
|
+
return other === void 0 ? why : `${why} ${DOT} try ${other.label ?? "an agent"} ${other.id.slice(0, 8)}`;
|
|
1977
|
+
};
|
|
1978
|
+
const command = (ready) => {
|
|
1979
|
+
if (!canPress(options) || screen.confirming !== void 0) {
|
|
1980
|
+
return [];
|
|
1981
|
+
}
|
|
1982
|
+
if (screen.mode !== "runs" && screen.mode !== "run") {
|
|
1983
|
+
return [];
|
|
1984
|
+
}
|
|
1985
|
+
const run = selectedRun(ready);
|
|
1986
|
+
if (run === void 0) {
|
|
1987
|
+
return [];
|
|
1988
|
+
}
|
|
1989
|
+
const here = standing(ready.getActions(run.id));
|
|
1990
|
+
const id = run.id.slice(0, 8);
|
|
1991
|
+
if (here.undoable > 0) {
|
|
1992
|
+
return [
|
|
1993
|
+
"",
|
|
1994
|
+
` ${style.quiet("u undoes it here")} ${style.quiet(DOT)} ${style.quiet("or from any terminal:")} ` + style.strong(`${cliCommand()} undo ${id}`)
|
|
1995
|
+
];
|
|
1996
|
+
}
|
|
1997
|
+
if (here.conflicted > 0) {
|
|
1998
|
+
return [
|
|
1999
|
+
"",
|
|
2000
|
+
` ${style.accent(`${String(here.conflicted)} changed since this ran`)} ${style.quiet(DOT)} ` + style.quiet("u shows what undoing would write over"),
|
|
2001
|
+
` ${style.quiet("or from any terminal:")} ${style.strong(`${cliCommand()} undo ${id} --force`)}`
|
|
2002
|
+
];
|
|
2003
|
+
}
|
|
2004
|
+
return ["", ` ${style.quiet("nothing to undo in this session")}`];
|
|
2005
|
+
};
|
|
1063
2006
|
const decide = (approve) => {
|
|
1064
2007
|
const ready = open();
|
|
1065
2008
|
if (ready === void 0) {
|
|
@@ -1076,7 +2019,7 @@ async function openConsole(options) {
|
|
|
1076
2019
|
);
|
|
1077
2020
|
screen.cursor = 0;
|
|
1078
2021
|
};
|
|
1079
|
-
const perform = async (runId, dryRun) => {
|
|
2022
|
+
const perform = async (runId, dryRun, force = false) => {
|
|
1080
2023
|
if (options.undo === void 0) {
|
|
1081
2024
|
say("no way to undo was configured");
|
|
1082
2025
|
return;
|
|
@@ -1087,11 +2030,18 @@ async function openConsole(options) {
|
|
|
1087
2030
|
}
|
|
1088
2031
|
screen.busy = dryRun ? "reading the current state..." : "putting it back...";
|
|
1089
2032
|
try {
|
|
1090
|
-
const report2 = await options.undo(runId, dryRun);
|
|
2033
|
+
const report2 = await options.undo(runId, dryRun, force);
|
|
1091
2034
|
const reverted = report2.steps.filter((step) => step.kind === "revert").length;
|
|
1092
|
-
const
|
|
2035
|
+
const why = report2.halted === void 0 ? "" : ` ${DOT} halted: ${firstLine(report2.halted.detail) || report2.halted.reason}`;
|
|
2036
|
+
const headline = dryRun ? `${String(reverted)} would be reverted ${DOT} nothing changed${why}` : `${report2.status} ${DOT} ${String(reverted)} reverted${why}`;
|
|
2037
|
+
const over = report2.halted?.overwrites ?? "";
|
|
2038
|
+
if (over === "") {
|
|
2039
|
+
say(headline);
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
1093
2042
|
say(
|
|
1094
|
-
|
|
2043
|
+
["changed since this ran, so nothing was written", "", "undoing anyway would write:", ...over.split("\n"), "", "u again to do it"].join("\n"),
|
|
2044
|
+
READING_TICKS
|
|
1095
2045
|
);
|
|
1096
2046
|
} catch (error) {
|
|
1097
2047
|
say(error instanceof Error ? error.message : "the undo failed");
|
|
@@ -1099,12 +2049,41 @@ async function openConsole(options) {
|
|
|
1099
2049
|
screen.busy = void 0;
|
|
1100
2050
|
}
|
|
1101
2051
|
};
|
|
2052
|
+
const rescan = () => {
|
|
2053
|
+
screen.groups = options.scan?.() ?? [];
|
|
2054
|
+
};
|
|
2055
|
+
const connect = async (targets) => {
|
|
2056
|
+
if (options.connect === void 0) {
|
|
2057
|
+
say("no way to connect was configured");
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
if (targets.length === 0) {
|
|
2061
|
+
say("everything here is already covered");
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
if (screen.busy !== void 0) {
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
screen.busy = `connecting ${String(targets.length)}${targets.length === 1 ? " server" : " servers"}`;
|
|
2068
|
+
try {
|
|
2069
|
+
say(await options.connect(targets));
|
|
2070
|
+
} catch (error) {
|
|
2071
|
+
say(error instanceof Error ? error.message : "connecting failed");
|
|
2072
|
+
} finally {
|
|
2073
|
+
screen.busy = void 0;
|
|
2074
|
+
rescan();
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
1102
2077
|
const press = (key) => {
|
|
1103
2078
|
if (screen.confirming !== void 0) {
|
|
1104
2079
|
const runId = screen.confirming;
|
|
2080
|
+
const forcing = screen.confirmingForce;
|
|
1105
2081
|
screen.confirming = void 0;
|
|
2082
|
+
screen.confirmingForce = false;
|
|
2083
|
+
screen.confirmingLabel = void 0;
|
|
1106
2084
|
if (key === "y") {
|
|
1107
|
-
void
|
|
2085
|
+
screen.warned = void 0;
|
|
2086
|
+
void perform(runId, false, forcing);
|
|
1108
2087
|
} else {
|
|
1109
2088
|
say("left alone");
|
|
1110
2089
|
}
|
|
@@ -1127,7 +2106,23 @@ async function openConsole(options) {
|
|
|
1127
2106
|
screen.mode = "gates";
|
|
1128
2107
|
screen.cursor = 0;
|
|
1129
2108
|
return;
|
|
2109
|
+
case "c":
|
|
2110
|
+
screen.mode = "connections";
|
|
2111
|
+
screen.cursor = 0;
|
|
2112
|
+
rescan();
|
|
2113
|
+
return;
|
|
2114
|
+
case "f":
|
|
2115
|
+
if (screen.mode === "run") {
|
|
2116
|
+
screen.expanded = !screen.expanded;
|
|
2117
|
+
say(screen.expanded ? "showing everything recorded" : "back to a summary");
|
|
2118
|
+
}
|
|
2119
|
+
return;
|
|
1130
2120
|
case "r":
|
|
2121
|
+
if (screen.mode === "connections") {
|
|
2122
|
+
rescan();
|
|
2123
|
+
say("rescanned");
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
1131
2126
|
screen.mode = "runs";
|
|
1132
2127
|
screen.cursor = 0;
|
|
1133
2128
|
return;
|
|
@@ -1138,6 +2133,11 @@ async function openConsole(options) {
|
|
|
1138
2133
|
return;
|
|
1139
2134
|
case "\r":
|
|
1140
2135
|
case "\n": {
|
|
2136
|
+
if (screen.mode === "connections") {
|
|
2137
|
+
const chosen = connectionRows(screen)[screen.cursor];
|
|
2138
|
+
void connect(chosen === void 0 ? [] : [chosen]);
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
1141
2141
|
const ready = open();
|
|
1142
2142
|
const run = ready === void 0 ? void 0 : selectedRun(ready);
|
|
1143
2143
|
if (run !== void 0) {
|
|
@@ -1149,6 +2149,10 @@ async function openConsole(options) {
|
|
|
1149
2149
|
case "a":
|
|
1150
2150
|
if (screen.mode === "gates") {
|
|
1151
2151
|
decide(true);
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
if (screen.mode === "connections") {
|
|
2155
|
+
void connect(needsConnecting(screen.groups));
|
|
1152
2156
|
}
|
|
1153
2157
|
return;
|
|
1154
2158
|
case "d":
|
|
@@ -1159,9 +2163,15 @@ async function openConsole(options) {
|
|
|
1159
2163
|
case "p": {
|
|
1160
2164
|
const ready = open();
|
|
1161
2165
|
const run = ready === void 0 ? void 0 : selectedRun(ready);
|
|
1162
|
-
if (run
|
|
1163
|
-
|
|
2166
|
+
if (ready === void 0 || run === void 0) {
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
const here = standing(ready.getActions(run.id));
|
|
2170
|
+
if (here.undoable === 0 && here.conflicted === 0) {
|
|
2171
|
+
say(nothingToUndo(ready, run));
|
|
2172
|
+
return;
|
|
1164
2173
|
}
|
|
2174
|
+
void perform(run.id, true);
|
|
1165
2175
|
return;
|
|
1166
2176
|
}
|
|
1167
2177
|
case "u": {
|
|
@@ -1171,9 +2181,29 @@ async function openConsole(options) {
|
|
|
1171
2181
|
}
|
|
1172
2182
|
const ready = open();
|
|
1173
2183
|
const run = ready === void 0 ? void 0 : selectedRun(ready);
|
|
1174
|
-
if (run
|
|
2184
|
+
if (ready === void 0 || run === void 0) {
|
|
2185
|
+
return;
|
|
2186
|
+
}
|
|
2187
|
+
const here = standing(ready.getActions(run.id));
|
|
2188
|
+
if (here.undoable === 0 && here.conflicted === 0) {
|
|
2189
|
+
say(nothingToUndo(ready, run));
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
const label = `${run.label ?? "an agent"} ${shortTime(run.startedAt).trim()}`;
|
|
2193
|
+
if (here.undoable === 0) {
|
|
2194
|
+
if (screen.warned !== run.id) {
|
|
2195
|
+
screen.warned = run.id;
|
|
2196
|
+
void perform(run.id, true);
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
1175
2199
|
screen.confirming = run.id;
|
|
2200
|
+
screen.confirmingForce = true;
|
|
2201
|
+
screen.confirmingLabel = label;
|
|
2202
|
+
return;
|
|
1176
2203
|
}
|
|
2204
|
+
screen.confirming = run.id;
|
|
2205
|
+
screen.confirmingForce = false;
|
|
2206
|
+
screen.confirmingLabel = label;
|
|
1177
2207
|
return;
|
|
1178
2208
|
}
|
|
1179
2209
|
default:
|
|
@@ -1216,7 +2246,7 @@ async function openConsole(options) {
|
|
|
1216
2246
|
if (options.maxTicks !== void 0 && tick + 1 >= options.maxTicks) {
|
|
1217
2247
|
break;
|
|
1218
2248
|
}
|
|
1219
|
-
await new Promise((
|
|
2249
|
+
await new Promise((resolve4) => setTimeout(resolve4, interval));
|
|
1220
2250
|
}
|
|
1221
2251
|
return 0;
|
|
1222
2252
|
} finally {
|
|
@@ -1232,7 +2262,7 @@ async function openConsole(options) {
|
|
|
1232
2262
|
await reader?.return?.(void 0);
|
|
1233
2263
|
await reading;
|
|
1234
2264
|
})(),
|
|
1235
|
-
new Promise((
|
|
2265
|
+
new Promise((resolve4) => setTimeout(resolve4, 50).unref())
|
|
1236
2266
|
]);
|
|
1237
2267
|
journal?.close();
|
|
1238
2268
|
}
|
|
@@ -1248,24 +2278,36 @@ if (NODE_MAJOR < 22) {
|
|
|
1248
2278
|
process.exit(2);
|
|
1249
2279
|
}
|
|
1250
2280
|
var COMMANDS = `
|
|
1251
|
-
synartesis
|
|
1252
|
-
|
|
2281
|
+
synartesis start here. Live activity,
|
|
2282
|
+
what is waiting for you, and
|
|
2283
|
+
undo -- all in one place, with
|
|
2284
|
+
the arrow keys. Everything
|
|
2285
|
+
below can be done from it.
|
|
2286
|
+
synartesis install [--client <name>] [--dry-run] [--print]
|
|
2287
|
+
synartesis uninstall [--client <name>]
|
|
2288
|
+
synartesis status
|
|
1253
2289
|
synartesis init <server> -- <command> [args...] [--manifest <path>]
|
|
1254
2290
|
synartesis check [--manifest <path>]
|
|
1255
2291
|
synartesis list [--journal <path>]
|
|
1256
|
-
synartesis show <runId> [--journal <path>]
|
|
2292
|
+
synartesis show <runId> [--full] [--journal <path>]
|
|
1257
2293
|
synartesis gates [--journal <path>]
|
|
1258
2294
|
synartesis close [runId] [--journal <path>]
|
|
1259
2295
|
synartesis prune [--older-than <days>] [--dry-run] [--journal <path>]
|
|
1260
|
-
synartesis proxy --manifest <path> [--
|
|
2296
|
+
synartesis proxy --manifest <path> [--server <name>] what your agent runs
|
|
2297
|
+
[--journal <path>]
|
|
1261
2298
|
[--http <port> --token <secret>] for a client that
|
|
1262
2299
|
cannot start one
|
|
1263
2300
|
synartesis watch [--by <name>] [--journal <path>]
|
|
1264
2301
|
synartesis approve [actionId|--all] [--by <name>] [--journal <path>]
|
|
1265
2302
|
synartesis deny [actionId|--all] [--by <name>] [--reason <text>] [--journal <path>]
|
|
1266
|
-
synartesis undo [runId] [--to <seq>] [--dry-run] [--replan]
|
|
2303
|
+
synartesis undo [runId] [--to <seq>] [--dry-run] [--replan] [--force [--yes]]
|
|
1267
2304
|
[--manifest <path>] [--journal <path>]
|
|
1268
2305
|
|
|
2306
|
+
install is the short way in: it finds what Claude Code, Claude Desktop,
|
|
2307
|
+
Cursor or Codex already list, writes a policy covering all of it -- using the ones that
|
|
2308
|
+
ship where they fit -- and points each entry at the proxy. The original config
|
|
2309
|
+
is copied aside first, and uninstall puts it back. status says what is covered.
|
|
2310
|
+
|
|
1269
2311
|
close ends a run left active by a proxy that was killed; nothing guesses at
|
|
1270
2312
|
that, since several proxies can share one journal.
|
|
1271
2313
|
|
|
@@ -1280,6 +2322,14 @@ adds to an existing manifest rather than replacing it.
|
|
|
1280
2322
|
watch is the one to leave running. Anything held for approval appears there,
|
|
1281
2323
|
and a and d answer it without a second terminal or an id to copy.
|
|
1282
2324
|
|
|
2325
|
+
undo stops when somebody has changed the resource since, rather than writing
|
|
2326
|
+
over them. Three ways past that, and it prints all three: leave it, put the
|
|
2327
|
+
resource back as the run left it and --replan, or --force to overwrite.
|
|
2328
|
+
|
|
2329
|
+
--client claude-code, claude-desktop, cursor or codex; all by default
|
|
2330
|
+
--print show the entries install would write, and write nothing
|
|
2331
|
+
--full show every argument, snapshot and inverse in full, nothing elided
|
|
2332
|
+
--server serve one server from the manifest, keeping its tool names
|
|
1283
2333
|
--manifest synartesis.yaml, looked for here and upwards, then in the home
|
|
1284
2334
|
--journal beside the manifest, or the one in the home
|
|
1285
2335
|
--to lowest sequence to undo; earlier actions are left alone
|
|
@@ -1290,6 +2340,8 @@ and a and d answer it without a second terminal or an id to copy.
|
|
|
1290
2340
|
--dry-run read current state and print the plan without changing anything
|
|
1291
2341
|
--replan rebuild each undo from the current manifest, for a run recorded
|
|
1292
2342
|
under a policy that turned out to be wrong
|
|
2343
|
+
--force undo even where the resource changed after the run. On its own it
|
|
2344
|
+
prints the lines it would write over and stops; add --yes to do it
|
|
1293
2345
|
--older-than days of history prune keeps; defaults to 30
|
|
1294
2346
|
--version print the version and exit
|
|
1295
2347
|
|
|
@@ -1314,7 +2366,7 @@ function flag(argv, name) {
|
|
|
1314
2366
|
return value;
|
|
1315
2367
|
}
|
|
1316
2368
|
function positional(argv) {
|
|
1317
|
-
const skip = /* @__PURE__ */ new Set(["--manifest", "--journal", "--to", "--by", "--reason", "--gate-timeout", "--older-than"]);
|
|
2369
|
+
const skip = /* @__PURE__ */ new Set(["--manifest", "--journal", "--to", "--by", "--reason", "--gate-timeout", "--older-than", "--client"]);
|
|
1318
2370
|
const values = [];
|
|
1319
2371
|
const end = argv.indexOf("--");
|
|
1320
2372
|
const ours = end === -1 ? argv : argv.slice(0, end);
|
|
@@ -1369,6 +2421,197 @@ async function runCheck(argv) {
|
|
|
1369
2421
|
out("");
|
|
1370
2422
|
return 0;
|
|
1371
2423
|
}
|
|
2424
|
+
async function runInstall(argv) {
|
|
2425
|
+
const manifestPath = findManifest(flag(argv, "--manifest"));
|
|
2426
|
+
const only = flag(argv, "--client");
|
|
2427
|
+
const dryRun = argv.includes("--dry-run");
|
|
2428
|
+
const printOnly = argv.includes("--print");
|
|
2429
|
+
const sites = discover(process.cwd()).filter(
|
|
2430
|
+
(site) => only === void 0 || site.client === only
|
|
2431
|
+
);
|
|
2432
|
+
if (sites.length === 0) {
|
|
2433
|
+
out("");
|
|
2434
|
+
out(` ${style.quiet("No MCP client config was found on this machine.")}`);
|
|
2435
|
+
out(` ${style.quiet("Looked for Claude Code, Claude Desktop, Cursor and Codex.")}`);
|
|
2436
|
+
out("");
|
|
2437
|
+
return 0;
|
|
2438
|
+
}
|
|
2439
|
+
const invoker = invokerFor(version(), fileURLToPath2(import.meta.url));
|
|
2440
|
+
const { plans, yaml } = await planInstall(sites, manifestPath, invoker);
|
|
2441
|
+
const total = plans.reduce((sum, plan) => sum + plan.servers.length, 0);
|
|
2442
|
+
out("");
|
|
2443
|
+
if (invoker.note !== void 0 && total > 0) {
|
|
2444
|
+
out(` ${style.accent("note")} ${style.quiet(invoker.note)}`);
|
|
2445
|
+
}
|
|
2446
|
+
out(` ${style.label(dryRun || printOnly ? "would cover" : "covering")} ${style.strong(manifestPath)}`);
|
|
2447
|
+
out(` ${rule(60)}`);
|
|
2448
|
+
for (const plan of plans) {
|
|
2449
|
+
out("");
|
|
2450
|
+
out(` ${style.strong(plan.site.label)} ${style.quiet(plan.site.scope)}`);
|
|
2451
|
+
out(` ${style.quiet(plan.site.path)}`);
|
|
2452
|
+
for (const server of plan.servers) {
|
|
2453
|
+
const note = server.adopted === void 0 ? style.accent("drafted, every tool held until you say how to undo it") : style.quiet(`the policy that ships for ${server.adopted} (${String(server.tools ?? 0)} tools)`);
|
|
2454
|
+
out(` ${style.strong(server.name.padEnd(18))} ${note}`);
|
|
2455
|
+
}
|
|
2456
|
+
for (const skip of plan.skipped) {
|
|
2457
|
+
out(` ${style.quiet(skip.name.padEnd(18))} ${style.quiet(skip.why)}`);
|
|
2458
|
+
}
|
|
2459
|
+
if (plan.servers.length === 0 && plan.skipped.length === 0) {
|
|
2460
|
+
out(` ${style.quiet("no servers listed")}`);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
if (printOnly) {
|
|
2464
|
+
out("");
|
|
2465
|
+
out(` ${style.label("entries")}`);
|
|
2466
|
+
for (const plan of plans) {
|
|
2467
|
+
for (const server of plan.servers) {
|
|
2468
|
+
out(` ${JSON.stringify({ [server.name]: server.wrapped }, void 0, 2)}`);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
out("");
|
|
2472
|
+
return 0;
|
|
2473
|
+
}
|
|
2474
|
+
if (total === 0) {
|
|
2475
|
+
out("");
|
|
2476
|
+
out(` ${style.quiet("Nothing to do; everything found is already covered.")}`);
|
|
2477
|
+
out("");
|
|
2478
|
+
return 0;
|
|
2479
|
+
}
|
|
2480
|
+
if (dryRun) {
|
|
2481
|
+
out("");
|
|
2482
|
+
out(` ${style.quiet("Nothing was written. Run without --dry-run to apply.")}`);
|
|
2483
|
+
out("");
|
|
2484
|
+
return 0;
|
|
2485
|
+
}
|
|
2486
|
+
const applied = applyInstall(plans, manifestPath, yaml);
|
|
2487
|
+
out("");
|
|
2488
|
+
for (const entry of applied) {
|
|
2489
|
+
out(` ${style.quiet("backed up to")} ${entry.backup}`);
|
|
2490
|
+
}
|
|
2491
|
+
out("");
|
|
2492
|
+
out(` ${style.quiet("Restart your client, and its servers now run through Synartesis.")}`);
|
|
2493
|
+
out("");
|
|
2494
|
+
out(` ${style.quiet("One command shows everything and does everything:")}`);
|
|
2495
|
+
out(` ${style.accent(cliCommand())}`);
|
|
2496
|
+
out("");
|
|
2497
|
+
out(
|
|
2498
|
+
` ${style.quiet("Live activity, what is held for approval, and undo, all from there.")}`
|
|
2499
|
+
);
|
|
2500
|
+
out(` ${style.quiet("You do not need a second terminal unless you want one.")}`);
|
|
2501
|
+
out("");
|
|
2502
|
+
return 0;
|
|
2503
|
+
}
|
|
2504
|
+
async function runUninstall(argv) {
|
|
2505
|
+
const manifestPath = findManifest(flag(argv, "--manifest"));
|
|
2506
|
+
const only = flag(argv, "--client");
|
|
2507
|
+
const sites = discover(process.cwd()).filter(
|
|
2508
|
+
(site) => only === void 0 || site.client === only
|
|
2509
|
+
);
|
|
2510
|
+
const restored = applyUninstall(sites, manifestPath);
|
|
2511
|
+
out("");
|
|
2512
|
+
if (restored.length === 0) {
|
|
2513
|
+
out(` ${style.quiet("Nothing was covered, so nothing was changed.")}`);
|
|
2514
|
+
out("");
|
|
2515
|
+
return 0;
|
|
2516
|
+
}
|
|
2517
|
+
out(` ${style.label("restored")}`);
|
|
2518
|
+
out(` ${rule(60)}`);
|
|
2519
|
+
for (const entry of restored) {
|
|
2520
|
+
out("");
|
|
2521
|
+
out(` ${style.strong(entry.site.label)} ${style.quiet(entry.site.scope)}`);
|
|
2522
|
+
for (const name of entry.servers) {
|
|
2523
|
+
out(` ${style.strong(name)}`);
|
|
2524
|
+
}
|
|
2525
|
+
for (const name of entry.unknown) {
|
|
2526
|
+
out(
|
|
2527
|
+
` ${style.accent(name)} ${style.quiet("is wrapped but its original was not recorded; left as it is")}`
|
|
2528
|
+
);
|
|
2529
|
+
}
|
|
2530
|
+
if (entry.backup !== "") {
|
|
2531
|
+
out(` ${style.quiet(`backed up to ${entry.backup}`)}`);
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
out("");
|
|
2535
|
+
out(` ${style.quiet("The policy and journal were left alone.")}`);
|
|
2536
|
+
out("");
|
|
2537
|
+
return await Promise.resolve(0);
|
|
2538
|
+
}
|
|
2539
|
+
function openIfPresent(journalPath) {
|
|
2540
|
+
try {
|
|
2541
|
+
return existsSync7(journalPath) ? openJournal(journalPath, { mustExist: true }) : void 0;
|
|
2542
|
+
} catch {
|
|
2543
|
+
return void 0;
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
async function connectThese(targets, manifestPath) {
|
|
2547
|
+
const invoker = invokerFor(version(), fileURLToPath2(import.meta.url));
|
|
2548
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
2549
|
+
const sites = /* @__PURE__ */ new Map();
|
|
2550
|
+
for (const target of targets) {
|
|
2551
|
+
const key = `${target.site.path}${target.site.scope}`;
|
|
2552
|
+
sites.set(key, target.site);
|
|
2553
|
+
(wanted.get(key) ?? wanted.set(key, /* @__PURE__ */ new Set()).get(key))?.add(target.server);
|
|
2554
|
+
}
|
|
2555
|
+
const { plans, yaml } = await planInstall([...sites.values()], manifestPath, invoker);
|
|
2556
|
+
const narrowed = plans.map((plan) => ({
|
|
2557
|
+
...plan,
|
|
2558
|
+
servers: plan.servers.filter(
|
|
2559
|
+
(server) => wanted.get(`${plan.site.path}${plan.site.scope}`)?.has(server.name) === true
|
|
2560
|
+
)
|
|
2561
|
+
}));
|
|
2562
|
+
const applied = applyInstall(narrowed, manifestPath, yaml);
|
|
2563
|
+
const count = applied.reduce((sum, entry) => sum + entry.servers.length, 0);
|
|
2564
|
+
if (count === 0) {
|
|
2565
|
+
return "nothing was connected; see the reasons above";
|
|
2566
|
+
}
|
|
2567
|
+
return `connected ${String(count)}${count === 1 ? " server" : " servers"} \xB7 restart the client to pick it up`;
|
|
2568
|
+
}
|
|
2569
|
+
function runStatus(argv) {
|
|
2570
|
+
const manifestPath = findManifest(flag(argv, "--manifest"));
|
|
2571
|
+
const journalPath = findJournal(flag(argv, "--journal"), manifestPath);
|
|
2572
|
+
out("");
|
|
2573
|
+
out(
|
|
2574
|
+
` ${style.label("policy")} ${existsSync7(manifestPath) ? style.strong(manifestPath) : style.quiet(`${manifestPath} (none yet)`)}`
|
|
2575
|
+
);
|
|
2576
|
+
out(
|
|
2577
|
+
` ${style.label("journal")} ${bytesOf(journalPath) === void 0 ? style.quiet(`${journalPath} (none yet)`) : `${style.strong(journalPath)} ${style.quiet(sizeOf(journalPath))}`}`
|
|
2578
|
+
);
|
|
2579
|
+
out("");
|
|
2580
|
+
const journal = openIfPresent(journalPath);
|
|
2581
|
+
try {
|
|
2582
|
+
const groups = scan(journal, process.cwd());
|
|
2583
|
+
if (groups.length === 0) {
|
|
2584
|
+
out(` ${style.quiet("No MCP client config found.")}`);
|
|
2585
|
+
out(` ${style.quiet("Looked for Claude Code, Claude Desktop, Cursor and Codex.")}`);
|
|
2586
|
+
out("");
|
|
2587
|
+
return 0;
|
|
2588
|
+
}
|
|
2589
|
+
const now = /* @__PURE__ */ new Date();
|
|
2590
|
+
for (const group of groups) {
|
|
2591
|
+
out(` ${style.strong(group.label)} ${style.quiet(group.scope)}`);
|
|
2592
|
+
if (group.problem !== void 0) {
|
|
2593
|
+
out(` ${style.accent(group.problem)}`);
|
|
2594
|
+
} else if (group.connections.length === 0) {
|
|
2595
|
+
out(` ${style.quiet("no servers listed")}`);
|
|
2596
|
+
}
|
|
2597
|
+
for (const connection of group.connections) {
|
|
2598
|
+
const state = stateOf(connection, now);
|
|
2599
|
+
out(
|
|
2600
|
+
` ${connection.server.padEnd(20)} ${connection.covered ? style.quiet(state) : style.accent(state)}`
|
|
2601
|
+
);
|
|
2602
|
+
}
|
|
2603
|
+
out("");
|
|
2604
|
+
}
|
|
2605
|
+
const waiting = needsConnecting(groups).length;
|
|
2606
|
+
out(
|
|
2607
|
+
waiting === 0 ? ` ${style.quiet("Everything found is covered.")}` : ` ${style.accent(`${String(waiting)} not covered.`)} ${style.quiet(`${cliCommand()} install covers them.`)}`
|
|
2608
|
+
);
|
|
2609
|
+
out("");
|
|
2610
|
+
} finally {
|
|
2611
|
+
journal?.close();
|
|
2612
|
+
}
|
|
2613
|
+
return 0;
|
|
2614
|
+
}
|
|
1372
2615
|
async function runInit(argv) {
|
|
1373
2616
|
const name = positional(argv)[1];
|
|
1374
2617
|
const separator = argv.indexOf("--");
|
|
@@ -1381,7 +2624,7 @@ async function runInit(argv) {
|
|
|
1381
2624
|
}
|
|
1382
2625
|
const path = findManifest(flag(argv, "--manifest"));
|
|
1383
2626
|
const force = argv.includes("--force");
|
|
1384
|
-
const present =
|
|
2627
|
+
const present = existsSync7(path);
|
|
1385
2628
|
if (present && force) {
|
|
1386
2629
|
throw new UsageError(
|
|
1387
2630
|
`--force would discard ${path}. Delete it yourself if that is what you want; init will otherwise add to it.`
|
|
@@ -1391,11 +2634,11 @@ async function runInit(argv) {
|
|
|
1391
2634
|
name,
|
|
1392
2635
|
command,
|
|
1393
2636
|
args: argv.slice(separator + 2),
|
|
1394
|
-
...present ? { existing:
|
|
2637
|
+
...present ? { existing: readFileSync4(path, "utf8") } : {}
|
|
1395
2638
|
});
|
|
1396
2639
|
parseManifest(draft.yaml, path);
|
|
1397
|
-
|
|
1398
|
-
|
|
2640
|
+
mkdirSync2(dirname3(resolve3(path)), { recursive: true, mode: 448 });
|
|
2641
|
+
writeFileSync3(path, draft.yaml);
|
|
1399
2642
|
out("");
|
|
1400
2643
|
out(` ${style.label(present ? "extended" : "wrote")} ${style.strong(path)}`);
|
|
1401
2644
|
out(` ${rule(54)}`);
|
|
@@ -1410,7 +2653,7 @@ async function runInit(argv) {
|
|
|
1410
2653
|
out(` ${style.quiet("Read it before you trust it, then point your MCP client at:")}`);
|
|
1411
2654
|
}
|
|
1412
2655
|
out("");
|
|
1413
|
-
out(` ${style.accent(`${proxyCommand()} --manifest ${
|
|
2656
|
+
out(` ${style.accent(`${proxyCommand()} --manifest ${resolve3(path)}`)}`);
|
|
1414
2657
|
out("");
|
|
1415
2658
|
return 0;
|
|
1416
2659
|
}
|
|
@@ -1472,12 +2715,12 @@ function runList(journal, asJson, journalPath) {
|
|
|
1472
2715
|
return 0;
|
|
1473
2716
|
}
|
|
1474
2717
|
out("");
|
|
1475
|
-
out(` ${style.label("
|
|
2718
|
+
out(` ${style.label("sessions")} ${style.quiet("most recent first")}`);
|
|
1476
2719
|
out(` ${rule(96)}`);
|
|
1477
2720
|
out("");
|
|
1478
2721
|
out(
|
|
1479
2722
|
style.quiet(
|
|
1480
|
-
` ${"
|
|
2723
|
+
` ${"session".padEnd(36)} ${"started".padEnd(15)} ${"status".padEnd(12)} actions agent`
|
|
1481
2724
|
)
|
|
1482
2725
|
);
|
|
1483
2726
|
for (const run of runs) {
|
|
@@ -1490,7 +2733,7 @@ function runList(journal, asJson, journalPath) {
|
|
|
1490
2733
|
].filter((note2) => note2 !== "");
|
|
1491
2734
|
const note = notes.length === 0 ? "" : ` ${style.accent(`(${notes.join("; ")})`)}`;
|
|
1492
2735
|
out(
|
|
1493
|
-
` ${style.strong(run.id)} ${style.quiet(run.startedAt)} ${run.status.padEnd(12)} ${String(actions.length).padStart(7)} ${run.label ?? "-"}${note}`
|
|
2736
|
+
` ${style.strong(run.id)} ${style.quiet(shortTime(run.startedAt).trimEnd().padEnd(13))} ${run.status.padEnd(12)} ${String(actions.length).padStart(7)} ${run.label ?? "-"}${note}`
|
|
1494
2737
|
);
|
|
1495
2738
|
}
|
|
1496
2739
|
out("");
|
|
@@ -1501,6 +2744,7 @@ function runList(journal, asJson, journalPath) {
|
|
|
1501
2744
|
return 0;
|
|
1502
2745
|
}
|
|
1503
2746
|
function runShow(argv, journal, asJson) {
|
|
2747
|
+
const full = argv.includes("--full");
|
|
1504
2748
|
const runs = [...journal.listRuns()].reverse();
|
|
1505
2749
|
const run = pick(runs, positional(argv)[1], RUN, true);
|
|
1506
2750
|
const runId = run.id;
|
|
@@ -1509,13 +2753,13 @@ function runShow(argv, journal, asJson) {
|
|
|
1509
2753
|
return 0;
|
|
1510
2754
|
}
|
|
1511
2755
|
out("");
|
|
1512
|
-
out(` ${style.label("
|
|
2756
|
+
out(` ${style.label("session")} ${style.strong(run.label ?? "an agent")} ${style.quiet(run.id)}`);
|
|
1513
2757
|
out(` ${rule(54)}`);
|
|
1514
2758
|
out("");
|
|
1515
2759
|
out(` ${style.quiet("agent ")} ${run.label ?? "-"}`);
|
|
1516
|
-
out(` ${style.quiet("started")} ${run.startedAt}`);
|
|
2760
|
+
out(` ${style.quiet("started")} ${fullTime(run.startedAt)} ${style.quiet(ago(run.startedAt))}`);
|
|
1517
2761
|
out(
|
|
1518
|
-
` ${style.quiet("status ")} ${run.status}` + (run.endedAt === void 0 ? "" : style.quiet(` ended ${run.endedAt}`))
|
|
2762
|
+
` ${style.quiet("status ")} ${run.status}` + (run.endedAt === void 0 ? "" : style.quiet(` ended ${fullTime(run.endedAt)}`))
|
|
1519
2763
|
);
|
|
1520
2764
|
const actions = journal.getActions(runId);
|
|
1521
2765
|
if (actions.length === 0) {
|
|
@@ -1530,20 +2774,40 @@ function runShow(argv, journal, asJson) {
|
|
|
1530
2774
|
out("");
|
|
1531
2775
|
for (const action of actions) {
|
|
1532
2776
|
out(
|
|
1533
|
-
` ${style.quiet(String(action.seq).padStart(3))} ${badgeOf(action)} ${
|
|
2777
|
+
` ${style.quiet(String(action.seq).padStart(3))} ${badgeOf(action)} ${statusOf(action)} ${style.strong(`${action.server}.${action.tool}`)}`
|
|
1534
2778
|
);
|
|
1535
|
-
|
|
2779
|
+
if (full) {
|
|
2780
|
+
out(` ${style.quiet("arguments")}`);
|
|
2781
|
+
for (const line2 of block(action.args)) {
|
|
2782
|
+
out(` ${line2}`);
|
|
2783
|
+
}
|
|
2784
|
+
} else {
|
|
2785
|
+
out(` ${style.quiet(summariseArgs(action.args, 96))}`);
|
|
2786
|
+
}
|
|
1536
2787
|
if (action.approvedAt !== void 0) {
|
|
1537
2788
|
const verb = action.status === "denied" ? "denied" : "approved";
|
|
1538
2789
|
out(
|
|
1539
|
-
` ${style.accent(`${verb} by ${action.approvedBy ?? "nobody"}`)} ${style.quiet(`at ${action.approvedAt}`)}`
|
|
2790
|
+
` ${style.accent(`${verb} by ${action.approvedBy ?? "nobody"}`)} ${style.quiet(`at ${fullTime(action.approvedAt)}`)}`
|
|
1540
2791
|
);
|
|
1541
2792
|
}
|
|
1542
2793
|
if (action.error !== void 0) {
|
|
1543
|
-
out(` ${style.quiet(`note: ${truncate3(action.error, 200)}`)}`);
|
|
2794
|
+
out(` ${style.quiet(`note: ${full ? action.error : truncate3(action.error, 200)}`)}`);
|
|
2795
|
+
}
|
|
2796
|
+
if (action.snapshot !== void 0 && full) {
|
|
2797
|
+
out(` ${style.quiet("what it replaced")}`);
|
|
2798
|
+
for (const line2 of block(action.snapshot)) {
|
|
2799
|
+
out(` ${line2}`);
|
|
2800
|
+
}
|
|
1544
2801
|
}
|
|
1545
2802
|
if (action.inverse !== void 0) {
|
|
1546
|
-
|
|
2803
|
+
if (full) {
|
|
2804
|
+
out(` ${style.quiet("undo")}`);
|
|
2805
|
+
for (const line2 of block(action.inverse)) {
|
|
2806
|
+
out(` ${line2}`);
|
|
2807
|
+
}
|
|
2808
|
+
} else {
|
|
2809
|
+
out(` ${style.quiet("undo:")} ${style.quiet(summariseArgs(inverseArgs(action.inverse), 90))}`);
|
|
2810
|
+
}
|
|
1547
2811
|
}
|
|
1548
2812
|
}
|
|
1549
2813
|
out("");
|
|
@@ -1563,7 +2827,7 @@ function badgeOf(action) {
|
|
|
1563
2827
|
const plain = `${CLASS_MARK[action.class]} ${action.class}`.padEnd(BADGE_WIDTH);
|
|
1564
2828
|
return action.class === "irreversible" ? style.accent(plain) : style.quiet(plain);
|
|
1565
2829
|
}
|
|
1566
|
-
function
|
|
2830
|
+
function statusOf(action) {
|
|
1567
2831
|
const text = labelFor(action).padEnd(13);
|
|
1568
2832
|
if (wasRefused(action)) {
|
|
1569
2833
|
return style.accent(text);
|
|
@@ -1591,6 +2855,15 @@ function wrapped(text, width) {
|
|
|
1591
2855
|
}
|
|
1592
2856
|
return lines;
|
|
1593
2857
|
}
|
|
2858
|
+
function block(value) {
|
|
2859
|
+
return JSON.stringify(value, void 0, 2).split("\n").map((line2) => style.quiet(line2));
|
|
2860
|
+
}
|
|
2861
|
+
function inverseArgs(inverse) {
|
|
2862
|
+
if (typeof inverse === "object" && inverse !== null && "args" in inverse) {
|
|
2863
|
+
return inverse.args;
|
|
2864
|
+
}
|
|
2865
|
+
return inverse;
|
|
2866
|
+
}
|
|
1594
2867
|
function truncate3(text, limit) {
|
|
1595
2868
|
return text.length <= limit ? text : `${text.slice(0, limit - 3)}...`;
|
|
1596
2869
|
}
|
|
@@ -1761,7 +3034,7 @@ function runDecision(argv, journal, approving) {
|
|
|
1761
3034
|
}
|
|
1762
3035
|
return failed === 0 ? 0 : 1;
|
|
1763
3036
|
}
|
|
1764
|
-
function report(result) {
|
|
3037
|
+
function report(result, alreadyForcing = false) {
|
|
1765
3038
|
out("");
|
|
1766
3039
|
out(` ${style.label(result.dryRun ? "dry run" : "undo")} ${style.strong(result.runId)}`);
|
|
1767
3040
|
out(` ${rule(72)}`);
|
|
@@ -1786,15 +3059,37 @@ function report(result) {
|
|
|
1786
3059
|
}
|
|
1787
3060
|
}
|
|
1788
3061
|
if (result.halted !== void 0) {
|
|
3062
|
+
const halt = result.halted;
|
|
1789
3063
|
out("");
|
|
1790
3064
|
out(
|
|
1791
|
-
` ${style.accent("halted")} ${style.quiet(`at
|
|
3065
|
+
` ${style.accent("halted")} ${style.quiet(`at ${String(halt.seq)}`)} ${halt.reason}
|
|
3066
|
+
${style.quiet("nothing was written here")}`
|
|
1792
3067
|
);
|
|
1793
|
-
if (
|
|
1794
|
-
|
|
3068
|
+
if (halt.detail !== "") {
|
|
3069
|
+
out("");
|
|
3070
|
+
for (const line2 of halt.detail.split("\n")) {
|
|
1795
3071
|
out(` ${style.quiet(line2)}`);
|
|
1796
3072
|
}
|
|
1797
3073
|
}
|
|
3074
|
+
if (halt.overwrites !== void 0 && halt.overwrites !== "") {
|
|
3075
|
+
out("");
|
|
3076
|
+
out(` ${style.accent("undoing anyway would write:")}`);
|
|
3077
|
+
for (const line2 of halt.overwrites.split("\n")) {
|
|
3078
|
+
out(` ${style.quiet(line2)}`);
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
if (halt.conflict === true && !alreadyForcing) {
|
|
3082
|
+
const self = cliCommand();
|
|
3083
|
+
const id = result.runId.slice(0, 8);
|
|
3084
|
+
out("");
|
|
3085
|
+
out(` ${style.quiet("keep the change, drop the undo:")} ${style.quiet("nothing to do")}`);
|
|
3086
|
+
out(
|
|
3087
|
+
` ${style.quiet("put it back as the run left it:")} ${style.strong(`${self} undo ${id} --replan`)}`
|
|
3088
|
+
);
|
|
3089
|
+
out(
|
|
3090
|
+
` ${style.quiet("undo anyway, losing the change:")} ${style.strong(`${self} undo ${id} --force`)}`
|
|
3091
|
+
);
|
|
3092
|
+
}
|
|
1798
3093
|
}
|
|
1799
3094
|
const permanent = result.steps.filter((step) => step.kind === "permanent");
|
|
1800
3095
|
if (permanent.length > 0) {
|
|
@@ -1825,13 +3120,23 @@ async function performUndo(manifestPath, journal, runId, options) {
|
|
|
1825
3120
|
})
|
|
1826
3121
|
);
|
|
1827
3122
|
}
|
|
1828
|
-
|
|
3123
|
+
const base = {
|
|
1829
3124
|
journal,
|
|
1830
3125
|
router: createRouter(upstreams, manifest),
|
|
1831
3126
|
runId,
|
|
1832
3127
|
...options.toSeq === void 0 ? {} : { toSeq: options.toSeq },
|
|
1833
|
-
dryRun: options.dryRun,
|
|
1834
3128
|
...options.replan === true ? { replanWith: manifest } : {}
|
|
3129
|
+
};
|
|
3130
|
+
if (options.preflight === true) {
|
|
3131
|
+
const seen = await rollback({ ...base, dryRun: true });
|
|
3132
|
+
if (seen.halted?.conflict === true) {
|
|
3133
|
+
return seen;
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
return await rollback({
|
|
3137
|
+
...base,
|
|
3138
|
+
dryRun: options.dryRun,
|
|
3139
|
+
...options.force === true ? { force: true } : {}
|
|
1835
3140
|
});
|
|
1836
3141
|
} finally {
|
|
1837
3142
|
for (const upstream of upstreams) {
|
|
@@ -1840,7 +3145,41 @@ async function performUndo(manifestPath, journal, runId, options) {
|
|
|
1840
3145
|
}
|
|
1841
3146
|
}
|
|
1842
3147
|
async function runUndo(argv, journal) {
|
|
1843
|
-
const
|
|
3148
|
+
const given = positional(argv)[1];
|
|
3149
|
+
const chosen = pick([...journal.listRuns()].reverse(), given, RUN, true);
|
|
3150
|
+
const runId = chosen.id;
|
|
3151
|
+
if (given === void 0) {
|
|
3152
|
+
const actions = journal.getActions(runId);
|
|
3153
|
+
const left = actions.filter((action) => action.status === "applied").length;
|
|
3154
|
+
out("");
|
|
3155
|
+
out(
|
|
3156
|
+
` ${style.quiet("no session named, so the most recent:")} ${style.strong(runId.slice(0, 8))} ` + style.quiet(`${chosen.label ?? "an agent"}, ${shortTime(chosen.startedAt).trim()}`)
|
|
3157
|
+
);
|
|
3158
|
+
if (left === 0) {
|
|
3159
|
+
out("");
|
|
3160
|
+
out(
|
|
3161
|
+
` ${style.quiet(
|
|
3162
|
+
actions.length === 0 ? "Nothing was recorded in it, so there is nothing to undo." : "Nothing in it is still applied; it has already been undone."
|
|
3163
|
+
)}`
|
|
3164
|
+
);
|
|
3165
|
+
const other = [...journal.listRuns()].reverse().find(
|
|
3166
|
+
(run) => run.id !== runId && journal.getActions(run.id).some((action) => action.status === "applied" && action.inverse !== void 0)
|
|
3167
|
+
);
|
|
3168
|
+
if (other !== void 0) {
|
|
3169
|
+
out("");
|
|
3170
|
+
out(
|
|
3171
|
+
` ${style.quiet("The session that did something:")} ${style.strong(other.id.slice(0, 8))} ` + style.quiet(`${other.label ?? "an agent"}, ${shortTime(other.startedAt).trim()}`)
|
|
3172
|
+
);
|
|
3173
|
+
out(` ${style.quiet(`${cliCommand()} undo ${other.id.slice(0, 8)}`)}`);
|
|
3174
|
+
} else {
|
|
3175
|
+
out(
|
|
3176
|
+
` ${style.quiet(`Name one to undo a different session: ${cliCommand()} undo <session>`)}`
|
|
3177
|
+
);
|
|
3178
|
+
}
|
|
3179
|
+
out("");
|
|
3180
|
+
return 0;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
1844
3183
|
const rawTo = flag(argv, "--to");
|
|
1845
3184
|
const toSeq = rawTo === void 0 ? void 0 : Number(rawTo);
|
|
1846
3185
|
if (toSeq !== void 0 && (!Number.isInteger(toSeq) || toSeq < 1)) {
|
|
@@ -1854,15 +3193,32 @@ async function runUndo(argv, journal) {
|
|
|
1854
3193
|
);
|
|
1855
3194
|
}
|
|
1856
3195
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
3196
|
+
const forcing = argv.includes("--force");
|
|
3197
|
+
const said = argv.includes("--yes");
|
|
3198
|
+
const result = await performUndo(findManifest(flag(argv, "--manifest")), journal, runId, {
|
|
3199
|
+
dryRun: argv.includes("--dry-run"),
|
|
3200
|
+
...toSeq === void 0 ? {} : { toSeq },
|
|
3201
|
+
replan: argv.includes("--replan"),
|
|
3202
|
+
...forcing ? { force: said, preflight: !said } : {}
|
|
3203
|
+
});
|
|
3204
|
+
const code = report(result, forcing);
|
|
3205
|
+
if (forcing && !said && result.halted?.conflict === true) {
|
|
3206
|
+
out(` ${style.quiet("nothing has been written. To go ahead and lose that change:")}`);
|
|
3207
|
+
out(
|
|
3208
|
+
` ${style.strong(`${cliCommand()} undo ${runId.slice(0, 8)} --force --yes`)}`
|
|
3209
|
+
);
|
|
3210
|
+
out("");
|
|
3211
|
+
}
|
|
3212
|
+
return code;
|
|
1864
3213
|
}
|
|
1865
3214
|
var FLAGS = /* @__PURE__ */ new Set([
|
|
3215
|
+
// Two lists have to agree about a flag: this one decides whether it is
|
|
3216
|
+
// accepted at all, and the skip set in positional() decides whether its
|
|
3217
|
+
// value is mistaken for a command. --client was in one and not the other,
|
|
3218
|
+
// so `install --client codex` printed the help instead of installing.
|
|
3219
|
+
"--client",
|
|
3220
|
+
"--print",
|
|
3221
|
+
"--full",
|
|
1866
3222
|
"--manifest",
|
|
1867
3223
|
"--journal",
|
|
1868
3224
|
"--to",
|
|
@@ -1874,6 +3230,7 @@ var FLAGS = /* @__PURE__ */ new Set([
|
|
|
1874
3230
|
"--replan",
|
|
1875
3231
|
"--reason",
|
|
1876
3232
|
"--force",
|
|
3233
|
+
"--yes",
|
|
1877
3234
|
"--older-than",
|
|
1878
3235
|
"--help",
|
|
1879
3236
|
"-h",
|
|
@@ -1882,8 +3239,8 @@ var FLAGS = /* @__PURE__ */ new Set([
|
|
|
1882
3239
|
]);
|
|
1883
3240
|
function version() {
|
|
1884
3241
|
try {
|
|
1885
|
-
const root =
|
|
1886
|
-
const parsed = JSON.parse(
|
|
3242
|
+
const root = dirname3(fileURLToPath2(import.meta.url));
|
|
3243
|
+
const parsed = JSON.parse(readFileSync4(join2(root, "..", "package.json"), "utf8"));
|
|
1887
3244
|
const found = typeof parsed === "object" && parsed !== null ? parsed.version : void 0;
|
|
1888
3245
|
return typeof found === "string" ? found : "unknown";
|
|
1889
3246
|
} catch {
|
|
@@ -1901,7 +3258,7 @@ function rejectUnknownFlags(argv) {
|
|
|
1901
3258
|
}
|
|
1902
3259
|
}
|
|
1903
3260
|
function openJournalOrExplain(journalPath) {
|
|
1904
|
-
if (!
|
|
3261
|
+
if (!existsSync7(journalPath)) {
|
|
1905
3262
|
throw new UsageError(
|
|
1906
3263
|
`nothing has been recorded yet: there is no journal at ${journalPath}. One appears the first time an agent calls a tool through synartesis proxy.`
|
|
1907
3264
|
);
|
|
@@ -1930,13 +3287,18 @@ ${COMMANDS}`);
|
|
|
1930
3287
|
const journalPath2 = findJournal(flag(argv, "--journal"), manifestPath);
|
|
1931
3288
|
return await openConsole({
|
|
1932
3289
|
journalPath: journalPath2,
|
|
3290
|
+
scan: () => scan(openIfPresent(journalPath2), process.cwd()),
|
|
3291
|
+
connect: async (targets) => await connectThese(targets, manifestPath),
|
|
1933
3292
|
write: (text) => process.stdout.write(text),
|
|
1934
3293
|
live: process.stdout.isTTY,
|
|
1935
3294
|
decideAs: flag(argv, "--by") ?? process.env["USER"] ?? process.env["LOGNAME"] ?? "unknown",
|
|
1936
|
-
undo: async (runId, dryRun) => {
|
|
3295
|
+
undo: async (runId, dryRun, force) => {
|
|
1937
3296
|
const journal2 = openJournal(journalPath2, { mustExist: true });
|
|
1938
3297
|
try {
|
|
1939
|
-
return await performUndo(manifestPath, journal2, runId, {
|
|
3298
|
+
return await performUndo(manifestPath, journal2, runId, {
|
|
3299
|
+
dryRun,
|
|
3300
|
+
...force === true ? { force: true } : {}
|
|
3301
|
+
});
|
|
1940
3302
|
} finally {
|
|
1941
3303
|
journal2.close();
|
|
1942
3304
|
}
|
|
@@ -1949,6 +3311,15 @@ ${COMMANDS}`);
|
|
|
1949
3311
|
if (command === "check") {
|
|
1950
3312
|
return await runCheck(argv);
|
|
1951
3313
|
}
|
|
3314
|
+
if (command === "install") {
|
|
3315
|
+
return await runInstall(argv);
|
|
3316
|
+
}
|
|
3317
|
+
if (command === "uninstall") {
|
|
3318
|
+
return await runUninstall(argv);
|
|
3319
|
+
}
|
|
3320
|
+
if (command === "status") {
|
|
3321
|
+
return runStatus(argv);
|
|
3322
|
+
}
|
|
1952
3323
|
const asJson = argv.includes("--json");
|
|
1953
3324
|
const given = flag(argv, "--journal");
|
|
1954
3325
|
const journalPath = findJournal(given, findManifest(flag(argv, "--manifest")));
|
|
@@ -1964,8 +3335,8 @@ ${COMMANDS}`);
|
|
|
1964
3335
|
decideAs: flag(argv, "--by") ?? process.env["USER"] ?? process.env["LOGNAME"] ?? "unknown"
|
|
1965
3336
|
});
|
|
1966
3337
|
}
|
|
1967
|
-
journalArg = given === void 0 ? "" : ` --journal ${
|
|
1968
|
-
if (!
|
|
3338
|
+
journalArg = given === void 0 ? "" : ` --journal ${resolve3(given)}`;
|
|
3339
|
+
if (!existsSync7(journalPath) && (command === "list" || command === "show" || command === "gates")) {
|
|
1969
3340
|
if (asJson) {
|
|
1970
3341
|
out(JSON.stringify(command === "show" ? { run: null, actions: [] } : []));
|
|
1971
3342
|
return 0;
|