@elisym/mcp 0.15.1 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +509 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import { createIrohTransport, loadGlobalConfig, writeGlobalConfig } from '@elisy
|
|
|
5
5
|
import { getBase58Encoder, getBase58Decoder, generateKeyPairSigner, createSolanaRpc, address, createSolanaRpcSubscriptions, sendAndConfirmTransactionFactory, getSignatureFromTransaction, pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, signTransactionMessageWithSigners, createKeyPairSignerFromBytes, isAddress } from '@solana/kit';
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import { generateSecretKey, nip19, getPublicKey } from 'nostr-tools';
|
|
8
|
-
import { realpath, readFile, stat, rm, writeFile, rename, unlink } from 'node:fs/promises';
|
|
9
|
-
import { tmpdir,
|
|
8
|
+
import { realpath, readFile, stat, mkdir, rm, writeFile, rename, unlink } from 'node:fs/promises';
|
|
9
|
+
import { tmpdir, platform, homedir } from 'node:os';
|
|
10
10
|
import { dirname, join, resolve, isAbsolute, basename, relative } from 'node:path';
|
|
11
11
|
import { readFileSync, mkdtempSync } from 'node:fs';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
@@ -370,6 +370,9 @@ function decodeNpub(npub) {
|
|
|
370
370
|
function elisymPackageArgs() {
|
|
371
371
|
return ["-y", `@elisym/mcp@~${PACKAGE_VERSION}`];
|
|
372
372
|
}
|
|
373
|
+
function userHome() {
|
|
374
|
+
return process.env.HOME ?? homedir();
|
|
375
|
+
}
|
|
373
376
|
function validateClientName(name) {
|
|
374
377
|
if (name === void 0) {
|
|
375
378
|
return;
|
|
@@ -399,8 +402,9 @@ async function safeRewriteJson(path, expectedRaw, newConfig) {
|
|
|
399
402
|
var CLIENTS = [
|
|
400
403
|
{
|
|
401
404
|
name: "claude-desktop",
|
|
405
|
+
format: "json",
|
|
402
406
|
configPath() {
|
|
403
|
-
const home =
|
|
407
|
+
const home = userHome();
|
|
404
408
|
switch (platform()) {
|
|
405
409
|
case "darwin":
|
|
406
410
|
return join(home, "Library/Application Support/Claude/claude_desktop_config.json");
|
|
@@ -413,21 +417,29 @@ var CLIENTS = [
|
|
|
413
417
|
},
|
|
414
418
|
{
|
|
415
419
|
name: "claude-code",
|
|
420
|
+
format: "json",
|
|
416
421
|
// Claude Code CLI keeps user-scope MCP servers under `mcpServers` at the top
|
|
417
422
|
// level of `~/.claude.json`. Project-scope (`.mcp.json` in cwd) and local-scope
|
|
418
423
|
// (`projects.<path>.mcpServers` inside the same file) are deliberately not
|
|
419
424
|
// touched here - this installer only writes user scope so the server is
|
|
420
425
|
// available across all projects.
|
|
421
|
-
configPath: () => join(
|
|
426
|
+
configPath: () => join(userHome(), ".claude.json")
|
|
422
427
|
},
|
|
423
428
|
{
|
|
424
429
|
name: "cursor",
|
|
425
|
-
|
|
430
|
+
format: "json",
|
|
431
|
+
configPath: () => join(userHome(), ".cursor/mcp.json")
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
name: "codex",
|
|
435
|
+
format: "codex-toml",
|
|
436
|
+
configPath: () => join(userHome(), ".codex/config.toml")
|
|
426
437
|
},
|
|
427
438
|
{
|
|
428
439
|
name: "windsurf",
|
|
440
|
+
format: "json",
|
|
429
441
|
configPath() {
|
|
430
|
-
const home =
|
|
442
|
+
const home = userHome();
|
|
431
443
|
if (platform() === "darwin") {
|
|
432
444
|
return join(home, "Library/Application Support/Windsurf/mcp.json");
|
|
433
445
|
}
|
|
@@ -480,7 +492,7 @@ async function runInstall(options) {
|
|
|
480
492
|
continue;
|
|
481
493
|
}
|
|
482
494
|
try {
|
|
483
|
-
const result = await installToConfig(path, entry,
|
|
495
|
+
const result = client.format === "codex-toml" ? await installToCodexConfig(path, entry, options.agent) : await installToConfig(path, entry, options.agent);
|
|
484
496
|
if (result === "installed") {
|
|
485
497
|
console.log(`Installed to ${client.name}: ${path}`);
|
|
486
498
|
installed++;
|
|
@@ -499,7 +511,7 @@ async function runInstall(options) {
|
|
|
499
511
|
}
|
|
500
512
|
}
|
|
501
513
|
async function resolveDefaultAgent() {
|
|
502
|
-
const agents = await listAgents(
|
|
514
|
+
const agents = await listAgents(userHome());
|
|
503
515
|
const [first, second] = agents;
|
|
504
516
|
if (!first) {
|
|
505
517
|
return { kind: "none" };
|
|
@@ -523,6 +535,20 @@ async function runUpdate(options) {
|
|
|
523
535
|
if (!path) {
|
|
524
536
|
continue;
|
|
525
537
|
}
|
|
538
|
+
if (client.format === "codex-toml") {
|
|
539
|
+
try {
|
|
540
|
+
const result = await updateCodexConfig(path, options.agent);
|
|
541
|
+
if (result === "updated") {
|
|
542
|
+
console.log(`Updated ${client.name}: ${path} -> @elisym/mcp@~${PACKAGE_VERSION}`);
|
|
543
|
+
updated++;
|
|
544
|
+
}
|
|
545
|
+
} catch (err) {
|
|
546
|
+
if (err.code !== "ENOENT") {
|
|
547
|
+
console.log(`Skipped ${client.name}: ${err.message}`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
526
552
|
let raw;
|
|
527
553
|
try {
|
|
528
554
|
raw = await readFile(path, "utf-8");
|
|
@@ -561,12 +587,16 @@ async function runUpdate(options) {
|
|
|
561
587
|
}
|
|
562
588
|
const entry = existing;
|
|
563
589
|
const newArgs = elisymPackageArgs();
|
|
590
|
+
const packageArg = newArgs[1];
|
|
591
|
+
if (packageArg === void 0) {
|
|
592
|
+
throw new Error("Internal error: missing package argument for elisym MCP install.");
|
|
593
|
+
}
|
|
564
594
|
if (Array.isArray(entry.args)) {
|
|
565
595
|
const idx = entry.args.findIndex(
|
|
566
596
|
(a) => typeof a === "string" && a.startsWith("@elisym/mcp@")
|
|
567
597
|
);
|
|
568
598
|
if (idx >= 0) {
|
|
569
|
-
entry.args[idx] =
|
|
599
|
+
entry.args[idx] = packageArg;
|
|
570
600
|
} else {
|
|
571
601
|
entry.args = newArgs;
|
|
572
602
|
}
|
|
@@ -601,6 +631,16 @@ async function runUninstall(options) {
|
|
|
601
631
|
if (!path) {
|
|
602
632
|
continue;
|
|
603
633
|
}
|
|
634
|
+
if (client.format === "codex-toml") {
|
|
635
|
+
try {
|
|
636
|
+
const result = await uninstallFromCodexConfig(path);
|
|
637
|
+
if (result === "removed") {
|
|
638
|
+
console.log(`Removed from ${client.name}: ${path}`);
|
|
639
|
+
}
|
|
640
|
+
} catch {
|
|
641
|
+
}
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
604
644
|
let raw;
|
|
605
645
|
try {
|
|
606
646
|
raw = await readFile(path, "utf-8");
|
|
@@ -634,8 +674,7 @@ async function runList() {
|
|
|
634
674
|
}
|
|
635
675
|
try {
|
|
636
676
|
const raw = await readFile(path, "utf-8");
|
|
637
|
-
const
|
|
638
|
-
const installed = !!config.mcpServers?.elisym;
|
|
677
|
+
const installed = client.format === "codex-toml" ? findCodexElisymBlock(raw) !== null : !!JSON.parse(raw).mcpServers?.elisym;
|
|
639
678
|
console.log(`${client.name}: ${installed ? "installed" : "available"} (${path})`);
|
|
640
679
|
} catch {
|
|
641
680
|
console.log(`${client.name}: not found`);
|
|
@@ -687,6 +726,457 @@ async function installToConfig(path, entry, agentRebind) {
|
|
|
687
726
|
await safeRewriteJson(path, raw, config);
|
|
688
727
|
return "installed";
|
|
689
728
|
}
|
|
729
|
+
function findCodexElisymBlock(raw) {
|
|
730
|
+
const table = findTomlTableRange(raw, "mcp_servers.elisym");
|
|
731
|
+
if (!table) {
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
return { start: table.start, end: table.end, body: raw.slice(table.start, table.end) };
|
|
735
|
+
}
|
|
736
|
+
function findTomlTableRange(raw, path) {
|
|
737
|
+
return findTomlTableRanges(raw).find((table) => table.path === path) ?? null;
|
|
738
|
+
}
|
|
739
|
+
function findTomlTableRanges(raw) {
|
|
740
|
+
const lines = raw.match(/^.*(?:\n|$)/gm) ?? [];
|
|
741
|
+
const offsets = [];
|
|
742
|
+
let offset = 0;
|
|
743
|
+
for (const line of lines) {
|
|
744
|
+
offsets.push(offset);
|
|
745
|
+
offset += line.length;
|
|
746
|
+
}
|
|
747
|
+
const ranges = [];
|
|
748
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
749
|
+
const path = parseTomlTableHeader(line);
|
|
750
|
+
if (!path) {
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
let end = raw.length;
|
|
754
|
+
for (let nextLineIndex = lineIndex + 1; nextLineIndex < lines.length; nextLineIndex++) {
|
|
755
|
+
if (parseTomlTableHeader(lines[nextLineIndex] ?? "")) {
|
|
756
|
+
end = offsets[nextLineIndex] ?? raw.length;
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
ranges.push({ start: offsets[lineIndex] ?? 0, end, path });
|
|
761
|
+
}
|
|
762
|
+
return ranges;
|
|
763
|
+
}
|
|
764
|
+
function parseTomlTableHeader(line) {
|
|
765
|
+
const match = /^\s*\[([^\]]+)\]\s*(?:#.*)?$/.exec(line.trimEnd());
|
|
766
|
+
return match ? match[1] : null;
|
|
767
|
+
}
|
|
768
|
+
function parseCodexEnv(block) {
|
|
769
|
+
const env = {};
|
|
770
|
+
let section = "other";
|
|
771
|
+
for (const line of block.split(/\r?\n/)) {
|
|
772
|
+
const trimmed = line.trim();
|
|
773
|
+
if (trimmed.startsWith("[")) {
|
|
774
|
+
if (/^\[mcp_servers\.elisym\]\s*(?:#.*)?$/.test(trimmed)) {
|
|
775
|
+
section = "elisym";
|
|
776
|
+
} else if (/^\[mcp_servers\.elisym\.env\]\s*(?:#.*)?$/.test(trimmed)) {
|
|
777
|
+
section = "env";
|
|
778
|
+
} else {
|
|
779
|
+
section = "other";
|
|
780
|
+
}
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
if (trimmed === "" || trimmed.startsWith("#")) {
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
if (section === "elisym") {
|
|
787
|
+
Object.assign(env, parseCodexInlineEnv(trimmed));
|
|
788
|
+
} else if (section === "env") {
|
|
789
|
+
const match = /^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.+?)\s*(?:#.*)?$/.exec(trimmed);
|
|
790
|
+
if (!match) {
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
const [, key, rawValue] = match;
|
|
794
|
+
const value = parseTomlString(rawValue);
|
|
795
|
+
if (value !== void 0) {
|
|
796
|
+
env[key] = value;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return env;
|
|
801
|
+
}
|
|
802
|
+
function parseCodexInlineEnv(line) {
|
|
803
|
+
const env = {};
|
|
804
|
+
const match = /^env\s*=\s*\{(.*)\}\s*(?:#.*)?$/.exec(line);
|
|
805
|
+
if (!match) {
|
|
806
|
+
return env;
|
|
807
|
+
}
|
|
808
|
+
const [, body] = match;
|
|
809
|
+
for (const entry of splitInlineTableEntries(body)) {
|
|
810
|
+
const entryMatch = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.+?)\s*$/.exec(entry);
|
|
811
|
+
if (!entryMatch) {
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
const [, key, rawValue] = entryMatch;
|
|
815
|
+
const value = parseTomlString(rawValue);
|
|
816
|
+
if (value !== void 0) {
|
|
817
|
+
env[key] = value;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return env;
|
|
821
|
+
}
|
|
822
|
+
function splitInlineTableEntries(body) {
|
|
823
|
+
const entries = [];
|
|
824
|
+
let current = "";
|
|
825
|
+
let inString = false;
|
|
826
|
+
let escaped = false;
|
|
827
|
+
for (const char of body) {
|
|
828
|
+
if (escaped) {
|
|
829
|
+
current += char;
|
|
830
|
+
escaped = false;
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
if (char === "\\" && inString) {
|
|
834
|
+
current += char;
|
|
835
|
+
escaped = true;
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
if (char === '"') {
|
|
839
|
+
current += char;
|
|
840
|
+
inString = !inString;
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
if (char === "," && !inString) {
|
|
844
|
+
entries.push(current);
|
|
845
|
+
current = "";
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
current += char;
|
|
849
|
+
}
|
|
850
|
+
if (current.trim() !== "") {
|
|
851
|
+
entries.push(current);
|
|
852
|
+
}
|
|
853
|
+
return entries;
|
|
854
|
+
}
|
|
855
|
+
function parseTomlString(rawValue) {
|
|
856
|
+
if (rawValue.startsWith("'") && rawValue.endsWith("'")) {
|
|
857
|
+
return rawValue.slice(1, -1);
|
|
858
|
+
}
|
|
859
|
+
try {
|
|
860
|
+
return JSON.parse(rawValue);
|
|
861
|
+
} catch {
|
|
862
|
+
return void 0;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
function quoteTomlString(value) {
|
|
866
|
+
return JSON.stringify(value);
|
|
867
|
+
}
|
|
868
|
+
function renderTomlStringArray(values) {
|
|
869
|
+
return `[${values.map((value) => quoteTomlString(value)).join(", ")}]`;
|
|
870
|
+
}
|
|
871
|
+
function renderCodexTomlBlock(entry) {
|
|
872
|
+
const lines = [
|
|
873
|
+
"[mcp_servers.elisym]",
|
|
874
|
+
`command = ${quoteTomlString(String(entry.command ?? "npx"))}`,
|
|
875
|
+
`args = ${renderTomlStringArray(Array.isArray(entry.args) ? entry.args.map(String) : [])}`
|
|
876
|
+
];
|
|
877
|
+
const env = entry.env && typeof entry.env === "object" && !Array.isArray(entry.env) ? entry.env : {};
|
|
878
|
+
const envEntries = Object.entries(env);
|
|
879
|
+
if (envEntries.length > 0) {
|
|
880
|
+
lines.push("", "[mcp_servers.elisym.env]");
|
|
881
|
+
for (const [key, value] of envEntries) {
|
|
882
|
+
lines.push(`${key} = ${quoteTomlString(String(value))}`);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return `${lines.join("\n")}
|
|
886
|
+
`;
|
|
887
|
+
}
|
|
888
|
+
function updateCodexTomlBlock(body, env, rewriteEnv) {
|
|
889
|
+
const withFreshPackagePin = updateCodexPackagePin(body);
|
|
890
|
+
if (!rewriteEnv) {
|
|
891
|
+
return withFreshPackagePin;
|
|
892
|
+
}
|
|
893
|
+
const agent = env.ELISYM_AGENT;
|
|
894
|
+
if (agent === void 0) {
|
|
895
|
+
return withFreshPackagePin;
|
|
896
|
+
}
|
|
897
|
+
return replaceCodexAgentEnv(withFreshPackagePin, agent);
|
|
898
|
+
}
|
|
899
|
+
function updateCodexPackagePin(body) {
|
|
900
|
+
const lines = body.match(/^.*(?:\n|$)/gm) ?? [];
|
|
901
|
+
let section = "other";
|
|
902
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
903
|
+
const trimmed = line.trim();
|
|
904
|
+
if (trimmed.startsWith("[")) {
|
|
905
|
+
if (/^\[mcp_servers\.elisym\]\s*(?:#.*)?$/.test(trimmed)) {
|
|
906
|
+
section = "elisym";
|
|
907
|
+
} else if (/^\[mcp_servers\.elisym\.env\]\s*(?:#.*)?$/.test(trimmed)) {
|
|
908
|
+
section = "env";
|
|
909
|
+
} else {
|
|
910
|
+
section = "other";
|
|
911
|
+
}
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
if (section !== "elisym" || !/^\s*args\s*=/.test(line)) {
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
const packageSpec = `@elisym/mcp@~${PACKAGE_VERSION}`;
|
|
918
|
+
const assignmentEndIndex = findTomlAssignmentEnd(lines, lineIndex);
|
|
919
|
+
const assignmentLines = lines.slice(lineIndex, assignmentEndIndex + 1);
|
|
920
|
+
const packageLineOffset = assignmentLines.findIndex(
|
|
921
|
+
(assignmentLine) => assignmentLine.includes("@elisym/mcp@")
|
|
922
|
+
);
|
|
923
|
+
if (packageLineOffset >= 0) {
|
|
924
|
+
const packageLineIndex = lineIndex + packageLineOffset;
|
|
925
|
+
const packageLine = lines[packageLineIndex] ?? "";
|
|
926
|
+
lines[packageLineIndex] = packageLine.replace(/@elisym\/mcp@[^"\],\s]+/, packageSpec);
|
|
927
|
+
} else {
|
|
928
|
+
lines.splice(
|
|
929
|
+
lineIndex,
|
|
930
|
+
assignmentEndIndex - lineIndex + 1,
|
|
931
|
+
replaceTomlAssignmentValue(line, renderTomlStringArray(elisymPackageArgs()))
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
break;
|
|
935
|
+
}
|
|
936
|
+
return lines.join("");
|
|
937
|
+
}
|
|
938
|
+
function findTomlAssignmentEnd(lines, startIndex) {
|
|
939
|
+
const firstLine = lines[startIndex] ?? "";
|
|
940
|
+
const assignmentStart = firstLine.indexOf("=");
|
|
941
|
+
if (assignmentStart < 0) {
|
|
942
|
+
return startIndex;
|
|
943
|
+
}
|
|
944
|
+
let depth = 0;
|
|
945
|
+
let sawArray = false;
|
|
946
|
+
for (let lineIndex = startIndex; lineIndex < lines.length; lineIndex++) {
|
|
947
|
+
const line = lines[lineIndex] ?? "";
|
|
948
|
+
if (lineIndex > startIndex && /^\s*\[/.test(line) && depth > 0) {
|
|
949
|
+
return lineIndex - 1;
|
|
950
|
+
}
|
|
951
|
+
const scanFrom = lineIndex === startIndex ? assignmentStart + 1 : 0;
|
|
952
|
+
const scan = scanTomlArrayLine(line, scanFrom, depth);
|
|
953
|
+
depth = scan.depth;
|
|
954
|
+
sawArray = sawArray || scan.sawArray;
|
|
955
|
+
if (!sawArray) {
|
|
956
|
+
return startIndex;
|
|
957
|
+
}
|
|
958
|
+
if (depth <= 0) {
|
|
959
|
+
return lineIndex;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return lines.length - 1;
|
|
963
|
+
}
|
|
964
|
+
function scanTomlArrayLine(line, startIndex, initialDepth) {
|
|
965
|
+
let depth = initialDepth;
|
|
966
|
+
let sawArray = false;
|
|
967
|
+
let inString = false;
|
|
968
|
+
let escaped = false;
|
|
969
|
+
for (let charIndex = startIndex; charIndex < line.length; charIndex++) {
|
|
970
|
+
const char = line[charIndex];
|
|
971
|
+
if (escaped) {
|
|
972
|
+
escaped = false;
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
if (char === "\\" && inString) {
|
|
976
|
+
escaped = true;
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
if (char === '"') {
|
|
980
|
+
inString = !inString;
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
if (char === "#" && !inString) {
|
|
984
|
+
break;
|
|
985
|
+
}
|
|
986
|
+
if (char === "[" && !inString) {
|
|
987
|
+
depth++;
|
|
988
|
+
sawArray = true;
|
|
989
|
+
} else if (char === "]" && !inString) {
|
|
990
|
+
depth--;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return { depth, sawArray };
|
|
994
|
+
}
|
|
995
|
+
function replaceTomlAssignmentValue(line, value) {
|
|
996
|
+
const match = /^(\s*[A-Za-z_][A-Za-z0-9_]*\s*=\s*).*(\r?\n)?$/.exec(line);
|
|
997
|
+
if (!match) {
|
|
998
|
+
return line;
|
|
999
|
+
}
|
|
1000
|
+
const [, prefix, newline = ""] = match;
|
|
1001
|
+
return `${prefix}${value}${newline}`;
|
|
1002
|
+
}
|
|
1003
|
+
function replaceCodexAgentEnv(body, agent) {
|
|
1004
|
+
const lines = body.match(/^.*(?:\n|$)/gm) ?? [];
|
|
1005
|
+
let section = "other";
|
|
1006
|
+
let envInsertionIndex = -1;
|
|
1007
|
+
let elisymInsertionIndex = -1;
|
|
1008
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
1009
|
+
const trimmed = line.trim();
|
|
1010
|
+
if (trimmed.startsWith("[")) {
|
|
1011
|
+
if (section === "env" && envInsertionIndex < 0) {
|
|
1012
|
+
envInsertionIndex = lineIndex;
|
|
1013
|
+
}
|
|
1014
|
+
if (section === "elisym") {
|
|
1015
|
+
elisymInsertionIndex = lineIndex;
|
|
1016
|
+
}
|
|
1017
|
+
const header = parseTomlTableHeader(trimmed);
|
|
1018
|
+
if (header === "mcp_servers.elisym") {
|
|
1019
|
+
section = "elisym";
|
|
1020
|
+
} else if (header === "mcp_servers.elisym.env") {
|
|
1021
|
+
section = "env";
|
|
1022
|
+
continue;
|
|
1023
|
+
} else {
|
|
1024
|
+
section = "other";
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
if (section === "env") {
|
|
1028
|
+
if (/^\s*ELISYM_AGENT\s*=/.test(line)) {
|
|
1029
|
+
lines[lineIndex] = replaceTomlAssignmentValue(line, quoteTomlString(agent));
|
|
1030
|
+
return lines.join("");
|
|
1031
|
+
}
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
if (section === "elisym") {
|
|
1035
|
+
elisymInsertionIndex = lineIndex + 1;
|
|
1036
|
+
if (/^\s*env\s*=/.test(line)) {
|
|
1037
|
+
const nextLine = replaceCodexInlineEnvAgent(line, agent);
|
|
1038
|
+
if (nextLine !== null) {
|
|
1039
|
+
lines[lineIndex] = nextLine;
|
|
1040
|
+
return lines.join("");
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
if (section === "env") {
|
|
1046
|
+
envInsertionIndex = lines.length;
|
|
1047
|
+
} else if (section === "elisym") {
|
|
1048
|
+
elisymInsertionIndex = lines.length;
|
|
1049
|
+
}
|
|
1050
|
+
const agentLine = `ELISYM_AGENT = ${quoteTomlString(agent)}
|
|
1051
|
+
`;
|
|
1052
|
+
if (envInsertionIndex >= 0) {
|
|
1053
|
+
lines.splice(envInsertionIndex, 0, agentLine);
|
|
1054
|
+
return lines.join("");
|
|
1055
|
+
}
|
|
1056
|
+
const insertionIndex = elisymInsertionIndex >= 0 ? elisymInsertionIndex : lines.length;
|
|
1057
|
+
lines.splice(insertionIndex, 0, "\n", "[mcp_servers.elisym.env]\n", agentLine);
|
|
1058
|
+
return lines.join("");
|
|
1059
|
+
}
|
|
1060
|
+
function replaceCodexInlineEnvAgent(line, agent) {
|
|
1061
|
+
const match = /^(\s*env\s*=\s*\{)(.*)(\}\s*(?:#.*)?(?:\r?\n)?)$/.exec(line);
|
|
1062
|
+
if (!match) {
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1065
|
+
const [, prefix, body, suffix] = match;
|
|
1066
|
+
const entries = splitInlineTableEntries(body);
|
|
1067
|
+
const nextEntries = [];
|
|
1068
|
+
let replaced = false;
|
|
1069
|
+
for (const entry of entries) {
|
|
1070
|
+
if (/^\s*ELISYM_AGENT\s*=/.test(entry)) {
|
|
1071
|
+
const entryMatch = /^(\s*ELISYM_AGENT\s*=\s*).*$/.exec(entry);
|
|
1072
|
+
if (!entryMatch) {
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
nextEntries.push(`${entryMatch[1]}${quoteTomlString(agent)}`);
|
|
1076
|
+
replaced = true;
|
|
1077
|
+
} else {
|
|
1078
|
+
nextEntries.push(entry);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
if (!replaced) {
|
|
1082
|
+
nextEntries.push(` ELISYM_AGENT = ${quoteTomlString(agent)} `);
|
|
1083
|
+
}
|
|
1084
|
+
return `${prefix}${nextEntries.join(",")}${suffix}`;
|
|
1085
|
+
}
|
|
1086
|
+
function removeCodexElisymTables(raw) {
|
|
1087
|
+
const ranges = findTomlTableRanges(raw).filter((table) => isCodexElisymPath(table.path)).sort((left, right) => right.start - left.start);
|
|
1088
|
+
let nextRaw = raw;
|
|
1089
|
+
for (const range of ranges) {
|
|
1090
|
+
nextRaw = `${nextRaw.slice(0, range.start)}${nextRaw.slice(range.end)}`;
|
|
1091
|
+
}
|
|
1092
|
+
return nextRaw;
|
|
1093
|
+
}
|
|
1094
|
+
function isCodexElisymPath(path) {
|
|
1095
|
+
return path === "mcp_servers.elisym" || path.startsWith("mcp_servers.elisym.");
|
|
1096
|
+
}
|
|
1097
|
+
function replaceCodexBlock(raw, block, replacement) {
|
|
1098
|
+
{
|
|
1099
|
+
let separator = "\n\n";
|
|
1100
|
+
if (raw.length === 0 || raw.endsWith("\n\n")) {
|
|
1101
|
+
separator = "";
|
|
1102
|
+
} else if (raw.endsWith("\n")) {
|
|
1103
|
+
separator = "\n";
|
|
1104
|
+
}
|
|
1105
|
+
return `${raw}${separator}${replacement}`;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
async function installToCodexConfig(path, entry, agentRebind) {
|
|
1109
|
+
let raw;
|
|
1110
|
+
try {
|
|
1111
|
+
raw = await readFile(path, "utf-8");
|
|
1112
|
+
} catch (err) {
|
|
1113
|
+
if (err.code !== "ENOENT") {
|
|
1114
|
+
throw err;
|
|
1115
|
+
}
|
|
1116
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1117
|
+
await writeFileAtomic(path, renderCodexTomlBlock(entry), 384);
|
|
1118
|
+
return "installed";
|
|
1119
|
+
}
|
|
1120
|
+
const block = findCodexElisymBlock(raw);
|
|
1121
|
+
if (block) {
|
|
1122
|
+
if (agentRebind === void 0) {
|
|
1123
|
+
return "unchanged";
|
|
1124
|
+
}
|
|
1125
|
+
const env = parseCodexEnv(raw);
|
|
1126
|
+
if (env.ELISYM_AGENT === agentRebind) {
|
|
1127
|
+
return "unchanged";
|
|
1128
|
+
}
|
|
1129
|
+
env.ELISYM_AGENT = agentRebind;
|
|
1130
|
+
const replacement = updateCodexTomlBlock(raw, env, true);
|
|
1131
|
+
await safeRewriteRaw(path, raw, replacement);
|
|
1132
|
+
return "rebound";
|
|
1133
|
+
}
|
|
1134
|
+
await safeRewriteRaw(path, raw, replaceCodexBlock(raw, null, renderCodexTomlBlock(entry)));
|
|
1135
|
+
return "installed";
|
|
1136
|
+
}
|
|
1137
|
+
async function updateCodexConfig(path, agentOverride) {
|
|
1138
|
+
const raw = await readFile(path, "utf-8");
|
|
1139
|
+
const block = findCodexElisymBlock(raw);
|
|
1140
|
+
if (!block) {
|
|
1141
|
+
return "unchanged";
|
|
1142
|
+
}
|
|
1143
|
+
const env = parseCodexEnv(raw);
|
|
1144
|
+
const existingAgentRaw = typeof env.ELISYM_AGENT === "string" ? env.ELISYM_AGENT : void 0;
|
|
1145
|
+
if (existingAgentRaw !== void 0 && agentOverride === void 0) {
|
|
1146
|
+
validateAgentName(existingAgentRaw);
|
|
1147
|
+
}
|
|
1148
|
+
if (agentOverride !== void 0) {
|
|
1149
|
+
env.ELISYM_AGENT = agentOverride;
|
|
1150
|
+
}
|
|
1151
|
+
const replacement = updateCodexTomlBlock(raw, env, agentOverride !== void 0);
|
|
1152
|
+
await safeRewriteRaw(path, raw, replacement);
|
|
1153
|
+
return "updated";
|
|
1154
|
+
}
|
|
1155
|
+
async function uninstallFromCodexConfig(path) {
|
|
1156
|
+
const raw = await readFile(path, "utf-8");
|
|
1157
|
+
const replacement = removeCodexElisymTables(raw);
|
|
1158
|
+
if (replacement === raw) {
|
|
1159
|
+
return "unchanged";
|
|
1160
|
+
}
|
|
1161
|
+
await safeRewriteRaw(path, raw, replacement);
|
|
1162
|
+
return "removed";
|
|
1163
|
+
}
|
|
1164
|
+
async function safeRewriteRaw(path, expectedRaw, nextRaw) {
|
|
1165
|
+
let recheck;
|
|
1166
|
+
try {
|
|
1167
|
+
recheck = await readFile(path, "utf-8");
|
|
1168
|
+
} catch (err) {
|
|
1169
|
+
throw new Error(
|
|
1170
|
+
`${path} disappeared between read and write: ${err.message}. Re-run after the file is restored.`
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
if (recheck !== expectedRaw) {
|
|
1174
|
+
throw new Error(
|
|
1175
|
+
`${path} was modified by another process during update. Close the MCP client and re-run.`
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
await writeFileAtomic(path, nextRaw, 384);
|
|
1179
|
+
}
|
|
690
1180
|
function ensureIrohTransport(agent) {
|
|
691
1181
|
if (agent.irohTransport) {
|
|
692
1182
|
return agent.irohTransport;
|
|
@@ -4446,7 +4936,10 @@ program.command("init [name]").description("Create a new agent identity").option
|
|
|
4446
4936
|
}
|
|
4447
4937
|
})
|
|
4448
4938
|
);
|
|
4449
|
-
program.command("install").description("Install elisym MCP server into client configs").option(
|
|
4939
|
+
program.command("install").description("Install elisym MCP server into client configs").option(
|
|
4940
|
+
"--client <name>",
|
|
4941
|
+
"Specific client (claude-desktop, claude-code, cursor, codex, windsurf)"
|
|
4942
|
+
).option("--agent <name>", "Bind to specific agent").option("--list", "List detected clients").action(
|
|
4450
4943
|
safe(async (options) => {
|
|
4451
4944
|
if (options.list) {
|
|
4452
4945
|
await runList();
|
|
@@ -4455,7 +4948,10 @@ program.command("install").description("Install elisym MCP server into client co
|
|
|
4455
4948
|
}
|
|
4456
4949
|
})
|
|
4457
4950
|
);
|
|
4458
|
-
program.command("update").description("Refresh the elisym MCP entry in installed client configs").option(
|
|
4951
|
+
program.command("update").description("Refresh the elisym MCP entry in installed client configs").option(
|
|
4952
|
+
"--client <name>",
|
|
4953
|
+
"Specific client (claude-desktop, claude-code, cursor, codex, windsurf)"
|
|
4954
|
+
).option("--agent <name>", "Override the agent binding").action(
|
|
4459
4955
|
safe(async (options) => {
|
|
4460
4956
|
await runUpdate({ client: options.client, agent: options.agent });
|
|
4461
4957
|
})
|