mcp-squared 0.3.3 → 0.3.4
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/dist/index.js +23 -9
- package/dist/tui/config.js +248 -129
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1872,18 +1872,32 @@ function readManifestFile(manifestUrl) {
|
|
|
1872
1872
|
}
|
|
1873
1873
|
function readBundledManifestFile() {
|
|
1874
1874
|
const require2 = createRequire(import.meta.url);
|
|
1875
|
-
|
|
1875
|
+
const candidates = ["../package.json", "../../package.json"];
|
|
1876
|
+
for (const candidate of candidates) {
|
|
1877
|
+
try {
|
|
1878
|
+
return require2(candidate);
|
|
1879
|
+
} catch {}
|
|
1880
|
+
}
|
|
1881
|
+
throw new Error("Unable to resolve bundled package.json");
|
|
1882
|
+
}
|
|
1883
|
+
function getDefaultManifestUrls() {
|
|
1884
|
+
return [
|
|
1885
|
+
new URL("../package.json", import.meta.url),
|
|
1886
|
+
new URL("../../package.json", import.meta.url)
|
|
1887
|
+
];
|
|
1876
1888
|
}
|
|
1877
1889
|
function resolveVersion(options = {}) {
|
|
1878
1890
|
const readManifest = options.readManifest ?? readManifestFile;
|
|
1879
|
-
const
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1891
|
+
const manifestUrls = options.manifestUrls ? [...options.manifestUrls] : options.manifestUrl ? [options.manifestUrl] : getDefaultManifestUrls();
|
|
1892
|
+
for (const manifestUrl of manifestUrls) {
|
|
1893
|
+
try {
|
|
1894
|
+
const manifest = readManifest(manifestUrl);
|
|
1895
|
+
const manifestVersion = normalizeVersion(manifest.version);
|
|
1896
|
+
if (manifestVersion) {
|
|
1897
|
+
return manifestVersion;
|
|
1898
|
+
}
|
|
1899
|
+
} catch {}
|
|
1900
|
+
}
|
|
1887
1901
|
const envVersion = normalizeVersion((options.env ?? process.env)["npm_package_version"]);
|
|
1888
1902
|
if (envVersion) {
|
|
1889
1903
|
return envVersion;
|
package/dist/tui/config.js
CHANGED
|
@@ -1006,18 +1006,32 @@ function readManifestFile(manifestUrl) {
|
|
|
1006
1006
|
}
|
|
1007
1007
|
function readBundledManifestFile() {
|
|
1008
1008
|
const require2 = createRequire(import.meta.url);
|
|
1009
|
-
|
|
1009
|
+
const candidates = ["../package.json", "../../package.json"];
|
|
1010
|
+
for (const candidate of candidates) {
|
|
1011
|
+
try {
|
|
1012
|
+
return require2(candidate);
|
|
1013
|
+
} catch {}
|
|
1014
|
+
}
|
|
1015
|
+
throw new Error("Unable to resolve bundled package.json");
|
|
1016
|
+
}
|
|
1017
|
+
function getDefaultManifestUrls() {
|
|
1018
|
+
return [
|
|
1019
|
+
new URL("../package.json", import.meta.url),
|
|
1020
|
+
new URL("../../package.json", import.meta.url)
|
|
1021
|
+
];
|
|
1010
1022
|
}
|
|
1011
1023
|
function resolveVersion(options = {}) {
|
|
1012
1024
|
const readManifest = options.readManifest ?? readManifestFile;
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1025
|
+
const manifestUrls = options.manifestUrls ? [...options.manifestUrls] : options.manifestUrl ? [options.manifestUrl] : getDefaultManifestUrls();
|
|
1026
|
+
for (const manifestUrl of manifestUrls) {
|
|
1027
|
+
try {
|
|
1028
|
+
const manifest = readManifest(manifestUrl);
|
|
1029
|
+
const manifestVersion = normalizeVersion(manifest.version);
|
|
1030
|
+
if (manifestVersion) {
|
|
1031
|
+
return manifestVersion;
|
|
1032
|
+
}
|
|
1033
|
+
} catch {}
|
|
1034
|
+
}
|
|
1021
1035
|
const envVersion = normalizeVersion((options.env ?? process.env)["npm_package_version"]);
|
|
1022
1036
|
if (envVersion) {
|
|
1023
1037
|
return envVersion;
|
|
@@ -2241,6 +2255,132 @@ async function testUpstreamConnection(name, config, options = {}) {
|
|
|
2241
2255
|
log("Done");
|
|
2242
2256
|
}
|
|
2243
2257
|
}
|
|
2258
|
+
// src/tui/upstream-edit.ts
|
|
2259
|
+
function hasNameConflict(upstreams, nextName, existingName) {
|
|
2260
|
+
return Boolean(existingName && nextName !== existingName && typeof upstreams[nextName] !== "undefined");
|
|
2261
|
+
}
|
|
2262
|
+
function parseKeyValuePairsInput(input) {
|
|
2263
|
+
const result = {};
|
|
2264
|
+
if (!input.trim())
|
|
2265
|
+
return result;
|
|
2266
|
+
const pairs = input.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2267
|
+
for (const pair of pairs) {
|
|
2268
|
+
const eqIndex = pair.indexOf("=");
|
|
2269
|
+
if (eqIndex > 0) {
|
|
2270
|
+
const key = pair.substring(0, eqIndex).trim();
|
|
2271
|
+
const value = pair.substring(eqIndex + 1).trim();
|
|
2272
|
+
if (key) {
|
|
2273
|
+
result[key] = value;
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
return result;
|
|
2278
|
+
}
|
|
2279
|
+
function stringifyKeyValuePairsInput(pairs) {
|
|
2280
|
+
const entries = Object.entries(pairs || {});
|
|
2281
|
+
if (entries.length === 0) {
|
|
2282
|
+
return "";
|
|
2283
|
+
}
|
|
2284
|
+
return entries.map(([key, value]) => `${key}=${value}`).join(", ");
|
|
2285
|
+
}
|
|
2286
|
+
function saveStdioUpstreamFromForm(input) {
|
|
2287
|
+
const trimmedName = input.name.trim();
|
|
2288
|
+
const trimmedCommand = input.commandLine.trim();
|
|
2289
|
+
if (!trimmedName) {
|
|
2290
|
+
return { ok: false, reason: "name_required" };
|
|
2291
|
+
}
|
|
2292
|
+
if (!trimmedCommand) {
|
|
2293
|
+
return { ok: false, reason: "command_required" };
|
|
2294
|
+
}
|
|
2295
|
+
if (hasNameConflict(input.upstreams, trimmedName, input.existingName)) {
|
|
2296
|
+
return { ok: false, reason: "name_conflict" };
|
|
2297
|
+
}
|
|
2298
|
+
const parts = trimmedCommand.split(/\s+/);
|
|
2299
|
+
const command = parts[0] || "";
|
|
2300
|
+
const args = parts.slice(1);
|
|
2301
|
+
const env = parseKeyValuePairsInput(input.envInput);
|
|
2302
|
+
if (input.existingName && input.existingName !== trimmedName) {
|
|
2303
|
+
delete input.upstreams[input.existingName];
|
|
2304
|
+
}
|
|
2305
|
+
input.upstreams[trimmedName] = {
|
|
2306
|
+
transport: "stdio",
|
|
2307
|
+
enabled: input.existingUpstream?.enabled ?? true,
|
|
2308
|
+
env,
|
|
2309
|
+
stdio: { command, args }
|
|
2310
|
+
};
|
|
2311
|
+
return { ok: true, savedName: trimmedName };
|
|
2312
|
+
}
|
|
2313
|
+
function saveSseUpstreamFromForm(input) {
|
|
2314
|
+
const trimmedName = input.name.trim();
|
|
2315
|
+
const trimmedUrl = input.url.trim();
|
|
2316
|
+
if (!trimmedName) {
|
|
2317
|
+
return { ok: false, reason: "name_required" };
|
|
2318
|
+
}
|
|
2319
|
+
if (!trimmedUrl) {
|
|
2320
|
+
return { ok: false, reason: "url_required" };
|
|
2321
|
+
}
|
|
2322
|
+
try {
|
|
2323
|
+
new URL(trimmedUrl);
|
|
2324
|
+
} catch {
|
|
2325
|
+
return { ok: false, reason: "invalid_url" };
|
|
2326
|
+
}
|
|
2327
|
+
if (hasNameConflict(input.upstreams, trimmedName, input.existingName)) {
|
|
2328
|
+
return { ok: false, reason: "name_conflict" };
|
|
2329
|
+
}
|
|
2330
|
+
const headers = parseKeyValuePairsInput(input.headersInput);
|
|
2331
|
+
const env = parseKeyValuePairsInput(input.envInput);
|
|
2332
|
+
if (input.existingName && input.existingName !== trimmedName) {
|
|
2333
|
+
delete input.upstreams[input.existingName];
|
|
2334
|
+
}
|
|
2335
|
+
input.upstreams[trimmedName] = {
|
|
2336
|
+
transport: "sse",
|
|
2337
|
+
enabled: input.existingUpstream?.enabled ?? true,
|
|
2338
|
+
env,
|
|
2339
|
+
sse: {
|
|
2340
|
+
url: trimmedUrl,
|
|
2341
|
+
headers,
|
|
2342
|
+
auth: input.authEnabled ? true : undefined
|
|
2343
|
+
}
|
|
2344
|
+
};
|
|
2345
|
+
return { ok: true, savedName: trimmedName };
|
|
2346
|
+
}
|
|
2347
|
+
function deleteUpstreamByName(upstreams, name) {
|
|
2348
|
+
if (typeof upstreams[name] === "undefined") {
|
|
2349
|
+
return false;
|
|
2350
|
+
}
|
|
2351
|
+
delete upstreams[name];
|
|
2352
|
+
return true;
|
|
2353
|
+
}
|
|
2354
|
+
function getUpstreamEditMenuOptions(upstream) {
|
|
2355
|
+
return [
|
|
2356
|
+
{
|
|
2357
|
+
name: "Edit Configuration",
|
|
2358
|
+
description: "Update connection details and environment",
|
|
2359
|
+
value: "edit"
|
|
2360
|
+
},
|
|
2361
|
+
{
|
|
2362
|
+
name: "Test Connection",
|
|
2363
|
+
description: "Connect and list available tools",
|
|
2364
|
+
value: "test"
|
|
2365
|
+
},
|
|
2366
|
+
{
|
|
2367
|
+
name: upstream.enabled ? "Disable" : "Enable",
|
|
2368
|
+
description: upstream.enabled ? "Stop using this upstream" : "Start using this upstream",
|
|
2369
|
+
value: "toggle"
|
|
2370
|
+
},
|
|
2371
|
+
{
|
|
2372
|
+
name: "Delete",
|
|
2373
|
+
description: "Remove this upstream configuration",
|
|
2374
|
+
value: "delete"
|
|
2375
|
+
},
|
|
2376
|
+
{
|
|
2377
|
+
name: "\u2190 Back",
|
|
2378
|
+
description: "",
|
|
2379
|
+
value: "back"
|
|
2380
|
+
}
|
|
2381
|
+
];
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2244
2384
|
// src/tui/config.ts
|
|
2245
2385
|
var PROJECT_DESCRIPTION = "Mercury Control Plane";
|
|
2246
2386
|
async function runConfigTui() {
|
|
@@ -2572,12 +2712,13 @@ class ConfigTuiApp {
|
|
|
2572
2712
|
menu.focus();
|
|
2573
2713
|
this.addInstructions("\u2191\u2193 Navigate | Enter Select | Esc Back");
|
|
2574
2714
|
}
|
|
2575
|
-
showAddStdioScreen() {
|
|
2715
|
+
showAddStdioScreen(existingName, existingUpstream) {
|
|
2576
2716
|
this.state.currentScreen = "add-stdio";
|
|
2577
2717
|
this.clearScreen();
|
|
2578
2718
|
this.addHeader();
|
|
2579
2719
|
if (!this.container)
|
|
2580
2720
|
return;
|
|
2721
|
+
const isEditMode = Boolean(existingName && existingUpstream);
|
|
2581
2722
|
const formBox = new BoxRenderable(this.renderer, {
|
|
2582
2723
|
id: "add-stdio-box",
|
|
2583
2724
|
width: 60,
|
|
@@ -2585,7 +2726,7 @@ class ConfigTuiApp {
|
|
|
2585
2726
|
border: true,
|
|
2586
2727
|
borderStyle: "single",
|
|
2587
2728
|
borderColor: "#475569",
|
|
2588
|
-
title: "Add Stdio Upstream",
|
|
2729
|
+
title: isEditMode ? `Edit Stdio Upstream: ${existingName}` : "Add Stdio Upstream",
|
|
2589
2730
|
titleAlignment: "center",
|
|
2590
2731
|
backgroundColor: "#1e293b",
|
|
2591
2732
|
flexDirection: "column",
|
|
@@ -2612,6 +2753,7 @@ class ConfigTuiApp {
|
|
|
2612
2753
|
}
|
|
2613
2754
|
});
|
|
2614
2755
|
formBox.add(nameInput);
|
|
2756
|
+
nameInput.value = existingName ?? "";
|
|
2615
2757
|
const commandLabel = new TextRenderable(this.renderer, {
|
|
2616
2758
|
id: "command-label",
|
|
2617
2759
|
content: "Command (with arguments):",
|
|
@@ -2631,6 +2773,7 @@ class ConfigTuiApp {
|
|
|
2631
2773
|
}
|
|
2632
2774
|
});
|
|
2633
2775
|
formBox.add(commandInput);
|
|
2776
|
+
commandInput.value = existingUpstream ? [existingUpstream.stdio.command, ...existingUpstream.stdio.args].filter(Boolean).join(" ") : "";
|
|
2634
2777
|
const envLabel = new TextRenderable(this.renderer, {
|
|
2635
2778
|
id: "env-label",
|
|
2636
2779
|
content: "Environment variables (optional, comma-separated):",
|
|
@@ -2651,6 +2794,7 @@ class ConfigTuiApp {
|
|
|
2651
2794
|
}
|
|
2652
2795
|
});
|
|
2653
2796
|
formBox.add(envInput);
|
|
2797
|
+
envInput.value = stringifyKeyValuePairsInput(existingUpstream?.env);
|
|
2654
2798
|
const submitOptions = [
|
|
2655
2799
|
{ name: "[ Save Upstream ]", description: "", value: "save" },
|
|
2656
2800
|
{ name: "[ Cancel ]", description: "", value: "cancel" }
|
|
@@ -2675,44 +2819,28 @@ class ConfigTuiApp {
|
|
|
2675
2819
|
if (field)
|
|
2676
2820
|
field.focus();
|
|
2677
2821
|
};
|
|
2678
|
-
const parseEnvVars = (input) => {
|
|
2679
|
-
const env = {};
|
|
2680
|
-
if (!input.trim())
|
|
2681
|
-
return env;
|
|
2682
|
-
const pairs = input.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2683
|
-
for (const pair of pairs) {
|
|
2684
|
-
const eqIndex = pair.indexOf("=");
|
|
2685
|
-
if (eqIndex > 0) {
|
|
2686
|
-
const key = pair.substring(0, eqIndex).trim();
|
|
2687
|
-
const value = pair.substring(eqIndex + 1).trim();
|
|
2688
|
-
if (key) {
|
|
2689
|
-
env[key] = value;
|
|
2690
|
-
}
|
|
2691
|
-
}
|
|
2692
|
-
}
|
|
2693
|
-
return env;
|
|
2694
|
-
};
|
|
2695
2822
|
const saveUpstream = () => {
|
|
2696
|
-
const
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2823
|
+
const result = saveStdioUpstreamFromForm({
|
|
2824
|
+
upstreams: this.state.config.upstreams,
|
|
2825
|
+
name: nameInput.value || "",
|
|
2826
|
+
commandLine: commandInput.value || "",
|
|
2827
|
+
envInput: envInput.value || "",
|
|
2828
|
+
existingName: isEditMode ? existingName : undefined,
|
|
2829
|
+
existingUpstream
|
|
2830
|
+
});
|
|
2831
|
+
if (!result.ok) {
|
|
2832
|
+
switch (result.reason) {
|
|
2833
|
+
case "name_required":
|
|
2834
|
+
case "name_conflict":
|
|
2835
|
+
nameInput.focus();
|
|
2836
|
+
return;
|
|
2837
|
+
case "command_required":
|
|
2838
|
+
commandInput.focus();
|
|
2839
|
+
return;
|
|
2840
|
+
default:
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2705
2843
|
}
|
|
2706
|
-
const envVars = parseEnvVars(envInput.value || "");
|
|
2707
|
-
const parts = trimmedCommand.split(/\s+/);
|
|
2708
|
-
const command = parts[0] || "";
|
|
2709
|
-
const args = parts.slice(1);
|
|
2710
|
-
this.state.config.upstreams[trimmedName] = {
|
|
2711
|
-
transport: "stdio",
|
|
2712
|
-
enabled: true,
|
|
2713
|
-
env: envVars,
|
|
2714
|
-
stdio: { command, args }
|
|
2715
|
-
};
|
|
2716
2844
|
this.state.isDirty = true;
|
|
2717
2845
|
cleanup();
|
|
2718
2846
|
this.showUpstreamsScreen();
|
|
@@ -2722,13 +2850,21 @@ class ConfigTuiApp {
|
|
|
2722
2850
|
saveUpstream();
|
|
2723
2851
|
} else {
|
|
2724
2852
|
cleanup();
|
|
2725
|
-
|
|
2853
|
+
if (isEditMode && existingName) {
|
|
2854
|
+
this.showEditUpstreamScreen(existingName);
|
|
2855
|
+
} else {
|
|
2856
|
+
this.showAddUpstreamScreen();
|
|
2857
|
+
}
|
|
2726
2858
|
}
|
|
2727
2859
|
});
|
|
2728
2860
|
const handleKeypress = (key) => {
|
|
2729
2861
|
if (key.name === "escape") {
|
|
2730
2862
|
cleanup();
|
|
2731
|
-
|
|
2863
|
+
if (isEditMode && existingName) {
|
|
2864
|
+
this.showEditUpstreamScreen(existingName);
|
|
2865
|
+
} else {
|
|
2866
|
+
this.showAddUpstreamScreen();
|
|
2867
|
+
}
|
|
2732
2868
|
return;
|
|
2733
2869
|
}
|
|
2734
2870
|
if (key.name === "tab" && !key.shift) {
|
|
@@ -2745,14 +2881,15 @@ class ConfigTuiApp {
|
|
|
2745
2881
|
};
|
|
2746
2882
|
this.renderer.keyInput.on("keypress", handleKeypress);
|
|
2747
2883
|
focusField(0);
|
|
2748
|
-
this.addInstructions("Tab: next field | Shift+Tab: prev | Esc:
|
|
2884
|
+
this.addInstructions("Tab: next field | Shift+Tab: prev | Esc: back");
|
|
2749
2885
|
}
|
|
2750
|
-
showAddSseScreen() {
|
|
2886
|
+
showAddSseScreen(existingName, existingUpstream) {
|
|
2751
2887
|
this.state.currentScreen = "add-sse";
|
|
2752
2888
|
this.clearScreen();
|
|
2753
2889
|
this.addHeader();
|
|
2754
2890
|
if (!this.container)
|
|
2755
2891
|
return;
|
|
2892
|
+
const isEditMode = Boolean(existingName && existingUpstream);
|
|
2756
2893
|
const formBox = new BoxRenderable(this.renderer, {
|
|
2757
2894
|
id: "add-sse-box",
|
|
2758
2895
|
width: 60,
|
|
@@ -2760,7 +2897,7 @@ class ConfigTuiApp {
|
|
|
2760
2897
|
border: true,
|
|
2761
2898
|
borderStyle: "single",
|
|
2762
2899
|
borderColor: "#475569",
|
|
2763
|
-
title: "Add HTTP/SSE Upstream",
|
|
2900
|
+
title: isEditMode ? `Edit HTTP/SSE Upstream: ${existingName}` : "Add HTTP/SSE Upstream",
|
|
2764
2901
|
titleAlignment: "center",
|
|
2765
2902
|
backgroundColor: "#1e293b",
|
|
2766
2903
|
flexDirection: "column",
|
|
@@ -2787,6 +2924,7 @@ class ConfigTuiApp {
|
|
|
2787
2924
|
}
|
|
2788
2925
|
});
|
|
2789
2926
|
formBox.add(nameInput);
|
|
2927
|
+
nameInput.value = existingName ?? "";
|
|
2790
2928
|
const urlLabel = new TextRenderable(this.renderer, {
|
|
2791
2929
|
id: "url-label",
|
|
2792
2930
|
content: "Server URL:",
|
|
@@ -2806,6 +2944,7 @@ class ConfigTuiApp {
|
|
|
2806
2944
|
}
|
|
2807
2945
|
});
|
|
2808
2946
|
formBox.add(urlInput);
|
|
2947
|
+
urlInput.value = existingUpstream?.sse.url ?? "";
|
|
2809
2948
|
const headersLabel = new TextRenderable(this.renderer, {
|
|
2810
2949
|
id: "headers-label",
|
|
2811
2950
|
content: "HTTP Headers (optional, comma-separated):",
|
|
@@ -2826,6 +2965,7 @@ class ConfigTuiApp {
|
|
|
2826
2965
|
}
|
|
2827
2966
|
});
|
|
2828
2967
|
formBox.add(headersInput);
|
|
2968
|
+
headersInput.value = stringifyKeyValuePairsInput(existingUpstream?.sse.headers);
|
|
2829
2969
|
const authLabel = new TextRenderable(this.renderer, {
|
|
2830
2970
|
id: "auth-label",
|
|
2831
2971
|
content: "OAuth Authentication:",
|
|
@@ -2833,7 +2973,14 @@ class ConfigTuiApp {
|
|
|
2833
2973
|
marginBottom: 0
|
|
2834
2974
|
});
|
|
2835
2975
|
formBox.add(authLabel);
|
|
2836
|
-
const authOptions = [
|
|
2976
|
+
const authOptions = existingUpstream?.sse.auth ? [
|
|
2977
|
+
{
|
|
2978
|
+
name: "Enabled (default port 8089)",
|
|
2979
|
+
description: "",
|
|
2980
|
+
value: "enabled"
|
|
2981
|
+
},
|
|
2982
|
+
{ name: "Disabled", description: "", value: "disabled" }
|
|
2983
|
+
] : [
|
|
2837
2984
|
{ name: "Disabled", description: "", value: "disabled" },
|
|
2838
2985
|
{
|
|
2839
2986
|
name: "Enabled (default port 8089)",
|
|
@@ -2874,6 +3021,7 @@ class ConfigTuiApp {
|
|
|
2874
3021
|
}
|
|
2875
3022
|
});
|
|
2876
3023
|
formBox.add(envInput);
|
|
3024
|
+
envInput.value = stringifyKeyValuePairsInput(existingUpstream?.env);
|
|
2877
3025
|
const submitOptions = [
|
|
2878
3026
|
{ name: "[ Save Upstream ]", description: "", value: "save" },
|
|
2879
3027
|
{ name: "[ Cancel ]", description: "", value: "cancel" }
|
|
@@ -2899,63 +3047,41 @@ class ConfigTuiApp {
|
|
|
2899
3047
|
submitSelect
|
|
2900
3048
|
];
|
|
2901
3049
|
let focusIndex = 0;
|
|
2902
|
-
let
|
|
3050
|
+
let selectedAuthValue = String(authOptions[0]?.value ?? "disabled");
|
|
2903
3051
|
const focusField = (index) => {
|
|
2904
3052
|
focusIndex = index;
|
|
2905
3053
|
const field = fields[index];
|
|
2906
3054
|
if (field)
|
|
2907
3055
|
field.focus();
|
|
2908
3056
|
};
|
|
2909
|
-
authSelect.on(SelectRenderableEvents.ITEM_SELECTED, (
|
|
2910
|
-
|
|
2911
|
-
});
|
|
2912
|
-
const parseKeyValuePairs = (input) => {
|
|
2913
|
-
const result = {};
|
|
2914
|
-
if (!input.trim())
|
|
2915
|
-
return result;
|
|
2916
|
-
const pairs = input.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2917
|
-
for (const pair of pairs) {
|
|
2918
|
-
const eqIndex = pair.indexOf("=");
|
|
2919
|
-
if (eqIndex > 0) {
|
|
2920
|
-
const key = pair.substring(0, eqIndex).trim();
|
|
2921
|
-
const value = pair.substring(eqIndex + 1).trim();
|
|
2922
|
-
if (key) {
|
|
2923
|
-
result[key] = value;
|
|
2924
|
-
}
|
|
2925
|
-
}
|
|
2926
|
-
}
|
|
2927
|
-
return result;
|
|
2928
|
-
};
|
|
3057
|
+
authSelect.on(SelectRenderableEvents.ITEM_SELECTED, (_index, opt) => {
|
|
3058
|
+
selectedAuthValue = String(opt.value);
|
|
3059
|
+
});
|
|
2929
3060
|
const saveUpstream = () => {
|
|
2930
|
-
const
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
}
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
env: envVars,
|
|
2953
|
-
sse: {
|
|
2954
|
-
url: trimmedUrl,
|
|
2955
|
-
headers,
|
|
2956
|
-
auth: authEnabled ? true : undefined
|
|
3061
|
+
const result = saveSseUpstreamFromForm({
|
|
3062
|
+
upstreams: this.state.config.upstreams,
|
|
3063
|
+
name: nameInput.value || "",
|
|
3064
|
+
url: urlInput.value || "",
|
|
3065
|
+
headersInput: headersInput.value || "",
|
|
3066
|
+
envInput: envInput.value || "",
|
|
3067
|
+
authEnabled: selectedAuthValue === "enabled",
|
|
3068
|
+
existingName: isEditMode ? existingName : undefined,
|
|
3069
|
+
existingUpstream
|
|
3070
|
+
});
|
|
3071
|
+
if (!result.ok) {
|
|
3072
|
+
switch (result.reason) {
|
|
3073
|
+
case "name_required":
|
|
3074
|
+
case "name_conflict":
|
|
3075
|
+
nameInput.focus();
|
|
3076
|
+
return;
|
|
3077
|
+
case "url_required":
|
|
3078
|
+
case "invalid_url":
|
|
3079
|
+
urlInput.focus();
|
|
3080
|
+
return;
|
|
3081
|
+
default:
|
|
3082
|
+
return;
|
|
2957
3083
|
}
|
|
2958
|
-
}
|
|
3084
|
+
}
|
|
2959
3085
|
this.state.isDirty = true;
|
|
2960
3086
|
cleanup();
|
|
2961
3087
|
this.showUpstreamsScreen();
|
|
@@ -2965,13 +3091,21 @@ class ConfigTuiApp {
|
|
|
2965
3091
|
saveUpstream();
|
|
2966
3092
|
} else {
|
|
2967
3093
|
cleanup();
|
|
2968
|
-
|
|
3094
|
+
if (isEditMode && existingName) {
|
|
3095
|
+
this.showEditUpstreamScreen(existingName);
|
|
3096
|
+
} else {
|
|
3097
|
+
this.showAddUpstreamScreen();
|
|
3098
|
+
}
|
|
2969
3099
|
}
|
|
2970
3100
|
});
|
|
2971
3101
|
const handleKeypress = (key) => {
|
|
2972
3102
|
if (key.name === "escape") {
|
|
2973
3103
|
cleanup();
|
|
2974
|
-
|
|
3104
|
+
if (isEditMode && existingName) {
|
|
3105
|
+
this.showEditUpstreamScreen(existingName);
|
|
3106
|
+
} else {
|
|
3107
|
+
this.showAddUpstreamScreen();
|
|
3108
|
+
}
|
|
2975
3109
|
return;
|
|
2976
3110
|
}
|
|
2977
3111
|
if (key.name === "tab" && !key.shift) {
|
|
@@ -2988,7 +3122,7 @@ class ConfigTuiApp {
|
|
|
2988
3122
|
};
|
|
2989
3123
|
this.renderer.keyInput.on("keypress", handleKeypress);
|
|
2990
3124
|
focusField(0);
|
|
2991
|
-
this.addInstructions("Tab: next field | Shift+Tab: prev | Esc:
|
|
3125
|
+
this.addInstructions("Tab: next field | Shift+Tab: prev | Esc: back");
|
|
2992
3126
|
}
|
|
2993
3127
|
showEditUpstreamScreen(name) {
|
|
2994
3128
|
this.state.currentScreen = "edit-upstream";
|
|
@@ -3091,28 +3225,7 @@ class ConfigTuiApp {
|
|
|
3091
3225
|
});
|
|
3092
3226
|
menuBox.add(noEnvText);
|
|
3093
3227
|
}
|
|
3094
|
-
const options =
|
|
3095
|
-
{
|
|
3096
|
-
name: "Test Connection",
|
|
3097
|
-
description: "Connect and list available tools",
|
|
3098
|
-
value: "test"
|
|
3099
|
-
},
|
|
3100
|
-
{
|
|
3101
|
-
name: upstream.enabled ? "Disable" : "Enable",
|
|
3102
|
-
description: upstream.enabled ? "Stop using this upstream" : "Start using this upstream",
|
|
3103
|
-
value: "toggle"
|
|
3104
|
-
},
|
|
3105
|
-
{
|
|
3106
|
-
name: "Delete",
|
|
3107
|
-
description: "Remove this upstream configuration",
|
|
3108
|
-
value: "delete"
|
|
3109
|
-
},
|
|
3110
|
-
{
|
|
3111
|
-
name: "\u2190 Back",
|
|
3112
|
-
description: "",
|
|
3113
|
-
value: "back"
|
|
3114
|
-
}
|
|
3115
|
-
];
|
|
3228
|
+
const options = getUpstreamEditMenuOptions(upstream);
|
|
3116
3229
|
const menu = new SelectRenderable(this.renderer, {
|
|
3117
3230
|
id: "edit-menu",
|
|
3118
3231
|
width: "100%",
|
|
@@ -3129,6 +3242,13 @@ class ConfigTuiApp {
|
|
|
3129
3242
|
menuBox.add(menu);
|
|
3130
3243
|
menu.on(SelectRenderableEvents.ITEM_SELECTED, (_index, option) => {
|
|
3131
3244
|
switch (option.value) {
|
|
3245
|
+
case "edit":
|
|
3246
|
+
if (upstream.transport === "stdio") {
|
|
3247
|
+
this.showAddStdioScreen(name, upstream);
|
|
3248
|
+
} else {
|
|
3249
|
+
this.showAddSseScreen(name, upstream);
|
|
3250
|
+
}
|
|
3251
|
+
break;
|
|
3132
3252
|
case "test":
|
|
3133
3253
|
this.showTestScreen(name, upstream);
|
|
3134
3254
|
break;
|
|
@@ -3138,8 +3258,7 @@ class ConfigTuiApp {
|
|
|
3138
3258
|
this.showEditUpstreamScreen(name);
|
|
3139
3259
|
break;
|
|
3140
3260
|
case "delete":
|
|
3141
|
-
|
|
3142
|
-
this.state.isDirty = true;
|
|
3261
|
+
this.state.isDirty = deleteUpstreamByName(this.state.config.upstreams, name) || this.state.isDirty;
|
|
3143
3262
|
this.showUpstreamsScreen();
|
|
3144
3263
|
break;
|
|
3145
3264
|
case "back":
|