@elisym/mcp 0.15.0 → 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 +629 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { LIMITS, DEFAULT_KIND_OFFSET, SolanaPaymentStrategy, makeCensor, DEFAULT_REDACT_PATHS, validateAgentName, RELAYS, toDTag, JobWaitTimeoutError, decodeJobPayload, utf8ByteLength, formatAssetAmount, estimateNetworkBaseline, formatSol as formatSol$1, USDC_SOLANA_DEVNET, estimateSolFeeLamports, formatFeeBreakdown, resolveAssetFromPaymentRequest as resolveAssetFromPaymentRequest$1, parseAssetAmount, ElisymIdentity, ElisymClient, NATIVE_SOL, resolveKnownAsset,
|
|
2
|
+
import { LIMITS, DEFAULT_KIND_OFFSET, SolanaPaymentStrategy, makeCensor, DEFAULT_REDACT_PATHS, validateAgentName, RELAYS, toDTag, JobWaitTimeoutError, decodeJobPayload, utf8ByteLength, formatAssetAmount, estimateNetworkBaseline, formatSol as formatSol$1, USDC_SOLANA_DEVNET, estimateSolFeeLamports, formatFeeBreakdown, resolveAssetFromPaymentRequest as resolveAssetFromPaymentRequest$1, parseAssetAmount, ElisymIdentity, ElisymClient, NATIVE_SOL, resolveKnownAsset, getProtocolProgramId, getProtocolConfig, assetKey, assetByKey, formatNetworkBaseline, KNOWN_ASSETS } from '@elisym/sdk';
|
|
3
3
|
import { listAgents, createAgentDir, writeYamlInitial, writeExampleSkillTemplate, writeSecrets, resolveAgent, loadAgent, globalConfigPath, writeYaml, writeFileAtomic as writeFileAtomic$1 } from '@elisym/sdk/agent-store';
|
|
4
4
|
import { createIrohTransport, loadGlobalConfig, writeGlobalConfig } from '@elisym/sdk/node';
|
|
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;
|
|
@@ -1244,11 +1734,45 @@ async function assertGitRepo(repoPath) {
|
|
|
1244
1734
|
throw new Error(`"${repoPath}" is not inside a git work tree: ${message}`);
|
|
1245
1735
|
}
|
|
1246
1736
|
}
|
|
1247
|
-
async function
|
|
1737
|
+
async function validateRepoPath(repoPath, options) {
|
|
1248
1738
|
if (repoPath.length > MAX_INPUT_PATH_LEN) {
|
|
1249
1739
|
throw new Error(`repo_path too long: ${repoPath.length} chars (max ${MAX_INPUT_PATH_LEN}).`);
|
|
1250
1740
|
}
|
|
1251
|
-
const
|
|
1741
|
+
const cwd = resolve(process.cwd());
|
|
1742
|
+
const logicalPath = isAbsolute(repoPath) ? resolve(repoPath) : resolve(cwd, repoPath);
|
|
1743
|
+
let absPath;
|
|
1744
|
+
try {
|
|
1745
|
+
absPath = await realpath(logicalPath);
|
|
1746
|
+
} catch (e) {
|
|
1747
|
+
const code = e.code;
|
|
1748
|
+
if (code === "ENOENT") {
|
|
1749
|
+
throw new Error(`repo_path does not exist: ${logicalPath}`);
|
|
1750
|
+
}
|
|
1751
|
+
throw new Error(`Cannot resolve repo_path "${logicalPath}": ${e.message}`);
|
|
1752
|
+
}
|
|
1753
|
+
if (isSensitiveInputPath(absPath) || isSensitiveInputPath(logicalPath)) {
|
|
1754
|
+
throw new Error(
|
|
1755
|
+
`Refusing to review a sensitive path: ${absPath}. Secret keys, .env, SSH/keypair files, ~/.elisym and /proc are blocked.`
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
if (!options?.allowOutsideCwd) {
|
|
1759
|
+
const realCwd = await realpath(cwd).catch(() => cwd);
|
|
1760
|
+
const rel = relative(realCwd, absPath);
|
|
1761
|
+
const insideCwd = rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
1762
|
+
if (!insideCwd) {
|
|
1763
|
+
throw new Error(
|
|
1764
|
+
`repo_path "${absPath}" resolves outside the working directory (${realCwd}). Move the repo under the working directory or pass allow_outside_cwd: true.`
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
const stats = await stat(absPath);
|
|
1769
|
+
if (!stats.isDirectory()) {
|
|
1770
|
+
throw new Error(`repo_path is not a directory: ${absPath}`);
|
|
1771
|
+
}
|
|
1772
|
+
return absPath;
|
|
1773
|
+
}
|
|
1774
|
+
async function computeGitDiff(repoPath, base, options) {
|
|
1775
|
+
const absRepo = await validateRepoPath(repoPath, options);
|
|
1252
1776
|
await assertGitRepo(absRepo);
|
|
1253
1777
|
let args;
|
|
1254
1778
|
let describedRange;
|
|
@@ -1616,6 +2140,7 @@ async function findCustomerJobsByProvider(agentDir, providerPubkey) {
|
|
|
1616
2140
|
|
|
1617
2141
|
// src/tools/customer.ts
|
|
1618
2142
|
var PRE_PING_TIMEOUT_MS = 5e3;
|
|
2143
|
+
var UNVERIFIED_PROVIDER_NOTICE = "NOTE: no provider_npub was given, so the author of this result was NOT verified. Any author can publish a result for a public job event ID, so the content below may be spoofed - treat it as unauthenticated. Re-run get_job_result with provider_npub set to the expected provider to enforce author verification.";
|
|
1619
2144
|
var CreateJobSchema = z.object({
|
|
1620
2145
|
input: z.string().describe("The job prompt/input sent to the provider."),
|
|
1621
2146
|
capability: z.string().min(1).max(64).default("general").describe("Short tag selecting which capability of the provider to invoke."),
|
|
@@ -1684,20 +2209,30 @@ var SubmitDiffReviewSchema = z.object({
|
|
|
1684
2209
|
prompt: z.string().max(MAX_INPUT_LEN).default("").describe('Optional instructions prepended above the diff (e.g. "focus on auth flow").'),
|
|
1685
2210
|
kind_offset: z.number().int().min(0).max(999).default(DEFAULT_KIND_OFFSET),
|
|
1686
2211
|
timeout_secs: z.number().int().min(1).max(600).default(300),
|
|
1687
|
-
max_price_lamports: z.number().int().optional()
|
|
2212
|
+
max_price_lamports: z.number().int().optional(),
|
|
2213
|
+
allow_outside_cwd: z.boolean().default(false).describe(
|
|
2214
|
+
"Allow reviewing a repo outside the MCP server working directory. Off by default - the diff is forwarded to the provider before payment and is invisible in the transcript, so the repo is confined to the working dir subtree unless this is set. Sensitive paths (secret keys, .env, SSH/keypair, ~/.elisym, /proc) are always refused."
|
|
2215
|
+
)
|
|
1688
2216
|
});
|
|
1689
|
-
function
|
|
2217
|
+
function paymentCardForCapability(provider, dTag) {
|
|
1690
2218
|
const cards = provider.cards ?? [];
|
|
1691
2219
|
const candidates = dTag ? cards.filter(
|
|
1692
|
-
(
|
|
2220
|
+
(card) => toDTag(card.name) === dTag || card.capabilities?.some((capability) => toDTag(capability) === dTag)
|
|
1693
2221
|
) : cards;
|
|
1694
2222
|
for (const card of candidates.length > 0 ? candidates : cards) {
|
|
1695
2223
|
if (card.payment?.chain === "solana" && card.payment?.address) {
|
|
1696
|
-
return card
|
|
2224
|
+
return card;
|
|
1697
2225
|
}
|
|
1698
2226
|
}
|
|
1699
2227
|
return void 0;
|
|
1700
2228
|
}
|
|
2229
|
+
function providerSolanaAddress(provider, dTag) {
|
|
2230
|
+
return paymentCardForCapability(provider, dTag)?.payment?.address;
|
|
2231
|
+
}
|
|
2232
|
+
function advertisedPriceForCapability(provider, dTag) {
|
|
2233
|
+
const card = paymentCardForCapability(provider, dTag);
|
|
2234
|
+
return { price: card?.payment?.job_price ?? 0, asset: assetFromCardPayment(card?.payment) };
|
|
2235
|
+
}
|
|
1701
2236
|
function wsUrlFor(httpUrl) {
|
|
1702
2237
|
return httpUrl.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://");
|
|
1703
2238
|
}
|
|
@@ -1747,6 +2282,29 @@ ${formatNetworkBaseline(baseline)}`;
|
|
|
1747
2282
|
return "";
|
|
1748
2283
|
}
|
|
1749
2284
|
}
|
|
2285
|
+
async function confirmPriceGate(opts) {
|
|
2286
|
+
const { agent, providerLabel, capability, price, asset, maxPriceLamports, toolName } = opts;
|
|
2287
|
+
if (maxPriceLamports !== void 0 && price > maxPriceLamports) {
|
|
2288
|
+
return errorResult(
|
|
2289
|
+
`Price ${formatAssetAmount(asset, BigInt(price))} exceeds max ${formatAssetAmount(asset, BigInt(maxPriceLamports))}`
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
if (price > 0 && maxPriceLamports === void 0) {
|
|
2293
|
+
const gasLine = await gasHintForCardAsset(agent, asset);
|
|
2294
|
+
const subject = toolName === "buy_capability" ? `Capability "${capability}" from "${providerLabel}"` : `Job for capability "${capability}" from "${providerLabel}"`;
|
|
2295
|
+
const { text } = sanitizeUntrusted(
|
|
2296
|
+
`${subject} costs ${formatAssetAmount(asset, BigInt(price))}.${gasLine}
|
|
2297
|
+
|
|
2298
|
+
To confirm, call ${toolName} again with max_price_lamports set (e.g. ${price} or higher).`,
|
|
2299
|
+
"text"
|
|
2300
|
+
);
|
|
2301
|
+
return { content: [{ type: "text", text }] };
|
|
2302
|
+
}
|
|
2303
|
+
return null;
|
|
2304
|
+
}
|
|
2305
|
+
function rejectWithProviderError(reject, providerError) {
|
|
2306
|
+
reject(new Error(`Job error: ${sanitizeUntrusted(providerError, "text").text}`));
|
|
2307
|
+
}
|
|
1750
2308
|
var paymentStrategy = new SolanaPaymentStrategy();
|
|
1751
2309
|
async function executePaymentFlow(agent, paymentRequest, jobId, providerPubkey, expectedRecipient) {
|
|
1752
2310
|
let requestData;
|
|
@@ -1989,6 +2547,22 @@ async function executeSubmitAndPay(ctx, agent, params) {
|
|
|
1989
2547
|
`Cannot buy from yourself - your agent's Solana wallet (${buyerWallet}) matches the provider's payment address. Use a different agent or provider.`
|
|
1990
2548
|
);
|
|
1991
2549
|
}
|
|
2550
|
+
const { price: advertisedPrice, asset: advertisedAsset } = advertisedPriceForCapability(
|
|
2551
|
+
provider,
|
|
2552
|
+
params.dTag
|
|
2553
|
+
);
|
|
2554
|
+
const priceGate = await confirmPriceGate({
|
|
2555
|
+
agent,
|
|
2556
|
+
providerLabel: sanitizeField(provider.name || params.providerNpub, 64),
|
|
2557
|
+
capability: params.capability,
|
|
2558
|
+
price: advertisedPrice,
|
|
2559
|
+
asset: advertisedAsset,
|
|
2560
|
+
maxPriceLamports: params.maxPriceLamports,
|
|
2561
|
+
toolName: params.toolName
|
|
2562
|
+
});
|
|
2563
|
+
if (priceGate) {
|
|
2564
|
+
return priceGate;
|
|
2565
|
+
}
|
|
1992
2566
|
const submittedAt = Date.now();
|
|
1993
2567
|
const jobId = await agent.client.marketplace.submitJobRequest(agent.identity, {
|
|
1994
2568
|
input: params.input,
|
|
@@ -2046,7 +2620,7 @@ ${sanitized.text}`);
|
|
|
2046
2620
|
},
|
|
2047
2621
|
onFeedback: payHandler.onFeedback,
|
|
2048
2622
|
onError(error) {
|
|
2049
|
-
reject
|
|
2623
|
+
rejectWithProviderError(reject, error);
|
|
2050
2624
|
},
|
|
2051
2625
|
onTimeout(timeoutMs) {
|
|
2052
2626
|
reject(new JobWaitTimeoutError(timeoutMs));
|
|
@@ -2216,7 +2790,7 @@ var customerTools = [
|
|
|
2216
2790
|
}
|
|
2217
2791
|
},
|
|
2218
2792
|
onError(error) {
|
|
2219
|
-
reject
|
|
2793
|
+
rejectWithProviderError(reject, error);
|
|
2220
2794
|
},
|
|
2221
2795
|
onTimeout(timeoutMs) {
|
|
2222
2796
|
reject(new JobWaitTimeoutError(timeoutMs));
|
|
@@ -2238,6 +2812,11 @@ var customerTools = [
|
|
|
2238
2812
|
}
|
|
2239
2813
|
return errorResult(`Failed to fetch result for event_id="${input.job_event_id}": ${msg}`);
|
|
2240
2814
|
}
|
|
2815
|
+
if (providerPubkey === void 0) {
|
|
2816
|
+
return textResult(`${UNVERIFIED_PROVIDER_NOTICE}
|
|
2817
|
+
|
|
2818
|
+
${result}`);
|
|
2819
|
+
}
|
|
2241
2820
|
return textResult(result);
|
|
2242
2821
|
}
|
|
2243
2822
|
}),
|
|
@@ -2425,7 +3004,7 @@ ${wrapped}`);
|
|
|
2425
3004
|
}),
|
|
2426
3005
|
defineTool({
|
|
2427
3006
|
name: "submit_and_pay_job",
|
|
2428
|
-
description: 'Full customer flow: submit job -> auto-pay -> wait for result. Validates that the payment recipient matches the provider card. If payment succeeded but no result arrives within the wait window, this returns a non-error "still processing" notice with the event ID (NOT a failure) - re-poll get_job_result later (results persist on the relays; for long jobs, poll periodically, e.g. from a subagent). Handles both free and paid providers automatically. If max_price_lamports is not set and
|
|
3007
|
+
description: 'Full customer flow: submit job -> auto-pay -> wait for result. Validates that the payment recipient matches the provider card. If payment succeeded but no result arrives within the wait window, this returns a non-error "still processing" notice with the event ID (NOT a failure) - re-poll get_job_result later (results persist on the relays; for long jobs, poll periodically, e.g. from a subagent). Handles both free and paid providers automatically. If max_price_lamports is not set and the capability is paid, this returns the advertised price for confirmation WITHOUT submitting a job - re-call with max_price_lamports set to approve payments up to that limit (this is a confirmation, not an error). COST: input is sent inline in the tool call, so a large input pays output tokens on the calling LLM. For files or git diffs, prefer submit_and_pay_job_from_file or submit_diff_review respectively.',
|
|
2429
3008
|
schema: SubmitAndPayJobSchema,
|
|
2430
3009
|
async handler(ctx, input) {
|
|
2431
3010
|
ctx.toolRateLimiter.check();
|
|
@@ -2454,7 +3033,8 @@ ${wrapped}`);
|
|
|
2454
3033
|
dTag: toDTag(input.capability),
|
|
2455
3034
|
kindOffset: input.kind_offset,
|
|
2456
3035
|
timeoutMs: Math.min(input.timeout_secs, MAX_TIMEOUT_SECS) * 1e3,
|
|
2457
|
-
maxPriceLamports: input.max_price_lamports
|
|
3036
|
+
maxPriceLamports: input.max_price_lamports,
|
|
3037
|
+
toolName: "submit_and_pay_job"
|
|
2458
3038
|
});
|
|
2459
3039
|
}
|
|
2460
3040
|
}),
|
|
@@ -2506,7 +3086,8 @@ ${wrapped}`);
|
|
|
2506
3086
|
dTag: toDTag(input.capability),
|
|
2507
3087
|
kindOffset: input.kind_offset,
|
|
2508
3088
|
timeoutMs: Math.min(input.timeout_secs, MAX_TIMEOUT_SECS) * 1e3,
|
|
2509
|
-
maxPriceLamports: input.max_price_lamports
|
|
3089
|
+
maxPriceLamports: input.max_price_lamports,
|
|
3090
|
+
toolName: "submit_and_pay_job_from_file"
|
|
2510
3091
|
});
|
|
2511
3092
|
}
|
|
2512
3093
|
}),
|
|
@@ -2519,7 +3100,9 @@ ${wrapped}`);
|
|
|
2519
3100
|
checkLen("provider_npub", input.provider_npub, MAX_NPUB_LEN);
|
|
2520
3101
|
let diffResult;
|
|
2521
3102
|
try {
|
|
2522
|
-
diffResult = await computeGitDiff(input.repo_path, input.base
|
|
3103
|
+
diffResult = await computeGitDiff(input.repo_path, input.base, {
|
|
3104
|
+
allowOutsideCwd: input.allow_outside_cwd
|
|
3105
|
+
});
|
|
2523
3106
|
} catch (e) {
|
|
2524
3107
|
return errorResult(e instanceof Error ? e.message : String(e));
|
|
2525
3108
|
}
|
|
@@ -2548,7 +3131,8 @@ ${diffResult.diff}`;
|
|
|
2548
3131
|
dTag: toDTag(input.capability),
|
|
2549
3132
|
kindOffset: input.kind_offset,
|
|
2550
3133
|
timeoutMs: Math.min(input.timeout_secs, MAX_TIMEOUT_SECS) * 1e3,
|
|
2551
|
-
maxPriceLamports: input.max_price_lamports
|
|
3134
|
+
maxPriceLamports: input.max_price_lamports,
|
|
3135
|
+
toolName: "submit_diff_review"
|
|
2552
3136
|
});
|
|
2553
3137
|
}
|
|
2554
3138
|
}),
|
|
@@ -2590,30 +3174,17 @@ ${diffResult.diff}`;
|
|
|
2590
3174
|
);
|
|
2591
3175
|
return errorResult(text);
|
|
2592
3176
|
}
|
|
2593
|
-
const
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
)
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
`Capability "${input.capability}" from "${safeProviderName}" costs ${formatAssetAmount(cardAsset, BigInt(price))}.${gasLine}
|
|
2605
|
-
|
|
2606
|
-
To confirm, call buy_capability again with max_price_lamports set (e.g. ${price} or higher).`,
|
|
2607
|
-
"text"
|
|
2608
|
-
);
|
|
2609
|
-
return {
|
|
2610
|
-
content: [
|
|
2611
|
-
{
|
|
2612
|
-
type: "text",
|
|
2613
|
-
text
|
|
2614
|
-
}
|
|
2615
|
-
]
|
|
2616
|
-
};
|
|
3177
|
+
const priceGate = await confirmPriceGate({
|
|
3178
|
+
agent,
|
|
3179
|
+
providerLabel: sanitizeField(provider.name || input.provider_npub, 64),
|
|
3180
|
+
capability: input.capability,
|
|
3181
|
+
price: card.payment?.job_price ?? 0,
|
|
3182
|
+
asset: assetFromCardPayment(card.payment),
|
|
3183
|
+
maxPriceLamports: input.max_price_lamports,
|
|
3184
|
+
toolName: "buy_capability"
|
|
3185
|
+
});
|
|
3186
|
+
if (priceGate) {
|
|
3187
|
+
return priceGate;
|
|
2617
3188
|
}
|
|
2618
3189
|
const expectedRecipient = card.payment?.chain === "solana" ? card.payment.address : void 0;
|
|
2619
3190
|
if (agent.solanaKeypair && !expectedRecipient) {
|
|
@@ -2682,7 +3253,7 @@ ${sanitized.text}`
|
|
|
2682
3253
|
},
|
|
2683
3254
|
onFeedback: payHandler.onFeedback,
|
|
2684
3255
|
onError(error) {
|
|
2685
|
-
reject
|
|
3256
|
+
rejectWithProviderError(reject, error);
|
|
2686
3257
|
},
|
|
2687
3258
|
onTimeout(timeoutMs) {
|
|
2688
3259
|
reject(new JobWaitTimeoutError(timeoutMs));
|
|
@@ -4365,7 +4936,10 @@ program.command("init [name]").description("Create a new agent identity").option
|
|
|
4365
4936
|
}
|
|
4366
4937
|
})
|
|
4367
4938
|
);
|
|
4368
|
-
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(
|
|
4369
4943
|
safe(async (options) => {
|
|
4370
4944
|
if (options.list) {
|
|
4371
4945
|
await runList();
|
|
@@ -4374,7 +4948,10 @@ program.command("install").description("Install elisym MCP server into client co
|
|
|
4374
4948
|
}
|
|
4375
4949
|
})
|
|
4376
4950
|
);
|
|
4377
|
-
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(
|
|
4378
4955
|
safe(async (options) => {
|
|
4379
4956
|
await runUpdate({ client: options.client, agent: options.agent });
|
|
4380
4957
|
})
|