@akms/mcp 0.3.2 → 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 +14 -0
- package/README.md +77 -109
- package/lib/cli.js +683 -654
- package/lib/cli.js.map +1 -0
- package/lib/db.js +8423 -0
- package/lib/db.js.map +1 -0
- package/lib/s3.js +4339 -0
- package/lib/s3.js.map +1 -0
- package/lib/ssh.js +7854 -0
- package/lib/ssh.js.map +1 -0
- package/lib/wsl.js +7256 -0
- package/lib/wsl.js.map +1 -0
- package/package.json +45 -6
package/lib/cli.js
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs, { existsSync, readFileSync } from 'fs';
|
|
3
|
+
import path2, { dirname, join } from 'path';
|
|
4
|
+
import { createHash, randomUUID } from 'crypto';
|
|
5
|
+
import os5 from 'os';
|
|
6
|
+
import { spawn, execFile, spawnSync } from 'child_process';
|
|
7
|
+
import { promisify, stripVTControlCharacters, isDeepStrictEqual } from 'util';
|
|
8
|
+
import { parse, TomlError } from 'smol-toml';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
// src/_defs/version.ts
|
|
12
|
+
var PACKAGE_VERSION = "0.4.0" ;
|
|
2
13
|
|
|
3
14
|
// src/_defs/index.ts
|
|
4
15
|
var MCP_PACKAGES = {
|
|
5
|
-
db: { label: "Database",
|
|
6
|
-
ssh: { label: "SSH",
|
|
7
|
-
wsl: { label: "WSL",
|
|
8
|
-
s3: { label: "S3",
|
|
16
|
+
db: { label: "Database", command: "akms-mcp-db", setupCommand: "config" },
|
|
17
|
+
ssh: { label: "SSH", command: "akms-mcp-ssh", setupCommand: "config" },
|
|
18
|
+
wsl: { label: "WSL", command: "akms-mcp-wsl", setupCommand: "config" },
|
|
19
|
+
s3: { label: "S3", command: "akms-mcp-s3", setupCommand: "config" }
|
|
9
20
|
};
|
|
10
21
|
var CLIENT_LABELS = {
|
|
11
22
|
"claude-code": "Claude Code",
|
|
@@ -57,16 +68,6 @@ function parseArguments(argv) {
|
|
|
57
68
|
}
|
|
58
69
|
return parsed;
|
|
59
70
|
}
|
|
60
|
-
|
|
61
|
-
// src/modules/registration/registry.ts
|
|
62
|
-
import fs4 from "fs";
|
|
63
|
-
import path5 from "path";
|
|
64
|
-
import { createHash } from "crypto";
|
|
65
|
-
|
|
66
|
-
// src/modules/registration/config-file.ts
|
|
67
|
-
import fs from "fs";
|
|
68
|
-
import path from "path";
|
|
69
|
-
import { randomUUID } from "crypto";
|
|
70
71
|
function readExisting(filePath) {
|
|
71
72
|
try {
|
|
72
73
|
return fs.readFileSync(filePath, "utf8");
|
|
@@ -96,8 +97,8 @@ function updateConfigFile(filePath, update, options = {}) {
|
|
|
96
97
|
if (before != null) {
|
|
97
98
|
fs.accessSync(destination, fs.constants.W_OK);
|
|
98
99
|
}
|
|
99
|
-
fs.mkdirSync(
|
|
100
|
-
const temporary =
|
|
100
|
+
fs.mkdirSync(path2.dirname(destination), { recursive: true });
|
|
101
|
+
const temporary = path2.join(path2.dirname(destination), `.${path2.basename(destination)}.${randomUUID()}.tmp`);
|
|
101
102
|
const mode = options.mode ?? (before == null ? 384 : fs.statSync(destination).mode & 511);
|
|
102
103
|
let descriptor;
|
|
103
104
|
let ownsTemporary = false;
|
|
@@ -136,25 +137,25 @@ var CLIENT_LABELS2 = {
|
|
|
136
137
|
cursor: "Cursor"
|
|
137
138
|
};
|
|
138
139
|
var KINDS = ["db", "ssh", "wsl", "s3"];
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
import os from "os";
|
|
143
|
-
import path2 from "path";
|
|
144
|
-
import { execFile } from "child_process";
|
|
145
|
-
import { promisify, stripVTControlCharacters } from "util";
|
|
146
|
-
var packageName = (kind) => `@akms/mcp-${kind}`;
|
|
140
|
+
var KIND_LABELS = { db: "Database", ssh: "SSH", wsl: "WSL", s3: "S3" };
|
|
141
|
+
var PACKAGE_NAME = "@akms/mcp";
|
|
142
|
+
var legacyPackageName = (kind) => `@akms/mcp-${kind}`;
|
|
147
143
|
var binary = (command) => command.replace(/\\/g, "/").split("/").pop().toLowerCase();
|
|
144
|
+
var posixPath = (value) => value?.replace(/\\/g, "/") ?? "";
|
|
145
|
+
var isNode = (command) => /^(node|nodejs)(\.exe)?$/.test(binary(command));
|
|
146
|
+
var isCurrentScript = (script, kind) => posixPath(script).endsWith(`/node_modules/@akms/mcp/lib/${kind}.js`);
|
|
147
|
+
var isLegacyScript = (script, kind) => posixPath(script).endsWith(`/node_modules/@akms/mcp-${kind}/lib/cli.js`);
|
|
148
|
+
var isLegacyPackageArgument = (argument, kind) => new RegExp(`^@akms/mcp-${kind}(?:@[^\\s]+)?$`).test(argument);
|
|
148
149
|
function runtimePrefix() {
|
|
149
|
-
return path2.join(process.env.AKMS_MCP_HOME ? path2.resolve(process.env.AKMS_MCP_HOME) : path2.join(
|
|
150
|
+
return path2.join(process.env.AKMS_MCP_HOME ? path2.resolve(process.env.AKMS_MCP_HOME) : path2.join(os5.homedir(), ".akms", "mcp"), "runtime");
|
|
150
151
|
}
|
|
151
152
|
function runtimeRoot(prefix = runtimePrefix(), platform = process.platform) {
|
|
152
153
|
return path2.join(prefix, ...platform === "win32" ? [] : ["lib"], "node_modules");
|
|
153
154
|
}
|
|
154
|
-
var runtimeInstallCommand = (
|
|
155
|
-
async function latestRuntimeVersion(
|
|
155
|
+
var runtimeInstallCommand = () => `npm install --global --prefix "${runtimePrefix()}" ${PACKAGE_NAME}@latest`;
|
|
156
|
+
async function latestRuntimeVersion(timeoutMs = 6e3) {
|
|
156
157
|
try {
|
|
157
|
-
const { stdout } = await promisify(execFile)(process.execPath, [npmCli(), "view", `${
|
|
158
|
+
const { stdout } = await promisify(execFile)(process.execPath, [npmCli(), "view", `${PACKAGE_NAME}@latest`, "version", "--json", "--prefer-online", "--fetch-retries=0", "--fetch-timeout=4000"], {
|
|
158
159
|
windowsHide: true,
|
|
159
160
|
timeout: timeoutMs,
|
|
160
161
|
maxBuffer: 65536
|
|
@@ -186,26 +187,33 @@ function runtimeIsOlder(installed, current) {
|
|
|
186
187
|
}
|
|
187
188
|
function launchKind(entry) {
|
|
188
189
|
for (const kind of KINDS) {
|
|
189
|
-
if (new RegExp(`^akms-mcp-${kind}(?:\\.cmd|\\.exe)?$`).test(binary(entry.command)) || entry.args.some((arg) =>
|
|
190
|
-
if (
|
|
190
|
+
if (new RegExp(`^akms-mcp-${kind}(?:\\.cmd|\\.exe)?$`).test(binary(entry.command)) || entry.args.some((arg) => isLegacyPackageArgument(arg, kind))) return kind;
|
|
191
|
+
if (isNode(entry.command) && (isCurrentScript(entry.args[0], kind) || isLegacyScript(entry.args[0], kind))) return kind;
|
|
191
192
|
}
|
|
192
193
|
return null;
|
|
193
194
|
}
|
|
194
195
|
function standardLaunch(entry, kind) {
|
|
195
196
|
if (launchKind(entry) !== kind) return false;
|
|
196
197
|
const command = binary(entry.command);
|
|
197
|
-
if (
|
|
198
|
+
if (isNode(entry.command)) return entry.args.length === 1 && (isCurrentScript(entry.args[0], kind) || isLegacyScript(entry.args[0], kind));
|
|
198
199
|
if (new RegExp(`^akms-mcp-${kind}(?:\\.cmd|\\.exe)?$`).test(command)) return entry.args.length === 0;
|
|
199
200
|
let args = entry.args;
|
|
200
201
|
if (command === "cmd" || command === "cmd.exe") {
|
|
201
202
|
if (args[0]?.toLowerCase() !== "/c" || !/^npx(?:\.cmd)?$/i.test(args[1] ?? "")) return false;
|
|
202
203
|
args = args.slice(2);
|
|
203
204
|
} else if (!/^npx(?:\.cmd)?$/.test(command)) return false;
|
|
204
|
-
return args.length === 2 && ["-y", "--yes"].includes(args[0]) &&
|
|
205
|
+
return args.length === 2 && ["-y", "--yes"].includes(args[0]) && isLegacyPackageArgument(args[1], kind);
|
|
205
206
|
}
|
|
206
207
|
function catalogLaunch(entry, kind = launchKind(entry)) {
|
|
207
208
|
return !!kind && entry.args.length === 2 && entry.args[1] === "--catalog" && standardLaunch({ ...entry, args: entry.args.slice(0, 1) }, kind);
|
|
208
209
|
}
|
|
210
|
+
function legacyLaunch(entry, kind = launchKind(entry)) {
|
|
211
|
+
if (!kind) return false;
|
|
212
|
+
const launch = catalogLaunch(entry, kind) ? { ...entry, args: entry.args.slice(0, 1) } : entry;
|
|
213
|
+
if (!standardLaunch(launch, kind)) return false;
|
|
214
|
+
if (isNode(launch.command)) return isLegacyScript(launch.args[0], kind);
|
|
215
|
+
return launch.args.some((arg) => isLegacyPackageArgument(arg, kind));
|
|
216
|
+
}
|
|
209
217
|
function npmCli() {
|
|
210
218
|
const candidates = [
|
|
211
219
|
path2.join(path2.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js"),
|
|
@@ -219,8 +227,8 @@ function npmCli() {
|
|
|
219
227
|
}
|
|
220
228
|
for (const file of candidates) {
|
|
221
229
|
try {
|
|
222
|
-
const resolved =
|
|
223
|
-
if (resolved.endsWith(".js") &&
|
|
230
|
+
const resolved = fs.realpathSync(file);
|
|
231
|
+
if (resolved.endsWith(".js") && fs.statSync(resolved).isFile()) return resolved;
|
|
224
232
|
} catch {
|
|
225
233
|
}
|
|
226
234
|
}
|
|
@@ -228,34 +236,46 @@ function npmCli() {
|
|
|
228
236
|
}
|
|
229
237
|
function findGlobalRuntime(kind, root) {
|
|
230
238
|
try {
|
|
231
|
-
const directory = path2.join(root ?? runtimeRoot(), "@akms",
|
|
232
|
-
const metadata = JSON.parse(
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return { command: process.execPath, args: [script], version: metadata.version, linked: fs2.lstatSync(directory).isSymbolicLink() };
|
|
239
|
+
const directory = path2.join(root ?? runtimeRoot(), "@akms", "mcp");
|
|
240
|
+
const metadata = JSON.parse(fs.readFileSync(path2.join(directory, "package.json"), "utf8"));
|
|
241
|
+
const script = path2.join(directory, "lib", `${kind}.js`);
|
|
242
|
+
if (metadata.name !== PACKAGE_NAME || typeof metadata.version !== "string" || !fs.statSync(script).isFile()) return null;
|
|
243
|
+
return { command: process.execPath, args: [script], version: metadata.version, linked: fs.lstatSync(directory).isSymbolicLink() };
|
|
237
244
|
} catch {
|
|
238
245
|
return null;
|
|
239
246
|
}
|
|
240
247
|
}
|
|
241
|
-
|
|
248
|
+
function findLegacyRuntimes(root) {
|
|
249
|
+
const found = [];
|
|
250
|
+
for (const kind of KINDS) {
|
|
251
|
+
try {
|
|
252
|
+
const directory = path2.join(root ?? runtimeRoot(), "@akms", `mcp-${kind}`);
|
|
253
|
+
const metadata = JSON.parse(fs.readFileSync(path2.join(directory, "package.json"), "utf8"));
|
|
254
|
+
if (metadata.name !== legacyPackageName(kind)) continue;
|
|
255
|
+
found.push({ kind, packageName: legacyPackageName(kind), version: typeof metadata.version === "string" ? metadata.version : "unknown", linked: fs.lstatSync(directory).isSymbolicLink() });
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return found;
|
|
260
|
+
}
|
|
261
|
+
async function runNpm(argumentsList, failure, retry) {
|
|
242
262
|
const cancellation = new AbortController();
|
|
243
263
|
const cancel = () => cancellation.abort();
|
|
244
264
|
process.on("SIGINT", cancel);
|
|
245
265
|
process.on("SIGTERM", cancel);
|
|
246
266
|
try {
|
|
247
|
-
await promisify(execFile)(process.execPath, [npmCli(),
|
|
267
|
+
await promisify(execFile)(process.execPath, [npmCli(), ...argumentsList], {
|
|
248
268
|
windowsHide: true,
|
|
249
269
|
timeout: 18e4,
|
|
250
270
|
maxBuffer: 1024 * 1024,
|
|
251
271
|
signal: cancellation.signal,
|
|
252
|
-
cwd:
|
|
272
|
+
cwd: os5.homedir()
|
|
253
273
|
});
|
|
254
274
|
} catch (error) {
|
|
255
275
|
if (cancellation.signal.aborted) throw new Error("AKMS runtime installation cancelled.");
|
|
256
276
|
const stderr = error.stderr;
|
|
257
277
|
const detail = stripVTControlCharacters(typeof stderr === "string" ? stderr : error instanceof Error ? error.message : "").replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "").trim().split(/\r?\n/).slice(-6).map((line) => line.slice(0, 500)).join("\n");
|
|
258
|
-
throw new Error(
|
|
278
|
+
throw new Error(`${failure} Run ${retry} in your terminal and retry.${detail ? `
|
|
259
279
|
|
|
260
280
|
${detail}` : ""}`);
|
|
261
281
|
} finally {
|
|
@@ -263,13 +283,24 @@ ${detail}` : ""}`);
|
|
|
263
283
|
process.off("SIGTERM", cancel);
|
|
264
284
|
}
|
|
265
285
|
}
|
|
286
|
+
async function installGlobalRuntime() {
|
|
287
|
+
await runNpm(
|
|
288
|
+
["install", "--global", "--prefix", runtimePrefix(), "--prefer-online", "--no-fund", "--no-audit", `${PACKAGE_NAME}@latest`],
|
|
289
|
+
"AKMS runtime installation failed.",
|
|
290
|
+
runtimeInstallCommand()
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
async function removeLegacyRuntimes(kinds) {
|
|
294
|
+
if (!kinds.length) return;
|
|
295
|
+
const names = kinds.map(legacyPackageName);
|
|
296
|
+
await runNpm(
|
|
297
|
+
["uninstall", "--global", "--prefix", runtimePrefix(), "--no-fund", "--no-audit", ...names],
|
|
298
|
+
"Removing the old packages failed.",
|
|
299
|
+
`npm uninstall --global --prefix "${runtimePrefix()}" ${names.join(" ")}`
|
|
300
|
+
);
|
|
301
|
+
}
|
|
266
302
|
|
|
267
|
-
// src/modules/
|
|
268
|
-
import { spawn, execFile as execFile2 } from "child_process";
|
|
269
|
-
import path3 from "path";
|
|
270
|
-
import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
|
|
271
|
-
|
|
272
|
-
// src/modules/launcher/prompter.ts
|
|
303
|
+
// src/modules/prompt-errors.ts
|
|
273
304
|
var PromptClosedError = class extends Error {
|
|
274
305
|
constructor() {
|
|
275
306
|
super("input closed before the prompt was answered");
|
|
@@ -282,6 +313,8 @@ var PromptBackError = class extends Error {
|
|
|
282
313
|
this.name = "PromptBackError";
|
|
283
314
|
}
|
|
284
315
|
};
|
|
316
|
+
|
|
317
|
+
// src/modules/launcher/prompter.ts
|
|
285
318
|
var ESC = "\x1B";
|
|
286
319
|
var KEY_UP = [`${ESC}[A`, `${ESC}OA`];
|
|
287
320
|
var KEY_DOWN = [`${ESC}[B`, `${ESC}OB`];
|
|
@@ -383,6 +416,26 @@ var Prompter = class {
|
|
|
383
416
|
constructor(input, output, initialInput = "") {
|
|
384
417
|
this.input = input;
|
|
385
418
|
this.output = output;
|
|
419
|
+
this.lineBuffer = "";
|
|
420
|
+
this.pendingLines = [];
|
|
421
|
+
this.lineWaiter = null;
|
|
422
|
+
this.inputEnded = false;
|
|
423
|
+
this.onInputData = (chunk) => {
|
|
424
|
+
this.onData(chunk);
|
|
425
|
+
};
|
|
426
|
+
this.onInputEnd = () => {
|
|
427
|
+
this.inputEnded = true;
|
|
428
|
+
if (this.lineWaiter != null) {
|
|
429
|
+
const waiter = this.lineWaiter;
|
|
430
|
+
this.lineWaiter = null;
|
|
431
|
+
waiter(null);
|
|
432
|
+
}
|
|
433
|
+
if (this.keyHandler != null) {
|
|
434
|
+
this.keyHandler(KEY_CTRL_D);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
/** Set while a key-driven prompt owns the input. */
|
|
438
|
+
this.keyHandler = null;
|
|
386
439
|
const ttyInput = input;
|
|
387
440
|
const ttyOutput = output;
|
|
388
441
|
this.interactive = ttyInput.isTTY == true && ttyInput.setRawMode != null && ttyOutput.isTTY == true;
|
|
@@ -398,31 +451,6 @@ var Prompter = class {
|
|
|
398
451
|
input.on("end", this.onInputEnd);
|
|
399
452
|
if (initialInput) this.onData(initialInput);
|
|
400
453
|
}
|
|
401
|
-
input;
|
|
402
|
-
output;
|
|
403
|
-
lineBuffer = "";
|
|
404
|
-
pendingLines = [];
|
|
405
|
-
lineWaiter = null;
|
|
406
|
-
inputEnded = false;
|
|
407
|
-
onInputData = (chunk) => {
|
|
408
|
-
this.onData(chunk);
|
|
409
|
-
};
|
|
410
|
-
onInputEnd = () => {
|
|
411
|
-
this.inputEnded = true;
|
|
412
|
-
if (this.lineWaiter != null) {
|
|
413
|
-
const waiter = this.lineWaiter;
|
|
414
|
-
this.lineWaiter = null;
|
|
415
|
-
waiter(null);
|
|
416
|
-
}
|
|
417
|
-
if (this.keyHandler != null) {
|
|
418
|
-
this.keyHandler(KEY_CTRL_D);
|
|
419
|
-
}
|
|
420
|
-
};
|
|
421
|
-
/** Set while a key-driven prompt owns the input. */
|
|
422
|
-
keyHandler = null;
|
|
423
|
-
interactive;
|
|
424
|
-
style;
|
|
425
|
-
symbols;
|
|
426
454
|
/** Stops reading so the process can exit once the command is done. */
|
|
427
455
|
close() {
|
|
428
456
|
if (this.interactive == true) {
|
|
@@ -597,6 +625,9 @@ var Prompter = class {
|
|
|
597
625
|
if (item.hint != null && item.hint !== "") {
|
|
598
626
|
line += ` ${item.hint}`;
|
|
599
627
|
}
|
|
628
|
+
if (item.notice != null && item.notice !== "") {
|
|
629
|
+
line += ` ${item.notice}`;
|
|
630
|
+
}
|
|
600
631
|
this.output.write(`${line.trimEnd()}
|
|
601
632
|
`);
|
|
602
633
|
}
|
|
@@ -721,6 +752,9 @@ var Prompter = class {
|
|
|
721
752
|
if (item.hint != null && item.hint !== "") {
|
|
722
753
|
parts.push({ text: ` ${item.hint}`, paint: this.style.dim });
|
|
723
754
|
}
|
|
755
|
+
if (item.notice != null && item.notice !== "") {
|
|
756
|
+
parts.push({ text: ` ${item.notice}`, paint: this.style[item.noticeTone ?? "warning"] });
|
|
757
|
+
}
|
|
724
758
|
lines.push(this.fitLine(parts, width));
|
|
725
759
|
}
|
|
726
760
|
lines.push(this.fitLine([{ text: "\u2191\u2193 move \xB7 enter select \xB7 esc back", paint: this.style.dim }], width));
|
|
@@ -798,7 +832,7 @@ function writeWrapped(prompter, text, indent = "", changed, style) {
|
|
|
798
832
|
let segment = "";
|
|
799
833
|
let highlighted = false;
|
|
800
834
|
const flush = () => {
|
|
801
|
-
line += highlighted ? prompter.paint.accent(prompter.paint.bold(segment)) : segment;
|
|
835
|
+
line += highlighted ? prompter.paint.accent(prompter.paint.bold(segment)) : style ? style(segment) : segment;
|
|
802
836
|
segment = "";
|
|
803
837
|
};
|
|
804
838
|
for (let index = 0; index < characters.length; index++) {
|
|
@@ -806,7 +840,7 @@ function writeWrapped(prompter, text, indent = "", changed, style) {
|
|
|
806
840
|
const size = displayWidth(character);
|
|
807
841
|
if (width + size > prompter.width) {
|
|
808
842
|
flush();
|
|
809
|
-
prompter.write(
|
|
843
|
+
prompter.write(line + "\n");
|
|
810
844
|
line = indent;
|
|
811
845
|
width = displayWidth(indent);
|
|
812
846
|
}
|
|
@@ -819,7 +853,7 @@ function writeWrapped(prompter, text, indent = "", changed, style) {
|
|
|
819
853
|
width += size;
|
|
820
854
|
}
|
|
821
855
|
flush();
|
|
822
|
-
prompter.write(
|
|
856
|
+
prompter.write(line + "\n");
|
|
823
857
|
}
|
|
824
858
|
function changedRange(values, index) {
|
|
825
859
|
if (values.length !== 2 || values[0] === values[1]) return void 0;
|
|
@@ -836,7 +870,10 @@ function changedRange(values, index) {
|
|
|
836
870
|
function compareEntries(prompter, versions) {
|
|
837
871
|
const entries = versions.map((version) => version.entry);
|
|
838
872
|
const fields = differingFields(entries);
|
|
839
|
-
|
|
873
|
+
if (descriptionOnly(entries)) prompter.write(`${prompter.paint.warning("Description differs.")} ${prompter.paint.dim("Connection settings match.")}
|
|
874
|
+
`);
|
|
875
|
+
else prompter.write(`${prompter.paint.accent(String(fields.length))} ${prompter.paint.warning("setting(s) differ.")}
|
|
876
|
+
`);
|
|
840
877
|
const values = fields.map((field) => {
|
|
841
878
|
const key = field.replace(/^env\./, "");
|
|
842
879
|
return versions.map(({ entry }) => field.startsWith("env.") && visibleField.test(key) ? Object.hasOwn(entry.env, key) ? plainText(entry.env[key]) || "(empty)" : "(not set)" : "(hidden)");
|
|
@@ -849,7 +886,7 @@ function compareEntries(prompter, versions) {
|
|
|
849
886
|
const size = displayWidth(character);
|
|
850
887
|
if (lines[lines.length - 1].width + size > width) lines.push({ text: "", width: 0 });
|
|
851
888
|
const line = lines[lines.length - 1];
|
|
852
|
-
line.text += changed && index >= changed[0] && index < changed[1] ? prompter.paint.
|
|
889
|
+
line.text += prompter.paint.accent(changed && index >= changed[0] && index < changed[1] ? prompter.paint.bold(character) : character);
|
|
853
890
|
line.width += size;
|
|
854
891
|
}
|
|
855
892
|
return lines;
|
|
@@ -874,13 +911,13 @@ ${prompter.paint.dim(fieldName(fields[field]))}
|
|
|
874
911
|
}
|
|
875
912
|
for (let index = 0; index < versions.length; index++) {
|
|
876
913
|
prompter.write("\n");
|
|
877
|
-
writeWrapped(prompter, `[${index + 1}] ${versions[index].label}
|
|
914
|
+
writeWrapped(prompter, `[${index + 1}] ${versions[index].label}`, "", void 0, prompter.paint.bold);
|
|
878
915
|
for (let field = 0; field < fields.length; field++) {
|
|
879
|
-
writeWrapped(prompter, fieldName(fields[field]), " ");
|
|
880
|
-
writeWrapped(prompter, values[field][index], " ", changedRange(values[field], index));
|
|
916
|
+
writeWrapped(prompter, fieldName(fields[field]), " ", void 0, prompter.paint.dim);
|
|
917
|
+
writeWrapped(prompter, values[field][index], " ", changedRange(values[field], index), prompter.paint.accent);
|
|
881
918
|
}
|
|
882
919
|
}
|
|
883
|
-
if (values.some((row) => row.includes("(hidden)"))) prompter.write("\nSecret values are hidden.\n");
|
|
920
|
+
if (values.some((row) => row.includes("(hidden)"))) prompter.write(prompter.paint.dim("\nSecret values are hidden.\n"));
|
|
884
921
|
prompter.write("\n");
|
|
885
922
|
}
|
|
886
923
|
|
|
@@ -890,7 +927,7 @@ function visibleRuntimePath(entry) {
|
|
|
890
927
|
return kind && standardLaunch({ ...entry, args: entry.args.slice(0, 1) }, kind) && (entry.args.length === 1 || entry.args.length === 2 && entry.args[1] === "--catalog") ? entry.args[0] : void 0;
|
|
891
928
|
}
|
|
892
929
|
function redactStartupText(text, entry) {
|
|
893
|
-
const normalize2 = (value) =>
|
|
930
|
+
const normalize2 = (value) => stripVTControlCharacters(value).replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "");
|
|
894
931
|
const direct = visibleRuntimePath(entry);
|
|
895
932
|
const privateValues = [...Object.entries(entry.env).filter(([key]) => !visibleField.test(key)).map(([, value]) => value), ...entry.args.slice(direct ? 1 : 0)];
|
|
896
933
|
const secrets = /* @__PURE__ */ new Set();
|
|
@@ -959,7 +996,7 @@ async function checkStartup(entry, timeoutMs = 1e4) {
|
|
|
959
996
|
child.stdin?.end();
|
|
960
997
|
killTimer = setTimeout(() => {
|
|
961
998
|
if (process.platform === "win32" && child.pid) {
|
|
962
|
-
|
|
999
|
+
execFile(path2.join(process.env.SYSTEMROOT ?? "C:\\Windows", "System32", "taskkill.exe"), ["/PID", String(child.pid), "/T", "/F"], { windowsHide: true, timeout: 4e3 }, () => {
|
|
963
1000
|
});
|
|
964
1001
|
} else if (child.pid) {
|
|
965
1002
|
try {
|
|
@@ -1086,20 +1123,24 @@ async function checkConnections(prompter, entries, check = checkStartup) {
|
|
|
1086
1123
|
}
|
|
1087
1124
|
if (failed) prompter.write("Settings remain saved. Fix failed startup checks before restarting or reconnecting your AI clients.\n");
|
|
1088
1125
|
}
|
|
1089
|
-
|
|
1090
|
-
// src/modules/registration/catalog.ts
|
|
1091
|
-
import fs3 from "fs";
|
|
1092
|
-
import os2 from "os";
|
|
1093
|
-
import path4 from "path";
|
|
1094
1126
|
var LONGEST_TOOL_NAME_LENGTH = { db: 13, ssh: 17, wsl: 17, s3: 11 };
|
|
1095
1127
|
var maxConnectionNameLength = (kind) => 64 - `mcp__akms_${kind}__`.length - 2 - LONGEST_TOOL_NAME_LENGTH[kind];
|
|
1096
|
-
var defaultCatalogPath = () =>
|
|
1128
|
+
var defaultCatalogPath = () => path2.join(process.env.AKMS_MCP_HOME ? path2.resolve(process.env.AKMS_MCP_HOME) : path2.join(os5.homedir(), ".akms", "mcp"), "registry.json");
|
|
1097
1129
|
|
|
1098
1130
|
// src/modules/registration/registry.ts
|
|
1099
1131
|
var sessionName = (kind) => `akms_${kind}`;
|
|
1132
|
+
function legacyLaunches(state) {
|
|
1133
|
+
return KINDS.map((kind) => ({ kind, names: [
|
|
1134
|
+
...state.sessions?.[kind] && legacyLaunch(state.sessions[kind].entry, kind) ? [sessionName(kind)] : [],
|
|
1135
|
+
...Object.entries(state.connections).filter(([, item]) => item.kind === kind && item.entry && legacyLaunch(item.entry, kind)).map(([name]) => name)
|
|
1136
|
+
] })).filter((item) => item.names.length);
|
|
1137
|
+
}
|
|
1100
1138
|
var MIGRATION_STEPS = [
|
|
1101
1139
|
{ id: "v1_to_v2", label: "Upgrade registry version 1 to version 2", needed: (state) => state.version === 1 },
|
|
1102
|
-
{ id: "catalog_default", label: "
|
|
1140
|
+
{ id: "catalog_default", label: "Make one entry per kind the default", needed: (state) => state.defaultMode === void 0 },
|
|
1141
|
+
// Every kind is grouped: one akms_<kind> entry per AI service. Per-connection entries are a 0.2 layout to convert, not a choice.
|
|
1142
|
+
{ id: "group_all", label: "Group every kind's connections into one akms_<kind> entry", needed: (state) => state.version === 2 && (state.defaultMode === "individual" || Object.values(state.sessions ?? {}).some((session) => session?.mode === "individual")) },
|
|
1143
|
+
{ id: "single_package", label: "Start servers from @akms/mcp instead of the separate per-kind packages", needed: (state) => legacyLaunches(state).length > 0 }
|
|
1103
1144
|
];
|
|
1104
1145
|
var own = (value, key) => Object.hasOwn(value, key);
|
|
1105
1146
|
var record = (value) => value != null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -1134,18 +1175,45 @@ var Registry = class {
|
|
|
1134
1175
|
constructor(targets, file = defaultRegistryPath(), runtimeOptions = {}) {
|
|
1135
1176
|
this.targets = targets;
|
|
1136
1177
|
this.runtimeOptions = runtimeOptions;
|
|
1178
|
+
this.backupCaptured = false;
|
|
1137
1179
|
this.file = file;
|
|
1138
1180
|
}
|
|
1139
|
-
targets;
|
|
1140
|
-
runtimeOptions;
|
|
1141
|
-
file;
|
|
1142
|
-
observedText;
|
|
1143
|
-
backupCaptured = false;
|
|
1144
1181
|
globalRuntime(kind) {
|
|
1145
1182
|
return (this.runtimeOptions.find ?? findGlobalRuntime)(kind);
|
|
1146
1183
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1184
|
+
/** The installed @akms/mcp, whichever kind is asked: one package carries them all. */
|
|
1185
|
+
installedRuntime() {
|
|
1186
|
+
return this.globalRuntime("db");
|
|
1187
|
+
}
|
|
1188
|
+
latestRuntime() {
|
|
1189
|
+
return (this.runtimeOptions.latest ?? latestRuntimeVersion)();
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Per-kind packages left in the runtime directory, with the services or catalog entries
|
|
1193
|
+
* that still start them. A service that cannot be read counts as using every one of them.
|
|
1194
|
+
*/
|
|
1195
|
+
legacyRuntimes() {
|
|
1196
|
+
const state = this.read();
|
|
1197
|
+
return (this.runtimeOptions.legacy ?? findLegacyRuntimes)().map((runtime) => {
|
|
1198
|
+
const usedBy = legacyLaunches(state).filter((item) => item.kind === runtime.kind).flatMap((item) => item.names.map((name) => `AKMS catalog: ${name}`));
|
|
1199
|
+
for (const target of this.targets) {
|
|
1200
|
+
try {
|
|
1201
|
+
for (const [name, entry] of Object.entries(target.readEntries())) {
|
|
1202
|
+
const launch = catalogLaunch(entry, runtime.kind) ? { ...entry, args: entry.args.slice(0, 1) } : entry;
|
|
1203
|
+
if (legacyLaunch(launch, runtime.kind) && /^(node|nodejs)(\.exe)?$/i.test(path2.basename(launch.command))) usedBy.push(`${target.label}: ${name}`);
|
|
1204
|
+
}
|
|
1205
|
+
} catch {
|
|
1206
|
+
usedBy.push(`${target.label}: cannot be read`);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return { runtime, usedBy };
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
/** Removes only packages nothing uses at the moment of removal. */
|
|
1213
|
+
async removeLegacyRuntimes(kinds) {
|
|
1214
|
+
const inUse = this.legacyRuntimes().filter((item) => kinds.includes(item.runtime.kind) && item.usedBy.length);
|
|
1215
|
+
if (inUse.length) throw new RegistryError(`Still in use: ${inUse.map((item) => item.runtime.packageName).join(", ")}. Run Migrate registry and Sync first.`);
|
|
1216
|
+
await (this.runtimeOptions.removeLegacy ?? removeLegacyRuntimes)(kinds);
|
|
1149
1217
|
}
|
|
1150
1218
|
isSessionMode(kind, state = this.read()) {
|
|
1151
1219
|
return state.version === 2 && (state.sessions?.[kind]?.mode ?? state.defaultMode) === "catalog";
|
|
@@ -1156,9 +1224,10 @@ var Registry = class {
|
|
|
1156
1224
|
planMigration() {
|
|
1157
1225
|
const state = this.read();
|
|
1158
1226
|
const steps = MIGRATION_STEPS.filter((step) => step.needed(state)).map(({ id, label }) => ({ id, label }));
|
|
1159
|
-
const
|
|
1227
|
+
const launches = legacyLaunches(state).map((item) => ({ ...item, ready: this.globalRuntime(item.kind) != null }));
|
|
1228
|
+
const layoutChanges = steps.some((step) => step.id === "v1_to_v2" || step.id === "catalog_default" || step.id === "group_all");
|
|
1229
|
+
const kinds = layoutChanges ? KINDS.map((kind) => {
|
|
1160
1230
|
const names = Object.entries(state.connections).filter(([, item]) => item.kind === kind && item.entry).map(([name]) => name);
|
|
1161
|
-
if (state.sessions?.[kind]?.mode === "individual") return { kind, names, status: "individual" };
|
|
1162
1231
|
if (this.isSessionMode(kind, state)) return { kind, names, status: "catalog" };
|
|
1163
1232
|
if (!names.length) return { kind, names, status: "empty" };
|
|
1164
1233
|
try {
|
|
@@ -1185,7 +1254,7 @@ var Registry = class {
|
|
|
1185
1254
|
return { kind, names, status: "blocked", detail: error instanceof RegistryError ? error.message : "Cannot preview this kind. Check its runtime and catalog settings." };
|
|
1186
1255
|
}
|
|
1187
1256
|
}) : [];
|
|
1188
|
-
return { expected: JSON.stringify(state), steps, kinds };
|
|
1257
|
+
return { expected: JSON.stringify(state), steps, kinds, launches };
|
|
1189
1258
|
}
|
|
1190
1259
|
applyMigration(plan) {
|
|
1191
1260
|
if (!plan.steps.length) return this.locked(() => {
|
|
@@ -1204,21 +1273,28 @@ var Registry = class {
|
|
|
1204
1273
|
state.version = 2;
|
|
1205
1274
|
state.sessions ??= {};
|
|
1206
1275
|
} else if (step.id === "catalog_default") state.defaultMode = "catalog";
|
|
1207
|
-
else
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1276
|
+
else if (step.id === "group_all") {
|
|
1277
|
+
state.defaultMode = "catalog";
|
|
1278
|
+
for (const kind of KINDS) if (state.sessions?.[kind]?.mode === "individual") delete state.sessions[kind];
|
|
1279
|
+
} else if (step.id === "single_package") {
|
|
1280
|
+
for (const item of plan.launches) {
|
|
1281
|
+
const runtime = this.globalRuntime(item.kind);
|
|
1282
|
+
if (runtime != null !== item.ready) throw new RegistryError("Runtime availability changed since review. Reopen Migrate registry.");
|
|
1283
|
+
if (!runtime) continue;
|
|
1284
|
+
const session = state.sessions?.[item.kind];
|
|
1285
|
+
if (session && legacyLaunch(session.entry, item.kind)) session.entry = this.sessionEntry(item.kind);
|
|
1286
|
+
for (const connection of Object.values(state.connections)) {
|
|
1287
|
+
if (connection.kind === item.kind && connection.entry && legacyLaunch(connection.entry, item.kind)) {
|
|
1288
|
+
connection.entry = { ...connection.entry, command: runtime.command, args: [...runtime.args] };
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
} else throw new RegistryError("Unknown registry migration step. Reopen Migrate registry.");
|
|
1219
1293
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1294
|
+
}, [.../* @__PURE__ */ new Set([
|
|
1295
|
+
...plan.kinds.filter((item) => item.status === "convert" || item.status === "pending-runtime").map((item) => item.kind),
|
|
1296
|
+
...plan.launches.filter((item) => item.ready).map((item) => item.kind)
|
|
1297
|
+
])]);
|
|
1222
1298
|
}
|
|
1223
1299
|
kindForName(name, state = this.read()) {
|
|
1224
1300
|
return own(state.connections, name) ? state.connections[name].kind : KINDS.find((kind) => sessionName(kind) === name && (state.sessions?.[kind] || this.isSessionMode(kind, state)));
|
|
@@ -1228,10 +1304,10 @@ var Registry = class {
|
|
|
1228
1304
|
if (KINDS.some((value) => sessionName(value) === name)) throw new RegistryError("Names akms_db, akms_ssh, akms_wsl and akms_s3 are reserved for session servers. Choose another name.");
|
|
1229
1305
|
}
|
|
1230
1306
|
sessionEntry(kind) {
|
|
1231
|
-
if (
|
|
1307
|
+
if (path2.basename(this.file) !== "registry.json") throw new RegistryError("Session mode requires a catalog named registry.json in the AKMS catalog directory.");
|
|
1232
1308
|
const runtime = this.requireRuntime(kind);
|
|
1233
1309
|
const env = {};
|
|
1234
|
-
if (process.env.AKMS_MCP_HOME ||
|
|
1310
|
+
if (process.env.AKMS_MCP_HOME || path2.resolve(this.file) !== path2.resolve(defaultCatalogPath())) env.AKMS_MCP_HOME = path2.dirname(path2.resolve(this.file));
|
|
1235
1311
|
return { command: runtime.command, args: [...runtime.args, "--catalog"], env };
|
|
1236
1312
|
}
|
|
1237
1313
|
planSession(kind, mode) {
|
|
@@ -1299,49 +1375,21 @@ var Registry = class {
|
|
|
1299
1375
|
}
|
|
1300
1376
|
await checkConnections(prompter, entries, this.runtimeOptions.check);
|
|
1301
1377
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1378
|
+
/** Installs or updates @akms/mcp, which carries every kind; returns the given kind's launch. */
|
|
1379
|
+
async installRuntime(kind = "db") {
|
|
1380
|
+
if (this.installedRuntime()?.linked) throw new RegistryError("This is a linked development installation. Build or update its source checkout instead.");
|
|
1381
|
+
await (this.runtimeOptions.install ?? installGlobalRuntime)();
|
|
1305
1382
|
return this.requireRuntime(kind);
|
|
1306
1383
|
}
|
|
1307
1384
|
requireRuntime(kind) {
|
|
1308
1385
|
const runtime = this.globalRuntime(kind);
|
|
1309
|
-
if (!runtime) throw new RegistryError(`Install the AKMS runtime first: ${runtimeInstallCommand(
|
|
1386
|
+
if (!runtime) throw new RegistryError(`Install the AKMS runtime first: ${runtimeInstallCommand()}. Then reopen Runtime.`);
|
|
1310
1387
|
return runtime;
|
|
1311
1388
|
}
|
|
1312
|
-
runtimeSnapshot(state, kind) {
|
|
1313
|
-
return JSON.stringify([state.clients, Object.entries(state.connections).filter(([, item]) => item.kind === kind).map(([name, item]) => [name, item.entry ? entryHash(item.entry) : null]).sort(([a], [b]) => String(a).localeCompare(String(b)))]);
|
|
1314
|
-
}
|
|
1315
|
-
planRuntime(kind) {
|
|
1316
|
-
const state = this.read();
|
|
1317
|
-
const runtime = this.requireRuntime(kind);
|
|
1318
|
-
const names = [], skipped = [];
|
|
1319
|
-
for (const [name, item] of Object.entries(state.connections)) {
|
|
1320
|
-
if (item.kind !== kind || !item.entry) continue;
|
|
1321
|
-
if (!standardLaunch(item.entry, kind)) {
|
|
1322
|
-
skipped.push(name);
|
|
1323
|
-
continue;
|
|
1324
|
-
}
|
|
1325
|
-
if (item.entry.command !== runtime.command || JSON.stringify(item.entry.args) !== JSON.stringify(runtime.args)) names.push(name);
|
|
1326
|
-
}
|
|
1327
|
-
return { kind, runtime, names, skipped, expected: this.runtimeSnapshot(state, kind) };
|
|
1328
|
-
}
|
|
1329
|
-
applyRuntime(plan) {
|
|
1330
|
-
return this.change((state) => {
|
|
1331
|
-
if (this.runtimeSnapshot(state, plan.kind) !== plan.expected) throw new RegistryError("Connections or registration changed since review. Reopen Runtime before applying.");
|
|
1332
|
-
const runtime = this.requireRuntime(plan.kind);
|
|
1333
|
-
if (JSON.stringify(runtime) !== JSON.stringify(plan.runtime)) throw new RegistryError("The global installation changed since review. Reopen Runtime before applying.");
|
|
1334
|
-
for (const name of plan.names) {
|
|
1335
|
-
const item = state.connections[name];
|
|
1336
|
-
if (!item?.entry || item.kind !== plan.kind || !standardLaunch(item.entry, plan.kind)) throw new RegistryError("This connection has a custom launch command. Review it individually.");
|
|
1337
|
-
item.entry = { ...item.entry, command: runtime.command, args: [...runtime.args] };
|
|
1338
|
-
}
|
|
1339
|
-
});
|
|
1340
|
-
}
|
|
1341
1389
|
read() {
|
|
1342
1390
|
let text;
|
|
1343
1391
|
try {
|
|
1344
|
-
text =
|
|
1392
|
+
text = fs.readFileSync(this.file, "utf8");
|
|
1345
1393
|
this.observedText = text;
|
|
1346
1394
|
} catch (error) {
|
|
1347
1395
|
if (error.code === "ENOENT") {
|
|
@@ -1377,7 +1425,7 @@ var Registry = class {
|
|
|
1377
1425
|
return after;
|
|
1378
1426
|
}, { beforeCommit: (before) => {
|
|
1379
1427
|
if (before !== null && !this.backupCaptured) {
|
|
1380
|
-
updateConfigFile(
|
|
1428
|
+
updateConfigFile(path2.join(path2.dirname(this.file), "registry.previous.json"), () => before, { mode: fs.statSync(this.file).mode & 511 });
|
|
1381
1429
|
this.backupCaptured = true;
|
|
1382
1430
|
}
|
|
1383
1431
|
} });
|
|
@@ -1385,16 +1433,16 @@ var Registry = class {
|
|
|
1385
1433
|
}
|
|
1386
1434
|
/** Serializes standalone editors across packages, including their config-file writes. */
|
|
1387
1435
|
locked(action) {
|
|
1388
|
-
|
|
1436
|
+
fs.mkdirSync(path2.dirname(this.file), { recursive: true, mode: 448 });
|
|
1389
1437
|
const lock = this.file + ".lock";
|
|
1390
1438
|
let descriptor;
|
|
1391
1439
|
try {
|
|
1392
|
-
descriptor =
|
|
1440
|
+
descriptor = fs.openSync(lock, "wx", 384);
|
|
1393
1441
|
} catch (error) {
|
|
1394
1442
|
if (error.code !== "EEXIST") throw error;
|
|
1395
1443
|
let abandoned = false;
|
|
1396
1444
|
try {
|
|
1397
|
-
const pid = Number(
|
|
1445
|
+
const pid = Number(fs.readFileSync(lock, "utf8"));
|
|
1398
1446
|
if (Number.isSafeInteger(pid) && pid > 0) {
|
|
1399
1447
|
try {
|
|
1400
1448
|
process.kill(pid, 0);
|
|
@@ -1405,17 +1453,17 @@ var Registry = class {
|
|
|
1405
1453
|
} catch {
|
|
1406
1454
|
}
|
|
1407
1455
|
if (!abandoned) throw new RegistryError("Another AKMS configuration command is running. Finish it and retry.");
|
|
1408
|
-
|
|
1409
|
-
descriptor =
|
|
1456
|
+
fs.unlinkSync(lock);
|
|
1457
|
+
descriptor = fs.openSync(lock, "wx", 384);
|
|
1410
1458
|
}
|
|
1411
1459
|
this.backupCaptured = false;
|
|
1412
1460
|
try {
|
|
1413
|
-
|
|
1461
|
+
fs.writeFileSync(descriptor, String(process.pid));
|
|
1414
1462
|
return action();
|
|
1415
1463
|
} finally {
|
|
1416
1464
|
this.backupCaptured = false;
|
|
1417
|
-
|
|
1418
|
-
|
|
1465
|
+
fs.closeSync(descriptor);
|
|
1466
|
+
fs.unlinkSync(lock);
|
|
1419
1467
|
}
|
|
1420
1468
|
}
|
|
1421
1469
|
change(action, kinds) {
|
|
@@ -1721,26 +1769,29 @@ var Registry = class {
|
|
|
1721
1769
|
function formatResults(results, prefix = "", options = {}) {
|
|
1722
1770
|
const plain = (text) => text;
|
|
1723
1771
|
const paint = options.paint ?? { success: plain, warning: plain, error: plain, dim: plain, bold: plain };
|
|
1772
|
+
const accent = options.paint?.accent ?? paint.bold;
|
|
1724
1773
|
const safe = (text) => text.replace(/[\x00-\x1f\x7f]/g, " ");
|
|
1725
1774
|
const lines = prefix ? [paint.bold(prefix)] : [];
|
|
1726
1775
|
const total = options.connectionCount ?? new Set(results.flatMap((item) => item.connections ?? [item.name])).size;
|
|
1727
|
-
lines.push(
|
|
1776
|
+
lines.push(`${paint.dim(" Connections: ")}${accent(String(total))}`);
|
|
1728
1777
|
const updated = results.filter((item) => item.status === "synced" && item.detail === "Applied").length;
|
|
1729
1778
|
const unchanged = results.filter((item) => item.status === "synced" && item.detail !== "Applied").length;
|
|
1730
1779
|
const removed = results.filter((item) => item.status === "removed").length;
|
|
1731
1780
|
const failed = results.filter((item) => item.status === "error");
|
|
1732
|
-
|
|
1781
|
+
const count = (value, label, tone) => value ? `${accent(String(value))} ${paint[tone](label)}` : paint.dim(`0 ${label}`);
|
|
1782
|
+
lines.push(`${paint.dim(" Service entries: ")}${count(updated, "updated", "success")} ${paint.dim("\xB7")} ${count(unchanged, "already up to date", "dim")}${removed ? ` ${paint.dim("\xB7")} ${count(removed, "removed", "success")}` : ""} ${paint.dim("\xB7")} ${count(failed.length, "failed", "error")}`);
|
|
1733
1783
|
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."));
|
|
1784
|
+
if (results.length && results.every((item) => item.status === "synced" || item.status === "removed")) lines.push(`${paint.success(" OK:")} ${paint.dim("all service entries are up to date.")}`);
|
|
1735
1785
|
const waiting = results.filter((item) => item.status === "pending");
|
|
1736
1786
|
for (const kind of KINDS) {
|
|
1737
1787
|
const runtime = waiting.filter((item) => item.kind === kind && item.waitingFor === "runtime");
|
|
1738
1788
|
const kept = waiting.filter((item) => item.kind === kind && item.waitingFor === "session");
|
|
1739
1789
|
if (!runtime.length && !kept.length) continue;
|
|
1740
|
-
lines.push(`
|
|
1741
|
-
${paint.warning(
|
|
1742
|
-
|
|
1743
|
-
|
|
1790
|
+
lines.push(runtime.length ? `
|
|
1791
|
+
${paint.warning("Waiting for runtime")} - ${accent(kind.toUpperCase())}` : `
|
|
1792
|
+
${paint.warning("Waiting for")} ${accent(sessionName(kind))}`);
|
|
1793
|
+
if (runtime.length) lines.push(paint.dim(" Install it from Runtime, then run Sync. Your existing entries keep working."));
|
|
1794
|
+
else lines.push(paint.dim(" Resolve this session entry in Sync. Existing connection entries stay until it is ready."));
|
|
1744
1795
|
const retained = /* @__PURE__ */ new Map();
|
|
1745
1796
|
for (const client of new Set(kept.map((item) => item.client))) {
|
|
1746
1797
|
const names = [...new Set(kept.filter((item) => item.client === client).map((item) => item.name))];
|
|
@@ -1751,7 +1802,7 @@ ${paint.warning(runtime.length ? `Waiting for runtime - ${kind.toUpperCase()}` :
|
|
|
1751
1802
|
}
|
|
1752
1803
|
for (const group of retained.values()) {
|
|
1753
1804
|
const clients = group.clients.map((client) => CLIENT_LABELS2[client]).join(" \xB7 ");
|
|
1754
|
-
lines.push(paint.dim(
|
|
1805
|
+
lines.push(`${paint.dim(" Kept in ")}${paint.bold(clients)}${paint.dim(` until ${sessionName(kind)} is applied: `)}${accent(group.names.map(safe).join(", "))}`);
|
|
1755
1806
|
}
|
|
1756
1807
|
}
|
|
1757
1808
|
for (const [heading, rows, color] of [
|
|
@@ -1763,258 +1814,376 @@ ${paint.warning(runtime.length ? `Waiting for runtime - ${kind.toUpperCase()}` :
|
|
|
1763
1814
|
lines.push(`
|
|
1764
1815
|
${color(`${heading}:`)}`);
|
|
1765
1816
|
for (const name of new Set(rows.map((item) => item.name))) {
|
|
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)}`);
|
|
1817
|
+
lines.push(` ${accent(safe(name))}`);
|
|
1818
|
+
for (const row of rows.filter((item) => item.name === name)) lines.push(` ${row.status === "error" ? paint.error("FAILED ") : ""}${paint.dim(`${CLIENT_LABELS2[row.client]}: `)}${safe(row.detail)}`);
|
|
1768
1819
|
}
|
|
1769
1820
|
}
|
|
1770
|
-
if (results.some((item) => item.status === "conflict")) lines.push(paint.
|
|
1821
|
+
if (results.some((item) => item.status === "conflict")) lines.push(paint.dim(`
|
|
1771
1822
|
${options.reviewHint ?? "Next: open Sync to compare settings and choose which version to use."}`));
|
|
1772
|
-
if (failed.length) lines.push(paint.
|
|
1823
|
+
if (failed.length) lines.push(`${paint.dim("\nNext: ")}${paint.error("fix the errors above")}${paint.dim(", then run ")}${accent("Sync")}${paint.dim(" again.")}`);
|
|
1773
1824
|
return lines.join("\n");
|
|
1774
1825
|
}
|
|
1775
1826
|
|
|
1776
|
-
// src/modules/registration/
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
...installed && !sessionMode ? [{ value: "apply", label: "Use for existing connections", hint: "convert npx and old runtime paths together" }] : [],
|
|
1801
|
-
...installed ? [{ value: "catalog", key: "catalog", label: sessionMode ? "Reapply session launch command" : "Switch to session server", hint: `one ${sessionName(kind)} entry per registered service` }] : [],
|
|
1802
|
-
...sessionMode && (installed || registry.read().sessions?.[kind]) ? [{ value: "individual", key: "individual", label: "Switch back to per-connection entries", hint: "restore connection settings from the catalog" }] : [],
|
|
1803
|
-
...!installed?.linked ? [{ value: "install", label: installed ? "Update runtime installation" : "Install AKMS runtime", hint: runtimeInstallCommand(kind) }] : [],
|
|
1804
|
-
...!registry.needsMigration() ? [{ value: "default", key: "default", label: registry.read().defaultMode === "catalog" ? "Use individual entries for new kinds" : "Start new kinds as session servers", hint: "keep existing kinds and rollback choices" }] : [],
|
|
1805
|
-
{ value: "back", key: "b", label: "Back" }
|
|
1806
|
-
], { cancelValue: "back" });
|
|
1807
|
-
if (picked === "back") {
|
|
1808
|
-
if (requestedKind) return;
|
|
1809
|
-
kind = void 0;
|
|
1810
|
-
continue;
|
|
1811
|
-
}
|
|
1812
|
-
if (picked === "default") {
|
|
1813
|
-
const mode = registry.read().defaultMode === "catalog" ? "individual" : "catalog";
|
|
1814
|
-
prompter.write(`New kinds will use ${mode === "catalog" ? "session servers" : "individual entries"}. Existing kinds keep their settings.
|
|
1827
|
+
// src/modules/registration/migration-menu.ts
|
|
1828
|
+
function migrationStatus(registry) {
|
|
1829
|
+
if (!registry.needsMigration()) return { status: "none" };
|
|
1830
|
+
const plan = registry.planMigration();
|
|
1831
|
+
const blocked = plan.kinds.find((item) => item.status === "blocked");
|
|
1832
|
+
if (blocked) return { status: "blocked", detail: blocked.detail };
|
|
1833
|
+
if (plan.steps.some((step) => step.id !== "single_package") || plan.launches.some((item) => item.ready)) return { status: "ready" };
|
|
1834
|
+
return { status: "waiting", detail: `Install ${PACKAGE_NAME} first.` };
|
|
1835
|
+
}
|
|
1836
|
+
function migrationVersions(registry, plan = registry.planMigration()) {
|
|
1837
|
+
const pairs = [];
|
|
1838
|
+
if (plan.steps.some((step) => step.id === "v1_to_v2")) pairs.push({ label: "Registry", from: "v1", to: "v2" });
|
|
1839
|
+
const ready = plan.launches.filter((item) => item.ready);
|
|
1840
|
+
if (ready.length) {
|
|
1841
|
+
const installed = new Map(registry.legacyRuntimes().map((item) => [item.runtime.kind, `${item.runtime.packageName} ${item.runtime.version}`]));
|
|
1842
|
+
const from = [...new Set(ready.map((item) => installed.get(item.kind) ?? legacyPackageName(item.kind)))].join(", ");
|
|
1843
|
+
pairs.push({ label: "Servers", from, to: `${PACKAGE_NAME} ${registry.installedRuntime()?.version ?? ""}`.trim() });
|
|
1844
|
+
}
|
|
1845
|
+
return pairs;
|
|
1846
|
+
}
|
|
1847
|
+
function writePlan(prompter, registry, plan) {
|
|
1848
|
+
const paint = prompter.paint;
|
|
1849
|
+
prompter.clearScreen();
|
|
1850
|
+
prompter.write(`${paint.bold("Migrate registry")}
|
|
1815
1851
|
`);
|
|
1816
|
-
|
|
1817
|
-
|
|
1852
|
+
for (const pair of migrationVersions(registry, plan)) {
|
|
1853
|
+
prompter.write(` ${paint.dim(pair.label.padEnd(10))}${paint.warning(plainText(pair.from))} ${paint.dim("\u2192")} ${paint.bold(paint.success(plainText(pair.to)))}
|
|
1818
1854
|
`);
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
prompter.write("Registry version 2 prevents older AKMS setup packages from changing these settings.\nThe previous catalog revision is kept as registry.previous.json.\n\n");
|
|
1828
|
-
for (const change of plan2.changes) {
|
|
1829
|
-
writeWrapped(prompter, CLIENT_LABELS2[change.client]);
|
|
1830
|
-
writeWrapped(prompter, `Apply: ${change.add.join(", ") || "none"}`, " ");
|
|
1831
|
-
writeWrapped(prompter, `Remove after replacements are applied: ${change.remove.join(", ") || "none"}`, " ");
|
|
1832
|
-
if (change.review.length) writeWrapped(prompter, `Keep for Sync review: ${change.review.join(", ")}`, " ");
|
|
1833
|
-
}
|
|
1834
|
-
if (picked === "catalog") {
|
|
1835
|
-
prompter.write("\nPasswords stay in the catalog and its previous-revision copy. Session entries contain no connection credentials.\n");
|
|
1836
|
-
writeWrapped(prompter, `Command: ${plan2.entry.command}`);
|
|
1837
|
-
writeWrapped(prompter, `Runtime: ${plan2.entry.args[0]} --catalog`);
|
|
1838
|
-
if (plan2.custom.length) {
|
|
1839
|
-
prompter.write("Custom commands will not run in catalog mode; they are retained for switching back:\n");
|
|
1840
|
-
for (const name of plan2.custom) writeWrapped(prompter, plainText(name), " ");
|
|
1841
|
-
}
|
|
1842
|
-
} else prompter.write("\nConnection credentials will be copied back into registered client settings. The registry stays version 2.\n");
|
|
1843
|
-
if (!await prompter.askYesNo("Apply this configuration mode?", false)) continue;
|
|
1844
|
-
const results2 = registry.applySession(plan2);
|
|
1845
|
-
prompter.clearScreen();
|
|
1846
|
-
prompter.write(`${registry.formatResults(results2, "Configuration mode saved.", prompter.paint)}
|
|
1855
|
+
}
|
|
1856
|
+
plan.steps.forEach((step, index) => prompter.write(` ${paint.accent(`${index + 1}.`)} ${step.label}
|
|
1857
|
+
`));
|
|
1858
|
+
if (plan.kinds.length) prompter.write(paint.dim(" Every kind is listed as one akms_<kind> entry in each AI service; connection settings stay in the catalog.\n"));
|
|
1859
|
+
prompter.write("\n");
|
|
1860
|
+
for (const item of plan.launches) {
|
|
1861
|
+
const names = item.names.length > 3 ? `${item.names.length} entries` : item.names.map(plainText).join(", ");
|
|
1862
|
+
prompter.write(` ${paint.dim(KIND_LABELS[item.kind].padEnd(10))}${names} ${paint.dim("\u2192")} ${paint.accent(`${PACKAGE_NAME}/lib/${item.kind}.js`)}${item.ready ? "" : paint.warning(" after installing it")}
|
|
1847
1863
|
`);
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
if (picked === "install") {
|
|
1854
|
-
prompter.clearScreen();
|
|
1855
|
-
writeWrapped(prompter, `Installing ${packageName(kind)}@latest in ${runtimePrefix()}...`);
|
|
1856
|
-
const runtime = await registry.installRuntime(kind);
|
|
1857
|
-
prompter.write(`Installed ${runtime.version}. Reconnect your AI clients to load the update.
|
|
1864
|
+
}
|
|
1865
|
+
for (const item of plan.kinds) {
|
|
1866
|
+
const target = sessionName(item.kind);
|
|
1867
|
+
const action = item.status === "catalog" ? `already ${target}` : item.status === "empty" ? `no connections \xB7 new ones use ${target}` : item.status === "blocked" ? paint.error("blocked") : `${item.names.length} entries \u2192 ${target}${item.status === "pending-runtime" ? paint.warning(" after Runtime + Sync") : ""}`;
|
|
1868
|
+
prompter.write(` ${paint.dim(KIND_LABELS[item.kind].padEnd(10))}${action}
|
|
1858
1869
|
`);
|
|
1859
|
-
|
|
1860
|
-
continue;
|
|
1861
|
-
}
|
|
1862
|
-
const plan = registry.planRuntime(kind);
|
|
1863
|
-
prompter.clearScreen();
|
|
1864
|
-
writeWrapped(prompter, `Use AKMS runtime ${packageName(kind)} ${plan.runtime.version}`);
|
|
1865
|
-
prompter.write(`
|
|
1866
|
-
Convert ${plan.names.length} connection(s):
|
|
1870
|
+
if (item.detail) prompter.write(` ${" ".repeat(10)}${paint.dim(plainText(item.detail))}
|
|
1867
1871
|
`);
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
for (const name of plan.skipped) writeWrapped(prompter, plainText(name), " ");
|
|
1872
|
-
}
|
|
1873
|
-
prompter.write("\nConnection values and credentials stay the same.\nVersion-pinned npx entries will use the installed version shown above.\n");
|
|
1874
|
-
if (!plan.names.length) {
|
|
1875
|
-
prompter.write("No saved launch commands need converting.\nIf a legacy client entry is missing here, import it from Register or Import existing connections first.\n");
|
|
1876
|
-
await prompter.acknowledge();
|
|
1877
|
-
continue;
|
|
1878
|
-
}
|
|
1879
|
-
if (!await prompter.askYesNo("Apply these launch commands to registered services?", true)) continue;
|
|
1880
|
-
const results = registry.applyRuntime(plan);
|
|
1881
|
-
prompter.clearScreen();
|
|
1882
|
-
prompter.write(`${registry.formatResults(results, "Runtime settings saved.", prompter.paint)}
|
|
1883
|
-
|
|
1884
|
-
Restart or reconnect AI clients to replace running npx processes.
|
|
1872
|
+
for (const change of item.session?.changes ?? item.changes ?? []) {
|
|
1873
|
+
const remove = change.remove.length > 3 ? `${change.remove.length} entries` : change.remove.join(", ") || "nothing";
|
|
1874
|
+
prompter.write(` ${" ".repeat(10)}${paint.dim(CLIENT_LABELS2[change.client].padEnd(15))}${paint.accent(change.add.join(", ") || "nothing")} ${paint.dim("\xB7 then remove")} ${plainText(remove)}
|
|
1885
1875
|
`);
|
|
1886
|
-
|
|
1887
|
-
await prompter.acknowledge();
|
|
1888
|
-
} catch (error) {
|
|
1889
|
-
if (error instanceof PromptClosedError) throw error;
|
|
1890
|
-
if (error instanceof PromptBackError) continue;
|
|
1891
|
-
prompter.write(`${error instanceof Error ? error.message : "Could not configure the AKMS runtime."}
|
|
1876
|
+
if (change.review.length) prompter.write(` ${" ".repeat(25)}${paint.warning(`kept for Sync review: ${change.review.map(plainText).join(", ")}`)}
|
|
1892
1877
|
`);
|
|
1893
|
-
await prompter.acknowledge();
|
|
1894
1878
|
}
|
|
1879
|
+
if (item.session?.custom.length) prompter.write(` ${" ".repeat(10)}${paint.dim(`custom commands are not started; their settings still load in the grouped entry: ${item.session.custom.map(plainText).join(", ")}`)}
|
|
1880
|
+
`);
|
|
1895
1881
|
}
|
|
1882
|
+
prompter.write(paint.dim("\nServer entries hold no passwords; they stay in the catalog. The previous catalog is kept as registry.previous.json.\n"));
|
|
1896
1883
|
}
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
const refresh = async () => {
|
|
1903
|
-
prompter.clearScreen();
|
|
1904
|
-
prompter.write("Updates / checking published runtime versions...\n");
|
|
1905
|
-
await Promise.all(kinds.map(async (kind) => {
|
|
1906
|
-
try {
|
|
1907
|
-
latest.set(kind, await registry.latestRuntime(kind));
|
|
1908
|
-
} catch {
|
|
1909
|
-
latest.set(kind, null);
|
|
1910
|
-
}
|
|
1911
|
-
}));
|
|
1912
|
-
};
|
|
1913
|
-
await refresh();
|
|
1914
|
-
for (; ; ) {
|
|
1915
|
-
try {
|
|
1916
|
-
prompter.clearScreen();
|
|
1917
|
-
prompter.write("Updates / AKMS runtimes\n\n");
|
|
1918
|
-
const rows = kinds.map((kind) => {
|
|
1919
|
-
const installed = registry.globalRuntime(kind), published = latest.get(kind);
|
|
1920
|
-
const update = !!installed && !installed.linked && !!published && runtimeIsOlder(installed.version, published);
|
|
1921
|
-
const status = installed?.linked ? "Development link" : !published ? "Unavailable" : !installed ? "Not installed" : update ? "Update available" : "Up to date";
|
|
1922
|
-
writeWrapped(prompter, `${packageName(kind)} | installed: ${installed?.version ?? "not installed"} | latest: ${published ?? "unavailable"} | ${status}`);
|
|
1923
|
-
return { kind, installed, published, update };
|
|
1924
|
-
});
|
|
1925
|
-
const updates = rows.filter((row) => row.update);
|
|
1926
|
-
const available = rows.filter((row) => row.published && !row.installed?.linked && (!row.installed || row.update));
|
|
1927
|
-
prompter.write("\nDevelopment links are managed in their source checkouts.\n");
|
|
1928
|
-
const picked = await prompter.select("Updates", [
|
|
1929
|
-
...available.map((row) => ({ value: row.kind, key: row.kind, label: `${row.installed ? "Update" : "Install"} ${packageName(row.kind)}` })),
|
|
1930
|
-
...updates.length ? [{ value: "all", key: "a", label: `Update all available (${updates.length})` }] : [],
|
|
1931
|
-
{ value: "refresh", key: "r", label: "Refresh versions" },
|
|
1932
|
-
{ value: "back", key: "b", label: "Back" }
|
|
1933
|
-
], { cancelValue: "back" });
|
|
1934
|
-
if (picked === "back") return;
|
|
1935
|
-
if (picked === "refresh") {
|
|
1936
|
-
await refresh();
|
|
1937
|
-
continue;
|
|
1938
|
-
}
|
|
1939
|
-
const selected = picked === "all" ? updates : available.filter((row) => row.kind === picked);
|
|
1940
|
-
if (!selected.length) continue;
|
|
1941
|
-
if (!await prompter.askYesNo(`Install latest published releases for ${selected.map((row) => packageName(row.kind)).join(", ")}?`, true)) continue;
|
|
1884
|
+
async function migrationMenu(prompter, registry) {
|
|
1885
|
+
try {
|
|
1886
|
+
const paint = prompter.paint;
|
|
1887
|
+
let plan = registry.planMigration();
|
|
1888
|
+
if (!plan.steps.length) {
|
|
1942
1889
|
prompter.clearScreen();
|
|
1943
|
-
|
|
1944
|
-
|
|
1890
|
+
prompter.write(`${paint.bold("Migrate registry")}
|
|
1891
|
+
${paint.success("Up to date")} ${paint.dim("\xB7 the registry is already in the current format")}
|
|
1892
|
+
`);
|
|
1893
|
+
await prompter.acknowledge();
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
writePlan(prompter, registry, plan);
|
|
1897
|
+
if (plan.launches.some((item) => !item.ready) && !registry.installedRuntime()?.linked) {
|
|
1898
|
+
prompter.write(paint.warning(`! ${PACKAGE_NAME} is not installed in the runtime directory yet.
|
|
1899
|
+
`));
|
|
1900
|
+
if (await prompter.askYesNo(`Install ${PACKAGE_NAME} now?`, true)) {
|
|
1901
|
+
prompter.write(`Installing ${PACKAGE_NAME}@latest...
|
|
1902
|
+
`);
|
|
1945
1903
|
try {
|
|
1946
|
-
|
|
1947
|
-
writeWrapped(prompter, `Installed ${packageName(row.kind)} ${installed.version}.`);
|
|
1948
|
-
const names = Object.entries(registry.read().connections).filter(([, item]) => item.kind === row.kind && item.entry).map(([name]) => name);
|
|
1949
|
-
await registry.checkSaved(prompter, names);
|
|
1904
|
+
await registry.installRuntime();
|
|
1950
1905
|
} catch (error) {
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
if (/\b(?:EBUSY|EPERM|EACCES)\b/i.test(detail)) prompter.write("Close your AI clients, then retry the update. A running runtime may have locked its files.\n");
|
|
1906
|
+
prompter.write(`${paint.error("Failed")} ${plainText(error instanceof Error ? error.message : "Installation failed.")}
|
|
1907
|
+
`);
|
|
1908
|
+
await prompter.acknowledge();
|
|
1955
1909
|
}
|
|
1910
|
+
plan = registry.planMigration();
|
|
1911
|
+
writePlan(prompter, registry, plan);
|
|
1956
1912
|
}
|
|
1957
|
-
prompter.write("Reconnect or restart your AI clients to load updated runtimes. Fix any failed startup checks first.\n");
|
|
1958
|
-
await prompter.acknowledge();
|
|
1959
|
-
} catch (error) {
|
|
1960
|
-
if (error instanceof PromptClosedError) throw error;
|
|
1961
|
-
if (error instanceof PromptBackError) continue;
|
|
1962
|
-
throw error;
|
|
1963
1913
|
}
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
// src/modules/registration/migration-menu.ts
|
|
1968
|
-
async function migrationMenu(prompter, registry) {
|
|
1969
|
-
try {
|
|
1970
|
-
prompter.clearScreen();
|
|
1971
|
-
const plan = registry.planMigration();
|
|
1972
|
-
prompter.write("Migrate registry\n\n");
|
|
1973
|
-
if (!plan.steps.length) {
|
|
1974
|
-
prompter.write("The registry is already in the current format.\n");
|
|
1914
|
+
if (plan.kinds.some((item) => item.status === "blocked")) {
|
|
1915
|
+
prompter.write(paint.warning("! Resolve blocked kinds before migrating. No migration was applied.\n"));
|
|
1975
1916
|
await prompter.acknowledge();
|
|
1976
1917
|
return;
|
|
1977
1918
|
}
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
for (const item of plan.kinds) {
|
|
1982
|
-
prompter.write("\n");
|
|
1983
|
-
const target = sessionName(item.kind);
|
|
1984
|
-
const action = item.status === "catalog" ? `already ${target}; keep` : item.status === "individual" ? "keep explicit per-connection choice (rollback)" : item.status === "empty" ? `no connections; future connections use ${target}` : item.status === "blocked" ? "blocked" : `per-connection entries -> ${target}${item.status === "pending-runtime" ? " after runtime setup" : ""}`;
|
|
1985
|
-
writeWrapped(prompter, `${item.kind.toUpperCase()}: ${item.names.length} connection(s), ${action}`);
|
|
1986
|
-
if (item.detail) writeWrapped(prompter, plainText(item.detail), " ");
|
|
1987
|
-
for (const change of item.session?.changes ?? item.changes ?? []) {
|
|
1988
|
-
writeWrapped(prompter, `${CLIENT_LABELS2[change.client]}${item.status === "pending-runtime" ? " (after Runtime + Sync)" : ""}`, " ");
|
|
1989
|
-
writeWrapped(prompter, `Apply: ${change.add.join(", ") || "none"}`, " ");
|
|
1990
|
-
writeWrapped(prompter, `Remove after verification: ${change.remove.join(", ") || "none"}`, " ");
|
|
1991
|
-
if (change.review.length) writeWrapped(prompter, `Keep for Sync review: ${change.review.map(plainText).join(", ")}`, " ");
|
|
1992
|
-
}
|
|
1993
|
-
if (item.session?.custom.length) writeWrapped(prompter, `Custom commands retained for rollback: ${item.session.custom.map(plainText).join(", ")}`, " ");
|
|
1994
|
-
}
|
|
1995
|
-
prompter.write("\nThe previous catalog revision is kept in registry.previous.json.\nSession entries contain no connection credentials; those stay in the catalog and its previous revision.\n");
|
|
1996
|
-
if (plan.kinds.some((item) => item.status === "blocked")) {
|
|
1997
|
-
prompter.write("Resolve blocked kinds before migrating. No migration was applied.\n");
|
|
1919
|
+
if (plan.steps.every((step) => step.id === "single_package") && plan.launches.every((item) => !item.ready)) {
|
|
1920
|
+
prompter.write(`Nothing can be migrated until ${PACKAGE_NAME} is installed. Install it from Runtime, then reopen Migrate registry.
|
|
1921
|
+
`);
|
|
1998
1922
|
await prompter.acknowledge();
|
|
1999
1923
|
return;
|
|
2000
1924
|
}
|
|
2001
|
-
if (!await prompter.askYesNo("Migrate registry and apply these changes?", false)) return;
|
|
1925
|
+
if (!await prompter.askYesNo("Migrate the registry and apply these changes?", false)) return;
|
|
2002
1926
|
const results = registry.applyMigration(plan);
|
|
2003
1927
|
prompter.clearScreen();
|
|
2004
1928
|
prompter.write(`${registry.formatResults(results, "Registry migration saved.", prompter.paint)}
|
|
2005
1929
|
`);
|
|
2006
|
-
|
|
2007
|
-
|
|
1930
|
+
const state = registry.read();
|
|
1931
|
+
const launchKinds = plan.launches.filter((item) => item.ready).map((item) => item.kind);
|
|
1932
|
+
await registry.checkSaved(prompter, [.../* @__PURE__ */ new Set([
|
|
1933
|
+
...plan.kinds.flatMap((item) => item.names),
|
|
1934
|
+
...Object.entries(state.connections).filter(([, item]) => item.entry && launchKinds.includes(item.kind)).map(([name]) => name)
|
|
1935
|
+
])]);
|
|
1936
|
+
if (plan.launches.some((item) => !item.ready)) prompter.write(paint.warning(`! Entries waiting for ${PACKAGE_NAME} keep their old launch. Install it from Runtime, then reopen Migrate registry.
|
|
1937
|
+
`));
|
|
1938
|
+
prompter.write("Reconnect affected clients to load the new launch commands. Fix startup failures first.\n");
|
|
2008
1939
|
await prompter.acknowledge();
|
|
2009
1940
|
} catch (error) {
|
|
2010
1941
|
if (error instanceof PromptClosedError) throw error;
|
|
2011
1942
|
if (error instanceof PromptBackError) return;
|
|
2012
|
-
prompter.write(`${error instanceof Error ? error.message : "Could not migrate the registry."}
|
|
1943
|
+
prompter.write(`${error instanceof Error ? plainText(error.message) : "Could not migrate the registry."}
|
|
2013
1944
|
`);
|
|
2014
1945
|
await prompter.acknowledge();
|
|
2015
1946
|
}
|
|
2016
1947
|
}
|
|
2017
1948
|
|
|
1949
|
+
// src/modules/registration/runtime-menu.ts
|
|
1950
|
+
function updateTarget(installed, latest) {
|
|
1951
|
+
let target = null;
|
|
1952
|
+
for (const candidate of [PACKAGE_VERSION, latest]) {
|
|
1953
|
+
if (typeof candidate !== "string" || !runtimeIsOlder(installed, candidate)) continue;
|
|
1954
|
+
if (target == null || runtimeIsOlder(target, candidate)) target = candidate;
|
|
1955
|
+
}
|
|
1956
|
+
return target;
|
|
1957
|
+
}
|
|
1958
|
+
function runtimeNotice(registry) {
|
|
1959
|
+
const installed = registry.installedRuntime();
|
|
1960
|
+
if (!installed) return { hint: PACKAGE_NAME, notice: "Not installed" };
|
|
1961
|
+
const notices = [];
|
|
1962
|
+
const migration = migrationStatus(registry).status;
|
|
1963
|
+
if (migration === "ready") notices.push("Registry update available");
|
|
1964
|
+
else if (migration === "blocked") notices.push("Registry update blocked");
|
|
1965
|
+
const target = installed.linked ? null : updateTarget(installed.version, void 0);
|
|
1966
|
+
if (target) notices.push(`Update available ${target}`);
|
|
1967
|
+
const hint = `${PACKAGE_NAME} ${installed.version}${installed.linked ? " \xB7 development link" : ""}`;
|
|
1968
|
+
return notices.length ? { hint, notice: notices.join(" \xB7 ") } : { hint };
|
|
1969
|
+
}
|
|
1970
|
+
function connectionNames(registry, kind) {
|
|
1971
|
+
return Object.entries(registry.read().connections).filter(([, item]) => item.entry && (true)).map(([name]) => name);
|
|
1972
|
+
}
|
|
1973
|
+
function outdatedKinds(registry) {
|
|
1974
|
+
return KINDS.filter((kind) => {
|
|
1975
|
+
if (!registry.isSessionMode(kind) || !registry.globalRuntime(kind)) return false;
|
|
1976
|
+
const saved = registry.read().sessions?.[kind]?.entry;
|
|
1977
|
+
try {
|
|
1978
|
+
return saved != null && entryHash(saved) !== entryHash(registry.sessionEntry(kind));
|
|
1979
|
+
} catch {
|
|
1980
|
+
return false;
|
|
1981
|
+
}
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
async function installOrUpdate(prompter, registry) {
|
|
1985
|
+
const paint = prompter.paint;
|
|
1986
|
+
prompter.clearScreen();
|
|
1987
|
+
prompter.write(`Installing ${paint.accent(`${PACKAGE_NAME}@latest`)} into ${plainText(runtimePrefix())}...
|
|
1988
|
+
`);
|
|
1989
|
+
try {
|
|
1990
|
+
const runtime = await registry.installRuntime();
|
|
1991
|
+
prompter.write(`${paint.success("Installed")} ${PACKAGE_NAME} ${paint.accent(runtime.version)}.
|
|
1992
|
+
`);
|
|
1993
|
+
const names = connectionNames(registry);
|
|
1994
|
+
if (names.length) await registry.checkSaved(prompter, names);
|
|
1995
|
+
prompter.write("Reconnect or restart your AI clients to load it.\n");
|
|
1996
|
+
if (migrationStatus(registry).status === "ready") prompter.write(paint.warning("! Registry update available. Choose Migrate registry below.\n"));
|
|
1997
|
+
} catch (error) {
|
|
1998
|
+
let detail = error instanceof Error ? error.message : "Installation failed.";
|
|
1999
|
+
for (const item of Object.values(registry.read().connections)) if (item.entry) detail = redactStartupText(detail, item.entry);
|
|
2000
|
+
prompter.write(`${paint.error("Failed")} ${plainText(detail)}
|
|
2001
|
+
`);
|
|
2002
|
+
if (/\b(?:EBUSY|EPERM|EACCES)\b/i.test(detail)) prompter.write("Close your AI clients, then retry. A running server may have locked its files.\n");
|
|
2003
|
+
}
|
|
2004
|
+
await prompter.acknowledge();
|
|
2005
|
+
}
|
|
2006
|
+
async function runtimeMenu(prompter, registry) {
|
|
2007
|
+
let latest;
|
|
2008
|
+
for (; ; ) {
|
|
2009
|
+
try {
|
|
2010
|
+
prompter.clearScreen();
|
|
2011
|
+
const paint = prompter.paint;
|
|
2012
|
+
const installed = registry.installedRuntime();
|
|
2013
|
+
const target = installed && !installed.linked ? updateTarget(installed.version, latest) : null;
|
|
2014
|
+
const legacy = registry.legacyRuntimes();
|
|
2015
|
+
const removable = legacy.filter((item) => !item.usedBy.length);
|
|
2016
|
+
const inUse = legacy.filter((item) => item.usedBy.length);
|
|
2017
|
+
const migration = migrationStatus(registry);
|
|
2018
|
+
const packages = (list) => [...new Set(list.map((item) => item.runtime.version))].map((version) => `${list.filter((item) => item.runtime.version === version).map((item) => item.runtime.packageName).join(", ")} ${version}`).join("; ");
|
|
2019
|
+
const row = (label, value) => prompter.write(` ${paint.dim(label.padEnd(10))}${value}
|
|
2020
|
+
`);
|
|
2021
|
+
prompter.write(`${paint.bold("Runtime")} ${PACKAGE_NAME} ${installed ? paint.accent(installed.version) : paint.warning("not installed")}${installed?.linked ? paint.dim(" development link") : ""}
|
|
2022
|
+
`);
|
|
2023
|
+
row("Location", plainText(runtimePrefix()));
|
|
2024
|
+
if (inUse.length) row("In use", `${paint.warning(packages(inUse))} ${paint.dim("\xB7 old packages")}`);
|
|
2025
|
+
if (removable.length) row("Unused", `${packages(removable)} ${paint.dim("\xB7 old packages")}`);
|
|
2026
|
+
if (latest === null) prompter.write(paint.warning("! Could not reach npm to check for updates.\n"));
|
|
2027
|
+
else if (latest !== void 0 && installed && !target) prompter.write(`${paint.success("Up to date")} ${paint.dim(`\xB7 latest ${latest}`)}
|
|
2028
|
+
`);
|
|
2029
|
+
const upgrading = legacy.length > 0 || migration.status !== "none";
|
|
2030
|
+
let next = null;
|
|
2031
|
+
if (upgrading) {
|
|
2032
|
+
const plan = migration.status === "none" ? null : registry.planMigration();
|
|
2033
|
+
const sessions = (plan?.launches ?? []).flatMap((item) => item.names.filter((name) => /^akms_/.test(name)));
|
|
2034
|
+
const pairs = migration.status === "ready" ? migrationVersions(registry, plan ?? void 0) : [];
|
|
2035
|
+
const moves = pairs.map((pair) => pair.label === "Registry" ? `registry ${pair.from} \u2192 ${pair.to}` : `${pair.from} \u2192 ${pair.to}`).join(" \xB7 ");
|
|
2036
|
+
const steps = [
|
|
2037
|
+
{
|
|
2038
|
+
id: "install",
|
|
2039
|
+
label: `Install ${PACKAGE_NAME}`,
|
|
2040
|
+
done: installed != null,
|
|
2041
|
+
blocked: false,
|
|
2042
|
+
detail: installed ? installed.version : "one package for every kind"
|
|
2043
|
+
},
|
|
2044
|
+
{
|
|
2045
|
+
id: "migrate",
|
|
2046
|
+
label: "Migrate registry",
|
|
2047
|
+
done: migration.status === "none",
|
|
2048
|
+
blocked: migration.status === "blocked",
|
|
2049
|
+
detail: migration.status === "none" ? "" : migration.status === "blocked" ? plainText(migration.detail ?? "") : moves || (sessions.length ? `moves ${sessions.join(", ")} to ${PACKAGE_NAME}` : "one akms_<kind> entry per kind")
|
|
2050
|
+
},
|
|
2051
|
+
{
|
|
2052
|
+
id: "remove",
|
|
2053
|
+
label: "Remove old packages",
|
|
2054
|
+
done: legacy.length === 0,
|
|
2055
|
+
blocked: false,
|
|
2056
|
+
detail: inUse.length && migration.status === "none" ? "run Sync first: an AI service still starts them" : "then restart your AI clients"
|
|
2057
|
+
}
|
|
2058
|
+
];
|
|
2059
|
+
prompter.write(`
|
|
2060
|
+
${paint.bold("Upgrade steps")}
|
|
2061
|
+
`);
|
|
2062
|
+
for (const [index, step] of steps.entries()) {
|
|
2063
|
+
let state = paint.dim("later ");
|
|
2064
|
+
if (step.done) state = paint.success("done ");
|
|
2065
|
+
else if (step.blocked) state = paint.error("blocked");
|
|
2066
|
+
else if (next == null) {
|
|
2067
|
+
state = paint.warning("next ");
|
|
2068
|
+
next = step.id;
|
|
2069
|
+
}
|
|
2070
|
+
const label = step.done ? paint.dim(step.label.padEnd(22)) : step.id === next ? paint.bold(step.label.padEnd(22)) : step.label.padEnd(22);
|
|
2071
|
+
prompter.write(` ${paint.dim(`${index + 1}`)} ${label}${state} ${step.id === next ? step.detail : paint.dim(step.detail)}
|
|
2072
|
+
`);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
prompter.write("\n");
|
|
2076
|
+
const items = [];
|
|
2077
|
+
if (!installed) items.push({ value: "install", key: "install", label: `Install ${PACKAGE_NAME}`, hint: upgrading ? "step 1" : "one package for every kind" });
|
|
2078
|
+
else if (target) items.push({ value: "update", key: "update", label: `Update to ${target}`, hint: `installed ${installed.version}` });
|
|
2079
|
+
else if (!installed.linked) items.push({ value: "check", key: "check", label: "Check for updates", hint: "asks npm for the latest release" });
|
|
2080
|
+
if (migration.status === "ready") {
|
|
2081
|
+
const pairs = migrationVersions(registry).map((pair) => pair.label === "Registry" ? { from: `registry ${pair.from}`, to: pair.to } : pair);
|
|
2082
|
+
const last = pairs.at(-1);
|
|
2083
|
+
items.push({
|
|
2084
|
+
value: "migrate",
|
|
2085
|
+
key: "migrate",
|
|
2086
|
+
label: "Migrate registry",
|
|
2087
|
+
hint: last ? ["step 2", ...pairs.slice(0, -1).map((pair) => `${pair.from} \u2192 ${pair.to}`), `${last.from} \u2192`].join(" \xB7 ") : "step 2 \xB7 one akms_<kind> entry per kind",
|
|
2088
|
+
...last ? { notice: last.to, noticeTone: "success" } : {}
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
2091
|
+
const outdated = migration.status === "none" ? outdatedKinds(registry) : [];
|
|
2092
|
+
if (outdated.length) items.push({ value: "relaunch", key: "relaunch", label: "Update launch commands", hint: outdated.map(sessionName).join(", "), notice: "out of date" });
|
|
2093
|
+
if (removable.length) {
|
|
2094
|
+
items.push({
|
|
2095
|
+
value: "remove",
|
|
2096
|
+
key: "remove",
|
|
2097
|
+
label: inUse.length ? "Remove unused packages" : "Remove old packages",
|
|
2098
|
+
hint: `${inUse.length ? "" : "step 3 \xB7 "}${removable.map((item) => item.runtime.packageName).join(", ")}`
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
items.push({ value: "back", key: "b", label: "Back" });
|
|
2102
|
+
const defaultValue = next === "migrate" && migration.status === "ready" ? "migrate" : next === "remove" && removable.length && !inUse.length ? "remove" : void 0;
|
|
2103
|
+
const picked = await prompter.select("Runtime", items, { cancelValue: "back", ...defaultValue ? { defaultValue } : {} });
|
|
2104
|
+
if (picked === "back") return;
|
|
2105
|
+
if (picked === "migrate") {
|
|
2106
|
+
await migrationMenu(prompter, registry);
|
|
2107
|
+
continue;
|
|
2108
|
+
}
|
|
2109
|
+
if (picked === "install" || picked === "update") {
|
|
2110
|
+
await installOrUpdate(prompter, registry);
|
|
2111
|
+
latest = void 0;
|
|
2112
|
+
continue;
|
|
2113
|
+
}
|
|
2114
|
+
if (picked === "check") {
|
|
2115
|
+
prompter.write(`Checking the latest ${PACKAGE_NAME} release...
|
|
2116
|
+
`);
|
|
2117
|
+
latest = await registry.latestRuntime();
|
|
2118
|
+
continue;
|
|
2119
|
+
}
|
|
2120
|
+
if (picked === "relaunch") {
|
|
2121
|
+
await updateLaunchCommands(prompter, registry, outdated);
|
|
2122
|
+
continue;
|
|
2123
|
+
}
|
|
2124
|
+
if (picked === "remove") {
|
|
2125
|
+
prompter.clearScreen();
|
|
2126
|
+
prompter.write(`${paint.bold("Remove old packages")}
|
|
2127
|
+
`);
|
|
2128
|
+
for (const item of removable) prompter.write(` ${item.runtime.packageName} ${paint.dim(`${item.runtime.version}${item.runtime.linked ? " \xB7 development link (the checkout stays)" : ""}`)}
|
|
2129
|
+
`);
|
|
2130
|
+
prompter.write(paint.dim("No AI service starts them any more; every kind now runs from @akms/mcp.\n"));
|
|
2131
|
+
if (!await prompter.askYesNo("Remove these packages?", false)) continue;
|
|
2132
|
+
await registry.removeLegacyRuntimes(removable.map((item) => item.runtime.kind));
|
|
2133
|
+
prompter.write(`${paint.success("Removed")} ${removable.map((item) => item.runtime.packageName).join(", ")}.
|
|
2134
|
+
`);
|
|
2135
|
+
await prompter.acknowledge();
|
|
2136
|
+
}
|
|
2137
|
+
} catch (error) {
|
|
2138
|
+
if (error instanceof PromptClosedError) throw error;
|
|
2139
|
+
if (error instanceof PromptBackError) continue;
|
|
2140
|
+
prompter.write(`${error instanceof Error ? plainText(error.message) : "Could not configure the AKMS runtime."}
|
|
2141
|
+
`);
|
|
2142
|
+
await prompter.acknowledge();
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
async function updateLaunchCommands(prompter, registry, kinds) {
|
|
2147
|
+
const paint = prompter.paint;
|
|
2148
|
+
prompter.clearScreen();
|
|
2149
|
+
prompter.write(`${paint.bold("Update launch commands")}
|
|
2150
|
+
`);
|
|
2151
|
+
for (const kind of kinds) prompter.write(` ${paint.dim(sessionName(kind).padEnd(10))}${paint.accent(plainText(registry.sessionEntry(kind).args[0]))} --catalog
|
|
2152
|
+
`);
|
|
2153
|
+
prompter.write(paint.dim("Only the command that starts each entry changes; connection settings stay in the catalog.\n"));
|
|
2154
|
+
if (!await prompter.askYesNo("Update these entries?", true)) return;
|
|
2155
|
+
const results = [];
|
|
2156
|
+
for (const kind of kinds) results.push(...registry.applySession(registry.planSession(kind, "catalog")));
|
|
2157
|
+
prompter.clearScreen();
|
|
2158
|
+
prompter.write(`${registry.formatResults(results, "Launch commands saved.", prompter.paint)}
|
|
2159
|
+
`);
|
|
2160
|
+
const connections = registry.read().connections;
|
|
2161
|
+
await registry.checkSaved(prompter, connectionNames(registry).filter((name) => kinds.includes(connections[name].kind)));
|
|
2162
|
+
prompter.write("Restart or reconnect your AI clients to use them.\n");
|
|
2163
|
+
await prompter.acknowledge();
|
|
2164
|
+
}
|
|
2165
|
+
var screenText = (value) => stripVTControlCharacters(value).replace(/[\x00-\x1f\x7f]/g, " ");
|
|
2166
|
+
var countLabel = (count, noun, plural = `${noun}s`) => `${count} ${count === 1 ? noun : plural}`;
|
|
2167
|
+
function writeHeading(screen, title) {
|
|
2168
|
+
screen.write(`${screen.paint.bold(screenText(title))}
|
|
2169
|
+
`);
|
|
2170
|
+
}
|
|
2171
|
+
function writeField(screen, label, value, note = "", tone = "accent") {
|
|
2172
|
+
const paint = screen.paint;
|
|
2173
|
+
const key = screenText(label);
|
|
2174
|
+
const padded = key.padEnd(Math.max(13, key.length + 2));
|
|
2175
|
+
screen.write(` ${paint.dim(padded)}${paint[tone](screenText(value))}${note ? ` ${paint.dim(screenText(note))}` : ""}
|
|
2176
|
+
`);
|
|
2177
|
+
}
|
|
2178
|
+
function writeHint(screen, text) {
|
|
2179
|
+
screen.write(`${screen.paint.dim(screenText(text))}
|
|
2180
|
+
`);
|
|
2181
|
+
}
|
|
2182
|
+
function writeWarning(screen, text) {
|
|
2183
|
+
screen.write(`${screen.paint.warning(`! ${screenText(text)}`)}
|
|
2184
|
+
`);
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2018
2187
|
// src/modules/registration/menu.ts
|
|
2019
2188
|
function pendingImports(registry, kind) {
|
|
2020
2189
|
const state = registry.read();
|
|
@@ -2024,11 +2193,10 @@ function pendingImports(registry, kind) {
|
|
|
2024
2193
|
}
|
|
2025
2194
|
function preview(prompter, entry) {
|
|
2026
2195
|
if (!entry) {
|
|
2027
|
-
prompter
|
|
2196
|
+
writeField(prompter, "Action", "Remove from service", "", "warning");
|
|
2028
2197
|
return;
|
|
2029
2198
|
}
|
|
2030
|
-
for (const [key, value] of Object.entries(entry.env)) prompter.
|
|
2031
|
-
`);
|
|
2199
|
+
for (const [key, value] of Object.entries(entry.env)) writeField(prompter, key, visibleField.test(key) ? value : "(hidden)", "", visibleField.test(key) ? "accent" : "dim");
|
|
2032
2200
|
}
|
|
2033
2201
|
async function registrationMenu(prompter, registry, initialClient) {
|
|
2034
2202
|
const initial = registry.read();
|
|
@@ -2040,9 +2208,9 @@ async function registrationMenu(prompter, registry, initialClient) {
|
|
|
2040
2208
|
for (; ; ) {
|
|
2041
2209
|
try {
|
|
2042
2210
|
prompter.clearScreen();
|
|
2043
|
-
prompter
|
|
2044
|
-
|
|
2045
|
-
|
|
2211
|
+
writeHeading(prompter, "Register \xB7 AI services");
|
|
2212
|
+
writeHint(prompter, "Toggle services, then Apply. Matching connections are imported; differences stay for review.");
|
|
2213
|
+
if (discovered.errors.length) writeWarning(prompter, discovered.errors.join(" "));
|
|
2046
2214
|
const picked = await prompter.select("Register", [
|
|
2047
2215
|
...registry.targets.filter((target) => target.id !== "registered").map((target) => ({
|
|
2048
2216
|
value: target.id,
|
|
@@ -2062,64 +2230,59 @@ async function registrationMenu(prompter, registry, initialClient) {
|
|
|
2062
2230
|
continue;
|
|
2063
2231
|
}
|
|
2064
2232
|
const removed = initial.clients.filter((id) => !selected.has(id));
|
|
2065
|
-
const
|
|
2233
|
+
const current = registry.read();
|
|
2066
2234
|
const plan = registry.planRegistration([...selected]);
|
|
2067
2235
|
const importNames = [...new Set(plan.imports.map((entry) => entry.name))];
|
|
2236
|
+
const connections = new Map(Object.entries(current.connections).filter(([, item]) => item.entry).map(([name, item]) => [name, item.kind]));
|
|
2237
|
+
for (const item of plan.imports) connections.set(item.name, item.kind);
|
|
2238
|
+
const kinds = KINDS.filter((kind) => [...connections.values()].includes(kind));
|
|
2239
|
+
const sessions = kinds.filter((kind) => registry.isSessionMode(kind, current)).map(sessionName);
|
|
2240
|
+
const individuals = [...connections].filter(([, kind]) => !registry.isSessionMode(kind, current)).map(([name]) => name);
|
|
2241
|
+
const imported = new Set(plan.imports.map((item) => `${item.client}:${item.name}:${entryHash(item.entry)}`));
|
|
2242
|
+
const retiring = discovered.entries.filter((item) => selected.has(item.client) && connections.has(item.name) && registry.isSessionMode(item.kind, current));
|
|
2243
|
+
const owned = (item) => imported.has(`${item.client}:${item.name}:${entryHash(item.entry)}`) || Object.hasOwn(current.connections, item.name) && current.connections[item.name].applied[item.client] === entryHash(item.entry);
|
|
2244
|
+
const replaces = [...new Set(retiring.filter(owned).map((item) => item.name))];
|
|
2245
|
+
const kept = [.../* @__PURE__ */ new Set([...plan.conflicts, ...retiring.filter((item) => !owned(item)).map((item) => item.name)])];
|
|
2068
2246
|
prompter.clearScreen();
|
|
2069
|
-
prompter
|
|
2070
|
-
prompter.
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
`);
|
|
2074
|
-
if (importNames.length) prompter
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
if (plan.conflicts.length) prompter.write(`Keep ${plan.conflicts.length} differing connection(s) unchanged until you choose a version:
|
|
2078
|
-
${plan.conflicts.join("\n ")}
|
|
2079
|
-
`);
|
|
2080
|
-
for (const error of plan.errors) prompter.write(` ${error}
|
|
2081
|
-
`);
|
|
2247
|
+
writeHeading(prompter, "Review registration");
|
|
2248
|
+
writeField(prompter, "Services", [...selected].map((id) => CLIENT_LABELS2[id]).join(" \xB7 ") || "none");
|
|
2249
|
+
writeField(prompter, "Connections", String(connections.size), kinds.length ? `(${kinds.map((kind) => `${KIND_LABELS[kind]} ${[...connections.values()].filter((value) => value === kind).length}`).join(" \xB7 ")})` : "");
|
|
2250
|
+
writeField(prompter, "Entries", [...sessions, ...individuals.length ? [`${individuals.length} individual entries`] : []].join(" \xB7 ") || "none", "in each service");
|
|
2251
|
+
if (replaces.length) writeField(prompter, "Replaces", `${replaces.length} individual entries`, `with ${sessions.join(" \xB7 ")}`);
|
|
2252
|
+
if (importNames.length) writeField(prompter, "Import", countLabel(importNames.length, "matching connection"));
|
|
2253
|
+
if (kept.length) writeField(prompter, "Kept", `${kept.length} need a version choice`, kept.join(", "), "warning");
|
|
2254
|
+
if (plan.errors.length) writeField(prompter, "Unavailable", countLabel(plan.errors.length, "service"), plan.errors.join(" "), "error");
|
|
2082
2255
|
if (initial.defaultMode === "catalog") {
|
|
2083
|
-
|
|
2084
|
-
if (plan.imports.some((item) => !
|
|
2085
|
-
if (plan.imports.some((item) => !standardLaunch(item.entry, item.kind))) prompter.write("Custom launch commands are retained for rollback; session mode uses each connection's saved environment.\n");
|
|
2256
|
+
if (plan.imports.some((item) => !registry.globalRuntime(item.kind))) writeField(prompter, "Runtime", "Waiting", "install from Runtime, then Sync; existing entries stay", "warning");
|
|
2257
|
+
if (plan.imports.some((item) => !standardLaunch(item.entry, item.kind))) writeHint(prompter, "Custom commands are not started; the grouped entry loads their saved settings.");
|
|
2086
2258
|
}
|
|
2087
|
-
if (removed.length) prompter
|
|
2088
|
-
Remove only AKMS-managed entries from these services; keep the shared catalog.
|
|
2089
|
-
`);
|
|
2259
|
+
if (removed.length) writeField(prompter, "Unregister", removed.map((id) => CLIENT_LABELS2[id]).join(" \xB7 "), "remove managed entries; keep the catalog");
|
|
2090
2260
|
if (!privacyNoticeShown) {
|
|
2091
|
-
|
|
2092
|
-
prompter.write("Keep this file private: do not share, sync or commit it.\n");
|
|
2093
|
-
prompter.write("Its previous revision, registry.previous.json, also contains saved credentials.\n");
|
|
2261
|
+
writeWarning(prompter, `Plaintext passwords: ${registry.file} + registry.previous.json. Do not share, sync or commit.`);
|
|
2094
2262
|
privacyNoticeShown = true;
|
|
2095
2263
|
}
|
|
2096
2264
|
if (!await prompter.askYesNo("Apply this registration?", removed.length === 0)) continue;
|
|
2097
2265
|
const results = registry.register([...selected], initial.clients, plan.imports);
|
|
2098
2266
|
prompter.clearScreen();
|
|
2099
|
-
prompter.
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
`);
|
|
2103
|
-
if (importNames.length) prompter.write(`Imported ${importNames.length} existing connection(s) into the shared list.
|
|
2267
|
+
writeHeading(prompter, "Registration saved.");
|
|
2268
|
+
writeField(prompter, "Services", [...selected].map((id) => CLIENT_LABELS2[id]).join(" \xB7 ") || "none");
|
|
2269
|
+
prompter.write(`${registry.formatResults(results, "", prompter.paint)}
|
|
2104
2270
|
`);
|
|
2271
|
+
if (importNames.length) writeField(prompter, "Imported", countLabel(importNames.length, "connection"), "saved in the shared catalog", "success");
|
|
2105
2272
|
try {
|
|
2106
2273
|
const conflicts = new Set(results.filter((result) => result.status === "conflict").map((result) => result.name));
|
|
2107
2274
|
if (conflicts.size && await prompter.askYesNo(`Compare settings for ${conflicts.size} connection(s) now?`, true)) await syncMenu(prompter, registry);
|
|
2108
|
-
const
|
|
2109
|
-
const remaining = registry.candidates().entries.filter((candidate) => !Object.hasOwn(
|
|
2275
|
+
const current2 = registry.read();
|
|
2276
|
+
const remaining = registry.candidates().entries.filter((candidate) => !Object.hasOwn(current2.connections, candidate.name));
|
|
2110
2277
|
const names = [...new Set(remaining.map((candidate) => candidate.name))];
|
|
2111
2278
|
if (names.length) {
|
|
2112
|
-
prompter.
|
|
2113
|
-
${names.length} existing connection(s) still need a choice before joining the shared list:
|
|
2114
|
-
`);
|
|
2279
|
+
writeField(prompter, "Not imported", countLabel(names.length, "connection"), "choose a version", "warning");
|
|
2115
2280
|
for (const name of names) {
|
|
2116
2281
|
const copies = remaining.filter((candidate) => candidate.name === name);
|
|
2117
|
-
prompter
|
|
2118
|
-
`);
|
|
2119
|
-
if (new Set(copies.map((copy) => entryHash(copy.entry))).size > 1) prompter.write(` ${describeDifferences(copies.map((copy) => copy.entry))}
|
|
2120
|
-
`);
|
|
2282
|
+
writeField(prompter, name, copies.map((copy) => CLIENT_LABELS2[copy.client]).join(" \xB7 "));
|
|
2283
|
+
if (new Set(copies.map((copy) => entryHash(copy.entry))).size > 1) writeHint(prompter, ` ${describeDifferences(copies.map((copy) => copy.entry))}`);
|
|
2121
2284
|
}
|
|
2122
|
-
prompter
|
|
2285
|
+
writeHint(prompter, "Existing settings stay. Compare now or open Import existing connections later.");
|
|
2123
2286
|
if (await prompter.askYesNo("Compare existing settings and choose which to share?", true)) await importMenu(prompter, registry);
|
|
2124
2287
|
}
|
|
2125
2288
|
if (!conflicts.size && !names.length) await prompter.acknowledge();
|
|
@@ -2130,8 +2293,7 @@ ${names.length} existing connection(s) still need a choice before joining the sh
|
|
|
2130
2293
|
} catch (error) {
|
|
2131
2294
|
if (error instanceof PromptClosedError) throw error;
|
|
2132
2295
|
if (error instanceof PromptBackError) continue;
|
|
2133
|
-
prompter
|
|
2134
|
-
`);
|
|
2296
|
+
writeWarning(prompter, error instanceof Error ? error.message : "Registration could not be saved.");
|
|
2135
2297
|
await prompter.acknowledge();
|
|
2136
2298
|
}
|
|
2137
2299
|
}
|
|
@@ -2139,10 +2301,9 @@ ${names.length} existing connection(s) still need a choice before joining the sh
|
|
|
2139
2301
|
async function importMenu(prompter, registry, kind) {
|
|
2140
2302
|
for (; ; ) {
|
|
2141
2303
|
prompter.clearScreen();
|
|
2142
|
-
prompter
|
|
2304
|
+
writeHeading(prompter, "Import existing connections");
|
|
2143
2305
|
const { entries, errors } = registry.candidates(kind);
|
|
2144
|
-
for (const error of errors) prompter
|
|
2145
|
-
`);
|
|
2306
|
+
for (const error of errors) writeWarning(prompter, error);
|
|
2146
2307
|
const state = registry.read();
|
|
2147
2308
|
const matching = registry.planRegistration(state.clients, kind);
|
|
2148
2309
|
const matchingNames = [...new Set(matching.imports.map((entry) => entry.name))];
|
|
@@ -2151,10 +2312,11 @@ async function importMenu(prompter, registry, kind) {
|
|
|
2151
2312
|
return !current?.entry || current.applied[candidate.client] !== entryHash(candidate.entry) || entryHash(current.entry) !== entryHash(candidate.entry);
|
|
2152
2313
|
}).map((entry) => entry.name))];
|
|
2153
2314
|
if (!names.length) {
|
|
2154
|
-
prompter
|
|
2315
|
+
writeHint(prompter, "No existing connections need importing.");
|
|
2155
2316
|
await prompter.acknowledge();
|
|
2156
2317
|
return;
|
|
2157
2318
|
}
|
|
2319
|
+
writeField(prompter, "Available", countLabel(names.length, "connection"), `${matchingNames.length} matching`);
|
|
2158
2320
|
const picked = await prompter.select("Import existing connections", [
|
|
2159
2321
|
...names.map((name) => ({ value: `entry:${name}`, key: name, label: name, hint: [...new Set(entries.filter((entry) => entry.name === name).map((entry) => CLIENT_LABELS2[entry.client]))].join(" \xB7 ") })),
|
|
2160
2322
|
...matchingNames.length ? [{ value: "matching", key: "all", label: "Import all matching connections", hint: `${matchingNames.length} connections; differing versions stay unchanged` }] : [],
|
|
@@ -2164,10 +2326,9 @@ async function importMenu(prompter, registry, kind) {
|
|
|
2164
2326
|
try {
|
|
2165
2327
|
prompter.clearScreen();
|
|
2166
2328
|
if (picked === "matching") {
|
|
2167
|
-
prompter
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
`);
|
|
2329
|
+
writeHeading(prompter, "Review import");
|
|
2330
|
+
writeField(prompter, "Connections", String(matchingNames.length), matchingNames.join(", "));
|
|
2331
|
+
writeField(prompter, "Services", state.clients.map((client) => CLIENT_LABELS2[client]).join(" \xB7 ") || "none");
|
|
2171
2332
|
if (await prompter.askYesNo("Import matching connections and apply them?", true)) {
|
|
2172
2333
|
const results2 = registry.register(state.clients, state.clients, matching.imports);
|
|
2173
2334
|
prompter.clearScreen();
|
|
@@ -2181,9 +2342,8 @@ Import existing connections:
|
|
|
2181
2342
|
const candidates = entries.filter((entry) => entry.name === name);
|
|
2182
2343
|
let chosen = candidates[0];
|
|
2183
2344
|
if (new Set(candidates.map((candidate) => entryHash(candidate.entry))).size > 1) {
|
|
2184
|
-
prompter
|
|
2185
|
-
|
|
2186
|
-
`);
|
|
2345
|
+
writeHeading(prompter, `Compare ${name}`);
|
|
2346
|
+
writeField(prompter, "Services", state.clients.map((client) => CLIENT_LABELS2[client]).join(" \xB7 ") || "none");
|
|
2187
2347
|
compareEntries(prompter, candidates.map((candidate) => ({ label: CLIENT_LABELS2[candidate.client], entry: candidate.entry })));
|
|
2188
2348
|
const choice = await prompter.select("Use settings from", [
|
|
2189
2349
|
...candidates.map((candidate, index) => ({ value: String(index), label: `Use ${index + 1}: ${CLIENT_LABELS2[candidate.client]}` })),
|
|
@@ -2192,22 +2352,23 @@ ${plainText(name)} \xB7 Choose the settings to use on all registered services.
|
|
|
2192
2352
|
if (choice === "back") continue;
|
|
2193
2353
|
chosen = candidates[Number(choice)];
|
|
2194
2354
|
}
|
|
2355
|
+
writeField(prompter, "Connection", name);
|
|
2356
|
+
writeField(prompter, "Version", CLIENT_LABELS2[chosen.client]);
|
|
2195
2357
|
preview(prompter, chosen.entry);
|
|
2196
2358
|
const destination = await prompter.askRequired("Shared connection name (change it to keep both)", name);
|
|
2197
|
-
prompter
|
|
2359
|
+
writeField(prompter, "Apply to", state.clients.map((client) => CLIENT_LABELS2[client]).join(" \xB7 ") || "none");
|
|
2198
2360
|
if (!await prompter.askYesNo(`Import ${destination} and apply it?`, true)) continue;
|
|
2199
2361
|
const results = registry.importEntry(destination, chosen, candidates);
|
|
2200
2362
|
prompter.clearScreen();
|
|
2201
2363
|
prompter.write(`
|
|
2202
2364
|
${registry.formatResults(results, "Import complete", prompter.paint)}
|
|
2203
2365
|
`);
|
|
2204
|
-
if (destination !== name) prompter
|
|
2366
|
+
if (destination !== name) writeField(prompter, "Kept", name, "original entries retain this name", "warning");
|
|
2205
2367
|
await prompter.acknowledge();
|
|
2206
2368
|
} catch (error) {
|
|
2207
2369
|
if (error instanceof PromptClosedError) throw error;
|
|
2208
2370
|
if (error instanceof PromptBackError) continue;
|
|
2209
|
-
prompter
|
|
2210
|
-
`);
|
|
2371
|
+
writeWarning(prompter, error instanceof Error ? error.message : "Import failed.");
|
|
2211
2372
|
await prompter.acknowledge();
|
|
2212
2373
|
}
|
|
2213
2374
|
}
|
|
@@ -2222,9 +2383,7 @@ async function syncMenu(prompter, registry) {
|
|
|
2222
2383
|
${registry.formatResults(results, "Sync results", prompter.paint, "Next: choose an item below to compare settings and pick a version.")}
|
|
2223
2384
|
`);
|
|
2224
2385
|
const imports = pendingImports(registry);
|
|
2225
|
-
if (imports.names.length) prompter
|
|
2226
|
-
Not yet imported: ${imports.names.length} connection(s)${imports.differing === imports.names.length ? " with differing settings" : ""} - open Import existing connections.
|
|
2227
|
-
`));
|
|
2386
|
+
if (imports.names.length) writeField(prompter, "Not imported", countLabel(imports.names.length, "connection"), `${imports.differing === imports.names.length ? "different settings \xB7 " : ""}choose Import existing connections`, "warning");
|
|
2228
2387
|
const conflicts = results.filter((result) => result.status === "conflict");
|
|
2229
2388
|
if (!conflicts.length && !imports.names.length) {
|
|
2230
2389
|
await prompter.acknowledge();
|
|
@@ -2249,12 +2408,14 @@ Not yet imported: ${imports.names.length} connection(s)${imports.differing === i
|
|
|
2249
2408
|
const session = state.sessions[sessionKind];
|
|
2250
2409
|
const actual2 = registry.targets.find((target) => target.id === conflict.client)?.readEntries()[conflict.name];
|
|
2251
2410
|
if (!actual2 || entryKind(actual2) !== sessionKind) {
|
|
2252
|
-
prompter
|
|
2411
|
+
writeWarning(prompter, "This name belongs to another server. Rename its external entry; existing settings stay.");
|
|
2253
2412
|
await prompter.acknowledge();
|
|
2254
2413
|
continue;
|
|
2255
2414
|
}
|
|
2256
2415
|
const removing2 = !registry.isSessionMode(sessionKind, state) || !state.clients.includes(conflict.client) || !Object.values(state.connections).some((item) => item.kind === sessionKind && item.entry);
|
|
2257
|
-
|
|
2416
|
+
writeHeading(prompter, "Review session launch");
|
|
2417
|
+
writeField(prompter, "Server", conflict.name);
|
|
2418
|
+
writeField(prompter, "Service", CLIENT_LABELS2[conflict.client]);
|
|
2258
2419
|
compareEntries(prompter, [{ label: "AKMS session launch", entry: session.entry }, { label: "External settings", entry: actual2 }]);
|
|
2259
2420
|
const choice2 = await prompter.select("Resolve session launch", [
|
|
2260
2421
|
{ value: "apply", label: removing2 ? "Remove the old session entry" : "Restore the AKMS session launch" },
|
|
@@ -2267,7 +2428,7 @@ Not yet imported: ${imports.names.length} connection(s)${imports.differing === i
|
|
|
2267
2428
|
if (!connection) throw new Error("This connection changed. Reopen Sync to review its current settings.");
|
|
2268
2429
|
const actual = registry.targets.find((target) => target.id === conflict.client)?.readEntries()[conflict.name];
|
|
2269
2430
|
if (!actual || entryKind(actual) !== connection.kind) {
|
|
2270
|
-
prompter
|
|
2431
|
+
writeWarning(prompter, "This name belongs to another server. Rename that entry or the shared connection.");
|
|
2271
2432
|
await prompter.acknowledge();
|
|
2272
2433
|
continue;
|
|
2273
2434
|
}
|
|
@@ -2277,21 +2438,19 @@ Not yet imported: ${imports.names.length} connection(s)${imports.differing === i
|
|
|
2277
2438
|
const removing = !connection.entry || !registered;
|
|
2278
2439
|
const retiring = sessionMode && !removing;
|
|
2279
2440
|
const subject = connection.entry && descriptionOnly([connection.entry, actual]) ? "description" : "settings";
|
|
2280
|
-
|
|
2281
|
-
|
|
2441
|
+
writeHeading(prompter, `Sync / ${conflict.name}`);
|
|
2442
|
+
writeField(prompter, "Services", state.clients.map((id) => CLIENT_LABELS2[id]).join(" \xB7 ") || "none");
|
|
2282
2443
|
prompter.write("\n");
|
|
2283
2444
|
if (!removing && connection.entry) {
|
|
2284
2445
|
if (retiring) {
|
|
2285
|
-
|
|
2286
|
-
|
|
2446
|
+
writeField(prompter, "Server", sessionName(connection.kind));
|
|
2447
|
+
writeField(prompter, "Kept in", client, "old entry stays until this server is ready", "warning");
|
|
2287
2448
|
}
|
|
2288
2449
|
compareEntries(prompter, [{ label: "Saved in AKMS", entry: connection.entry }, { label: `Currently in ${client}`, entry: actual }]);
|
|
2289
2450
|
} else {
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
prompter
|
|
2293
|
-
Currently in ${client}
|
|
2294
|
-
`);
|
|
2451
|
+
writeField(prompter, "Action", !registered ? `${client} unregistered` : "Remove connection", "", "warning");
|
|
2452
|
+
writeHint(prompter, `Settings changed in ${client}; choose what to keep.`);
|
|
2453
|
+
writeHeading(prompter, `Currently in ${client}`);
|
|
2295
2454
|
preview(prompter, actual);
|
|
2296
2455
|
prompter.write("\n");
|
|
2297
2456
|
}
|
|
@@ -2304,10 +2463,10 @@ Currently in ${client}
|
|
|
2304
2463
|
if (choice === "back") continue;
|
|
2305
2464
|
prompter.write("\n");
|
|
2306
2465
|
if (choice === "client") {
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
} else
|
|
2310
|
-
if (retiring)
|
|
2466
|
+
writeField(prompter, "Save version", client, "in AKMS");
|
|
2467
|
+
writeField(prompter, "Apply to", state.clients.map((id) => CLIENT_LABELS2[id]).join(" \xB7 "));
|
|
2468
|
+
} else writeField(prompter, removing ? "Remove" : "Keep version", removing ? `${conflict.name} in ${client}` : "AKMS", removing ? "" : retiring ? `for ${sessionName(connection.kind)}` : `apply ${subject} to ${client}`);
|
|
2469
|
+
if (retiring) writeHint(prompter, `The old ${client} entry stays until ${sessionName(connection.kind)} is ready.`);
|
|
2311
2470
|
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?`;
|
|
2312
2471
|
if (await prompter.askYesNo(confirmation, false)) {
|
|
2313
2472
|
pendingResults = registry.resolve(conflict.client, conflict.name, actual, choice === "client");
|
|
@@ -2315,29 +2474,15 @@ Currently in ${client}
|
|
|
2315
2474
|
} catch (error) {
|
|
2316
2475
|
if (error instanceof PromptClosedError) throw error;
|
|
2317
2476
|
if (error instanceof PromptBackError) continue;
|
|
2318
|
-
prompter.write(
|
|
2477
|
+
prompter.write(`${prompter.paint.error("Failed")} ${screenText(error instanceof Error ? error.message : "Could not resolve the difference.")}
|
|
2319
2478
|
`);
|
|
2320
2479
|
await prompter.acknowledge();
|
|
2321
2480
|
}
|
|
2322
2481
|
}
|
|
2323
2482
|
}
|
|
2324
|
-
|
|
2325
|
-
// src/modules/registration/targets/index.ts
|
|
2326
|
-
import os5 from "os";
|
|
2327
|
-
import path10 from "path";
|
|
2328
|
-
|
|
2329
|
-
// src/modules/registration/targets/claude-code.ts
|
|
2330
|
-
import { spawnSync } from "child_process";
|
|
2331
|
-
import fs8 from "fs";
|
|
2332
|
-
import os3 from "os";
|
|
2333
|
-
import path8 from "path";
|
|
2334
|
-
|
|
2335
|
-
// src/modules/registration/paths.ts
|
|
2336
|
-
import fs5 from "fs";
|
|
2337
|
-
import path6 from "path";
|
|
2338
2483
|
function findExecutable(name) {
|
|
2339
2484
|
const searchPath = process.env.PATH ?? "";
|
|
2340
|
-
const directories = searchPath.split(
|
|
2485
|
+
const directories = searchPath.split(path2.delimiter).filter((directory) => directory !== "");
|
|
2341
2486
|
let suffixes = [""];
|
|
2342
2487
|
if (process.platform === "win32") {
|
|
2343
2488
|
const pathExt = process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD";
|
|
@@ -2345,25 +2490,17 @@ function findExecutable(name) {
|
|
|
2345
2490
|
}
|
|
2346
2491
|
for (const directory of directories) {
|
|
2347
2492
|
for (const suffix of suffixes) {
|
|
2348
|
-
const candidate =
|
|
2349
|
-
if (
|
|
2493
|
+
const candidate = path2.join(directory, `${name}${suffix}`);
|
|
2494
|
+
if (fs.existsSync(candidate) == true && fs.statSync(candidate).isFile() == true) {
|
|
2350
2495
|
return candidate;
|
|
2351
2496
|
}
|
|
2352
2497
|
}
|
|
2353
2498
|
}
|
|
2354
2499
|
return null;
|
|
2355
2500
|
}
|
|
2356
|
-
|
|
2357
|
-
// src/modules/registration/targets/json-file.ts
|
|
2358
|
-
import fs7 from "fs";
|
|
2359
|
-
|
|
2360
|
-
// src/modules/registration/targets/config-file.ts
|
|
2361
|
-
import fs6 from "fs";
|
|
2362
|
-
import path7 from "path";
|
|
2363
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
2364
2501
|
function readExisting2(filePath) {
|
|
2365
2502
|
try {
|
|
2366
|
-
return
|
|
2503
|
+
return fs.readFileSync(filePath, "utf8");
|
|
2367
2504
|
} catch (error) {
|
|
2368
2505
|
if (error.code === "ENOENT") {
|
|
2369
2506
|
return null;
|
|
@@ -2375,45 +2512,45 @@ function updateConfigFile2(filePath, update) {
|
|
|
2375
2512
|
let destination = filePath;
|
|
2376
2513
|
let symbolicLink = false;
|
|
2377
2514
|
try {
|
|
2378
|
-
symbolicLink =
|
|
2515
|
+
symbolicLink = fs.lstatSync(filePath).isSymbolicLink();
|
|
2379
2516
|
} catch (error) {
|
|
2380
2517
|
if (error.code !== "ENOENT") {
|
|
2381
2518
|
throw error;
|
|
2382
2519
|
}
|
|
2383
2520
|
}
|
|
2384
2521
|
if (symbolicLink) {
|
|
2385
|
-
destination =
|
|
2522
|
+
destination = fs.realpathSync(filePath);
|
|
2386
2523
|
}
|
|
2387
2524
|
const before = readExisting2(destination);
|
|
2388
2525
|
const updated = update(before ?? "");
|
|
2389
2526
|
if (before != null) {
|
|
2390
|
-
|
|
2527
|
+
fs.accessSync(destination, fs.constants.W_OK);
|
|
2391
2528
|
}
|
|
2392
|
-
|
|
2393
|
-
const temporary =
|
|
2394
|
-
const mode = before == null ? 384 :
|
|
2529
|
+
fs.mkdirSync(path2.dirname(destination), { recursive: true });
|
|
2530
|
+
const temporary = path2.join(path2.dirname(destination), `.${path2.basename(destination)}.${randomUUID()}.tmp`);
|
|
2531
|
+
const mode = before == null ? 384 : fs.statSync(destination).mode & 511;
|
|
2395
2532
|
let descriptor;
|
|
2396
2533
|
let ownsTemporary = false;
|
|
2397
2534
|
try {
|
|
2398
|
-
descriptor =
|
|
2535
|
+
descriptor = fs.openSync(temporary, "wx", mode);
|
|
2399
2536
|
ownsTemporary = true;
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2537
|
+
fs.writeFileSync(descriptor, updated, "utf8");
|
|
2538
|
+
fs.fsyncSync(descriptor);
|
|
2539
|
+
fs.closeSync(descriptor);
|
|
2403
2540
|
descriptor = void 0;
|
|
2404
2541
|
if (readExisting2(destination) !== before) {
|
|
2405
2542
|
throw new Error("The config changed while saving; reload it and try again.");
|
|
2406
2543
|
}
|
|
2407
|
-
|
|
2544
|
+
fs.renameSync(temporary, destination);
|
|
2408
2545
|
ownsTemporary = false;
|
|
2409
2546
|
} finally {
|
|
2410
2547
|
try {
|
|
2411
2548
|
if (descriptor != null) {
|
|
2412
|
-
|
|
2549
|
+
fs.closeSync(descriptor);
|
|
2413
2550
|
}
|
|
2414
2551
|
} finally {
|
|
2415
2552
|
if (ownsTemporary) {
|
|
2416
|
-
|
|
2553
|
+
fs.unlinkSync(temporary);
|
|
2417
2554
|
}
|
|
2418
2555
|
}
|
|
2419
2556
|
}
|
|
@@ -2466,10 +2603,10 @@ function readMcpServersJson(document) {
|
|
|
2466
2603
|
return entries;
|
|
2467
2604
|
}
|
|
2468
2605
|
function readDocument(filePath, contents) {
|
|
2469
|
-
if (contents == null &&
|
|
2606
|
+
if (contents == null && fs.existsSync(filePath) == false) {
|
|
2470
2607
|
return {};
|
|
2471
2608
|
}
|
|
2472
|
-
const text = contents ??
|
|
2609
|
+
const text = contents ?? fs.readFileSync(filePath, "utf8");
|
|
2473
2610
|
if (text.trim() === "") {
|
|
2474
2611
|
return {};
|
|
2475
2612
|
}
|
|
@@ -2494,7 +2631,7 @@ function createJsonFileTarget(options) {
|
|
|
2494
2631
|
id,
|
|
2495
2632
|
label,
|
|
2496
2633
|
location: filePath,
|
|
2497
|
-
detected:
|
|
2634
|
+
detected: fs.existsSync(filePath),
|
|
2498
2635
|
readEntries() {
|
|
2499
2636
|
const document = readDocument(filePath);
|
|
2500
2637
|
return readMcpServersJson(document);
|
|
@@ -2525,7 +2662,7 @@ function createJsonFileTarget(options) {
|
|
|
2525
2662
|
}
|
|
2526
2663
|
|
|
2527
2664
|
// src/modules/registration/targets/claude-code.ts
|
|
2528
|
-
var CLAUDE_CODE_CONFIG_PATH =
|
|
2665
|
+
var CLAUDE_CODE_CONFIG_PATH = path2.join(os5.homedir(), ".claude.json");
|
|
2529
2666
|
var CMD_META_CHARACTER_PATTERN = /([()\][%!^"`<>&|;, *?])/g;
|
|
2530
2667
|
function toClaudeAddJsonPayload(entry) {
|
|
2531
2668
|
return JSON.stringify({ type: "stdio", command: entry.command, args: entry.args, env: entry.env });
|
|
@@ -2567,10 +2704,10 @@ function runClaude(executable, args) {
|
|
|
2567
2704
|
return { status: "written", detail: CLAUDE_CODE_CONFIG_PATH };
|
|
2568
2705
|
}
|
|
2569
2706
|
function readRawClaudeEntries() {
|
|
2570
|
-
if (
|
|
2707
|
+
if (fs.existsSync(CLAUDE_CODE_CONFIG_PATH) == false) {
|
|
2571
2708
|
return {};
|
|
2572
2709
|
}
|
|
2573
|
-
const text =
|
|
2710
|
+
const text = fs.readFileSync(CLAUDE_CODE_CONFIG_PATH, "utf8");
|
|
2574
2711
|
let document;
|
|
2575
2712
|
try {
|
|
2576
2713
|
document = JSON.parse(text);
|
|
@@ -2589,7 +2726,7 @@ function createClaudeCodeTarget() {
|
|
|
2589
2726
|
id: "claude-code",
|
|
2590
2727
|
label: "Claude Code (user scope)",
|
|
2591
2728
|
location: CLAUDE_CODE_CONFIG_PATH,
|
|
2592
|
-
detected: executable != null ||
|
|
2729
|
+
detected: executable != null || fs.existsSync(CLAUDE_CODE_CONFIG_PATH) == true,
|
|
2593
2730
|
readEntries() {
|
|
2594
2731
|
return readMcpServersJson({ mcpServers: readRawClaudeEntries() });
|
|
2595
2732
|
},
|
|
@@ -2634,11 +2771,6 @@ function createClaudeCodeTarget() {
|
|
|
2634
2771
|
};
|
|
2635
2772
|
}
|
|
2636
2773
|
|
|
2637
|
-
// src/modules/registration/targets/codex.ts
|
|
2638
|
-
import fs9 from "fs";
|
|
2639
|
-
import os4 from "os";
|
|
2640
|
-
import path9 from "path";
|
|
2641
|
-
|
|
2642
2774
|
// src/modules/registration/targets/toml-lines.ts
|
|
2643
2775
|
function lineState() {
|
|
2644
2776
|
return { quote: null, multiline: false, depth: 0 };
|
|
@@ -2678,10 +2810,6 @@ function scanTomlLine(line, state) {
|
|
|
2678
2810
|
}
|
|
2679
2811
|
}
|
|
2680
2812
|
}
|
|
2681
|
-
|
|
2682
|
-
// src/modules/registration/targets/toml-validation.ts
|
|
2683
|
-
import { isDeepStrictEqual } from "util";
|
|
2684
|
-
import { parse, TomlError } from "smol-toml";
|
|
2685
2813
|
function isTable(value) {
|
|
2686
2814
|
return value != null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
|
|
2687
2815
|
}
|
|
@@ -2932,18 +3060,18 @@ function editMcpServerToml(text, name, entry) {
|
|
|
2932
3060
|
|
|
2933
3061
|
// src/modules/registration/targets/codex.ts
|
|
2934
3062
|
function createCodexTarget() {
|
|
2935
|
-
const CODEX_CONFIG_PATH =
|
|
3063
|
+
const CODEX_CONFIG_PATH = path2.join(process.env.CODEX_HOME ? path2.resolve(process.env.CODEX_HOME) : path2.join(os5.homedir(), ".codex"), "config.toml");
|
|
2936
3064
|
function readText() {
|
|
2937
|
-
if (
|
|
3065
|
+
if (fs.existsSync(CODEX_CONFIG_PATH) == false) {
|
|
2938
3066
|
return "";
|
|
2939
3067
|
}
|
|
2940
|
-
return
|
|
3068
|
+
return fs.readFileSync(CODEX_CONFIG_PATH, "utf8");
|
|
2941
3069
|
}
|
|
2942
3070
|
return {
|
|
2943
3071
|
id: "codex",
|
|
2944
3072
|
label: "Codex CLI",
|
|
2945
3073
|
location: CODEX_CONFIG_PATH,
|
|
2946
|
-
detected: findExecutable("codex") != null ||
|
|
3074
|
+
detected: findExecutable("codex") != null || fs.existsSync(CODEX_CONFIG_PATH) == true,
|
|
2947
3075
|
readEntries() {
|
|
2948
3076
|
const text = readText();
|
|
2949
3077
|
return parseMcpServersToml(text);
|
|
@@ -2971,20 +3099,20 @@ function createCodexTarget() {
|
|
|
2971
3099
|
// src/modules/registration/targets/index.ts
|
|
2972
3100
|
function claudeDesktopConfigPath() {
|
|
2973
3101
|
if (process.platform === "win32") {
|
|
2974
|
-
const appData = process.env.APPDATA ??
|
|
2975
|
-
return
|
|
3102
|
+
const appData = process.env.APPDATA ?? path2.join(os5.homedir(), "AppData", "Roaming");
|
|
3103
|
+
return path2.join(appData, "Claude", "claude_desktop_config.json");
|
|
2976
3104
|
}
|
|
2977
3105
|
if (process.platform === "darwin") {
|
|
2978
|
-
return
|
|
3106
|
+
return path2.join(os5.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
2979
3107
|
}
|
|
2980
|
-
return
|
|
3108
|
+
return path2.join(os5.homedir(), ".config", "Claude", "claude_desktop_config.json");
|
|
2981
3109
|
}
|
|
2982
3110
|
function listConfigTargets() {
|
|
2983
3111
|
const targets = [
|
|
2984
3112
|
createClaudeCodeTarget(),
|
|
2985
3113
|
createCodexTarget(),
|
|
2986
3114
|
createJsonFileTarget({ id: "claude-desktop", label: "Claude Desktop", filePath: claudeDesktopConfigPath() }),
|
|
2987
|
-
createJsonFileTarget({ id: "cursor", label: "Cursor", filePath:
|
|
3115
|
+
createJsonFileTarget({ id: "cursor", label: "Cursor", filePath: path2.join(os5.homedir(), ".cursor", "mcp.json") })
|
|
2988
3116
|
];
|
|
2989
3117
|
const detected = targets.filter((target) => target.detected == true);
|
|
2990
3118
|
const others = targets.filter((target) => target.detected == false);
|
|
@@ -3026,8 +3154,7 @@ ${failure}
|
|
|
3026
3154
|
prompter.clearScreen();
|
|
3027
3155
|
const state = registry.read();
|
|
3028
3156
|
const clients = state.clients;
|
|
3029
|
-
const
|
|
3030
|
-
if (migration) prompter.write(prompter.paint.warning("Registry update available. Choose Migrate registry to review the changes.\n"));
|
|
3157
|
+
const runtime = runtimeNotice(registry);
|
|
3031
3158
|
const picked = await prompter.select("AKMS MCP \xB7 Connection setup", [
|
|
3032
3159
|
{ value: "db", label: "Database", hint: "PostgreSQL connections" },
|
|
3033
3160
|
{ value: "ssh", label: "SSH", hint: "Remote hosts" },
|
|
@@ -3036,20 +3163,16 @@ ${failure}
|
|
|
3036
3163
|
{ separator: true },
|
|
3037
3164
|
{ value: "register", label: "Register", hint: clients.length ? clients.map((id) => CLIENT_LABELS2[id]).join(" \xB7 ") : "Choose AI services once" },
|
|
3038
3165
|
{ value: "sync", label: "Sync", hint: "Review and retry shared settings" },
|
|
3039
|
-
{ value: "runtime", label: "Runtime", hint:
|
|
3040
|
-
{ value: "updates", label: "Updates", hint: "Check installed MCP releases" },
|
|
3041
|
-
...migration ? [{ value: "migrate", key: "migrate", label: "Migrate registry", hint: "Review older registry format and session layout" }] : [],
|
|
3166
|
+
{ value: "runtime", label: "Runtime", hint: runtime.hint, notice: runtime.notice },
|
|
3042
3167
|
{ value: "quit", label: "Quit" }
|
|
3043
3168
|
], { defaultValue, cancelValue: "quit" });
|
|
3044
|
-
if (picked !== "register" && picked !== "sync" && picked !== "runtime"
|
|
3169
|
+
if (picked !== "register" && picked !== "sync" && picked !== "runtime") {
|
|
3045
3170
|
selected = picked;
|
|
3046
3171
|
break;
|
|
3047
3172
|
}
|
|
3048
3173
|
try {
|
|
3049
3174
|
if (picked === "register") await registrationMenu(prompter, registry, options.targetId);
|
|
3050
3175
|
else if (picked === "runtime") await runtimeMenu(prompter, registry);
|
|
3051
|
-
else if (picked === "updates") await updatesMenu(prompter, registry);
|
|
3052
|
-
else if (picked === "migrate") await migrationMenu(prompter, registry);
|
|
3053
3176
|
else await syncMenu(prompter, registry);
|
|
3054
3177
|
} catch (error) {
|
|
3055
3178
|
if (error instanceof PromptClosedError) throw error;
|
|
@@ -3078,66 +3201,18 @@ ${error instanceof Error ? error.message : "Could not update settings."}
|
|
|
3078
3201
|
}
|
|
3079
3202
|
}
|
|
3080
3203
|
}
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
import { basename, delimiter, dirname, join } from "path";
|
|
3088
|
-
import { promisify as promisify2 } from "util";
|
|
3089
|
-
var execFileAsync = promisify2(execFile3);
|
|
3090
|
-
async function findNpmCli() {
|
|
3091
|
-
const candidates = [];
|
|
3092
|
-
const npmExecPath = process.env.npm_execpath;
|
|
3093
|
-
if (npmExecPath != null && ["npm-cli.js", "npx-cli.js"].includes(basename(npmExecPath)) == true) {
|
|
3094
|
-
candidates.push(join(dirname(npmExecPath), "npm-cli.js"));
|
|
3095
|
-
}
|
|
3096
|
-
candidates.push(join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js"));
|
|
3097
|
-
candidates.push(join(dirname(process.execPath), "..", "lib", "node_modules", "npm", "bin", "npm-cli.js"));
|
|
3098
|
-
for (const directory of (process.env.PATH ?? "").split(delimiter)) {
|
|
3099
|
-
if (directory === "") {
|
|
3100
|
-
continue;
|
|
3101
|
-
}
|
|
3102
|
-
if (process.platform === "win32") {
|
|
3103
|
-
candidates.push(join(directory, "node_modules", "npm", "bin", "npm-cli.js"));
|
|
3104
|
-
} else {
|
|
3105
|
-
candidates.push(join(directory, "npm"));
|
|
3204
|
+
function bundledEntry(kind) {
|
|
3205
|
+
let directory = dirname(fileURLToPath(import.meta.url));
|
|
3206
|
+
for (; ; ) {
|
|
3207
|
+
const manifest = join(directory, "package.json");
|
|
3208
|
+
if (existsSync(manifest) == true && JSON.parse(readFileSync(manifest, "utf8")).name === PACKAGE_NAME) {
|
|
3209
|
+
return { command: process.execPath, args: [join(directory, "lib", `${kind}.js`)] };
|
|
3106
3210
|
}
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
const resolved = await realpath(candidate);
|
|
3111
|
-
if (resolved.endsWith(".js")) return resolved;
|
|
3211
|
+
const parent = dirname(directory);
|
|
3212
|
+
if (parent === directory) {
|
|
3213
|
+
throw new Error("The AKMS MCP package could not be located; setup was not started.");
|
|
3112
3214
|
}
|
|
3113
|
-
|
|
3114
|
-
throw new Error("npm could not be found. Install Node.js with npm, then reopen your terminal.");
|
|
3115
|
-
}
|
|
3116
|
-
async function resolveLatestVersion(options) {
|
|
3117
|
-
const metadataCache = await mkdtemp(join(tmpdir(), "akms-mcp-latest-"));
|
|
3118
|
-
try {
|
|
3119
|
-
const result = await execFileAsync(process.execPath, [
|
|
3120
|
-
options.npmCli,
|
|
3121
|
-
"view",
|
|
3122
|
-
`${options.packageName}@latest`,
|
|
3123
|
-
"version",
|
|
3124
|
-
"--json",
|
|
3125
|
-
"--prefer-online",
|
|
3126
|
-
"--prefer-offline=false",
|
|
3127
|
-
"--offline=false",
|
|
3128
|
-
"--fetch-retries=0",
|
|
3129
|
-
"--fetch-timeout=15000",
|
|
3130
|
-
"--update-notifier=false",
|
|
3131
|
-
"--cache",
|
|
3132
|
-
metadataCache
|
|
3133
|
-
], { signal: options.signal, timeout: 2e4, windowsHide: true });
|
|
3134
|
-
const version = JSON.parse(result.stdout);
|
|
3135
|
-
if (typeof version !== "string" || /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version) == false) {
|
|
3136
|
-
throw new Error("npm returned an invalid latest version.");
|
|
3137
|
-
}
|
|
3138
|
-
return version;
|
|
3139
|
-
} finally {
|
|
3140
|
-
await rm(metadataCache, { recursive: true, force: true });
|
|
3215
|
+
directory = parent;
|
|
3141
3216
|
}
|
|
3142
3217
|
}
|
|
3143
3218
|
async function launchMcp(options, dependencies = {}) {
|
|
@@ -3153,20 +3228,20 @@ async function launchMcp(options, dependencies = {}) {
|
|
|
3153
3228
|
const prompter = new Prompter(dependencies.input ?? process.stdin, output, pendingInput);
|
|
3154
3229
|
try {
|
|
3155
3230
|
prompter.write(`
|
|
3156
|
-
${
|
|
3231
|
+
${PACKAGE_NAME} is not installed in the AKMS runtime directory.
|
|
3157
3232
|
`);
|
|
3158
3233
|
if (!await prompter.askYesNo("Install the AKMS runtime before opening setup?", true)) return 0;
|
|
3159
3234
|
try {
|
|
3160
|
-
prompter.write(`Installing ${
|
|
3235
|
+
prompter.write(`Installing ${PACKAGE_NAME}...
|
|
3161
3236
|
`);
|
|
3162
|
-
await (dependencies.installRuntime ?? installGlobalRuntime)(
|
|
3237
|
+
await (dependencies.installRuntime ?? installGlobalRuntime)();
|
|
3163
3238
|
runtime = findRuntime(options.mcpId);
|
|
3164
3239
|
if (!runtime) throw new Error("Installation completed but the AKMS runtime could not be found.");
|
|
3165
3240
|
} catch (error) {
|
|
3166
3241
|
if (error instanceof Error && error.message === "AKMS runtime installation cancelled.") return 130;
|
|
3167
3242
|
prompter.write(`${error instanceof Error ? error.message : "AKMS runtime installation failed."}
|
|
3168
3243
|
`);
|
|
3169
|
-
if (!await prompter.askYesNo("Open
|
|
3244
|
+
if (!await prompter.askYesNo("Open setup from this launcher instead?", false)) return 1;
|
|
3170
3245
|
}
|
|
3171
3246
|
} catch (error) {
|
|
3172
3247
|
if (error instanceof PromptClosedError || error instanceof PromptBackError) return 0;
|
|
@@ -3176,61 +3251,16 @@ ${selected.packageName} is not installed in the AKMS runtime directory.
|
|
|
3176
3251
|
pendingInput = prompter.takePendingInput();
|
|
3177
3252
|
}
|
|
3178
3253
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3254
|
+
const launch = runtime ?? (dependencies.bundledEntry ?? bundledEntry)(options.mcpId);
|
|
3255
|
+
const argumentsList = [...launch.args, selected.setupCommand];
|
|
3256
|
+
if (options.targetId) argumentsList.push("--target", options.targetId);
|
|
3257
|
+
if (runtime) output.write(`Opening ${selected.label} setup \xB7 ${PACKAGE_NAME} ${runtime.version}${runtime.linked ? " (linked development checkout)" : ""}
|
|
3183
3258
|
|
|
3184
3259
|
`);
|
|
3185
|
-
|
|
3186
|
-
}
|
|
3187
|
-
return launchLatestSetup(options, dependencies, pendingInput);
|
|
3188
|
-
}
|
|
3189
|
-
async function launchLatestSetup(options, dependencies, pendingInput) {
|
|
3190
|
-
const selected = MCP_PACKAGES[options.mcpId];
|
|
3191
|
-
const output = dependencies.output ?? process.stdout;
|
|
3192
|
-
const npmCli2 = await (dependencies.findNpmCli ?? findNpmCli)();
|
|
3193
|
-
const cancellation = new AbortController();
|
|
3194
|
-
const cancelLookup = () => {
|
|
3195
|
-
cancellation.abort();
|
|
3196
|
-
};
|
|
3197
|
-
process.on("SIGINT", cancelLookup);
|
|
3198
|
-
process.on("SIGTERM", cancelLookup);
|
|
3199
|
-
let version;
|
|
3200
|
-
try {
|
|
3201
|
-
output.write(`
|
|
3202
|
-
Checking the latest ${selected.packageName} release...
|
|
3203
|
-
`);
|
|
3204
|
-
version = await (dependencies.resolveLatestVersion ?? resolveLatestVersion)({ npmCli: npmCli2, packageName: selected.packageName, signal: cancellation.signal });
|
|
3205
|
-
} catch (error) {
|
|
3206
|
-
if (cancellation.signal.aborted == true) {
|
|
3207
|
-
return 130;
|
|
3208
|
-
}
|
|
3209
|
-
throw new Error(`Could not check the latest ${selected.packageName} release; setup was not started.
|
|
3210
|
-
${String(error)}`, { cause: error });
|
|
3211
|
-
} finally {
|
|
3212
|
-
process.off("SIGINT", cancelLookup);
|
|
3213
|
-
process.off("SIGTERM", cancelLookup);
|
|
3214
|
-
}
|
|
3215
|
-
const argumentsList = [
|
|
3216
|
-
npmCli2,
|
|
3217
|
-
"exec",
|
|
3218
|
-
"--yes",
|
|
3219
|
-
"--prefer-online",
|
|
3220
|
-
"--prefer-offline=false",
|
|
3221
|
-
"--offline=false",
|
|
3222
|
-
`--package=${selected.packageName}@${version}`,
|
|
3223
|
-
"--",
|
|
3224
|
-
selected.command,
|
|
3225
|
-
selected.setupCommand
|
|
3226
|
-
];
|
|
3227
|
-
if (options.targetId != null) {
|
|
3228
|
-
argumentsList.push("--target", options.targetId);
|
|
3229
|
-
}
|
|
3230
|
-
output.write(`Opening ${selected.packageName} ${version} with npm exec
|
|
3260
|
+
else output.write(`Opening ${selected.label} setup \xB7 ${PACKAGE_NAME} ${PACKAGE_VERSION} from this launcher
|
|
3231
3261
|
|
|
3232
3262
|
`);
|
|
3233
|
-
return (dependencies.runChild ?? runChild)(
|
|
3263
|
+
return (dependencies.runChild ?? runChild)(launch.command, argumentsList, pendingInput);
|
|
3234
3264
|
}
|
|
3235
3265
|
async function runChild(command, argumentsList, pendingInput) {
|
|
3236
3266
|
return new Promise((resolve, reject) => {
|
|
@@ -3239,9 +3269,10 @@ async function runChild(command, argumentsList, pendingInput) {
|
|
|
3239
3269
|
if (interactiveInput == true) {
|
|
3240
3270
|
inputMode = "inherit";
|
|
3241
3271
|
}
|
|
3242
|
-
const child =
|
|
3272
|
+
const child = spawn(command, argumentsList, {
|
|
3243
3273
|
stdio: [inputMode, "inherit", "inherit"],
|
|
3244
|
-
windowsHide: true
|
|
3274
|
+
windowsHide: true,
|
|
3275
|
+
env: { ...process.env, AKMS_MCP_MENU: "1" }
|
|
3245
3276
|
});
|
|
3246
3277
|
const interrupt = () => {
|
|
3247
3278
|
child.kill("SIGINT");
|
|
@@ -3313,11 +3344,7 @@ async function main() {
|
|
|
3313
3344
|
return;
|
|
3314
3345
|
}
|
|
3315
3346
|
if (argumentsList.showVersion == true) {
|
|
3316
|
-
|
|
3317
|
-
if (true) {
|
|
3318
|
-
version = "0.3.2";
|
|
3319
|
-
}
|
|
3320
|
-
process.stdout.write(`${version}
|
|
3347
|
+
process.stdout.write(`${PACKAGE_VERSION}
|
|
3321
3348
|
`);
|
|
3322
3349
|
return;
|
|
3323
3350
|
}
|
|
@@ -3341,3 +3368,5 @@ main().catch((error) => {
|
|
|
3341
3368
|
`);
|
|
3342
3369
|
process.exitCode = 1;
|
|
3343
3370
|
});
|
|
3371
|
+
//# sourceMappingURL=cli.js.map
|
|
3372
|
+
//# sourceMappingURL=cli.js.map
|