@akms/mcp-ssh 0.1.7 → 0.1.8
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 +242 -205
- package/README.md +299 -287
- package/lib/cli.cjs +332 -326
- package/lib/cli.cjs.map +1 -1
- package/lib/cli.js +332 -326
- package/lib/cli.js.map +1 -1
- package/lib/index.cjs +1 -1
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/package.json +2 -1
package/lib/cli.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
5
|
-
var
|
|
5
|
+
var fs7 = require('fs');
|
|
6
6
|
var os5 = require('os');
|
|
7
7
|
var path = require('path');
|
|
8
8
|
var child_process = require('child_process');
|
|
@@ -11,12 +11,14 @@ var stream = require('stream');
|
|
|
11
11
|
var string_decoder = require('string_decoder');
|
|
12
12
|
var ssh2 = require('ssh2');
|
|
13
13
|
var timers = require('timers');
|
|
14
|
+
var util = require('util');
|
|
15
|
+
var smolToml = require('smol-toml');
|
|
14
16
|
var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
15
17
|
var zod = require('zod');
|
|
16
18
|
|
|
17
19
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
20
|
|
|
19
|
-
var
|
|
21
|
+
var fs7__default = /*#__PURE__*/_interopDefault(fs7);
|
|
20
22
|
var os5__default = /*#__PURE__*/_interopDefault(os5);
|
|
21
23
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
22
24
|
var ssh2__default = /*#__PURE__*/_interopDefault(ssh2);
|
|
@@ -36,7 +38,7 @@ var CWD_MARKER = "__AKMS_SSH_CWD__";
|
|
|
36
38
|
var MAX_DIRECTORY_ENTRIES = 1e3;
|
|
37
39
|
var SERVER_NAME = "akms-mcp-ssh";
|
|
38
40
|
var PACKAGE_NAME = "@akms/mcp-ssh";
|
|
39
|
-
var SERVER_VERSION = "0.1.
|
|
41
|
+
var SERVER_VERSION = "0.1.8";
|
|
40
42
|
|
|
41
43
|
// src/_defs/command-rules.ts
|
|
42
44
|
var CRITICAL_PATH_PATTERN = new RegExp(
|
|
@@ -622,7 +624,7 @@ function findExecutable(name) {
|
|
|
622
624
|
for (const directory of directories) {
|
|
623
625
|
for (const suffix of suffixes) {
|
|
624
626
|
const candidate = path__default.default.join(directory, `${name}${suffix}`);
|
|
625
|
-
if (
|
|
627
|
+
if (fs7__default.default.existsSync(candidate) == true && fs7__default.default.statSync(candidate).isFile() == true) {
|
|
626
628
|
return candidate;
|
|
627
629
|
}
|
|
628
630
|
}
|
|
@@ -763,7 +765,7 @@ function loadHostProfile(env = process.env) {
|
|
|
763
765
|
if (keyPath != null) {
|
|
764
766
|
const expandedKeyPath = expandHomePath(keyPath);
|
|
765
767
|
profile.privateKeyPath = expandedKeyPath;
|
|
766
|
-
if (
|
|
768
|
+
if (fs7__default.default.existsSync(expandedKeyPath) == false) {
|
|
767
769
|
_logger.warn(`${SINGLE_HOST_ENV.KEY} does not exist; connections will fail`, {
|
|
768
770
|
private_key_path: expandedKeyPath
|
|
769
771
|
});
|
|
@@ -795,7 +797,7 @@ function loadHostProfile(env = process.env) {
|
|
|
795
797
|
if (jumpKeyPath != null) {
|
|
796
798
|
profile.jump.privateKeyPath = expandHomePath(jumpKeyPath);
|
|
797
799
|
profile.jump.passphrase = readEnv(env, SINGLE_HOST_ENV.JUMP_PASSPHRASE);
|
|
798
|
-
if (
|
|
800
|
+
if (fs7__default.default.existsSync(profile.jump.privateKeyPath) == false) {
|
|
799
801
|
_logger.warn(`${SINGLE_HOST_ENV.JUMP_KEY} does not exist; connections will fail`, {
|
|
800
802
|
private_key_path: profile.jump.privateKeyPath
|
|
801
803
|
});
|
|
@@ -1281,14 +1283,14 @@ function inspectRemotePath(remotePath, policy, access) {
|
|
|
1281
1283
|
function toRealPath(inputPath) {
|
|
1282
1284
|
const _logger = $logger.child({ context: "toRealPath" });
|
|
1283
1285
|
try {
|
|
1284
|
-
return
|
|
1286
|
+
return fs7__default.default.realpathSync.native(inputPath);
|
|
1285
1287
|
} catch (ex) {
|
|
1286
1288
|
_logger.debug("path does not resolve, falling back to its parent directory", {
|
|
1287
1289
|
reason: ex instanceof Error ? ex.name : "unknown"
|
|
1288
1290
|
});
|
|
1289
1291
|
}
|
|
1290
1292
|
try {
|
|
1291
|
-
const parentRealPath =
|
|
1293
|
+
const parentRealPath = fs7__default.default.realpathSync.native(path__default.default.dirname(inputPath));
|
|
1292
1294
|
return path__default.default.join(parentRealPath, path__default.default.basename(inputPath));
|
|
1293
1295
|
} catch (ex) {
|
|
1294
1296
|
_logger.debug("parent directory does not resolve either, comparing lexically", {
|
|
@@ -1380,7 +1382,7 @@ function readPrivateKey(label, keyPath) {
|
|
|
1380
1382
|
return cached;
|
|
1381
1383
|
}
|
|
1382
1384
|
try {
|
|
1383
|
-
const contents =
|
|
1385
|
+
const contents = fs7__default.default.readFileSync(keyPath);
|
|
1384
1386
|
PRIVATE_KEY_CACHE.set(keyPath, contents);
|
|
1385
1387
|
return contents;
|
|
1386
1388
|
} catch (ex) {
|
|
@@ -2335,7 +2337,7 @@ async function writeRemoteFile(connection, args) {
|
|
|
2335
2337
|
}
|
|
2336
2338
|
async function uploadFile(connection, localPath, remotePath) {
|
|
2337
2339
|
requirePermittedPath(connection, remotePath, "write");
|
|
2338
|
-
const localStats =
|
|
2340
|
+
const localStats = fs7__default.default.statSync(localPath);
|
|
2339
2341
|
if (localStats.isFile() == false) {
|
|
2340
2342
|
throw new Error(`local path is not a file: ${localPath}`);
|
|
2341
2343
|
}
|
|
@@ -2372,7 +2374,7 @@ async function downloadFile(connection, remotePath, localPath) {
|
|
|
2372
2374
|
});
|
|
2373
2375
|
});
|
|
2374
2376
|
});
|
|
2375
|
-
const localStats =
|
|
2377
|
+
const localStats = fs7__default.default.statSync(localPath);
|
|
2376
2378
|
const _logger = $logger.child({
|
|
2377
2379
|
context: "downloadFile",
|
|
2378
2380
|
profile: connection.profile.name,
|
|
@@ -3202,6 +3204,61 @@ async function runConnectionCheck(profile, output) {
|
|
|
3202
3204
|
};
|
|
3203
3205
|
}
|
|
3204
3206
|
|
|
3207
|
+
// src/modules/manage/copy-entry.ts
|
|
3208
|
+
async function copyEntryToClient(prompter, source, targets, name, entry) {
|
|
3209
|
+
const destinations = targets.filter((target) => target.id !== source.id);
|
|
3210
|
+
if (destinations.length === 0) {
|
|
3211
|
+
return;
|
|
3212
|
+
}
|
|
3213
|
+
for (; ; ) {
|
|
3214
|
+
const items = destinations.map((target) => ({
|
|
3215
|
+
value: target.id,
|
|
3216
|
+
key: target.id,
|
|
3217
|
+
label: target.label,
|
|
3218
|
+
hint: target.detected ? target.location : `${target.location} \xB7 not found on this machine`
|
|
3219
|
+
}));
|
|
3220
|
+
items.push({ separator: true }, { value: "back", key: "b", label: "Back" });
|
|
3221
|
+
const picked = await prompter.select(`Copy ${name} to which client?`, items, { cancelValue: "back" });
|
|
3222
|
+
const destination = destinations.find((target) => target.id === picked);
|
|
3223
|
+
if (destination == null) {
|
|
3224
|
+
return;
|
|
3225
|
+
}
|
|
3226
|
+
const exists = destination.hasEntry?.(name) ?? Object.hasOwn(destination.readEntries(), name);
|
|
3227
|
+
if (exists) {
|
|
3228
|
+
try {
|
|
3229
|
+
const overwrite = await prompter.askYesNo(
|
|
3230
|
+
`${name} already exists in ${destination.label} (${destination.location}). Overwrite it?`,
|
|
3231
|
+
false
|
|
3232
|
+
);
|
|
3233
|
+
if (overwrite == false) {
|
|
3234
|
+
prompter.write(" Not copied; the destination entry was kept.\n\n");
|
|
3235
|
+
return;
|
|
3236
|
+
}
|
|
3237
|
+
} catch (ex) {
|
|
3238
|
+
if (ex instanceof PromptBackError == false) {
|
|
3239
|
+
throw ex;
|
|
3240
|
+
}
|
|
3241
|
+
continue;
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
const result = destination.writeEntry(name, {
|
|
3245
|
+
command: entry.command,
|
|
3246
|
+
args: [...entry.args],
|
|
3247
|
+
env: { ...entry.env }
|
|
3248
|
+
}, { replace: true });
|
|
3249
|
+
if (result.status === "written") {
|
|
3250
|
+
prompter.write(` ${prompter.paint.success(`Copied ${name} to ${destination.label}`)} ${prompter.paint.dim(result.detail)}
|
|
3251
|
+
`);
|
|
3252
|
+
prompter.write(prompter.paint.dim(" Restart the destination client (or reconnect it from its MCP menu) to pick up the change.\n\n"));
|
|
3253
|
+
} else {
|
|
3254
|
+
prompter.write(` Not copied: ${result.detail}
|
|
3255
|
+
|
|
3256
|
+
`);
|
|
3257
|
+
}
|
|
3258
|
+
return;
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3205
3262
|
// src/modules/manage/entry.ts
|
|
3206
3263
|
var MANAGED_ENV_NAMES = [
|
|
3207
3264
|
SINGLE_HOST_ENV.HOST,
|
|
@@ -3432,7 +3489,7 @@ function findPrivateKeys() {
|
|
|
3432
3489
|
const sshDirectory = path__default.default.join(os5__default.default.homedir(), ".ssh");
|
|
3433
3490
|
let names = [];
|
|
3434
3491
|
try {
|
|
3435
|
-
names =
|
|
3492
|
+
names = fs7__default.default.readdirSync(sshDirectory);
|
|
3436
3493
|
} catch {
|
|
3437
3494
|
return [];
|
|
3438
3495
|
}
|
|
@@ -3444,14 +3501,14 @@ function findPrivateKeys() {
|
|
|
3444
3501
|
const fullPath = path__default.default.join(sshDirectory, name);
|
|
3445
3502
|
let head = "";
|
|
3446
3503
|
try {
|
|
3447
|
-
const stat =
|
|
3504
|
+
const stat = fs7__default.default.statSync(fullPath);
|
|
3448
3505
|
if (stat.isFile() == false || stat.size > MAX_KEY_FILE_BYTES) {
|
|
3449
3506
|
continue;
|
|
3450
3507
|
}
|
|
3451
|
-
const descriptor =
|
|
3508
|
+
const descriptor = fs7__default.default.openSync(fullPath, "r");
|
|
3452
3509
|
const buffer = Buffer.alloc(64);
|
|
3453
|
-
const bytesRead =
|
|
3454
|
-
|
|
3510
|
+
const bytesRead = fs7__default.default.readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
3511
|
+
fs7__default.default.closeSync(descriptor);
|
|
3455
3512
|
head = buffer.subarray(0, bytesRead).toString("utf8");
|
|
3456
3513
|
} catch {
|
|
3457
3514
|
continue;
|
|
@@ -3463,7 +3520,7 @@ function findPrivateKeys() {
|
|
|
3463
3520
|
}
|
|
3464
3521
|
let hint = "";
|
|
3465
3522
|
try {
|
|
3466
|
-
const publicKey =
|
|
3523
|
+
const publicKey = fs7__default.default.readFileSync(`${fullPath}.pub`, "utf8").trim().split(/\s+/);
|
|
3467
3524
|
hint = publicKey[0].replace(/^ssh-/, "");
|
|
3468
3525
|
if (publicKey.length > 2) {
|
|
3469
3526
|
hint += ` \xB7 ${publicKey.slice(2).join(" ")}`;
|
|
@@ -3532,7 +3589,7 @@ async function askKeyPath(prompter, current) {
|
|
|
3532
3589
|
}
|
|
3533
3590
|
for (; ; ) {
|
|
3534
3591
|
const typed = await prompter.askRequired("Private key path");
|
|
3535
|
-
if (
|
|
3592
|
+
if (fs7__default.default.existsSync(expandHomePath(typed)) == true) {
|
|
3536
3593
|
return typed;
|
|
3537
3594
|
}
|
|
3538
3595
|
prompter.write(` ${expandHomePath(typed)} does not exist.
|
|
@@ -4066,6 +4123,65 @@ async function runHostForm(prompter, output, options) {
|
|
|
4066
4123
|
prompter.transient = previousTransient;
|
|
4067
4124
|
}
|
|
4068
4125
|
}
|
|
4126
|
+
function readExisting(filePath) {
|
|
4127
|
+
try {
|
|
4128
|
+
return fs7__default.default.readFileSync(filePath, "utf8");
|
|
4129
|
+
} catch (error) {
|
|
4130
|
+
if (error.code === "ENOENT") {
|
|
4131
|
+
return null;
|
|
4132
|
+
}
|
|
4133
|
+
throw error;
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
function updateConfigFile(filePath, update) {
|
|
4137
|
+
let destination = filePath;
|
|
4138
|
+
let symbolicLink = false;
|
|
4139
|
+
try {
|
|
4140
|
+
symbolicLink = fs7__default.default.lstatSync(filePath).isSymbolicLink();
|
|
4141
|
+
} catch (error) {
|
|
4142
|
+
if (error.code !== "ENOENT") {
|
|
4143
|
+
throw error;
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
if (symbolicLink) {
|
|
4147
|
+
destination = fs7__default.default.realpathSync(filePath);
|
|
4148
|
+
}
|
|
4149
|
+
const before = readExisting(destination);
|
|
4150
|
+
const updated = update(before ?? "");
|
|
4151
|
+
if (before != null) {
|
|
4152
|
+
fs7__default.default.accessSync(destination, fs7__default.default.constants.W_OK);
|
|
4153
|
+
}
|
|
4154
|
+
fs7__default.default.mkdirSync(path__default.default.dirname(destination), { recursive: true });
|
|
4155
|
+
const temporary = path__default.default.join(path__default.default.dirname(destination), `.${path__default.default.basename(destination)}.${crypto.randomUUID()}.tmp`);
|
|
4156
|
+
const mode = before == null ? 384 : fs7__default.default.statSync(destination).mode & 511;
|
|
4157
|
+
let descriptor;
|
|
4158
|
+
let ownsTemporary = false;
|
|
4159
|
+
try {
|
|
4160
|
+
descriptor = fs7__default.default.openSync(temporary, "wx", mode);
|
|
4161
|
+
ownsTemporary = true;
|
|
4162
|
+
fs7__default.default.writeFileSync(descriptor, updated, "utf8");
|
|
4163
|
+
fs7__default.default.fsyncSync(descriptor);
|
|
4164
|
+
fs7__default.default.closeSync(descriptor);
|
|
4165
|
+
descriptor = void 0;
|
|
4166
|
+
if (readExisting(destination) !== before) {
|
|
4167
|
+
throw new Error("The config changed while saving; reload it and try again.");
|
|
4168
|
+
}
|
|
4169
|
+
fs7__default.default.renameSync(temporary, destination);
|
|
4170
|
+
ownsTemporary = false;
|
|
4171
|
+
} finally {
|
|
4172
|
+
try {
|
|
4173
|
+
if (descriptor != null) {
|
|
4174
|
+
fs7__default.default.closeSync(descriptor);
|
|
4175
|
+
}
|
|
4176
|
+
} finally {
|
|
4177
|
+
if (ownsTemporary) {
|
|
4178
|
+
fs7__default.default.unlinkSync(temporary);
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
|
|
4184
|
+
// src/modules/manage/targets/json-file.ts
|
|
4069
4185
|
function toMcpServerEntry(value) {
|
|
4070
4186
|
if (typeof value !== "object" || value == null) {
|
|
4071
4187
|
return null;
|
|
@@ -4078,7 +4194,7 @@ function toMcpServerEntry(value) {
|
|
|
4078
4194
|
if (Array.isArray(record.args) == true) {
|
|
4079
4195
|
args = record.args.filter((item) => typeof item === "string");
|
|
4080
4196
|
}
|
|
4081
|
-
const env =
|
|
4197
|
+
const env = /* @__PURE__ */ Object.create(null);
|
|
4082
4198
|
if (typeof record.env === "object" && record.env != null) {
|
|
4083
4199
|
for (const [key, item] of Object.entries(record.env)) {
|
|
4084
4200
|
if (typeof item === "string") {
|
|
@@ -4088,16 +4204,22 @@ function toMcpServerEntry(value) {
|
|
|
4088
4204
|
}
|
|
4089
4205
|
return { command: record.command, args, env };
|
|
4090
4206
|
}
|
|
4091
|
-
function
|
|
4092
|
-
if (typeof document !== "object" || document == null || Array.isArray(document)
|
|
4207
|
+
function readRawMcpServersJson(document) {
|
|
4208
|
+
if (typeof document !== "object" || document == null || Array.isArray(document)) {
|
|
4093
4209
|
throw new Error("the config file is not a JSON object");
|
|
4094
4210
|
}
|
|
4095
|
-
const entries = {};
|
|
4096
4211
|
const servers = document.mcpServers;
|
|
4097
|
-
if (
|
|
4098
|
-
return
|
|
4212
|
+
if (servers === void 0) {
|
|
4213
|
+
return {};
|
|
4214
|
+
}
|
|
4215
|
+
if (typeof servers !== "object" || servers == null || Array.isArray(servers)) {
|
|
4216
|
+
throw new Error("mcpServers is not a JSON object");
|
|
4099
4217
|
}
|
|
4100
|
-
|
|
4218
|
+
return servers;
|
|
4219
|
+
}
|
|
4220
|
+
function readMcpServersJson(document) {
|
|
4221
|
+
const entries = /* @__PURE__ */ Object.create(null);
|
|
4222
|
+
for (const [name, value] of Object.entries(readRawMcpServersJson(document))) {
|
|
4101
4223
|
const entry = toMcpServerEntry(value);
|
|
4102
4224
|
if (entry != null) {
|
|
4103
4225
|
entries[name] = entry;
|
|
@@ -4105,11 +4227,11 @@ function readMcpServersJson(document) {
|
|
|
4105
4227
|
}
|
|
4106
4228
|
return entries;
|
|
4107
4229
|
}
|
|
4108
|
-
function readDocument(filePath) {
|
|
4109
|
-
if (
|
|
4230
|
+
function readDocument(filePath, contents) {
|
|
4231
|
+
if (contents == null && fs7__default.default.existsSync(filePath) == false) {
|
|
4110
4232
|
return {};
|
|
4111
4233
|
}
|
|
4112
|
-
const text =
|
|
4234
|
+
const text = contents ?? fs7__default.default.readFileSync(filePath, "utf8");
|
|
4113
4235
|
if (text.trim() === "") {
|
|
4114
4236
|
return {};
|
|
4115
4237
|
}
|
|
@@ -4130,39 +4252,35 @@ function readDocument(filePath) {
|
|
|
4130
4252
|
}
|
|
4131
4253
|
function createJsonFileTarget(options) {
|
|
4132
4254
|
const { id, label, filePath } = options;
|
|
4133
|
-
function writeDocument(document) {
|
|
4134
|
-
fs6__default.default.mkdirSync(path__default.default.dirname(filePath), { recursive: true });
|
|
4135
|
-
fs6__default.default.writeFileSync(filePath, `${JSON.stringify(document, null, 4)}
|
|
4136
|
-
`, "utf8");
|
|
4137
|
-
}
|
|
4138
4255
|
return {
|
|
4139
4256
|
id,
|
|
4140
4257
|
label,
|
|
4141
4258
|
location: filePath,
|
|
4142
|
-
detected:
|
|
4259
|
+
detected: fs7__default.default.existsSync(filePath),
|
|
4143
4260
|
readEntries() {
|
|
4144
4261
|
const document = readDocument(filePath);
|
|
4145
4262
|
return readMcpServersJson(document);
|
|
4146
4263
|
},
|
|
4264
|
+
hasEntry(name) {
|
|
4265
|
+
return Object.hasOwn(readRawMcpServersJson(readDocument(filePath)), name);
|
|
4266
|
+
},
|
|
4147
4267
|
writeEntry(name, entry) {
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
servers =
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
writeDocument(document);
|
|
4268
|
+
updateConfigFile(filePath, (contents) => {
|
|
4269
|
+
const document = readDocument(filePath, contents);
|
|
4270
|
+
const servers = Object.assign(/* @__PURE__ */ Object.create(null), readRawMcpServersJson(document));
|
|
4271
|
+
servers[name] = entry;
|
|
4272
|
+
document.mcpServers = servers;
|
|
4273
|
+
return JSON.stringify(document, null, 4) + "\n";
|
|
4274
|
+
});
|
|
4156
4275
|
return { status: "written", detail: filePath };
|
|
4157
4276
|
},
|
|
4158
4277
|
removeEntry(name) {
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
writeDocument(document);
|
|
4278
|
+
updateConfigFile(filePath, (contents) => {
|
|
4279
|
+
const document = readDocument(filePath, contents);
|
|
4280
|
+
const servers = readRawMcpServersJson(document);
|
|
4281
|
+
delete servers[name];
|
|
4282
|
+
return JSON.stringify(document, null, 4) + "\n";
|
|
4283
|
+
});
|
|
4166
4284
|
return { status: "written", detail: filePath };
|
|
4167
4285
|
}
|
|
4168
4286
|
};
|
|
@@ -4214,11 +4332,11 @@ function runClaude(executable, args) {
|
|
|
4214
4332
|
}
|
|
4215
4333
|
return { status: "written", detail: CLAUDE_CODE_CONFIG_PATH };
|
|
4216
4334
|
}
|
|
4217
|
-
function
|
|
4218
|
-
if (
|
|
4335
|
+
function readRawClaudeEntries() {
|
|
4336
|
+
if (fs7__default.default.existsSync(CLAUDE_CODE_CONFIG_PATH) == false) {
|
|
4219
4337
|
return {};
|
|
4220
4338
|
}
|
|
4221
|
-
const text =
|
|
4339
|
+
const text = fs7__default.default.readFileSync(CLAUDE_CODE_CONFIG_PATH, "utf8");
|
|
4222
4340
|
let document;
|
|
4223
4341
|
try {
|
|
4224
4342
|
document = JSON.parse(text);
|
|
@@ -4229,7 +4347,7 @@ function readClaudeEntries() {
|
|
|
4229
4347
|
}
|
|
4230
4348
|
throw new Error(`${CLAUDE_CODE_CONFIG_PATH} is not valid JSON: ${detail}`);
|
|
4231
4349
|
}
|
|
4232
|
-
return
|
|
4350
|
+
return readRawMcpServersJson(document);
|
|
4233
4351
|
}
|
|
4234
4352
|
function createClaudeCodeTarget() {
|
|
4235
4353
|
const executable = findExecutable("claude");
|
|
@@ -4237,28 +4355,35 @@ function createClaudeCodeTarget() {
|
|
|
4237
4355
|
id: "claude-code",
|
|
4238
4356
|
label: "Claude Code (user scope)",
|
|
4239
4357
|
location: CLAUDE_CODE_CONFIG_PATH,
|
|
4240
|
-
detected: executable != null ||
|
|
4241
|
-
readEntries
|
|
4358
|
+
detected: executable != null || fs7__default.default.existsSync(CLAUDE_CODE_CONFIG_PATH) == true,
|
|
4359
|
+
readEntries() {
|
|
4360
|
+
return readMcpServersJson({ mcpServers: readRawClaudeEntries() });
|
|
4361
|
+
},
|
|
4362
|
+
hasEntry(name) {
|
|
4363
|
+
return Object.hasOwn(readRawClaudeEntries(), name);
|
|
4364
|
+
},
|
|
4242
4365
|
writeEntry(name, entry) {
|
|
4243
4366
|
if (executable == null) {
|
|
4244
4367
|
return {
|
|
4245
4368
|
status: "unavailable",
|
|
4246
|
-
detail:
|
|
4247
|
-
${formatClaudeAddCommand(name, entry)}`
|
|
4369
|
+
detail: "the claude CLI is not on PATH \u2014 install it, or use Print to get a command to run yourself"
|
|
4248
4370
|
};
|
|
4249
4371
|
}
|
|
4250
|
-
const
|
|
4251
|
-
|
|
4372
|
+
const previousEntries = readRawClaudeEntries();
|
|
4373
|
+
const hadPrevious = Object.hasOwn(previousEntries, name);
|
|
4374
|
+
const previous = previousEntries[name];
|
|
4375
|
+
if (hadPrevious) {
|
|
4252
4376
|
const removed = runClaude(executable, ["mcp", "remove", name, "-s", "user"]);
|
|
4253
4377
|
if (removed.status === "unavailable") {
|
|
4254
4378
|
return removed;
|
|
4255
4379
|
}
|
|
4256
4380
|
}
|
|
4257
4381
|
const added = runClaude(executable, ["mcp", "add-json", name, toClaudeAddJsonPayload(entry), "-s", "user"]);
|
|
4258
|
-
if (added.status === "unavailable" &&
|
|
4259
|
-
runClaude(executable, ["mcp", "add-json", name,
|
|
4382
|
+
if (added.status === "unavailable" && hadPrevious) {
|
|
4383
|
+
const restored = runClaude(executable, ["mcp", "add-json", name, JSON.stringify(previous), "-s", "user"]);
|
|
4384
|
+
const recovery = restored.status === "written" ? "the previous entry was restored" : `could not restore the previous entry: ${restored.detail}`;
|
|
4260
4385
|
return { status: "unavailable", detail: `${added.detail}
|
|
4261
|
-
|
|
4386
|
+
${recovery}` };
|
|
4262
4387
|
}
|
|
4263
4388
|
return added;
|
|
4264
4389
|
},
|
|
@@ -4275,194 +4400,121 @@ function createClaudeCodeTarget() {
|
|
|
4275
4400
|
};
|
|
4276
4401
|
}
|
|
4277
4402
|
|
|
4278
|
-
// src/modules/manage/targets/toml-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
}
|
|
4298
|
-
parseValue() {
|
|
4299
|
-
this.skipWhitespace();
|
|
4300
|
-
const current = this.text[this.position];
|
|
4301
|
-
if (current === '"') {
|
|
4302
|
-
return this.parseBasicString();
|
|
4303
|
-
}
|
|
4304
|
-
if (current === "'") {
|
|
4305
|
-
return this.parseLiteralString();
|
|
4306
|
-
}
|
|
4307
|
-
if (current === "[") {
|
|
4308
|
-
return this.parseArray();
|
|
4309
|
-
}
|
|
4310
|
-
if (current === "{") {
|
|
4311
|
-
return this.parseInlineTable();
|
|
4312
|
-
}
|
|
4313
|
-
throw new Error(`unsupported toml value at offset ${this.position}`);
|
|
4314
|
-
}
|
|
4315
|
-
parseBasicString() {
|
|
4316
|
-
this.position += 1;
|
|
4317
|
-
let result = "";
|
|
4318
|
-
while (this.position < this.text.length) {
|
|
4319
|
-
const current = this.text[this.position];
|
|
4320
|
-
if (current === '"') {
|
|
4321
|
-
this.position += 1;
|
|
4322
|
-
return result;
|
|
4323
|
-
}
|
|
4324
|
-
if (current !== "\\") {
|
|
4325
|
-
result += current;
|
|
4326
|
-
this.position += 1;
|
|
4327
|
-
continue;
|
|
4328
|
-
}
|
|
4329
|
-
const escaped = this.text[this.position + 1];
|
|
4330
|
-
this.position += 2;
|
|
4331
|
-
if (escaped === "n") {
|
|
4332
|
-
result += "\n";
|
|
4333
|
-
} else if (escaped === "t") {
|
|
4334
|
-
result += " ";
|
|
4335
|
-
} else if (escaped === "r") {
|
|
4336
|
-
result += "\r";
|
|
4337
|
-
} else if (escaped === "u" || escaped === "U") {
|
|
4338
|
-
let length = 4;
|
|
4339
|
-
if (escaped === "U") {
|
|
4340
|
-
length = 8;
|
|
4403
|
+
// src/modules/manage/targets/toml-lines.ts
|
|
4404
|
+
function lineState() {
|
|
4405
|
+
return { quote: null, multiline: false, depth: 0 };
|
|
4406
|
+
}
|
|
4407
|
+
function scanTomlLine(line, state) {
|
|
4408
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
4409
|
+
const character = line[index];
|
|
4410
|
+
if (state.quote != null) {
|
|
4411
|
+
if (character === "\\" && state.quote === '"') {
|
|
4412
|
+
index += 1;
|
|
4413
|
+
} else if (character === state.quote) {
|
|
4414
|
+
if (!state.multiline) {
|
|
4415
|
+
state.quote = null;
|
|
4416
|
+
} else if (line.slice(index, index + 3) === state.quote.repeat(3)) {
|
|
4417
|
+
while (line[index + 1] === character) {
|
|
4418
|
+
index += 1;
|
|
4419
|
+
}
|
|
4420
|
+
state.quote = null;
|
|
4421
|
+
state.multiline = false;
|
|
4341
4422
|
}
|
|
4342
|
-
const hex = this.text.slice(this.position, this.position + length);
|
|
4343
|
-
result += String.fromCodePoint(Number.parseInt(hex, 16));
|
|
4344
|
-
this.position += length;
|
|
4345
|
-
} else {
|
|
4346
|
-
result += escaped;
|
|
4347
4423
|
}
|
|
4424
|
+
continue;
|
|
4348
4425
|
}
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
parseLiteralString() {
|
|
4352
|
-
const end = this.text.indexOf("'", this.position + 1);
|
|
4353
|
-
if (end < 0) {
|
|
4354
|
-
throw new Error("unterminated toml literal string");
|
|
4426
|
+
if (character === "#") {
|
|
4427
|
+
break;
|
|
4355
4428
|
}
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
this.position += 1;
|
|
4362
|
-
const items = [];
|
|
4363
|
-
for (; ; ) {
|
|
4364
|
-
this.skipWhitespace();
|
|
4365
|
-
if (this.position >= this.text.length) {
|
|
4366
|
-
throw new Error("unterminated toml array");
|
|
4367
|
-
}
|
|
4368
|
-
if (this.text[this.position] === "]") {
|
|
4369
|
-
this.position += 1;
|
|
4370
|
-
return items;
|
|
4371
|
-
}
|
|
4372
|
-
items.push(this.parseValue());
|
|
4373
|
-
this.skipWhitespace();
|
|
4374
|
-
if (this.text[this.position] === ",") {
|
|
4375
|
-
this.position += 1;
|
|
4429
|
+
if (character === '"' || character === "'") {
|
|
4430
|
+
state.quote = character;
|
|
4431
|
+
state.multiline = line.slice(index, index + 3) === character.repeat(3);
|
|
4432
|
+
if (state.multiline) {
|
|
4433
|
+
index += 2;
|
|
4376
4434
|
}
|
|
4435
|
+
} else if (character === "[" || character === "{") {
|
|
4436
|
+
state.depth += 1;
|
|
4437
|
+
} else if (character === "]" || character === "}") {
|
|
4438
|
+
state.depth -= 1;
|
|
4377
4439
|
}
|
|
4378
4440
|
}
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
}
|
|
4391
|
-
let key;
|
|
4392
|
-
if (this.text[this.position] === '"') {
|
|
4393
|
-
key = this.parseBasicString();
|
|
4394
|
-
} else {
|
|
4395
|
-
const match = /^[A-Za-z0-9_-]+/.exec(this.text.slice(this.position));
|
|
4396
|
-
if (match == null) {
|
|
4397
|
-
throw new Error(`invalid inline table key at offset ${this.position}`);
|
|
4398
|
-
}
|
|
4399
|
-
key = match[0];
|
|
4400
|
-
this.position += key.length;
|
|
4401
|
-
}
|
|
4402
|
-
this.skipWhitespace();
|
|
4403
|
-
if (this.text[this.position] !== "=") {
|
|
4404
|
-
throw new Error(`expected '=' at offset ${this.position}`);
|
|
4405
|
-
}
|
|
4406
|
-
this.position += 1;
|
|
4407
|
-
table[key] = this.parseValue();
|
|
4408
|
-
this.skipWhitespace();
|
|
4409
|
-
if (this.text[this.position] === ",") {
|
|
4410
|
-
this.position += 1;
|
|
4411
|
-
}
|
|
4412
|
-
}
|
|
4441
|
+
}
|
|
4442
|
+
function isTable(value) {
|
|
4443
|
+
return value != null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
|
|
4444
|
+
}
|
|
4445
|
+
function parseConfigToml(text) {
|
|
4446
|
+
let document;
|
|
4447
|
+
try {
|
|
4448
|
+
document = smolToml.parse(text, { integersAsBigInt: true });
|
|
4449
|
+
} catch (error) {
|
|
4450
|
+
const location = error instanceof smolToml.TomlError ? ` at line ${error.line}, column ${error.column}` : "";
|
|
4451
|
+
throw new Error(`Invalid TOML${location}; fix the config before changing it.`);
|
|
4413
4452
|
}
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
if (rawKey.startsWith('"') == false) {
|
|
4417
|
-
return rawKey;
|
|
4453
|
+
if (Object.hasOwn(document, "mcp_servers") && !isTable(document.mcp_servers)) {
|
|
4454
|
+
throw new Error("mcp_servers must be a TOML table.");
|
|
4418
4455
|
}
|
|
4419
|
-
|
|
4420
|
-
return parsed;
|
|
4456
|
+
return document;
|
|
4421
4457
|
}
|
|
4422
|
-
function
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
quote = current;
|
|
4437
|
-
} else if (current === "#") {
|
|
4438
|
-
break;
|
|
4439
|
-
} else if (current === "[" || current === "{") {
|
|
4440
|
-
balance += 1;
|
|
4441
|
-
} else if (current === "]" || current === "}") {
|
|
4442
|
-
balance -= 1;
|
|
4458
|
+
function readTomlEntries(text) {
|
|
4459
|
+
return readMcpServersJson({ mcpServers: parseConfigToml(text).mcp_servers ?? {} });
|
|
4460
|
+
}
|
|
4461
|
+
function hasTomlEntry(text, name) {
|
|
4462
|
+
return Object.hasOwn(parseConfigToml(text).mcp_servers ?? {}, name);
|
|
4463
|
+
}
|
|
4464
|
+
function normalize(document) {
|
|
4465
|
+
const servers = document.mcp_servers;
|
|
4466
|
+
if (servers == null) {
|
|
4467
|
+
return;
|
|
4468
|
+
}
|
|
4469
|
+
for (const server of Object.values(servers)) {
|
|
4470
|
+
if (isTable(server) && isTable(server.env) && Object.keys(server.env).length === 0) {
|
|
4471
|
+
delete server.env;
|
|
4443
4472
|
}
|
|
4444
4473
|
}
|
|
4445
|
-
|
|
4474
|
+
if (Object.keys(servers).length === 0) {
|
|
4475
|
+
delete document.mcp_servers;
|
|
4476
|
+
}
|
|
4446
4477
|
}
|
|
4447
|
-
function
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4478
|
+
function verifyTomlEdit(before, after, name, entry) {
|
|
4479
|
+
const expected = parseConfigToml(before);
|
|
4480
|
+
const servers = expected.mcp_servers ?? /* @__PURE__ */ Object.create(null);
|
|
4481
|
+
if (entry == null) {
|
|
4482
|
+
delete servers[name];
|
|
4483
|
+
} else {
|
|
4484
|
+
const previous = Object.hasOwn(servers, name) && isTable(servers[name]) ? servers[name] : {};
|
|
4485
|
+
Object.defineProperty(servers, name, {
|
|
4486
|
+
value: Object.assign(/* @__PURE__ */ Object.create(null), previous, { command: entry.command, args: [...entry.args], env: Object.assign(/* @__PURE__ */ Object.create(null), entry.env) }),
|
|
4487
|
+
configurable: true,
|
|
4488
|
+
enumerable: true,
|
|
4489
|
+
writable: true
|
|
4490
|
+
});
|
|
4491
|
+
}
|
|
4492
|
+
expected.mcp_servers = servers;
|
|
4493
|
+
let actual;
|
|
4494
|
+
try {
|
|
4495
|
+
actual = parseConfigToml(after);
|
|
4496
|
+
} catch {
|
|
4497
|
+
throw new Error("Cannot safely edit this TOML layout. Use [mcp_servers.<name>] tables; nothing was written.");
|
|
4464
4498
|
}
|
|
4465
|
-
|
|
4499
|
+
normalize(expected);
|
|
4500
|
+
normalize(actual);
|
|
4501
|
+
if (!util.isDeepStrictEqual(expected, actual)) {
|
|
4502
|
+
throw new Error("Cannot safely edit this TOML layout without changing other settings; nothing was written.");
|
|
4503
|
+
}
|
|
4504
|
+
return after;
|
|
4505
|
+
}
|
|
4506
|
+
|
|
4507
|
+
// src/modules/manage/targets/toml-subset.ts
|
|
4508
|
+
var BARE_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
4509
|
+
var ANY_HEADER_PATTERN = /^\s*\[/;
|
|
4510
|
+
var SERVER_HEADER_PATTERN = /^\s*\[\s*(?:mcp_servers|"mcp_servers"|'mcp_servers')\s*\.\s*([A-Za-z0-9_-]+|"(?:[^"\\]|\\.)*"|'[^']*')\s*((?:\.[^\]]*)?)\]\s*(?:#.*)?$/;
|
|
4511
|
+
var KEY_VALUE_PATTERN = /^\s*([A-Za-z0-9_-]+|"(?:[^"\\]|\\.)*"|'[^']*')\s*=\s*(.*)$/;
|
|
4512
|
+
var OWNED_TABLE_KEYS = ["command", "args", "env"];
|
|
4513
|
+
function unquoteKey(rawKey) {
|
|
4514
|
+
if (rawKey.startsWith('"') || rawKey.startsWith("'")) {
|
|
4515
|
+
return parseConfigToml(`value = ${rawKey}`).value;
|
|
4516
|
+
}
|
|
4517
|
+
return rawKey;
|
|
4466
4518
|
}
|
|
4467
4519
|
function readStatements(body) {
|
|
4468
4520
|
const statements = [];
|
|
@@ -4474,28 +4526,25 @@ function readStatements(body) {
|
|
|
4474
4526
|
continue;
|
|
4475
4527
|
}
|
|
4476
4528
|
const start = index;
|
|
4477
|
-
|
|
4478
|
-
|
|
4529
|
+
const state = lineState();
|
|
4530
|
+
scanTomlLine(match[2], state);
|
|
4479
4531
|
index += 1;
|
|
4480
|
-
while (
|
|
4481
|
-
|
|
4482
|
-
rawValue += `
|
|
4483
|
-
${continuation}`;
|
|
4484
|
-
balance += bracketBalance(continuation);
|
|
4532
|
+
while ((state.quote != null || state.depth > 0) && index < body.length) {
|
|
4533
|
+
scanTomlLine(body[index], state);
|
|
4485
4534
|
index += 1;
|
|
4486
4535
|
}
|
|
4487
|
-
statements.push({ key: unquoteKey(match[1]),
|
|
4536
|
+
statements.push({ key: unquoteKey(match[1]), start, end: index });
|
|
4488
4537
|
}
|
|
4489
4538
|
return statements;
|
|
4490
4539
|
}
|
|
4491
4540
|
function splitSections(lines) {
|
|
4492
4541
|
const sections = [{ header: null, serverName: null, subPath: "", body: [] }];
|
|
4493
|
-
|
|
4542
|
+
const state = lineState();
|
|
4494
4543
|
for (const line of lines) {
|
|
4495
4544
|
const current = sections[sections.length - 1];
|
|
4496
|
-
if (
|
|
4545
|
+
if (state.quote != null || state.depth > 0) {
|
|
4497
4546
|
current.body.push(line);
|
|
4498
|
-
|
|
4547
|
+
scanTomlLine(line, state);
|
|
4499
4548
|
continue;
|
|
4500
4549
|
}
|
|
4501
4550
|
if (ANY_HEADER_PATTERN.test(line) == true) {
|
|
@@ -4510,73 +4559,18 @@ function splitSections(lines) {
|
|
|
4510
4559
|
continue;
|
|
4511
4560
|
}
|
|
4512
4561
|
current.body.push(line);
|
|
4513
|
-
|
|
4514
|
-
if (keyValueMatch != null) {
|
|
4515
|
-
balance = bracketBalance(stripComment(keyValueMatch[2]));
|
|
4516
|
-
}
|
|
4562
|
+
scanTomlLine(line, state);
|
|
4517
4563
|
}
|
|
4518
4564
|
return sections;
|
|
4519
4565
|
}
|
|
4520
|
-
function
|
|
4521
|
-
|
|
4522
|
-
const value = new TomlValueParser(statement.rawValue).parse();
|
|
4523
|
-
return value;
|
|
4524
|
-
} catch {
|
|
4525
|
-
return null;
|
|
4526
|
-
}
|
|
4566
|
+
function hasMcpServerToml(text, name) {
|
|
4567
|
+
return hasTomlEntry(text, name);
|
|
4527
4568
|
}
|
|
4528
4569
|
function parseMcpServersToml(text) {
|
|
4529
|
-
|
|
4530
|
-
const entries = {};
|
|
4531
|
-
const envTables = {};
|
|
4532
|
-
for (const section of sections) {
|
|
4533
|
-
if (section.serverName == null) {
|
|
4534
|
-
continue;
|
|
4535
|
-
}
|
|
4536
|
-
if (section.subPath === "env") {
|
|
4537
|
-
const envTable = {};
|
|
4538
|
-
for (const statement of readStatements(section.body)) {
|
|
4539
|
-
const value = parseStatementValue(statement);
|
|
4540
|
-
if (typeof value === "string") {
|
|
4541
|
-
envTable[statement.key] = value;
|
|
4542
|
-
}
|
|
4543
|
-
}
|
|
4544
|
-
envTables[section.serverName] = envTable;
|
|
4545
|
-
continue;
|
|
4546
|
-
}
|
|
4547
|
-
if (section.subPath !== "") {
|
|
4548
|
-
continue;
|
|
4549
|
-
}
|
|
4550
|
-
let command = null;
|
|
4551
|
-
let args = [];
|
|
4552
|
-
const env = {};
|
|
4553
|
-
for (const statement of readStatements(section.body)) {
|
|
4554
|
-
const value = parseStatementValue(statement);
|
|
4555
|
-
if (statement.key === "command" && typeof value === "string") {
|
|
4556
|
-
command = value;
|
|
4557
|
-
} else if (statement.key === "args" && Array.isArray(value) == true) {
|
|
4558
|
-
args = value.filter((item) => typeof item === "string");
|
|
4559
|
-
} else if (statement.key === "env" && value != null && typeof value === "object" && Array.isArray(value) == false) {
|
|
4560
|
-
for (const [key, item] of Object.entries(value)) {
|
|
4561
|
-
if (typeof item === "string") {
|
|
4562
|
-
env[key] = item;
|
|
4563
|
-
}
|
|
4564
|
-
}
|
|
4565
|
-
}
|
|
4566
|
-
}
|
|
4567
|
-
if (command != null) {
|
|
4568
|
-
entries[section.serverName] = { command, args, env };
|
|
4569
|
-
}
|
|
4570
|
-
}
|
|
4571
|
-
for (const [name, envTable] of Object.entries(envTables)) {
|
|
4572
|
-
if (entries[name] != null) {
|
|
4573
|
-
Object.assign(entries[name].env, envTable);
|
|
4574
|
-
}
|
|
4575
|
-
}
|
|
4576
|
-
return entries;
|
|
4570
|
+
return readTomlEntries(text);
|
|
4577
4571
|
}
|
|
4578
4572
|
function quoteTomlString(value) {
|
|
4579
|
-
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
4573
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
4580
4574
|
return `"${escaped}"`;
|
|
4581
4575
|
}
|
|
4582
4576
|
function formatTomlKey(key) {
|
|
@@ -4620,6 +4614,11 @@ function joinSections(sections, eol) {
|
|
|
4620
4614
|
return lines.join(eol);
|
|
4621
4615
|
}
|
|
4622
4616
|
function writeMcpServerToml(text, name, entry) {
|
|
4617
|
+
parseConfigToml(text);
|
|
4618
|
+
const updated = editMcpServerToml(text, name, entry);
|
|
4619
|
+
return verifyTomlEdit(text, updated, name, entry);
|
|
4620
|
+
}
|
|
4621
|
+
function editMcpServerToml(text, name, entry) {
|
|
4623
4622
|
let eol = "\n";
|
|
4624
4623
|
if (text.includes("\r\n") == true) {
|
|
4625
4624
|
eol = "\r\n";
|
|
@@ -4692,34 +4691,36 @@ function writeMcpServerToml(text, name, entry) {
|
|
|
4692
4691
|
var CODEX_CONFIG_PATH = path__default.default.join(os5__default.default.homedir(), ".codex", "config.toml");
|
|
4693
4692
|
function createCodexTarget() {
|
|
4694
4693
|
function readText() {
|
|
4695
|
-
if (
|
|
4694
|
+
if (fs7__default.default.existsSync(CODEX_CONFIG_PATH) == false) {
|
|
4696
4695
|
return "";
|
|
4697
4696
|
}
|
|
4698
|
-
return
|
|
4699
|
-
}
|
|
4700
|
-
function writeText(text) {
|
|
4701
|
-
fs6__default.default.mkdirSync(path__default.default.dirname(CODEX_CONFIG_PATH), { recursive: true });
|
|
4702
|
-
fs6__default.default.writeFileSync(CODEX_CONFIG_PATH, text, "utf8");
|
|
4703
|
-
return { status: "written", detail: CODEX_CONFIG_PATH };
|
|
4697
|
+
return fs7__default.default.readFileSync(CODEX_CONFIG_PATH, "utf8");
|
|
4704
4698
|
}
|
|
4705
4699
|
return {
|
|
4706
4700
|
id: "codex",
|
|
4707
4701
|
label: "Codex CLI",
|
|
4708
4702
|
location: CODEX_CONFIG_PATH,
|
|
4709
|
-
detected: findExecutable("codex") != null ||
|
|
4703
|
+
detected: findExecutable("codex") != null || fs7__default.default.existsSync(CODEX_CONFIG_PATH) == true,
|
|
4710
4704
|
readEntries() {
|
|
4711
4705
|
const text = readText();
|
|
4712
4706
|
return parseMcpServersToml(text);
|
|
4713
4707
|
},
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4708
|
+
hasEntry(name) {
|
|
4709
|
+
return hasMcpServerToml(readText(), name);
|
|
4710
|
+
},
|
|
4711
|
+
writeEntry(name, entry, options) {
|
|
4712
|
+
updateConfigFile(CODEX_CONFIG_PATH, (original) => {
|
|
4713
|
+
let text = original;
|
|
4714
|
+
if (options?.replace) {
|
|
4715
|
+
text = writeMcpServerToml(text, name, null);
|
|
4716
|
+
}
|
|
4717
|
+
return writeMcpServerToml(text, name, entry);
|
|
4718
|
+
});
|
|
4719
|
+
return { status: "written", detail: CODEX_CONFIG_PATH };
|
|
4718
4720
|
},
|
|
4719
4721
|
removeEntry(name) {
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
return writeText(updated);
|
|
4722
|
+
updateConfigFile(CODEX_CONFIG_PATH, (text) => writeMcpServerToml(text, name, null));
|
|
4723
|
+
return { status: "written", detail: CODEX_CONFIG_PATH };
|
|
4723
4724
|
}
|
|
4724
4725
|
};
|
|
4725
4726
|
}
|
|
@@ -4927,13 +4928,14 @@ function buildHostItems(names, entries) {
|
|
|
4927
4928
|
};
|
|
4928
4929
|
});
|
|
4929
4930
|
}
|
|
4930
|
-
async function manageHost(prompter, output, target, entries, name) {
|
|
4931
|
+
async function manageHost(prompter, output, target, targets, entries, name) {
|
|
4931
4932
|
const action = await prompter.select(
|
|
4932
4933
|
name,
|
|
4933
4934
|
[
|
|
4934
4935
|
{ value: "edit", key: "e", label: "Edit", hint: "open its settings" },
|
|
4935
4936
|
{ value: "check", key: "c", label: "Check the connection", hint: "connect now and offer to pin the host key" },
|
|
4936
4937
|
{ value: "print", key: "p", label: "Print the entry", hint: "JSON, Codex TOML and a claude command, ready to paste" },
|
|
4938
|
+
...targets.some((candidate) => candidate.id !== target.id) ? [{ value: "copy", key: "o", label: "Copy to another client", hint: "keep this entry and copy its settings" }] : [],
|
|
4937
4939
|
{ value: "remove", key: "r", label: "Remove", hint: `from ${target.label}` },
|
|
4938
4940
|
{ separator: true },
|
|
4939
4941
|
{ value: "back", key: "b", label: "Back" }
|
|
@@ -4952,6 +4954,10 @@ async function manageHost(prompter, output, target, entries, name) {
|
|
|
4952
4954
|
printHost(prompter, name, entries[name]);
|
|
4953
4955
|
return;
|
|
4954
4956
|
}
|
|
4957
|
+
if (action === "copy") {
|
|
4958
|
+
await copyEntryToClient(prompter, target, targets, name, entries[name]);
|
|
4959
|
+
return;
|
|
4960
|
+
}
|
|
4955
4961
|
if (action === "remove") {
|
|
4956
4962
|
const remove = await prompter.askYesNo(`Remove ${name} from ${target.location}?`, false);
|
|
4957
4963
|
if (remove == true) {
|
|
@@ -5053,7 +5059,7 @@ ${paint.bold(target.label)} ${paint.dim(target.location)}
|
|
|
5053
5059
|
continue;
|
|
5054
5060
|
}
|
|
5055
5061
|
if (picked.startsWith(HOST_VALUE_PREFIX) == true) {
|
|
5056
|
-
await manageHost(prompter, output, target, entries, picked.slice(HOST_VALUE_PREFIX.length));
|
|
5062
|
+
await manageHost(prompter, output, target, targets, entries, picked.slice(HOST_VALUE_PREFIX.length));
|
|
5057
5063
|
}
|
|
5058
5064
|
} catch (ex) {
|
|
5059
5065
|
if (ex instanceof PromptClosedError) {
|
|
@@ -5506,7 +5512,7 @@ ${file.content}`);
|
|
|
5506
5512
|
const { profile, session: openSession } = resolveProfile(context, { session });
|
|
5507
5513
|
const resolvedLocalPath = expandHomePath(localPath);
|
|
5508
5514
|
requirePermittedLocalPath(resolvedLocalPath, "read");
|
|
5509
|
-
if (
|
|
5515
|
+
if (fs7__default.default.existsSync(resolvedLocalPath) == false) {
|
|
5510
5516
|
return textResult(`local file not found: ${resolvedLocalPath}`);
|
|
5511
5517
|
}
|
|
5512
5518
|
const transferredBytes = await withHostConnection(profile, openSession, async (connection) => {
|
|
@@ -5546,7 +5552,7 @@ ${file.content}`);
|
|
|
5546
5552
|
const { profile, session: openSession } = resolveProfile(context, { session });
|
|
5547
5553
|
const resolvedLocalPath = expandHomePath(localPath);
|
|
5548
5554
|
requirePermittedLocalPath(resolvedLocalPath, "write");
|
|
5549
|
-
|
|
5555
|
+
fs7__default.default.mkdirSync(path__default.default.dirname(resolvedLocalPath), { recursive: true });
|
|
5550
5556
|
const transferredBytes = await withHostConnection(profile, openSession, async (connection) => {
|
|
5551
5557
|
return await downloadFile(connection, remotePath, resolvedLocalPath);
|
|
5552
5558
|
});
|