@akms/mcp 0.3.1 → 0.3.2
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 +10 -0
- package/README.md +23 -0
- package/lib/cli.js +152 -45
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@akms/mcp`.
|
|
4
4
|
|
|
5
|
+
## [0.3.2] - 2026-10-26
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Point Sync review instructions to its current menu and name the services retaining old connection entries, grouping only identical retained lists.
|
|
10
|
+
- Separate runtime waits from failed changes in Register and Sync. Group retained connection names, count saved connections separately from service entries, and show semantic colors only on interactive terminals without NO_COLOR.
|
|
11
|
+
- Offer unimported connections directly from Sync and compare both saved and external settings before resolving an individual entry during session migration. Wide terminals show versions side by side; narrow terminals keep wrapped values.
|
|
12
|
+
- Preserve existing terminal symbols and keep status words visible when colors are disabled.
|
|
13
|
+
- Document the upgrade path from 0.2.x, including pending runtimes, differing imported settings, reconnecting clients, rollback and registry migration.
|
|
14
|
+
|
|
5
15
|
## [0.3.1] - 2026-10-26
|
|
6
16
|
|
|
7
17
|
### Verification
|
package/README.md
CHANGED
|
@@ -48,6 +48,29 @@ akms-mcp sync
|
|
|
48
48
|
|
|
49
49
|
Use `--help` for the command reference and `--version` for the launcher's version.
|
|
50
50
|
|
|
51
|
+
## Upgrading from 0.2.x
|
|
52
|
+
|
|
53
|
+
For clients with individual npx entries and no shared registry, start the current setup launcher:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
npx -y --prefer-online @akms/mcp@latest
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
1. Open **Runtime**, select each MCP kind you use, and choose **Install AKMS runtime** or **Update runtime installation**.
|
|
60
|
+
2. Open **Register**, select your AI services, then choose **Apply registration** and confirm the review. Matching existing connections are imported into the shared catalog, and their individual client entries are replaced by one `akms_<kind>` entry per service.
|
|
61
|
+
3. Answer **Yes** to **Compare existing settings and choose which to share?** when versions differ, even if only their descriptions differ. Select the connection, choose **Use 1** or **Use 2**, keep the shared connection name to replace its old entries, and confirm **Import ... and apply it?**. If you defer this choice, use **Import existing connections** or reopen Register and Apply to return to the comparison.
|
|
62
|
+
4. Run **Sync** if any saved changes remain unfinished.
|
|
63
|
+
5. Restart or reconnect your AI clients to load the new entries. In Codex, start a new thread and archive the old threads whose servers you want to retire: each thread owns its MCP processes, and archiving stopped those processes in the isolated Codex 0.164.0 check.
|
|
64
|
+
6. Call `db_catalog_status`, `ssh_catalog_status`, `wsl_catalog_status` or `s3_catalog_status` for each kind you use and check its loaded and skipped connections. Tools now include the connection prefix, such as `pg_alpha__db_query` or `ssh_dev__ssh_exec`.
|
|
65
|
+
|
|
66
|
+
**If you registered first:** a missing-runtime result is a normal pending state. Updated setup labels this **Waiting for runtime**. Existing client entries stay in place while the runtime is missing. Install it from **Runtime**, then run **Sync** to finish applying the session entries and removing the old entries; installing the runtime alone does not finish that conversion.
|
|
67
|
+
|
|
68
|
+
**Finish importing differences:** in 0.3.1, connections still awaiting import did not appear in Sync, even when only their descriptions differed. Finish the Register comparison or use **Import existing connections** to review them. Updated Sync screens show **Not yet imported** and offer **Import existing connections** on the same screen. Complete that import review even when the saved connections are already up to date.
|
|
69
|
+
|
|
70
|
+
**Existing development registry:** if setup shows **Registry update available**, open **Migrate registry**, review the proposed changes and confirm them. See [Migrate an older registry](#migrate-an-older-registry).
|
|
71
|
+
|
|
72
|
+
**Return to individual entries:** open **Runtime**, select the kind, choose **Switch back to per-connection entries**, then review and confirm. This restores the saved individual launch commands and credentials and removes the session entry after the replacements are verified; the registry stays version 2. Restart or reconnect clients after changing the layout.
|
|
73
|
+
|
|
51
74
|
## Shared registration
|
|
52
75
|
|
|
53
76
|
### Startup checks and updates
|
package/lib/cli.js
CHANGED
|
@@ -373,6 +373,8 @@ function createStyle(enabled) {
|
|
|
373
373
|
return {
|
|
374
374
|
accent: paint(36, 39),
|
|
375
375
|
success: paint(32, 39),
|
|
376
|
+
warning: paint(33, 39),
|
|
377
|
+
error: paint(31, 39),
|
|
376
378
|
dim: paint(2, 22),
|
|
377
379
|
bold: paint(1, 22)
|
|
378
380
|
};
|
|
@@ -789,7 +791,7 @@ var fieldName = (field) => {
|
|
|
789
791
|
const names = { command: "Launch command", args: "Launch arguments", DB_HOST: "Host", SSH_HOST: "Host", DB_PORT: "Port", SSH_PORT: "Port", DB_NAME: "Database", DB_USER: "User", SSH_USER: "User", WSL_USER: "User", WSL_DISTRO: "Linux distribution", S3_BUCKET: "Bucket", S3_REGION: "Region", S3_PREFIX: "Key prefix" };
|
|
790
792
|
return Object.hasOwn(names, key) ? names[key] : plainText(key);
|
|
791
793
|
};
|
|
792
|
-
function writeWrapped(prompter, text, indent = "", changed) {
|
|
794
|
+
function writeWrapped(prompter, text, indent = "", changed, style) {
|
|
793
795
|
const characters = Array.from(plainText(text));
|
|
794
796
|
let line = indent;
|
|
795
797
|
let width = displayWidth(indent);
|
|
@@ -804,7 +806,7 @@ function writeWrapped(prompter, text, indent = "", changed) {
|
|
|
804
806
|
const size = displayWidth(character);
|
|
805
807
|
if (width + size > prompter.width) {
|
|
806
808
|
flush();
|
|
807
|
-
prompter.write(line + "\n");
|
|
809
|
+
prompter.write((style ? style(line) : line) + "\n");
|
|
808
810
|
line = indent;
|
|
809
811
|
width = displayWidth(indent);
|
|
810
812
|
}
|
|
@@ -817,7 +819,7 @@ function writeWrapped(prompter, text, indent = "", changed) {
|
|
|
817
819
|
width += size;
|
|
818
820
|
}
|
|
819
821
|
flush();
|
|
820
|
-
prompter.write(line + "\n");
|
|
822
|
+
prompter.write((style ? style(line) : line) + "\n");
|
|
821
823
|
}
|
|
822
824
|
function changedRange(values, index) {
|
|
823
825
|
if (values.length !== 2 || values[0] === values[1]) return void 0;
|
|
@@ -839,6 +841,37 @@ function compareEntries(prompter, versions) {
|
|
|
839
841
|
const key = field.replace(/^env\./, "");
|
|
840
842
|
return versions.map(({ entry }) => field.startsWith("env.") && visibleField.test(key) ? Object.hasOwn(entry.env, key) ? plainText(entry.env[key]) || "(empty)" : "(not set)" : "(hidden)");
|
|
841
843
|
});
|
|
844
|
+
if (versions.length === 2 && prompter.width >= 90) {
|
|
845
|
+
const width = Math.floor((prompter.width - 3) / 2);
|
|
846
|
+
const wrap = (value, changed) => {
|
|
847
|
+
const lines = [{ text: "", width: 0 }];
|
|
848
|
+
for (const [index, character] of Array.from(value).entries()) {
|
|
849
|
+
const size = displayWidth(character);
|
|
850
|
+
if (lines[lines.length - 1].width + size > width) lines.push({ text: "", width: 0 });
|
|
851
|
+
const line = lines[lines.length - 1];
|
|
852
|
+
line.text += changed && index >= changed[0] && index < changed[1] ? prompter.paint.accent(prompter.paint.bold(character)) : character;
|
|
853
|
+
line.width += size;
|
|
854
|
+
}
|
|
855
|
+
return lines;
|
|
856
|
+
};
|
|
857
|
+
const row = (left, right) => {
|
|
858
|
+
for (let line = 0; line < Math.max(left.length, right.length); line++) {
|
|
859
|
+
prompter.write(`${left[line]?.text ?? ""}${" ".repeat(width - (left[line]?.width ?? 0))} | ${right[line]?.text ?? ""}
|
|
860
|
+
`);
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
prompter.write("\n");
|
|
864
|
+
row(wrap(`[1] ${plainText(versions[0].label)}`), wrap(`[2] ${plainText(versions[1].label)}`));
|
|
865
|
+
for (let field = 0; field < fields.length; field++) {
|
|
866
|
+
prompter.write(`
|
|
867
|
+
${prompter.paint.dim(fieldName(fields[field]))}
|
|
868
|
+
`);
|
|
869
|
+
row(wrap(values[field][0], changedRange(values[field], 0)), wrap(values[field][1], changedRange(values[field], 1)));
|
|
870
|
+
}
|
|
871
|
+
if (values.some((row2) => row2.includes("(hidden)"))) prompter.write(prompter.paint.dim("\nSecret values are hidden.\n"));
|
|
872
|
+
prompter.write("\n");
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
842
875
|
for (let index = 0; index < versions.length; index++) {
|
|
843
876
|
prompter.write("\n");
|
|
844
877
|
writeWrapped(prompter, `[${index + 1}] ${versions[index].label}`);
|
|
@@ -1045,10 +1078,10 @@ async function checkConnections(prompter, entries, check = checkStartup) {
|
|
|
1045
1078
|
const missing = prefixes?.filter((prefix) => !covered.has(prefix)) ?? [];
|
|
1046
1079
|
if (result.ok && missing.length) result = { ...result, ok: false, detail: `Missing connection tools: ${missing.map((prefix) => prefix.slice(0, -2)).join(", ")}. Review catalog status before reconnecting.` };
|
|
1047
1080
|
const safe = (value) => [entry, ...maskEntries ?? []].reduce((text, source) => redactStartupText(text, source), value);
|
|
1048
|
-
writeWrapped(prompter, safe(result.ok ? `OK ${name}: ${result.server}@${result.version}, ${result.tools} tools` : `FAILED ${name}: exit ${result.exitCode ?? "unavailable"}. ${result.detail ?? "Server failed to start."}`));
|
|
1081
|
+
writeWrapped(prompter, safe(result.ok ? `OK ${name}: ${result.server}@${result.version}, ${result.tools} tools` : `FAILED ${name}: exit ${result.exitCode ?? "unavailable"}. ${result.detail ?? "Server failed to start."}`), "", void 0, result.ok ? prompter.paint.success : prompter.paint.error);
|
|
1049
1082
|
if (!result.ok) {
|
|
1050
1083
|
failed = true;
|
|
1051
|
-
for (const line of result.stderr) writeWrapped(prompter, safe(line), " ");
|
|
1084
|
+
for (const line of result.stderr) writeWrapped(prompter, safe(line), " ", void 0, prompter.paint.dim);
|
|
1052
1085
|
}
|
|
1053
1086
|
}
|
|
1054
1087
|
if (failed) prompter.write("Settings remain saved. Fix failed startup checks before restarting or reconnecting your AI clients.\n");
|
|
@@ -1478,11 +1511,26 @@ var Registry = class {
|
|
|
1478
1511
|
inspect() {
|
|
1479
1512
|
return this.reconcile(this.read(), false);
|
|
1480
1513
|
}
|
|
1514
|
+
formatResults(results, prefix = "", paint, reviewHint) {
|
|
1515
|
+
return formatResults(results, prefix, {
|
|
1516
|
+
paint,
|
|
1517
|
+
reviewHint,
|
|
1518
|
+
connectionCount: Object.values(this.read().connections).filter((item) => item.entry).length
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1481
1521
|
reconcile(state, apply, kinds) {
|
|
1482
1522
|
const results = [];
|
|
1483
1523
|
const active = (kind) => Object.entries(state.connections).filter(([, item]) => item.kind === kind && item.entry);
|
|
1484
1524
|
for (const kind of kinds ?? KINDS) if (this.isSessionMode(kind, state) && active(kind).length && !state.sessions?.[kind]) {
|
|
1485
|
-
for (const client of state.clients) results.push({
|
|
1525
|
+
for (const client of state.clients) results.push({
|
|
1526
|
+
client,
|
|
1527
|
+
name: sessionName(kind),
|
|
1528
|
+
kind,
|
|
1529
|
+
connections: active(kind).map(([name]) => name),
|
|
1530
|
+
status: "pending",
|
|
1531
|
+
waitingFor: "runtime",
|
|
1532
|
+
detail: "Install it from Runtime, then run Sync. Your existing entries keep working."
|
|
1533
|
+
});
|
|
1486
1534
|
}
|
|
1487
1535
|
const work = [
|
|
1488
1536
|
...Object.entries(state.connections).filter(([, connection]) => !kinds || kinds.includes(connection.kind)).map(([name, connection]) => ({ name, connection, bundle: false })),
|
|
@@ -1498,7 +1546,15 @@ var Registry = class {
|
|
|
1498
1546
|
const previous = connection.applied[client];
|
|
1499
1547
|
if (!desired && !previous) continue;
|
|
1500
1548
|
const target = this.targets.find((candidate) => candidate.id === client);
|
|
1501
|
-
const report = (status, detail) => results.push({
|
|
1549
|
+
const report = (status, detail, waitingFor) => results.push({
|
|
1550
|
+
client,
|
|
1551
|
+
name,
|
|
1552
|
+
status,
|
|
1553
|
+
detail,
|
|
1554
|
+
kind: connection.kind,
|
|
1555
|
+
connections: bundle ? active(connection.kind).map(([name2]) => name2) : [name],
|
|
1556
|
+
...waitingFor ? { waitingFor } : {}
|
|
1557
|
+
});
|
|
1502
1558
|
if (!target) {
|
|
1503
1559
|
report("error", "Service adapter unavailable; use a newer package to retry.");
|
|
1504
1560
|
continue;
|
|
@@ -1516,20 +1572,18 @@ var Registry = class {
|
|
|
1516
1572
|
report("removed", "Removed");
|
|
1517
1573
|
} else if (exists && (!actual || !previous || hash !== previous)) {
|
|
1518
1574
|
report("conflict", actual && desired ? describeDifferences([desired, actual]) : actual ? "Changed outside AKMS; review before removing this entry." : "This name is used by another server type; choose another name.");
|
|
1519
|
-
} else if (!apply) {
|
|
1520
|
-
report("pending", desired ? "Needs applying" : "Needs removal");
|
|
1521
1575
|
} else {
|
|
1522
1576
|
if (!desired && state.clients.includes(client)) {
|
|
1523
1577
|
if (!bundle && this.isSessionMode(connection.kind, state) && active(connection.kind).length) {
|
|
1524
1578
|
const session = state.sessions?.[connection.kind];
|
|
1525
1579
|
const replacement2 = entries[sessionName(connection.kind)];
|
|
1526
1580
|
if (!session || !replacement2 || entryHash(replacement2) !== entryHash(session.entry) || session.applied[client] !== entryHash(session.entry)) {
|
|
1527
|
-
report("pending", "Connection entry kept until the session server is applied.");
|
|
1581
|
+
report("pending", "Connection entry kept until the session server is applied.", "session");
|
|
1528
1582
|
continue;
|
|
1529
1583
|
}
|
|
1530
1584
|
}
|
|
1531
1585
|
if (bundle && !this.isSessionMode(connection.kind, state) && active(connection.kind).some(([connectionName, value]) => !entries[connectionName] || entryHash(entries[connectionName]) !== entryHash(value.entry))) {
|
|
1532
|
-
report("pending", "Session entry kept until all connection entries are applied.");
|
|
1586
|
+
report("pending", "Session entry kept until all connection entries are applied.", "connections");
|
|
1533
1587
|
continue;
|
|
1534
1588
|
}
|
|
1535
1589
|
}
|
|
@@ -1540,7 +1594,11 @@ var Registry = class {
|
|
|
1540
1594
|
replacement = state.connections[replacement.replacement];
|
|
1541
1595
|
}
|
|
1542
1596
|
if (!desired && state.clients.includes(client) && !this.isSessionMode(connection.kind, state) && replacement && replacement.entry && replacement.applied[client] !== entryHash(replacement.entry)) {
|
|
1543
|
-
report("pending", "Old name kept until the new name is applied.");
|
|
1597
|
+
report("pending", "Old name kept until the new name is applied.", "rename");
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
if (!apply) {
|
|
1601
|
+
report("pending", desired ? "Needs applying" : "Needs removal");
|
|
1544
1602
|
continue;
|
|
1545
1603
|
}
|
|
1546
1604
|
const result = desired ? target.writeEntry(name, desired) : target.removeEntry(name);
|
|
@@ -1631,7 +1689,7 @@ var Registry = class {
|
|
|
1631
1689
|
const registry = this;
|
|
1632
1690
|
let snapshot;
|
|
1633
1691
|
const expected = (name) => snapshot ? own(snapshot, name) ? entryHash(snapshot[name]) : null : void 0;
|
|
1634
|
-
const saved = (results) => ({ status: "written", detail: formatResults(results, "Saved in the shared AKMS catalog.") });
|
|
1692
|
+
const saved = (results) => ({ status: "written", detail: registry.formatResults(results, "Saved in the shared AKMS catalog.") });
|
|
1635
1693
|
const prepare = (name, entry, oldName) => {
|
|
1636
1694
|
if (!registry.read().clients.length) throw new RegistryError("Register at least one AI service before saving a connection.");
|
|
1637
1695
|
const prior = snapshot && own(snapshot, oldName ?? name) ? snapshot[oldName ?? name] : void 0;
|
|
@@ -1660,30 +1718,58 @@ var Registry = class {
|
|
|
1660
1718
|
};
|
|
1661
1719
|
}
|
|
1662
1720
|
};
|
|
1663
|
-
function formatResults(results, prefix = "") {
|
|
1664
|
-
const
|
|
1665
|
-
|
|
1666
|
-
const
|
|
1667
|
-
const
|
|
1668
|
-
const
|
|
1669
|
-
lines.push(` Connections: ${total
|
|
1721
|
+
function formatResults(results, prefix = "", options = {}) {
|
|
1722
|
+
const plain = (text) => text;
|
|
1723
|
+
const paint = options.paint ?? { success: plain, warning: plain, error: plain, dim: plain, bold: plain };
|
|
1724
|
+
const safe = (text) => text.replace(/[\x00-\x1f\x7f]/g, " ");
|
|
1725
|
+
const lines = prefix ? [paint.bold(prefix)] : [];
|
|
1726
|
+
const total = options.connectionCount ?? new Set(results.flatMap((item) => item.connections ?? [item.name])).size;
|
|
1727
|
+
lines.push(` Connections: ${total}`);
|
|
1670
1728
|
const updated = results.filter((item) => item.status === "synced" && item.detail === "Applied").length;
|
|
1671
1729
|
const unchanged = results.filter((item) => item.status === "synced" && item.detail !== "Applied").length;
|
|
1672
1730
|
const removed = results.filter((item) => item.status === "removed").length;
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1731
|
+
const failed = results.filter((item) => item.status === "error");
|
|
1732
|
+
lines.push(` Service entries: ${paint.success(`${updated} updated \xB7 ${unchanged} already up to date`)}${removed ? ` \xB7 ${paint.success(`${removed} removed`)}` : ""} \xB7 ${(failed.length ? paint.error : paint.dim)(`${failed.length} failed`)}`);
|
|
1733
|
+
if (!results.length) lines.push(paint.dim(" No shared connections need applying."));
|
|
1734
|
+
if (results.length && results.every((item) => item.status === "synced" || item.status === "removed")) lines.push(paint.success(" OK: all service entries are up to date."));
|
|
1735
|
+
const waiting = results.filter((item) => item.status === "pending");
|
|
1736
|
+
for (const kind of KINDS) {
|
|
1737
|
+
const runtime = waiting.filter((item) => item.kind === kind && item.waitingFor === "runtime");
|
|
1738
|
+
const kept = waiting.filter((item) => item.kind === kind && item.waitingFor === "session");
|
|
1739
|
+
if (!runtime.length && !kept.length) continue;
|
|
1740
|
+
lines.push(`
|
|
1741
|
+
${paint.warning(runtime.length ? `Waiting for runtime - ${kind.toUpperCase()}` : `Waiting for ${sessionName(kind)}`)}`);
|
|
1742
|
+
if (runtime.length) lines.push(" Install it from Runtime, then run Sync. Your existing entries keep working.");
|
|
1743
|
+
else lines.push(" Resolve this session entry in Sync. Existing connection entries stay until it is ready.");
|
|
1744
|
+
const retained = /* @__PURE__ */ new Map();
|
|
1745
|
+
for (const client of new Set(kept.map((item) => item.client))) {
|
|
1746
|
+
const names = [...new Set(kept.filter((item) => item.client === client).map((item) => item.name))];
|
|
1747
|
+
const key = JSON.stringify([...names].sort());
|
|
1748
|
+
const group = retained.get(key);
|
|
1749
|
+
if (group) group.clients.push(client);
|
|
1750
|
+
else retained.set(key, { clients: [client], names });
|
|
1751
|
+
}
|
|
1752
|
+
for (const group of retained.values()) {
|
|
1753
|
+
const clients = group.clients.map((client) => CLIENT_LABELS2[client]).join(" \xB7 ");
|
|
1754
|
+
lines.push(paint.dim(` Kept in ${clients} until ${sessionName(kind)} is applied: ${group.names.map(safe).join(", ")}`));
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
for (const [heading, rows, color] of [
|
|
1758
|
+
["Needs review (existing settings kept)", results.filter((item) => item.status === "conflict"), paint.warning],
|
|
1759
|
+
["Waiting for Sync", waiting.filter((item) => !item.waitingFor || !["runtime", "session"].includes(item.waitingFor)), paint.warning],
|
|
1760
|
+
["Could not finish", failed, paint.error]
|
|
1677
1761
|
]) {
|
|
1678
1762
|
if (!rows.length) continue;
|
|
1679
1763
|
lines.push(`
|
|
1680
|
-
${heading}:`);
|
|
1764
|
+
${color(`${heading}:`)}`);
|
|
1681
1765
|
for (const name of new Set(rows.map((item) => item.name))) {
|
|
1682
|
-
lines.push(` ${name
|
|
1683
|
-
for (const row of rows.filter((item) => item.name === name)) lines.push(` ${CLIENT_LABELS2[row.client]}: ${row.detail}`);
|
|
1766
|
+
lines.push(` ${safe(name)}`);
|
|
1767
|
+
for (const row of rows.filter((item) => item.name === name)) lines.push(` ${row.status === "error" ? paint.error("FAILED ") : ""}${CLIENT_LABELS2[row.client]}: ${safe(row.detail)}`);
|
|
1684
1768
|
}
|
|
1685
1769
|
}
|
|
1686
|
-
if (
|
|
1770
|
+
if (results.some((item) => item.status === "conflict")) lines.push(paint.warning(`
|
|
1771
|
+
${options.reviewHint ?? "Next: open Sync to compare settings and choose which version to use."}`));
|
|
1772
|
+
if (failed.length) lines.push(paint.error("\nNext: fix the errors above, then run Sync again."));
|
|
1687
1773
|
return lines.join("\n");
|
|
1688
1774
|
}
|
|
1689
1775
|
|
|
@@ -1728,7 +1814,7 @@ async function runtimeMenu(prompter, registry, requestedKind) {
|
|
|
1728
1814
|
prompter.write(`New kinds will use ${mode === "catalog" ? "session servers" : "individual entries"}. Existing kinds keep their settings.
|
|
1729
1815
|
`);
|
|
1730
1816
|
if (await prompter.askYesNo("Change the default for new kinds?", false)) {
|
|
1731
|
-
prompter.write(`${formatResults(registry.setDefaultMode(mode), "Default for new kinds saved.")}
|
|
1817
|
+
prompter.write(`${registry.formatResults(registry.setDefaultMode(mode), "Default for new kinds saved.", prompter.paint)}
|
|
1732
1818
|
`);
|
|
1733
1819
|
await prompter.acknowledge();
|
|
1734
1820
|
}
|
|
@@ -1757,7 +1843,7 @@ async function runtimeMenu(prompter, registry, requestedKind) {
|
|
|
1757
1843
|
if (!await prompter.askYesNo("Apply this configuration mode?", false)) continue;
|
|
1758
1844
|
const results2 = registry.applySession(plan2);
|
|
1759
1845
|
prompter.clearScreen();
|
|
1760
|
-
prompter.write(`${formatResults(results2, "Configuration mode saved.")}
|
|
1846
|
+
prompter.write(`${registry.formatResults(results2, "Configuration mode saved.", prompter.paint)}
|
|
1761
1847
|
`);
|
|
1762
1848
|
await registry.checkSaved(prompter, plan2.names);
|
|
1763
1849
|
prompter.write("Restart or reconnect your AI clients to load the new server layout. Fix startup failures first.\n");
|
|
@@ -1793,7 +1879,7 @@ Convert ${plan.names.length} connection(s):
|
|
|
1793
1879
|
if (!await prompter.askYesNo("Apply these launch commands to registered services?", true)) continue;
|
|
1794
1880
|
const results = registry.applyRuntime(plan);
|
|
1795
1881
|
prompter.clearScreen();
|
|
1796
|
-
prompter.write(`${formatResults(results, "Runtime settings saved.")}
|
|
1882
|
+
prompter.write(`${registry.formatResults(results, "Runtime settings saved.", prompter.paint)}
|
|
1797
1883
|
|
|
1798
1884
|
Restart or reconnect AI clients to replace running npx processes.
|
|
1799
1885
|
`);
|
|
@@ -1915,7 +2001,7 @@ async function migrationMenu(prompter, registry) {
|
|
|
1915
2001
|
if (!await prompter.askYesNo("Migrate registry and apply these changes?", false)) return;
|
|
1916
2002
|
const results = registry.applyMigration(plan);
|
|
1917
2003
|
prompter.clearScreen();
|
|
1918
|
-
prompter.write(`${formatResults(results, "Registry migration saved.")}
|
|
2004
|
+
prompter.write(`${registry.formatResults(results, "Registry migration saved.", prompter.paint)}
|
|
1919
2005
|
`);
|
|
1920
2006
|
await registry.checkSaved(prompter, plan.kinds.filter((item) => item.status !== "individual").flatMap((item) => item.names));
|
|
1921
2007
|
prompter.write("Reconnect affected clients after changes to their server layout. Fix startup failures first.\n");
|
|
@@ -1930,6 +2016,12 @@ async function migrationMenu(prompter, registry) {
|
|
|
1930
2016
|
}
|
|
1931
2017
|
|
|
1932
2018
|
// src/modules/registration/menu.ts
|
|
2019
|
+
function pendingImports(registry, kind) {
|
|
2020
|
+
const state = registry.read();
|
|
2021
|
+
const entries = registry.candidates(kind).entries.filter((candidate) => !Object.hasOwn(state.connections, candidate.name));
|
|
2022
|
+
const names = [...new Set(entries.map((candidate) => candidate.name))];
|
|
2023
|
+
return { names, differing: names.filter((name) => new Set(entries.filter((item) => item.name === name).map((item) => entryHash(item.entry))).size > 1).length };
|
|
2024
|
+
}
|
|
1933
2025
|
function preview(prompter, entry) {
|
|
1934
2026
|
if (!entry) {
|
|
1935
2027
|
prompter.write(" Remove this connection from the service.\n");
|
|
@@ -2005,8 +2097,8 @@ Remove only AKMS-managed entries from these services; keep the shared catalog.
|
|
|
2005
2097
|
const results = registry.register([...selected], initial.clients, plan.imports);
|
|
2006
2098
|
prompter.clearScreen();
|
|
2007
2099
|
prompter.write(`
|
|
2008
|
-
${formatResults(results, `Registration saved.
|
|
2009
|
-
AI services: ${[...selected].map((id) => CLIENT_LABELS2[id]).join(" \xB7 ") || "none"}
|
|
2100
|
+
${registry.formatResults(results, `Registration saved.
|
|
2101
|
+
AI services: ${[...selected].map((id) => CLIENT_LABELS2[id]).join(" \xB7 ") || "none"}`, prompter.paint)}
|
|
2010
2102
|
`);
|
|
2011
2103
|
if (importNames.length) prompter.write(`Imported ${importNames.length} existing connection(s) into the shared list.
|
|
2012
2104
|
`);
|
|
@@ -2079,7 +2171,7 @@ Import existing connections:
|
|
|
2079
2171
|
if (await prompter.askYesNo("Import matching connections and apply them?", true)) {
|
|
2080
2172
|
const results2 = registry.register(state.clients, state.clients, matching.imports);
|
|
2081
2173
|
prompter.clearScreen();
|
|
2082
|
-
prompter.write(`${formatResults(results2, "Import complete")}
|
|
2174
|
+
prompter.write(`${registry.formatResults(results2, "Import complete", prompter.paint)}
|
|
2083
2175
|
`);
|
|
2084
2176
|
await prompter.acknowledge();
|
|
2085
2177
|
}
|
|
@@ -2107,7 +2199,7 @@ ${plainText(name)} \xB7 Choose the settings to use on all registered services.
|
|
|
2107
2199
|
const results = registry.importEntry(destination, chosen, candidates);
|
|
2108
2200
|
prompter.clearScreen();
|
|
2109
2201
|
prompter.write(`
|
|
2110
|
-
${formatResults(results, "Import complete")}
|
|
2202
|
+
${registry.formatResults(results, "Import complete", prompter.paint)}
|
|
2111
2203
|
`);
|
|
2112
2204
|
if (destination !== name) prompter.write(" The original entries were kept under their old name.\n");
|
|
2113
2205
|
await prompter.acknowledge();
|
|
@@ -2127,19 +2219,28 @@ async function syncMenu(prompter, registry) {
|
|
|
2127
2219
|
const results = pendingResults ?? registry.sync();
|
|
2128
2220
|
pendingResults = void 0;
|
|
2129
2221
|
prompter.write(`
|
|
2130
|
-
${formatResults(results, "Sync results")}
|
|
2222
|
+
${registry.formatResults(results, "Sync results", prompter.paint, "Next: choose an item below to compare settings and pick a version.")}
|
|
2131
2223
|
`);
|
|
2224
|
+
const imports = pendingImports(registry);
|
|
2225
|
+
if (imports.names.length) prompter.write(prompter.paint.warning(`
|
|
2226
|
+
Not yet imported: ${imports.names.length} connection(s)${imports.differing === imports.names.length ? " with differing settings" : ""} - open Import existing connections.
|
|
2227
|
+
`));
|
|
2132
2228
|
const conflicts = results.filter((result) => result.status === "conflict");
|
|
2133
|
-
if (!conflicts.length) {
|
|
2229
|
+
if (!conflicts.length && !imports.names.length) {
|
|
2134
2230
|
await prompter.acknowledge();
|
|
2135
2231
|
return;
|
|
2136
2232
|
}
|
|
2137
2233
|
const picked = await prompter.select("Review differences", [
|
|
2138
2234
|
...conflicts.map((item, index) => ({ value: String(index), label: `${CLIENT_LABELS2[item.client]} \xB7 ${item.name}` })),
|
|
2235
|
+
...imports.names.length ? [{ value: "import", key: "import", label: "Import existing connections", hint: `${imports.names.length} connection(s) to review` }] : [],
|
|
2139
2236
|
{ value: "back", key: "b", label: "Later \u2014 keep existing settings" }
|
|
2140
2237
|
], { cancelValue: "back" });
|
|
2141
2238
|
if (picked === "back") return;
|
|
2142
2239
|
try {
|
|
2240
|
+
if (picked === "import") {
|
|
2241
|
+
await importMenu(prompter, registry);
|
|
2242
|
+
continue;
|
|
2243
|
+
}
|
|
2143
2244
|
prompter.clearScreen();
|
|
2144
2245
|
const conflict = conflicts[Number(picked)];
|
|
2145
2246
|
const state = registry.read();
|
|
@@ -2173,12 +2274,17 @@ ${formatResults(results, "Sync results")}
|
|
|
2173
2274
|
const client = CLIENT_LABELS2[conflict.client];
|
|
2174
2275
|
const registered = state.clients.includes(conflict.client);
|
|
2175
2276
|
const sessionMode = registry.isSessionMode(connection.kind, state);
|
|
2176
|
-
const removing = !connection.entry || !registered
|
|
2277
|
+
const removing = !connection.entry || !registered;
|
|
2278
|
+
const retiring = sessionMode && !removing;
|
|
2177
2279
|
const subject = connection.entry && descriptionOnly([connection.entry, actual]) ? "description" : "settings";
|
|
2178
2280
|
writeWrapped(prompter, `Sync / ${plainText(conflict.name)}`);
|
|
2179
2281
|
writeWrapped(prompter, `Registered services: ${state.clients.map((id) => CLIENT_LABELS2[id]).join(" \xB7 ") || "none"}`);
|
|
2180
2282
|
prompter.write("\n");
|
|
2181
2283
|
if (!removing && connection.entry) {
|
|
2284
|
+
if (retiring) {
|
|
2285
|
+
writeWrapped(prompter, `Choose settings for ${sessionName(connection.kind)} before removing the old ${client} entry.`);
|
|
2286
|
+
writeWrapped(prompter, "The old entry stays until the session server is ready.");
|
|
2287
|
+
}
|
|
2182
2288
|
compareEntries(prompter, [{ label: "Saved in AKMS", entry: connection.entry }, { label: `Currently in ${client}`, entry: actual }]);
|
|
2183
2289
|
} else {
|
|
2184
2290
|
writeWrapped(prompter, !registered ? `${client} is no longer registered.` : sessionMode ? `This kind now uses ${sessionName(connection.kind)}. The individual client entry is no longer needed.` : "This connection was removed from AKMS.");
|
|
@@ -2190,8 +2296,8 @@ Currently in ${client}
|
|
|
2190
2296
|
prompter.write("\n");
|
|
2191
2297
|
}
|
|
2192
2298
|
const choices = [
|
|
2193
|
-
{ value: "shared", label: removing ? `Remove from ${client}` : `Use 1: AKMS -> ${client}`, hint: removing ? "finish the pending removal" : `replace its ${subject} with version 1` },
|
|
2194
|
-
...registered ? [{ value: "client", label:
|
|
2299
|
+
{ value: "shared", label: removing ? `Remove from ${client}` : retiring ? "Use 1: keep AKMS settings" : `Use 1: AKMS -> ${client}`, hint: removing ? "finish the pending removal" : retiring ? `use version 1 in ${sessionName(connection.kind)}` : `replace its ${subject} with version 1` },
|
|
2300
|
+
...registered ? [{ value: "client", label: removing ? `Restore from ${client}` : `Use 2: ${client} -> all ${state.clients.length} services`, hint: retiring ? `save version 2 in AKMS for ${sessionName(connection.kind)}` : "save in AKMS and sync registered services" }] : [],
|
|
2195
2301
|
{ value: "back", key: "b", label: "Back without changes" }
|
|
2196
2302
|
];
|
|
2197
2303
|
const choice = await prompter.select(removing ? registered ? "Remove or restore this connection?" : `Remove this connection from ${client}?` : `Which ${subject} should be used?`, choices, { cancelValue: "back" });
|
|
@@ -2200,8 +2306,9 @@ Currently in ${client}
|
|
|
2200
2306
|
if (choice === "client") {
|
|
2201
2307
|
writeWrapped(prompter, `Save the ${client} version in AKMS.`);
|
|
2202
2308
|
writeWrapped(prompter, `Sync to: ${state.clients.map((id) => CLIENT_LABELS2[id]).join(" \xB7 ")}`);
|
|
2203
|
-
} else writeWrapped(prompter, removing ? `Remove ${plainText(conflict.name)} from ${client}.` : `Copy the saved AKMS ${subject} to ${client}.`);
|
|
2204
|
-
|
|
2309
|
+
} else writeWrapped(prompter, removing ? `Remove ${plainText(conflict.name)} from ${client}.` : retiring ? `Keep the saved AKMS ${subject}.` : `Copy the saved AKMS ${subject} to ${client}.`);
|
|
2310
|
+
if (retiring) writeWrapped(prompter, `Keep the old ${client} entry until ${sessionName(connection.kind)} is ready, then remove it.`);
|
|
2311
|
+
const confirmation = choice === "client" ? `Save in AKMS and sync ${state.clients.length} services?` : removing ? `Remove from ${client}?` : retiring ? "Keep version 1 and finish Sync?" : `Update ${client} with version 1?`;
|
|
2205
2312
|
if (await prompter.askYesNo(confirmation, false)) {
|
|
2206
2313
|
pendingResults = registry.resolve(conflict.client, conflict.name, actual, choice === "client");
|
|
2207
2314
|
}
|
|
@@ -2920,7 +3027,7 @@ ${failure}
|
|
|
2920
3027
|
const state = registry.read();
|
|
2921
3028
|
const clients = state.clients;
|
|
2922
3029
|
const migration = registry.needsMigration(state);
|
|
2923
|
-
if (migration) prompter.write("Registry update available. Choose Migrate registry to review the changes.\n");
|
|
3030
|
+
if (migration) prompter.write(prompter.paint.warning("Registry update available. Choose Migrate registry to review the changes.\n"));
|
|
2924
3031
|
const picked = await prompter.select("AKMS MCP \xB7 Connection setup", [
|
|
2925
3032
|
{ value: "db", label: "Database", hint: "PostgreSQL connections" },
|
|
2926
3033
|
{ value: "ssh", label: "SSH", hint: "Remote hosts" },
|
|
@@ -3208,7 +3315,7 @@ async function main() {
|
|
|
3208
3315
|
if (argumentsList.showVersion == true) {
|
|
3209
3316
|
let version = "development";
|
|
3210
3317
|
if (true) {
|
|
3211
|
-
version = "0.3.
|
|
3318
|
+
version = "0.3.2";
|
|
3212
3319
|
}
|
|
3213
3320
|
process.stdout.write(`${version}
|
|
3214
3321
|
`);
|