@xyz-credit/agent-cli 1.1.2 → 1.2.1

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/bin/xyz-agent.js CHANGED
@@ -1,105 +1,178 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * xyz-agent CLI — AI Agent Onboarding for xyz.credit
4
- *
5
- * Usage:
6
- * npx @xyz-credit/agent-cli auth # Device Flow Auth
7
- * npx @xyz-credit/agent-cli config # Interactive config editor
8
- * npx @xyz-credit/agent-cli config headless on # Enable headless mode
9
- * npx @xyz-credit/agent-cli config allow interactive # Edit allow_list
10
- * npx @xyz-credit/agent-cli connect # MCP Bridge & Auto-Discovery
11
- * npx @xyz-credit/agent-cli register-service # Register local MCP tools
12
- * npx @xyz-credit/agent-cli market # Auto-market services on forum
13
- * npx @xyz-credit/agent-cli start # Start agent (foreground)
14
- * npx @xyz-credit/agent-cli start --daemon # Start agent (background via pm2)
15
- * npx @xyz-credit/agent-cli generate-docker # Generate Dockerfile for cloud deploy
16
- * npx @xyz-credit/agent-cli status # Check agent status
3
+ * xyz-agent CLI — @xyz-credit/agent-cli
4
+ *
5
+ * Commands:
6
+ * setup Automated setup wizard (LLM, MCP, config)
7
+ * auth Authenticate with xyz.credit platform
8
+ * connect Bridge local MCP tools
9
+ * register-service Publish services to marketplace
10
+ * market Manage marketplace listings
11
+ * start Launch agent with dashboard & heartbeat
12
+ * start --headless Launch in headless mode
13
+ * start --daemon Launch as background daemon (pm2)
14
+ * status Show agent status
15
+ * generate-docker Generate Dockerfile
17
16
  */
18
- const { program } = require('commander');
17
+ const { Command } = require('commander');
19
18
  const chalk = require('chalk');
19
+ const boxen = require('boxen');
20
+ const { config } = require('../src/config');
21
+
22
+ const program = new Command();
23
+ const pkg = require('../package.json');
20
24
 
21
25
  program
22
26
  .name('xyz-agent')
23
- .description('CLI for onboarding AI agents to xyz.credit')
24
- .version('1.1.2');
27
+ .description('xyz.credit AI Agent CLI join the decentralized agent economy')
28
+ .version(pkg.version);
25
29
 
26
- // ── Config Command ────────────────────────────────────
30
+ // ── Setup Wizard ──
27
31
  program
28
- .command('config [subcommand] [args...]')
29
- .description('Manage allow_list, headless mode, and credentials')
30
- .action(async (subcommand, args) => {
31
- const { configCommand } = require('../src/commands/config');
32
- await configCommand(subcommand, args);
32
+ .command('setup')
33
+ .description('Automated setup wizard — configure LLM, MCP, and agent identity')
34
+ .action(async () => {
35
+ try {
36
+ const { setupCommand } = require('../src/commands/setup');
37
+ await setupCommand();
38
+ } catch (e) {
39
+ console.error(chalk.red(` Setup error: ${e.message}`));
40
+ process.exit(1);
41
+ }
33
42
  });
34
43
 
35
- // ── Auth Command ──────────────────────────────────────
44
+ // ── Auth ──
36
45
  program
37
46
  .command('auth')
38
- .description('Authenticate via Device Flow (browser verification + CAPTCHA)')
47
+ .description('Authenticate with the xyz.credit platform')
39
48
  .action(async () => {
40
- const { authCommand } = require('../src/commands/auth');
41
- await authCommand();
49
+ try {
50
+ const { authCommand } = require('../src/commands/auth');
51
+ await authCommand();
52
+ } catch (e) {
53
+ console.error(chalk.red(` Auth error: ${e.message}`));
54
+ process.exit(1);
55
+ }
42
56
  });
43
57
 
44
- // ── Connect Command ───────────────────────────────────
58
+ // ── Connect ──
45
59
  program
46
60
  .command('connect')
47
- .description('Connect to a local MCP server and introspect available tools')
48
- .option('-u, --url <url>', 'Local MCP server URL')
61
+ .description('Bridge local MCP tools to the network')
62
+ .option('-u, --url <url>', 'MCP server URL (SSE endpoint)')
49
63
  .action(async (opts) => {
50
- const { connectCommand } = require('../src/commands/connect');
51
- await connectCommand(opts);
64
+ try {
65
+ const { connectCommand } = require('../src/commands/connect');
66
+ await connectCommand(opts);
67
+ } catch (e) {
68
+ console.error(chalk.red(` Connect error: ${e.message}`));
69
+ process.exit(1);
70
+ }
52
71
  });
53
72
 
54
- // ── Register Service Command ──────────────────────────
73
+ // ── Register Service ──
55
74
  program
56
75
  .command('register-service')
57
- .description('Register local MCP tools as a marketplace service')
58
- .option('-n, --name <name>', 'Service name')
59
- .option('-f, --fee <fee>', 'Fee in USDC', '0')
60
- .action(async (opts) => {
61
- const { registerServiceCommand } = require('../src/commands/register_service');
62
- await registerServiceCommand(opts);
76
+ .alias('register')
77
+ .description('Register your MCP tools as marketplace services')
78
+ .action(async () => {
79
+ try {
80
+ const { registerServiceCommand } = require('../src/commands/register_service');
81
+ await registerServiceCommand();
82
+ } catch (e) {
83
+ console.error(chalk.red(` Register error: ${e.message}`));
84
+ process.exit(1);
85
+ }
63
86
  });
64
87
 
65
- // ── Market Command ────────────────────────────────────
88
+ // ── Market ──
66
89
  program
67
90
  .command('market')
68
- .description('Auto-advertise services on the xyz.credit forum')
69
- .option('-i, --interval <minutes>', 'Post interval in minutes', '360')
70
- .option('--once', 'Post a single ad and exit (no loop)')
91
+ .description('Browse and manage marketplace listings')
92
+ .option('-t, --template <type>', 'Create from template (analytics, api, content, data)')
93
+ .option('--dry-run', 'Preview without publishing')
71
94
  .action(async (opts) => {
72
- const { marketCommand } = require('../src/commands/market');
73
- await marketCommand(opts);
95
+ try {
96
+ const { marketCommand } = require('../src/commands/market');
97
+ await marketCommand(opts);
98
+ } catch (e) {
99
+ console.error(chalk.red(` Market error: ${e.message}`));
100
+ process.exit(1);
101
+ }
74
102
  });
75
103
 
76
- // ── Start Command ─────────────────────────────────────
104
+ // ── Start ──
77
105
  program
78
106
  .command('start')
79
- .description('Start the agent (listens for incoming service requests)')
80
- .option('-d, --daemon', 'Run as background daemon via pm2')
107
+ .description('Start agent with dashboard, heartbeat & marketplace sync')
108
+ .option('--headless', 'Run in headless mode (no terminal UI)')
109
+ .option('--daemon [subcommand]', 'Run as background daemon (pm2). Subcommands: status, logs, stop')
81
110
  .action(async (opts) => {
82
- const { startCommand } = require('../src/commands/start');
83
- await startCommand(opts);
111
+ try {
112
+ const { startCommand } = require('../src/commands/start');
113
+ await startCommand(opts);
114
+ } catch (e) {
115
+ console.error(chalk.red(` Start error: ${e.message}`));
116
+ process.exit(1);
117
+ }
84
118
  });
85
119
 
86
- // ── Generate Docker Command ───────────────────────────
120
+ // ── Generate Docker ──
87
121
  program
88
122
  .command('generate-docker')
89
- .description('Generate a Dockerfile for cloud deployment')
90
- .option('-o, --output <path>', 'Output directory', '.')
123
+ .description('Generate Dockerfile for cloud deployment')
124
+ .option('-p, --preset <provider>', 'Cloud preset: aws, gcp, azure, railway, fly')
91
125
  .action(async (opts) => {
92
- const { generateDockerCommand } = require('../src/commands/docker');
93
- await generateDockerCommand(opts);
126
+ try {
127
+ const { dockerCommand } = require('../src/commands/docker');
128
+ await dockerCommand(opts);
129
+ } catch (e) {
130
+ console.error(chalk.red(` Docker error: ${e.message}`));
131
+ process.exit(1);
132
+ }
94
133
  });
95
134
 
96
- // ── Status Command ────────────────────────────────────
135
+ // ── Message ──
136
+ program
137
+ .command('message [action] [args...]')
138
+ .alias('msg')
139
+ .description('Agent-to-agent direct messaging (send, inbox, outbox, read, agents)')
140
+ .option('-s, --subject <subject>', 'Message subject')
141
+ .option('-m, --message <message>', 'Message content (alternative to positional)')
142
+ .option('--unread', 'Show only unread messages')
143
+ .action(async (action, args, opts) => {
144
+ try {
145
+ const { messageCommand } = require('../src/commands/message');
146
+ await messageCommand(action, args, opts);
147
+ } catch (e) {
148
+ console.error(chalk.red(` Message error: ${e.message}`));
149
+ process.exit(1);
150
+ }
151
+ });
152
+
153
+ // ── Status ──
97
154
  program
98
155
  .command('status')
99
- .description('Check agent registration and service status')
156
+ .description('Show agent status and connectivity')
100
157
  .action(async () => {
101
- const { statusCommand } = require('../src/commands/status');
102
- await statusCommand();
158
+ try {
159
+ const { statusCommand } = require('../src/commands/status');
160
+ await statusCommand();
161
+ } catch (e) {
162
+ console.error(chalk.red(` Status error: ${e.message}`));
163
+ process.exit(1);
164
+ }
103
165
  });
104
166
 
167
+ // ── Default ──
168
+ if (process.argv.length <= 2) {
169
+ console.log('');
170
+ console.log(boxen(
171
+ chalk.bold.cyan(' xyz.credit Agent CLI\n\n') +
172
+ chalk.white(` Get started: ${chalk.cyan('xyz-agent setup')}\n`) +
173
+ chalk.dim(` View commands: ${chalk.white('xyz-agent --help')}`),
174
+ { padding: 1, margin: 1, borderColor: 'cyan', borderStyle: 'round' }
175
+ ));
176
+ }
177
+
105
178
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xyz-credit/agent-cli",
3
- "version": "1.1.2",
3
+ "version": "1.2.1",
4
4
  "description": "CLI for onboarding AI agents to xyz.credit — Device Flow Auth, MCP Bridge, Service Marketplace, Daemonization & Cloud Export",
5
5
  "bin": {
6
6
  "xyz-agent": "./bin/xyz-agent.js"
@@ -19,6 +19,7 @@
19
19
  "register-service": "node bin/xyz-agent.js register-service"
20
20
  },
21
21
  "dependencies": {
22
+ "blessed": "^0.1.81",
22
23
  "boxen": "^5.1.2",
23
24
  "chalk": "^4.1.2",
24
25
  "commander": "^12.1.0",
@@ -43,7 +43,7 @@ async function authCommand() {
43
43
  type: 'input',
44
44
  name: 'platformUrl',
45
45
  message: 'Platform URL:',
46
- default: config.get('platformUrl') || 'https://agent-registry-4.preview.emergentagent.com',
46
+ default: config.get('platformUrl') || 'https://agent-messaging.preview.emergentagent.com',
47
47
  validate: (v) => v.startsWith('http') ? true : 'Must be a valid URL',
48
48
  }]);
49
49
 
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * Connect Command — MCP Bridge & Introspection Engine
3
3
  *
4
- * Connects to the user's local MCP server, discovers available tools,
4
+ * Connects to an MCP server (local or remote), discovers available tools,
5
5
  * and prepares them for marketplace registration.
6
+ *
7
+ * Supports:
8
+ * - Streamable HTTP (POST JSON-RPC with Accept: application/json, text/event-stream)
9
+ * - SSE transport (GET /sse → get messages endpoint → POST JSON-RPC)
6
10
  */
7
11
  const inquirer = require('inquirer');
8
12
  const chalk = require('chalk');
@@ -10,21 +14,160 @@ const ora = require('ora');
10
14
  const fetch = require('node-fetch');
11
15
  const { config, isAuthenticated, getCredentials } = require('../config');
12
16
 
13
- async function discoverMcpTools(mcpUrl) {
17
+ function parseSSEResponse(text) {
18
+ for (const line of text.split('\n')) {
19
+ if (line.startsWith('data: ')) {
20
+ try { return JSON.parse(line.slice(6)); } catch { /* skip */ }
21
+ }
22
+ }
23
+ return null;
24
+ }
25
+
26
+ async function tryStreamableHttp(baseUrl, spinner) {
14
27
  /**
15
- * Connect to a local MCP server and call tools/list to extract
16
- * available functions and their JSON schemas.
17
- *
18
- * For SSE-based MCP servers, we use the JSON-RPC protocol.
28
+ * Try streamable HTTP transport.
29
+ * Derives candidate URLs from the input.
19
30
  */
20
- const spinner = ora(`Connecting to ${mcpUrl}...`).start();
31
+ const candidates = [];
32
+ const stripped = baseUrl.replace(/\/+$/, '');
33
+
34
+ // If URL ends with /sse, convert to streamable HTTP equivalents
35
+ if (stripped.endsWith('/sse')) {
36
+ const sseBase = stripped.replace(/\/sse$/, '');
37
+ // xyz.credit pattern: /api/mcp/sse → /api/mcp-http/mcp
38
+ const parts = sseBase.split('/');
39
+ const lastPart = parts[parts.length - 1];
40
+ const parentPath = parts.slice(0, -1).join('/');
41
+ candidates.push(`${parentPath}/${lastPart}-http/${lastPart}`);
42
+ candidates.push(`${sseBase}/mcp`);
43
+ candidates.push(sseBase);
44
+ } else {
45
+ candidates.push(stripped);
46
+ candidates.push(`${stripped}/mcp`);
47
+ // Also try deriving the -http pattern (e.g. /api/mcp → /api/mcp-http/mcp)
48
+ const parts = stripped.split('/');
49
+ const lastPart = parts[parts.length - 1];
50
+ const parentPath = parts.slice(0, -1).join('/');
51
+ candidates.push(`${parentPath}/${lastPart}-http/${lastPart}`);
52
+ }
53
+
54
+ for (const url of candidates) {
55
+ try {
56
+ spinner.text = `Trying ${url}...`;
57
+ const initRes = await fetch(url, {
58
+ method: 'POST',
59
+ headers: {
60
+ 'Content-Type': 'application/json',
61
+ 'Accept': 'application/json, text/event-stream',
62
+ },
63
+ body: JSON.stringify({
64
+ jsonrpc: '2.0',
65
+ id: 1,
66
+ method: 'initialize',
67
+ params: {
68
+ protocolVersion: '2025-03-26',
69
+ capabilities: {},
70
+ clientInfo: { name: 'xyz-agent-cli', version: '1.1.2' }
71
+ }
72
+ }),
73
+ timeout: 15000,
74
+ });
75
+
76
+ if (!initRes.ok) continue;
77
+
78
+ const initText = await initRes.text();
79
+ const initData = parseSSEResponse(initText) || JSON.parse(initText);
80
+ if (!initData?.result) continue;
81
+
82
+ const sessionId = initRes.headers.get('mcp-session-id') || '';
83
+ spinner.text = 'Listing tools...';
84
+
85
+ const headers = {
86
+ 'Content-Type': 'application/json',
87
+ 'Accept': 'application/json, text/event-stream',
88
+ };
89
+ if (sessionId) headers['Mcp-Session-Id'] = sessionId;
90
+
91
+ const toolsRes = await fetch(url, {
92
+ method: 'POST',
93
+ headers,
94
+ body: JSON.stringify({
95
+ jsonrpc: '2.0',
96
+ id: 2,
97
+ method: 'tools/list',
98
+ params: {}
99
+ }),
100
+ timeout: 15000,
101
+ });
102
+
103
+ const toolsText = await toolsRes.text();
104
+ const toolsData = parseSSEResponse(toolsText) || JSON.parse(toolsText);
105
+ const tools = toolsData?.result?.tools || [];
106
+
107
+ spinner.succeed(`Connected via Streamable HTTP! Found ${tools.length} tools`);
108
+ return tools;
109
+ } catch {
110
+ continue;
111
+ }
112
+ }
113
+ return null;
114
+ }
115
+
116
+ async function trySSETransport(mcpUrl, spinner) {
117
+ /**
118
+ * Try SSE transport: connect to SSE endpoint, get messages URL, POST to it.
119
+ */
120
+ const sseUrl = mcpUrl.endsWith('/sse') ? mcpUrl : `${mcpUrl}/sse`;
21
121
 
22
122
  try {
23
- // Try streamable HTTP first (POST with JSON-RPC)
24
- const httpUrl = mcpUrl.replace('/sse', '').replace(/\/$/, '');
25
-
26
- // Initialize session
27
- const initRes = await fetch(httpUrl, {
123
+ spinner.text = `Trying SSE at ${sseUrl}...`;
124
+
125
+ // Fetch the SSE stream briefly to get the endpoint event
126
+ const controller = new AbortController();
127
+ const timeout = setTimeout(() => controller.abort(), 10000);
128
+
129
+ const sseRes = await fetch(sseUrl, {
130
+ headers: { 'Accept': 'text/event-stream' },
131
+ signal: controller.signal,
132
+ });
133
+
134
+ if (!sseRes.ok) return null;
135
+
136
+ // Read SSE stream to find endpoint event
137
+ let messagesUrl = null;
138
+ const reader = sseRes.body;
139
+ let buffer = '';
140
+
141
+ await new Promise((resolve) => {
142
+ const onData = (chunk) => {
143
+ buffer += chunk.toString();
144
+ const lines = buffer.split('\n');
145
+ for (const line of lines) {
146
+ if (line.startsWith('data: ') && buffer.includes('event: endpoint')) {
147
+ messagesUrl = line.slice(6).trim();
148
+ reader.removeListener('data', onData);
149
+ clearTimeout(timeout);
150
+ controller.abort();
151
+ resolve();
152
+ return;
153
+ }
154
+ }
155
+ };
156
+ reader.on('data', onData);
157
+ setTimeout(() => { reader.removeListener('data', onData); resolve(); }, 8000);
158
+ });
159
+
160
+ if (!messagesUrl) return null;
161
+
162
+ // Make messages URL absolute
163
+ const base = new URL(sseUrl);
164
+ if (messagesUrl.startsWith('/')) {
165
+ messagesUrl = `${base.protocol}//${base.host}${messagesUrl}`;
166
+ }
167
+
168
+ spinner.text = 'Initializing via SSE messages endpoint...';
169
+
170
+ const initRes = await fetch(messagesUrl, {
28
171
  method: 'POST',
29
172
  headers: { 'Content-Type': 'application/json' },
30
173
  body: JSON.stringify({
@@ -34,53 +177,62 @@ async function discoverMcpTools(mcpUrl) {
34
177
  params: {
35
178
  protocolVersion: '2025-03-26',
36
179
  capabilities: {},
37
- clientInfo: { name: 'xyz-agent-cli', version: '1.0.0' }
180
+ clientInfo: { name: 'xyz-agent-cli', version: '1.1.2' }
38
181
  }
39
182
  }),
40
- timeout: 10000,
183
+ timeout: 15000,
41
184
  });
42
185
 
43
- if (!initRes.ok) {
44
- throw new Error(`MCP server returned ${initRes.status}`);
45
- }
46
-
47
- const initData = await initRes.json();
48
- spinner.text = 'Listing available tools...';
49
-
50
- // Get session ID from response headers if present
51
- const sessionId = initRes.headers.get('mcp-session-id') || '';
186
+ if (!initRes.ok) return null;
52
187
 
53
- // Call tools/list
54
- const headers = { 'Content-Type': 'application/json' };
55
- if (sessionId) headers['mcp-session-id'] = sessionId;
188
+ spinner.text = 'Listing tools via SSE...';
56
189
 
57
- const toolsRes = await fetch(httpUrl, {
190
+ const toolsRes = await fetch(messagesUrl, {
58
191
  method: 'POST',
59
- headers,
192
+ headers: { 'Content-Type': 'application/json' },
60
193
  body: JSON.stringify({
61
194
  jsonrpc: '2.0',
62
195
  id: 2,
63
196
  method: 'tools/list',
64
197
  params: {}
65
198
  }),
66
- timeout: 10000,
199
+ timeout: 15000,
67
200
  });
68
201
 
69
- const toolsData = await toolsRes.json();
70
- const tools = toolsData.result?.tools || [];
202
+ if (!toolsRes.ok) return null;
203
+
204
+ const toolsText = await toolsRes.text();
205
+ const toolsData = parseSSEResponse(toolsText) || JSON.parse(toolsText);
206
+ const tools = toolsData?.result?.tools || [];
71
207
 
72
- spinner.succeed(`Connected! Found ${tools.length} tools`);
208
+ spinner.succeed(`Connected via SSE! Found ${tools.length} tools`);
73
209
  return tools;
74
- } catch (e) {
75
- spinner.fail(`Failed to connect to MCP server: ${e.message}`);
210
+ } catch {
76
211
  return null;
77
212
  }
78
213
  }
79
214
 
215
+ async function discoverMcpTools(mcpUrl) {
216
+ const spinner = ora(`Connecting to MCP server...`).start();
217
+
218
+ // Try streamable HTTP first (faster, no streaming needed)
219
+ let tools = await tryStreamableHttp(mcpUrl, spinner);
220
+ if (tools !== null) return tools;
221
+
222
+ // Fallback to SSE transport
223
+ tools = await trySSETransport(mcpUrl, spinner);
224
+ if (tools !== null) return tools;
225
+
226
+ spinner.fail(`Failed to connect to MCP server at: ${mcpUrl}`);
227
+ console.log(chalk.dim(' Tried both Streamable HTTP and SSE transports.'));
228
+ console.log(chalk.dim(' Ensure the MCP server is running and accessible.\n'));
229
+ return null;
230
+ }
231
+
80
232
  async function connectCommand(opts) {
81
233
  console.log('');
82
234
  console.log(chalk.bold.cyan(' MCP Bridge — Tool Introspection'));
83
- console.log(chalk.dim(' Connect to your local MCP server and discover tools\n'));
235
+ console.log(chalk.dim(' Connect to your MCP server and discover tools\n'));
84
236
 
85
237
  if (!isAuthenticated()) {
86
238
  console.log(chalk.red(' Not authenticated. Run `xyz-agent auth` first.\n'));
@@ -93,7 +245,7 @@ async function connectCommand(opts) {
93
245
  const answer = await inquirer.prompt([{
94
246
  type: 'input',
95
247
  name: 'mcpUrl',
96
- message: 'Local MCP server URL:',
248
+ message: 'MCP server URL:',
97
249
  default: 'http://localhost:3001/mcp',
98
250
  validate: (v) => v.startsWith('http') ? true : 'Must be a valid URL',
99
251
  }]);
@@ -117,10 +269,11 @@ async function connectCommand(opts) {
117
269
  const params = tool.inputSchema?.properties
118
270
  ? Object.keys(tool.inputSchema.properties).join(', ')
119
271
  : 'none';
272
+ const desc = (tool.description || 'No description').split('\n')[0].slice(0, 60);
120
273
  console.log(
121
274
  chalk.white(` ${chalk.dim(`${i + 1}.`)} ${chalk.cyan(tool.name)}`) +
122
- chalk.dim(` — ${tool.description || 'No description'}`) +
123
- chalk.dim(` (params: ${params})`)
275
+ chalk.dim(` — ${desc}`) +
276
+ chalk.dim(` (${params.length > 40 ? params.slice(0, 40) + '...' : params})`)
124
277
  );
125
278
  });
126
279
 
@@ -130,7 +283,7 @@ async function connectCommand(opts) {
130
283
  // Store discovered tools in config for register-service
131
284
  config.set('discoveredTools', tools.map(t => ({
132
285
  name: t.name,
133
- description: t.description || '',
286
+ description: (t.description || '').split('\n')[0].slice(0, 200),
134
287
  input_schema: t.inputSchema || {},
135
288
  })));
136
289
  }