@elisym/mcp 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { LIMITS, DEFAULT_KIND_OFFSET, SolanaPaymentStrategy, ElisymIdentity, ElisymClient, RELAYS, validateAgentName, serializeConfig, toDTag, getProtocolProgramId, getProtocolConfig } from '@elisym/sdk';
3
- import { generateKeyPairSigner, createKeyPairSignerFromBytes, createSolanaRpc, address, createSolanaRpcSubscriptions, sendAndConfirmTransactionFactory, getSignatureFromTransaction, pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, signTransactionMessageWithSigners, isAddress } from '@solana/kit';
2
+ import { LIMITS, DEFAULT_KIND_OFFSET, SolanaPaymentStrategy, validateAgentName, RELAYS, toDTag, ElisymIdentity, ElisymClient, getProtocolProgramId, getProtocolConfig } from '@elisym/sdk';
3
+ import { generateKeyPairSigner, address, createSolanaRpcSubscriptions, sendAndConfirmTransactionFactory, getSignatureFromTransaction, pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, signTransactionMessageWithSigners, createKeyPairSignerFromBytes, createSolanaRpc, isAddress } from '@solana/kit';
4
4
  import bs58 from 'bs58';
5
5
  import { Command } from 'commander';
6
- import { nip19, generateSecretKey, getPublicKey } from 'nostr-tools';
7
- import { readFile, readdir, stat, mkdir, writeFile, rename, unlink } from 'node:fs/promises';
6
+ import { generateSecretKey, nip19, getPublicKey } from 'nostr-tools';
7
+ import { listAgents, createAgentDir, writeYaml, writeSecrets, loadAgent } from '@elisym/sdk/agent-store';
8
+ import { readFile, writeFile, rename, unlink } from 'node:fs/promises';
8
9
  import { homedir, platform } from 'node:os';
9
10
  import { dirname, join } from 'node:path';
10
- import { parseConfig } from '@elisym/sdk/node';
11
11
  import { readFileSync } from 'node:fs';
12
12
  import { fileURLToPath } from 'node:url';
13
13
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -18,160 +18,83 @@ import { zodToJsonSchema } from 'zod-to-json-schema';
18
18
  import { randomBytes } from 'node:crypto';
19
19
  import { getTransferSolInstruction } from '@solana-program/system';
20
20
 
21
- async function writeFileAtomic(path, content, mode = 384) {
22
- const tmpPath = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
23
- try {
24
- await writeFile(tmpPath, content, { mode });
25
- await rename(tmpPath, path);
26
- } catch (err) {
27
- try {
28
- await unlink(tmpPath);
29
- } catch {
30
- }
31
- throw err;
32
- }
33
- }
34
-
35
- // src/config.ts
36
- function rawConfigIsEncrypted(raw) {
37
- return raw.includes('"encrypted:v1:');
38
- }
39
- function agentsDir() {
40
- return join(homedir(), ".elisym", "agents");
41
- }
42
- function agentConfigPath(name) {
43
- return join(agentsDir(), name, "config.json");
44
- }
45
- function coerceNetwork(raw) {
46
- return raw === "mainnet" ? "mainnet" : "devnet";
47
- }
48
- async function loadAgentConfig(name, passphrase) {
49
- validateAgentName(name);
50
- const configPath = agentConfigPath(name);
51
- const raw = await readFile(configPath, "utf-8");
52
- const rawEncrypted = rawConfigIsEncrypted(raw);
53
- const effectivePassphrase = process.env.ELISYM_PASSPHRASE;
54
- if (rawEncrypted && !effectivePassphrase) {
55
- throw new Error(
56
- `Agent "${name}" has an encrypted config but no passphrase. Set the ELISYM_PASSPHRASE environment variable.`
57
- );
21
+ function coerceNetwork(raw, name) {
22
+ if (raw === void 0 || raw === "devnet") {
23
+ return "devnet";
58
24
  }
59
- let config;
60
- try {
61
- config = parseConfig(raw, effectivePassphrase);
62
- } catch (e) {
63
- const msg = e instanceof Error ? e.message : String(e);
25
+ if (raw === "mainnet") {
64
26
  throw new Error(
65
- `Failed to load agent "${name}": ${msg}. If this config was created by an earlier version, delete ~/.elisym/agents/${name} and run "elisym-mcp init ${name}" again.`
27
+ `Agent "${name}" is configured for mainnet, which is not supported until the elisym-config program is deployed there. Re-create the agent with --network devnet: rm -rf ~/.elisym/${name} && elisym-mcp init ${name} --network devnet`
66
28
  );
67
29
  }
68
- const network = coerceNetwork(config.wallet?.network ?? config.payments?.[0]?.network);
30
+ throw new Error(`Agent "${name}" has unsupported network "${raw}". Expected "devnet".`);
31
+ }
32
+ async function loadAgentConfig(name, passphrase) {
33
+ validateAgentName(name);
34
+ const loaded = await loadAgent(name, process.cwd(), passphrase);
35
+ const solPayment = loaded.yaml.payments.find((entry) => entry.chain === "solana");
36
+ const network = coerceNetwork(solPayment?.network, name);
69
37
  return {
70
- nostrSecretKey: config.identity.secret_key,
71
- solanaSecretKey: config.wallet?.secret_key,
72
- relays: config.relays?.length ? config.relays : void 0,
38
+ nostrSecretKey: loaded.secrets.nostr_secret_key,
39
+ solanaSecretKey: loaded.secrets.solana_secret_key,
40
+ relays: loaded.yaml.relays.length > 0 ? loaded.yaml.relays : void 0,
73
41
  network,
74
- payments: config.payments,
75
- security: config.security ?? {},
76
- encrypted: rawEncrypted
42
+ payments: loaded.yaml.payments.length > 0 ? loaded.yaml.payments.map((payment2) => ({
43
+ chain: payment2.chain,
44
+ network: payment2.network,
45
+ address: payment2.address
46
+ })) : void 0,
47
+ security: {
48
+ withdrawals_enabled: loaded.yaml.security.withdrawals_enabled,
49
+ agent_switch_enabled: loaded.yaml.security.agent_switch_enabled
50
+ },
51
+ encrypted: loaded.encrypted
77
52
  };
78
53
  }
79
- async function saveAgentConfig(name, config) {
54
+ async function saveAgentConfig(name, input) {
80
55
  validateAgentName(name);
81
- const agentDirPath = join(agentsDir(), name);
82
- await mkdir(agentDirPath, { recursive: true, mode: 448 });
83
- const network = config.network ?? "devnet";
84
- const { encryptSecret } = await import('@elisym/sdk/node');
85
- const encrypt = (v) => config.passphrase ? encryptSecret(v, config.passphrase) : v;
86
- const nostrSecret = encrypt(config.nostrSecretKey);
87
- const solanaSecret = config.solanaSecretKey ? encrypt(config.solanaSecretKey) : void 0;
88
- const agentConfig = {
89
- identity: {
90
- secret_key: nostrSecret,
91
- name: config.name,
92
- description: config.description
93
- },
94
- relays: config.relays,
95
- capabilities: config.capabilities ?? [],
96
- ...config.solanaAddress && {
97
- payments: [
98
- {
99
- chain: "solana",
100
- network,
101
- address: config.solanaAddress
102
- }
103
- ]
104
- },
105
- ...solanaSecret && {
106
- wallet: {
107
- chain: "solana",
108
- network,
109
- secret_key: solanaSecret
110
- }
56
+ const created = await createAgentDir({ target: "home", name, cwd: process.cwd() });
57
+ const network = input.network ?? "devnet";
58
+ await writeYaml(created.dir, {
59
+ display_name: void 0,
60
+ description: input.description,
61
+ picture: void 0,
62
+ banner: void 0,
63
+ relays: input.relays,
64
+ payments: input.solanaAddress ? [{ chain: "solana", network, address: input.solanaAddress }] : [],
65
+ llm: void 0,
66
+ security: input.security ?? {}
67
+ });
68
+ await writeSecrets(
69
+ created.dir,
70
+ {
71
+ nostr_secret_key: input.nostrSecretKey,
72
+ solana_secret_key: input.solanaSecretKey
111
73
  },
112
- ...config.security && { security: config.security }
113
- };
114
- await writeFileAtomic(join(agentDirPath, "config.json"), serializeConfig(agentConfig), 384);
74
+ input.passphrase
75
+ );
115
76
  }
116
77
  async function updateAgentSecurity(name, patch, passphrase) {
117
- validateAgentName(name);
118
- const configPath = agentConfigPath(name);
119
- const raw = await readFile(configPath, "utf-8");
120
- const effectivePassphrase = process.env.ELISYM_PASSPHRASE;
121
- const config = parseConfig(raw, effectivePassphrase);
122
- const rawEncrypted = rawConfigIsEncrypted(raw);
123
- if (rawEncrypted && !effectivePassphrase) {
124
- throw new Error(
125
- `Agent "${name}" is encrypted - set ELISYM_PASSPHRASE to update security flags.`
126
- );
127
- }
128
- const merged = { ...config.security, ...patch };
129
- const { encryptSecret } = await import('@elisym/sdk/node');
130
- const encrypt = (v) => effectivePassphrase ? encryptSecret(v, effectivePassphrase) : v;
131
- const next = {
132
- ...config,
133
- // parseConfig returns decrypted secrets; re-encrypt them if the file was encrypted
134
- identity: {
135
- ...config.identity,
136
- secret_key: rawEncrypted ? encrypt(config.identity.secret_key) : config.identity.secret_key
137
- },
138
- wallet: config.wallet ? {
139
- ...config.wallet,
140
- secret_key: rawEncrypted ? encrypt(config.wallet.secret_key) : config.wallet.secret_key
141
- } : void 0,
142
- security: merged
143
- };
144
- await writeFileAtomic(configPath, serializeConfig(next), 384);
78
+ const loaded = await loadAgent(name, process.cwd(), passphrase);
79
+ const merged = { ...loaded.yaml.security, ...patch };
80
+ await writeYaml(loaded.dir, { ...loaded.yaml, security: merged });
145
81
  return merged;
146
82
  }
147
83
  async function listAgentNames() {
148
- try {
149
- const entries = await readdir(agentsDir());
150
- const names = [];
151
- for (const entry of entries) {
152
- try {
153
- await stat(join(agentsDir(), entry, "config.json"));
154
- names.push(entry);
155
- } catch {
156
- }
157
- }
158
- return names;
159
- } catch {
160
- return [];
161
- }
84
+ const agents = await listAgents(process.cwd());
85
+ return agents.map((agent) => agent.name);
162
86
  }
163
- function rpcUrlFor(network) {
164
- return network === "mainnet" ? "https://api.mainnet-beta.solana.com" : "https://api.devnet.solana.com";
87
+ function rpcUrlFor(_network) {
88
+ return "https://api.devnet.solana.com";
165
89
  }
166
- async function fetchProtocolConfig(network) {
167
- const cluster = network === "mainnet" ? "mainnet" : "devnet";
168
- const programId = getProtocolProgramId(cluster);
169
- const rpc = createSolanaRpc(rpcUrlFor(cluster));
90
+ async function fetchProtocolConfig(_network) {
91
+ const programId = getProtocolProgramId("devnet");
92
+ const rpc = createSolanaRpc(rpcUrlFor());
170
93
  const config = await getProtocolConfig(rpc, programId, { forceRefresh: true });
171
94
  return { feeBps: config.feeBps, treasury: config.treasury };
172
95
  }
173
- function explorerClusterFor(network) {
174
- return network === "mainnet" ? "mainnet-beta" : "devnet";
96
+ function explorerClusterFor(_network) {
97
+ return "devnet";
175
98
  }
176
99
  var RateLimiter = class {
177
100
  constructor(maxCalls, windowSecs) {
@@ -251,6 +174,19 @@ var AgentContext = class _AgentContext {
251
174
  return nonce;
252
175
  }
253
176
  };
177
+ async function writeFileAtomic(path, content, mode = 384) {
178
+ const tmpPath = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
179
+ try {
180
+ await writeFile(tmpPath, content, { mode });
181
+ await rename(tmpPath, path);
182
+ } catch (err) {
183
+ try {
184
+ await unlink(tmpPath);
185
+ } catch {
186
+ }
187
+ throw err;
188
+ }
189
+ }
254
190
  var LAMPORTS_PER_SOL = 1000000000n;
255
191
  function readPackageVersion() {
256
192
  try {
@@ -632,7 +568,10 @@ var CreateAgentSchema = z.object({
632
568
  // customer-mode in 0.1.x and never publishes a NIP-89 capability card,
633
569
  // so an advertised capability list would be misleading. Provider-mode
634
570
  // (0.2.0) will reintroduce this field.
635
- network: z.enum(["devnet", "mainnet"]).default("devnet"),
571
+ // Only devnet is supported until the elisym-config program ships on mainnet.
572
+ // Mainnet was previously advertised here but every paid flow then crashed at
573
+ // payment time - rejecting at schema level surfaces the error up front.
574
+ network: z.enum(["devnet"]).default("devnet"),
636
575
  passphrase: z.string().optional().describe("Optional passphrase; if set, secret keys are encrypted at rest."),
637
576
  activate: z.boolean().default(true)
638
577
  });
@@ -680,7 +619,7 @@ async function buildAgentInstance(name, config) {
680
619
  var agentTools = [
681
620
  defineTool({
682
621
  name: "create_agent",
683
- description: "Create a new agent identity. Generates Nostr keypair and Solana wallet, saves config to ~/.elisym/agents/<name>/. When activate=true (default), the current active agent must have `security.agent_switch_enabled` set to true, otherwise the new agent is created but NOT activated (pass activate=false or run `elisym-mcp enable-agent-switch <current-agent>`).",
622
+ description: "Create a new agent identity. Generates Nostr keypair and Solana wallet, saves config to ~/.elisym/<name>/. When activate=true (default), the current active agent must have `security.agent_switch_enabled` set to true, otherwise the new agent is created but NOT activated (pass activate=false or run `elisym-mcp enable-agent-switch <current-agent>`).",
684
623
  schema: CreateAgentSchema,
685
624
  async handler(ctx, input) {
686
625
  ctx.toolRateLimiter.check();
@@ -1193,18 +1132,34 @@ function makePaymentFeedbackHandler(opts) {
1193
1132
  );
1194
1133
  return;
1195
1134
  }
1196
- if (opts.maxPriceLamports === void 0 && amount !== void 0 && amount > 0) {
1135
+ let parsedRequest;
1136
+ try {
1137
+ parsedRequest = JSON.parse(paymentRequest);
1138
+ } catch {
1139
+ opts.rejectPayment(new Error("Provider sent a malformed payment_request (not valid JSON)."));
1140
+ return;
1141
+ }
1142
+ const signedAmount = typeof parsedRequest.amount === "number" && Number.isInteger(parsedRequest.amount) && parsedRequest.amount > 0 ? parsedRequest.amount : void 0;
1143
+ if (amount !== void 0 && amount > 0 && signedAmount !== void 0 && amount !== signedAmount) {
1144
+ opts.rejectPayment(
1145
+ new Error(
1146
+ `Payment request mismatch: feedback tag amount=${amount} differs from signed amount=${signedAmount}. Refusing to proceed.`
1147
+ )
1148
+ );
1149
+ return;
1150
+ }
1151
+ if (opts.maxPriceLamports === void 0 && signedAmount !== void 0) {
1197
1152
  opts.rejectPayment(
1198
1153
  new Error(
1199
- `Payment of ${formatSol(BigInt(amount))} required but no max_price_lamports set. Retry with max_price_lamports to approve.`
1154
+ `Payment of ${formatSol(BigInt(signedAmount))} required but no max_price_lamports set. Retry with max_price_lamports to approve.`
1200
1155
  )
1201
1156
  );
1202
1157
  return;
1203
1158
  }
1204
- if (opts.maxPriceLamports !== void 0 && amount !== void 0 && amount > opts.maxPriceLamports) {
1159
+ if (opts.maxPriceLamports !== void 0 && signedAmount !== void 0 && signedAmount > opts.maxPriceLamports) {
1205
1160
  opts.rejectPayment(
1206
1161
  new Error(
1207
- `Price ${formatSol(BigInt(amount))} exceeds max ${formatSol(BigInt(opts.maxPriceLamports))}`
1162
+ `Price ${formatSol(BigInt(signedAmount))} exceeds max ${formatSol(BigInt(opts.maxPriceLamports))}`
1208
1163
  )
1209
1164
  );
1210
1165
  return;
@@ -1212,7 +1167,7 @@ function makePaymentFeedbackHandler(opts) {
1212
1167
  if (!opts.agent.solanaKeypair) {
1213
1168
  opts.resolveNoWallet(
1214
1169
  `Payment required but no Solana wallet configured.
1215
- Amount: ${amount ? formatSol(BigInt(amount)) : "unknown"}
1170
+ Amount: ${signedAmount !== void 0 ? formatSol(BigInt(signedAmount)) : "unknown"}
1216
1171
  Payment request: ${paymentRequest}`
1217
1172
  );
1218
1173
  return;
@@ -1642,7 +1597,7 @@ ${result}`);
1642
1597
  var GetDashboardSchema = z.object({
1643
1598
  top_n: z.number().int().min(1).max(100).default(10),
1644
1599
  chain: z.enum(["solana"]).default("solana"),
1645
- network: z.enum(["devnet", "mainnet"]).optional(),
1600
+ network: z.enum(["devnet"]).optional(),
1646
1601
  timeout_secs: z.number().int().min(1).max(60).default(15)
1647
1602
  });
1648
1603
  var dashboardTools = [
@@ -2382,143 +2337,170 @@ process.on("warning", (w) => {
2382
2337
  }
2383
2338
  console.warn(w);
2384
2339
  });
2385
- var program = new Command().name("elisym-mcp").description("MCP server for the elisym agent network").version(PACKAGE_VERSION);
2386
- program.action(async () => {
2387
- const ctx = new AgentContext();
2388
- const agentName = process.env.ELISYM_AGENT;
2389
- const nostrSecret = process.env.ELISYM_NOSTR_SECRET;
2390
- if (agentName) {
2340
+ function safe(fn) {
2341
+ return async (...args) => {
2391
2342
  try {
2392
- const config = await loadAgentConfig(agentName);
2393
- const instance = await buildAgentInstance(agentName, config);
2394
- ctx.register(instance);
2395
- console.error(`Loaded agent: ${agentName}`);
2343
+ await fn(...args);
2396
2344
  } catch (e) {
2397
- console.error(`Failed to load agent "${agentName}": ${e.message}`);
2345
+ console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
2398
2346
  process.exit(1);
2399
2347
  }
2400
- } else if (nostrSecret) {
2401
- let identity;
2402
- if (nostrSecret.startsWith("nsec")) {
2403
- const decoded = nip19.decode(nostrSecret);
2404
- if (decoded.type !== "nsec") {
2405
- console.error(`ELISYM_NOSTR_SECRET: expected nsec, got ${decoded.type}`);
2406
- process.exit(1);
2407
- }
2408
- identity = ElisymIdentity.fromSecretKey(decoded.data);
2409
- } else {
2410
- identity = ElisymIdentity.fromHex(nostrSecret);
2411
- }
2412
- const client = new ElisymClient({ relays: RELAYS });
2413
- const name = process.env.ELISYM_AGENT_NAME ?? "mcp-agent";
2414
- const network = process.env.ELISYM_NETWORK === "mainnet" ? "mainnet" : "devnet";
2415
- ctx.register({ client, identity, name, network, security: {} });
2416
- console.error(`Ephemeral agent: ${name} (${network})`);
2417
- } else {
2418
- const names = (await listAgentNames()).slice().sort();
2419
- if (names.length > 0) {
2420
- const name = names[0];
2348
+ };
2349
+ }
2350
+ var program = new Command().name("elisym-mcp").description("MCP server for the elisym agent network").version(PACKAGE_VERSION);
2351
+ program.action(
2352
+ safe(async () => {
2353
+ const ctx = new AgentContext();
2354
+ const agentName = process.env.ELISYM_AGENT;
2355
+ const nostrSecret = process.env.ELISYM_NOSTR_SECRET;
2356
+ if (agentName) {
2421
2357
  try {
2422
- const config = await loadAgentConfig(name);
2423
- const instance = await buildAgentInstance(name, config);
2358
+ const config = await loadAgentConfig(agentName);
2359
+ const instance = await buildAgentInstance(agentName, config);
2424
2360
  ctx.register(instance);
2425
- console.error(`Loaded default agent: ${name} (${instance.network})`);
2361
+ console.error(`Loaded agent: ${agentName}`);
2426
2362
  } catch (e) {
2427
- console.error(`Failed to load agent "${name}": ${e.message}`);
2363
+ console.error(`Failed to load agent "${agentName}": ${e.message}`);
2428
2364
  process.exit(1);
2429
2365
  }
2430
- } else {
2431
- const identity = ElisymIdentity.generate();
2366
+ } else if (nostrSecret) {
2367
+ let identity;
2368
+ if (nostrSecret.startsWith("nsec")) {
2369
+ const decoded = nip19.decode(nostrSecret);
2370
+ if (decoded.type !== "nsec") {
2371
+ console.error(`ELISYM_NOSTR_SECRET: expected nsec, got ${decoded.type}`);
2372
+ process.exit(1);
2373
+ }
2374
+ identity = ElisymIdentity.fromSecretKey(decoded.data);
2375
+ } else {
2376
+ identity = ElisymIdentity.fromHex(nostrSecret);
2377
+ }
2432
2378
  const client = new ElisymClient({ relays: RELAYS });
2433
- ctx.register({ client, identity, name: "mcp-agent", network: "devnet", security: {} });
2434
- console.error("Created ephemeral agent (no persistent identity, devnet).");
2379
+ const name = process.env.ELISYM_AGENT_NAME ?? "mcp-agent";
2380
+ if (process.env.ELISYM_NETWORK && process.env.ELISYM_NETWORK !== "devnet") {
2381
+ console.error(
2382
+ `ELISYM_NETWORK="${process.env.ELISYM_NETWORK}" is not supported. Only "devnet" is available until the on-chain protocol program ships on mainnet.`
2383
+ );
2384
+ process.exit(1);
2385
+ }
2386
+ ctx.register({ client, identity, name, network: "devnet", security: {} });
2387
+ console.error(`Ephemeral agent: ${name} (devnet)`);
2388
+ } else {
2389
+ const names = (await listAgentNames()).slice().sort();
2390
+ if (names.length > 0) {
2391
+ const name = names[0];
2392
+ try {
2393
+ const config = await loadAgentConfig(name);
2394
+ const instance = await buildAgentInstance(name, config);
2395
+ ctx.register(instance);
2396
+ console.error(`Loaded default agent: ${name} (${instance.network})`);
2397
+ } catch (e) {
2398
+ console.error(`Failed to load agent "${name}": ${e.message}`);
2399
+ process.exit(1);
2400
+ }
2401
+ } else {
2402
+ const identity = ElisymIdentity.generate();
2403
+ const client = new ElisymClient({ relays: RELAYS });
2404
+ ctx.register({ client, identity, name: "mcp-agent", network: "devnet", security: {} });
2405
+ console.error("Created ephemeral agent (no persistent identity, devnet).");
2406
+ }
2435
2407
  }
2436
- }
2437
- await startServer(ctx);
2438
- });
2439
- program.command("init [name]").description("Create a new agent identity").option("-d, --description <desc>", "Agent description", "Elisym MCP agent").option("-n, --network <network>", "Solana network (devnet|mainnet)", "devnet").option("--install", "Also install into MCP clients").action(async (name, options) => {
2440
- const { default: inquirer } = await import('inquirer');
2441
- if (!name) {
2442
- const answers = await inquirer.prompt([
2443
- {
2444
- type: "input",
2445
- name: "name",
2446
- message: "Agent name:",
2447
- validate: (v) => /^[a-zA-Z0-9_-]+$/.test(v) || "Alphanumeric, _, - only"
2448
- },
2449
- {
2450
- type: "input",
2451
- name: "description",
2452
- message: "Description:",
2453
- default: "Elisym MCP agent"
2454
- },
2408
+ await startServer(ctx);
2409
+ })
2410
+ );
2411
+ program.command("init [name]").description("Create a new agent identity").option("-d, --description <desc>", "Agent description", "Elisym MCP agent").option("-n, --network <network>", "Solana network (devnet only)", "devnet").option("--install", "Also install into MCP clients").action(
2412
+ safe(async (name, options) => {
2413
+ const { default: inquirer } = await import('inquirer');
2414
+ if (!name) {
2415
+ const answers = await inquirer.prompt([
2416
+ {
2417
+ type: "input",
2418
+ name: "name",
2419
+ message: "Agent name:",
2420
+ validate: (v) => /^[a-zA-Z0-9_-]+$/.test(v) || "Alphanumeric, _, - only"
2421
+ },
2422
+ {
2423
+ type: "input",
2424
+ name: "description",
2425
+ message: "Description:",
2426
+ default: "Elisym MCP agent"
2427
+ },
2428
+ {
2429
+ type: "list",
2430
+ name: "network",
2431
+ message: "Solana network:",
2432
+ // Only devnet is supported until the elisym-config program ships on mainnet.
2433
+ choices: ["devnet"],
2434
+ default: "devnet"
2435
+ }
2436
+ ]);
2437
+ name = answers.name;
2438
+ options.description = answers.description;
2439
+ options.network = answers.network;
2440
+ }
2441
+ if (options.network !== "devnet") {
2442
+ console.error(
2443
+ `Network must be "devnet", got "${options.network}". Mainnet is not supported until the on-chain protocol program is deployed.`
2444
+ );
2445
+ process.exit(1);
2446
+ }
2447
+ const { passphrase } = await inquirer.prompt([
2455
2448
  {
2456
- type: "list",
2457
- name: "network",
2458
- message: "Solana network:",
2459
- // testnet removed - only devnet and mainnet are supported.
2460
- choices: ["devnet", "mainnet"],
2461
- default: "devnet"
2449
+ type: "password",
2450
+ name: "passphrase",
2451
+ message: "Passphrase to encrypt secret keys (leave blank for none):",
2452
+ mask: "*"
2462
2453
  }
2463
2454
  ]);
2464
- name = answers.name;
2465
- options.description = answers.description;
2466
- options.network = answers.network;
2467
- }
2468
- if (options.network !== "devnet" && options.network !== "mainnet") {
2469
- console.error(`Network must be "devnet" or "mainnet", got "${options.network}".`);
2470
- process.exit(1);
2471
- }
2472
- const { passphrase } = await inquirer.prompt([
2473
- {
2474
- type: "password",
2475
- name: "passphrase",
2476
- message: "Passphrase to encrypt secret keys (leave blank for none):",
2477
- mask: "*"
2455
+ const nostrSecretKey = generateSecretKey();
2456
+ const nostrPubkey = getPublicKey(nostrSecretKey);
2457
+ const solanaSigner = await generateKeyPairSigner(true);
2458
+ const solanaSecretBytes = await exportKeyPairBytes(solanaSigner);
2459
+ await saveAgentConfig(name, {
2460
+ name,
2461
+ description: options.description,
2462
+ relays: [...RELAYS],
2463
+ nostrSecretKey: Buffer.from(nostrSecretKey).toString("hex"),
2464
+ solanaSecretKey: bs58.encode(solanaSecretBytes),
2465
+ solanaAddress: solanaSigner.address,
2466
+ network: "devnet",
2467
+ security: { withdrawals_enabled: false, agent_switch_enabled: false },
2468
+ passphrase: passphrase || void 0
2469
+ });
2470
+ const npub = nip19.npubEncode(nostrPubkey);
2471
+ console.log(`Agent "${name}" created.`);
2472
+ console.log(` Nostr: ${npub}`);
2473
+ console.log(` Solana: ${solanaSigner.address}`);
2474
+ console.log(` Network: ${options.network}`);
2475
+ console.log(` Encrypted: ${passphrase ? "yes" : "no"}`);
2476
+ console.log(` Config: ~/.elisym/${name}/elisym.yaml`);
2477
+ if (passphrase) {
2478
+ console.log(` Note: set ELISYM_PASSPHRASE before launching the MCP server.`);
2479
+ }
2480
+ if (options.install) {
2481
+ await runInstall({ agent: name });
2478
2482
  }
2479
- ]);
2480
- const nostrSecretKey = generateSecretKey();
2481
- const nostrPubkey = getPublicKey(nostrSecretKey);
2482
- const solanaSigner = await generateKeyPairSigner(true);
2483
- const solanaSecretBytes = await exportKeyPairBytes(solanaSigner);
2484
- await saveAgentConfig(name, {
2485
- name,
2486
- description: options.description,
2487
- relays: [...RELAYS],
2488
- nostrSecretKey: Buffer.from(nostrSecretKey).toString("hex"),
2489
- solanaSecretKey: bs58.encode(solanaSecretBytes),
2490
- solanaAddress: solanaSigner.address,
2491
- network: options.network,
2492
- security: { withdrawals_enabled: false, agent_switch_enabled: false },
2493
- passphrase: passphrase || void 0
2494
- });
2495
- const npub = nip19.npubEncode(nostrPubkey);
2496
- console.log(`Agent "${name}" created.`);
2497
- console.log(` Nostr: ${npub}`);
2498
- console.log(` Solana: ${solanaSigner.address}`);
2499
- console.log(` Network: ${options.network}`);
2500
- console.log(` Encrypted: ${passphrase ? "yes" : "no"}`);
2501
- console.log(` Config: ~/.elisym/agents/${name}/config.json`);
2502
- if (passphrase) {
2503
- console.log(` Note: set ELISYM_PASSPHRASE before launching the MCP server.`);
2504
- }
2505
- if (options.install) {
2506
- await runInstall({ agent: name });
2507
- }
2508
- });
2509
- program.command("install").description("Install elisym MCP server into client configs").option("--client <name>", "Specific client (claude-desktop, claude-code, cursor, windsurf)").option("--agent <name>", "Bind to specific agent").option("--list", "List detected clients").action(async (options) => {
2510
- if (options.list) {
2511
- await runList();
2512
- } else {
2513
- await runInstall({ client: options.client, agent: options.agent });
2514
- }
2515
- });
2516
- program.command("update").description("Refresh the elisym MCP entry in installed client configs").option("--client <name>", "Specific client (claude-desktop, claude-code, cursor, windsurf)").option("--agent <name>", "Override the agent binding").action(async (options) => {
2517
- await runUpdate({ client: options.client, agent: options.agent });
2518
- });
2519
- program.command("uninstall").description("Remove elisym from MCP client configs").option("--client <name>", "Specific client").action(async (options) => {
2520
- await runUninstall({ client: options.client });
2521
- });
2483
+ })
2484
+ );
2485
+ program.command("install").description("Install elisym MCP server into client configs").option("--client <name>", "Specific client (claude-desktop, claude-code, cursor, windsurf)").option("--agent <name>", "Bind to specific agent").option("--list", "List detected clients").action(
2486
+ safe(async (options) => {
2487
+ if (options.list) {
2488
+ await runList();
2489
+ } else {
2490
+ await runInstall({ client: options.client, agent: options.agent });
2491
+ }
2492
+ })
2493
+ );
2494
+ program.command("update").description("Refresh the elisym MCP entry in installed client configs").option("--client <name>", "Specific client (claude-desktop, claude-code, cursor, windsurf)").option("--agent <name>", "Override the agent binding").action(
2495
+ safe(async (options) => {
2496
+ await runUpdate({ client: options.client, agent: options.agent });
2497
+ })
2498
+ );
2499
+ program.command("uninstall").description("Remove elisym from MCP client configs").option("--client <name>", "Specific client").action(
2500
+ safe(async (options) => {
2501
+ await runUninstall({ client: options.client });
2502
+ })
2503
+ );
2522
2504
  async function toggleFlag(agentName, field, enable) {
2523
2505
  const { default: inquirer } = await import('inquirer');
2524
2506
  if (enable) {
@@ -2539,10 +2521,10 @@ async function toggleFlag(agentName, field, enable) {
2539
2521
  console.log(`Agent "${agentName}" security:`, merged);
2540
2522
  console.log("Note: restart the MCP server for changes to take effect on a running session.");
2541
2523
  }
2542
- program.command("enable-withdrawals <agent>").description("Enable SOL withdrawals for a specific agent (interactive confirmation)").action((agent) => toggleFlag(agent, "withdrawals_enabled", true));
2543
- program.command("disable-withdrawals <agent>").description("Disable SOL withdrawals for a specific agent").action((agent) => toggleFlag(agent, "withdrawals_enabled", false));
2544
- program.command("enable-agent-switch <agent>").description("Allow the MCP server to switch away from this agent at runtime").action((agent) => toggleFlag(agent, "agent_switch_enabled", true));
2545
- program.command("disable-agent-switch <agent>").description("Forbid the MCP server from switching away from this agent at runtime").action((agent) => toggleFlag(agent, "agent_switch_enabled", false));
2524
+ program.command("enable-withdrawals <agent>").description("Enable SOL withdrawals for a specific agent (interactive confirmation)").action(safe((agent) => toggleFlag(agent, "withdrawals_enabled", true)));
2525
+ program.command("disable-withdrawals <agent>").description("Disable SOL withdrawals for a specific agent").action(safe((agent) => toggleFlag(agent, "withdrawals_enabled", false)));
2526
+ program.command("enable-agent-switch <agent>").description("Allow the MCP server to switch away from this agent at runtime").action(safe((agent) => toggleFlag(agent, "agent_switch_enabled", true)));
2527
+ program.command("disable-agent-switch <agent>").description("Forbid the MCP server from switching away from this agent at runtime").action(safe((agent) => toggleFlag(agent, "agent_switch_enabled", false)));
2546
2528
  program.parse();
2547
2529
  //# sourceMappingURL=index.js.map
2548
2530
  //# sourceMappingURL=index.js.map