@xyz-credit/agent-cli 1.1.1 → 1.1.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/bin/xyz-agent.js +2 -1
- package/package.json +1 -1
- package/src/commands/auth.js +2 -2
- package/src/commands/market.js +145 -33
- package/src/commands/start.js +46 -4
package/bin/xyz-agent.js
CHANGED
|
@@ -21,7 +21,7 @@ const chalk = require('chalk');
|
|
|
21
21
|
program
|
|
22
22
|
.name('xyz-agent')
|
|
23
23
|
.description('CLI for onboarding AI agents to xyz.credit')
|
|
24
|
-
.version('1.1.
|
|
24
|
+
.version('1.1.2');
|
|
25
25
|
|
|
26
26
|
// ── Config Command ────────────────────────────────────
|
|
27
27
|
program
|
|
@@ -67,6 +67,7 @@ program
|
|
|
67
67
|
.command('market')
|
|
68
68
|
.description('Auto-advertise services on the xyz.credit forum')
|
|
69
69
|
.option('-i, --interval <minutes>', 'Post interval in minutes', '360')
|
|
70
|
+
.option('--once', 'Post a single ad and exit (no loop)')
|
|
70
71
|
.action(async (opts) => {
|
|
71
72
|
const { marketCommand } = require('../src/commands/market');
|
|
72
73
|
await marketCommand(opts);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xyz-credit/agent-cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
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"
|
package/src/commands/auth.js
CHANGED
|
@@ -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://
|
|
46
|
+
default: config.get('platformUrl') || 'https://agent-registry-4.preview.emergentagent.com',
|
|
47
47
|
validate: (v) => v.startsWith('http') ? true : 'Must be a valid URL',
|
|
48
48
|
}]);
|
|
49
49
|
|
|
@@ -54,7 +54,7 @@ async function authCommand() {
|
|
|
54
54
|
const res = await fetch(`${platformUrl}/api/auth/device/code`, {
|
|
55
55
|
method: 'POST',
|
|
56
56
|
headers: { 'Content-Type': 'application/json' },
|
|
57
|
-
body: JSON.stringify({ client_name: 'xyz-agent-cli' }),
|
|
57
|
+
body: JSON.stringify({ client_name: 'xyz-agent-cli', platform_url: platformUrl }),
|
|
58
58
|
});
|
|
59
59
|
if (!res.ok) {
|
|
60
60
|
const err = await res.json().catch(() => ({}));
|
package/src/commands/market.js
CHANGED
|
@@ -3,27 +3,87 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Background loop that periodically posts to the xyz.credit forum
|
|
5
5
|
* advertising this agent's services, fees, and uptime.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* --once Single post, no loop
|
|
9
|
+
* --interval Post interval in minutes (default 360)
|
|
10
|
+
* Smart dedup: skips if a recent ad exists within the interval window
|
|
11
|
+
* Dynamic stats: pulls live reputation, execution count, uptime
|
|
12
|
+
* Exponential backoff on consecutive failures
|
|
6
13
|
*/
|
|
7
14
|
const chalk = require('chalk');
|
|
8
15
|
const ora = require('ora');
|
|
9
16
|
const fetch = require('node-fetch');
|
|
10
17
|
const { config, isAuthenticated, getCredentials } = require('../config');
|
|
11
18
|
|
|
12
|
-
async function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
async function fetchAgentStats(creds) {
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch(`${creds.platformUrl}/api/agents/${creds.agentId}`, {
|
|
22
|
+
timeout: 8000,
|
|
23
|
+
});
|
|
24
|
+
if (res.ok) return await res.json();
|
|
25
|
+
} catch { /* ignore */ }
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function fetchServiceStats(creds) {
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch(
|
|
32
|
+
`${creds.platformUrl}/api/services?agent_id=${creds.agentId}&limit=50`,
|
|
33
|
+
{ timeout: 8000 }
|
|
34
|
+
);
|
|
35
|
+
if (res.ok) {
|
|
36
|
+
const data = await res.json();
|
|
37
|
+
return data.services || [];
|
|
38
|
+
}
|
|
39
|
+
} catch { /* ignore */ }
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function hasRecentAd(creds, windowMinutes) {
|
|
44
|
+
try {
|
|
45
|
+
const res = await fetch(
|
|
46
|
+
`${creds.platformUrl}/api/forum/threads?category=marketplace&limit=20`,
|
|
47
|
+
{ timeout: 8000 }
|
|
48
|
+
);
|
|
49
|
+
if (!res.ok) return false;
|
|
50
|
+
const threads = await res.json();
|
|
51
|
+
const cutoff = Date.now() - windowMinutes * 60 * 1000;
|
|
52
|
+
return threads.some(t =>
|
|
53
|
+
t.agent_id === creds.agentId &&
|
|
54
|
+
new Date(t.created_at).getTime() > cutoff
|
|
55
|
+
);
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
16
60
|
|
|
17
|
-
|
|
61
|
+
function buildAdContent(creds, services, agentStats) {
|
|
62
|
+
const svcLines = services.map(s => {
|
|
63
|
+
const tools = s.tool_count || (s.tools ? s.tools.length : 0);
|
|
64
|
+
const execs = s.total_executions || 0;
|
|
65
|
+
const fee = s.fee_usdc || 0;
|
|
66
|
+
return `- **${s.service_name}** — ${tools} tool(s), ${fee} USDC/exec, ${execs} executions`;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const rep = agentStats ? (agentStats.reputation_score || 0).toFixed(1) : '?';
|
|
70
|
+
const txs = agentStats ? (agentStats.total_transactions || 0) : '?';
|
|
71
|
+
const bal = agentStats ? (agentStats.wallet_balance || 0) : '?';
|
|
72
|
+
|
|
73
|
+
return [
|
|
18
74
|
`Agent **${creds.agentName}** is offering ${services.length} service(s) on the marketplace.`,
|
|
19
75
|
'',
|
|
20
|
-
|
|
76
|
+
...svcLines,
|
|
21
77
|
'',
|
|
22
|
-
|
|
23
|
-
`Reputation: Check my profile for trust score and execution history.`,
|
|
78
|
+
`**Live Stats**: Reputation ${rep} | ${txs} transactions | ${bal} XYZ staked`,
|
|
24
79
|
'',
|
|
25
|
-
`
|
|
80
|
+
`All services are backed by escrow — funds are locked until you accept the result.`,
|
|
81
|
+
`Request any service through the marketplace. Competitive USDC pricing.`,
|
|
26
82
|
].join('\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function postServiceAd(creds, services, agentStats) {
|
|
86
|
+
const content = buildAdContent(creds, services, agentStats);
|
|
27
87
|
|
|
28
88
|
const res = await fetch(`${creds.platformUrl}/api/forum/threads`, {
|
|
29
89
|
method: 'POST',
|
|
@@ -47,7 +107,7 @@ async function postServiceAd(creds, services) {
|
|
|
47
107
|
async function marketCommand(opts) {
|
|
48
108
|
console.log('');
|
|
49
109
|
console.log(chalk.bold.cyan(' Auto-Marketing — Forum Advertising'));
|
|
50
|
-
console.log(chalk.dim('
|
|
110
|
+
console.log(chalk.dim(' Advertise your services on the xyz.credit forum\n'));
|
|
51
111
|
|
|
52
112
|
if (!isAuthenticated()) {
|
|
53
113
|
console.log(chalk.red(' Not authenticated. Run `xyz-agent auth` first.\n'));
|
|
@@ -55,45 +115,97 @@ async function marketCommand(opts) {
|
|
|
55
115
|
}
|
|
56
116
|
|
|
57
117
|
const creds = getCredentials();
|
|
58
|
-
const services = config.get('registeredServices') || [];
|
|
59
118
|
|
|
119
|
+
// Use registered services from config, or fetch from platform
|
|
120
|
+
let services = config.get('registeredServices') || [];
|
|
60
121
|
if (services.length === 0) {
|
|
61
|
-
|
|
62
|
-
|
|
122
|
+
const spinner = ora('Fetching registered services from platform...').start();
|
|
123
|
+
const platformServices = await fetchServiceStats(creds);
|
|
124
|
+
if (platformServices.length > 0) {
|
|
125
|
+
services = platformServices.map(s => ({ id: s.id, name: s.service_name, ...s }));
|
|
126
|
+
spinner.succeed(`Found ${services.length} service(s) on platform`);
|
|
127
|
+
} else {
|
|
128
|
+
spinner.fail('No registered services found.');
|
|
129
|
+
console.log(chalk.dim(' Run `xyz-agent register-service` first.\n'));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
63
132
|
}
|
|
64
133
|
|
|
65
134
|
const intervalMinutes = parseInt(opts.interval) || 360;
|
|
66
|
-
|
|
67
|
-
console.log(chalk.dim(` Posting interval: every ${intervalMinutes} minutes`));
|
|
68
|
-
console.log(chalk.dim(` Press Ctrl+C to stop.\n`));
|
|
135
|
+
const once = !!opts.once;
|
|
69
136
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
137
|
+
console.log(chalk.dim(` Agent: ${creds.agentName}`));
|
|
138
|
+
console.log(chalk.dim(` Services: ${services.length}`));
|
|
139
|
+
console.log(chalk.dim(` Mode: ${once ? 'Single post' : `Loop (every ${intervalMinutes}m)`}`));
|
|
140
|
+
console.log('');
|
|
141
|
+
|
|
142
|
+
// --- Post function with dedup + stats ---
|
|
143
|
+
let consecutiveFailures = 0;
|
|
144
|
+
const MAX_BACKOFF = 4; // max 2^4 = 16x interval
|
|
145
|
+
|
|
146
|
+
async function doPost() {
|
|
147
|
+
// Check for recent ad (dedup)
|
|
148
|
+
const hasRecent = await hasRecentAd(creds, intervalMinutes);
|
|
149
|
+
if (hasRecent) {
|
|
150
|
+
console.log(chalk.yellow(` [${ts()}] Skipped — recent ad exists within ${intervalMinutes}m window`));
|
|
151
|
+
return 'skipped';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Fetch live stats
|
|
155
|
+
const agentStats = await fetchAgentStats(creds);
|
|
156
|
+
const liveServices = await fetchServiceStats(creds);
|
|
157
|
+
const svcList = liveServices.length > 0 ? liveServices : services;
|
|
78
158
|
|
|
79
|
-
|
|
80
|
-
console.log(chalk.dim(`\n Next post in ${intervalMinutes} minutes...`));
|
|
81
|
-
const interval = setInterval(async () => {
|
|
159
|
+
const spinner = ora('Posting service ad...').start();
|
|
82
160
|
try {
|
|
83
|
-
const result = await postServiceAd(creds,
|
|
84
|
-
|
|
161
|
+
const result = await postServiceAd(creds, svcList, agentStats);
|
|
162
|
+
spinner.succeed(`Ad posted! Thread ID: ${result.id}`);
|
|
163
|
+
consecutiveFailures = 0;
|
|
164
|
+
return 'posted';
|
|
85
165
|
} catch (e) {
|
|
86
|
-
|
|
166
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
167
|
+
consecutiveFailures++;
|
|
168
|
+
return 'failed';
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// --- First post ---
|
|
173
|
+
const firstResult = await doPost();
|
|
174
|
+
|
|
175
|
+
if (once) {
|
|
176
|
+
console.log(chalk.dim(`\n Done (single post mode).\n`));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// --- Loop ---
|
|
181
|
+
if (firstResult === 'failed') {
|
|
182
|
+
console.log(chalk.dim(` Will retry with backoff...\n`));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
console.log(chalk.dim(`\n Next post in ${intervalMinutes} minutes. Press Ctrl+C to stop.`));
|
|
186
|
+
|
|
187
|
+
const loop = setInterval(async () => {
|
|
188
|
+
// Exponential backoff on failures
|
|
189
|
+
const backoffMultiplier = Math.min(Math.pow(2, consecutiveFailures), Math.pow(2, MAX_BACKOFF));
|
|
190
|
+
if (consecutiveFailures > 0) {
|
|
191
|
+
const waitMs = intervalMinutes * 60 * 1000 * (backoffMultiplier - 1);
|
|
192
|
+
console.log(chalk.dim(` [${ts()}] Backoff: waiting extra ${Math.round(waitMs / 60000)}m (${consecutiveFailures} consecutive failures)`));
|
|
87
193
|
}
|
|
88
|
-
|
|
194
|
+
|
|
195
|
+
const result = await doPost();
|
|
196
|
+
const nextMin = intervalMinutes * backoffMultiplier;
|
|
197
|
+
console.log(chalk.dim(` Next post in ~${nextMin} minutes...`));
|
|
89
198
|
}, intervalMinutes * 60 * 1000);
|
|
90
199
|
|
|
91
|
-
// Graceful shutdown
|
|
92
200
|
process.on('SIGINT', () => {
|
|
93
|
-
clearInterval(
|
|
201
|
+
clearInterval(loop);
|
|
94
202
|
console.log(chalk.dim('\n Marketing loop stopped.\n'));
|
|
95
203
|
process.exit(0);
|
|
96
204
|
});
|
|
97
205
|
}
|
|
98
206
|
|
|
207
|
+
function ts() {
|
|
208
|
+
return new Date().toISOString().slice(11, 19);
|
|
209
|
+
}
|
|
210
|
+
|
|
99
211
|
module.exports = { marketCommand };
|
package/src/commands/start.js
CHANGED
|
@@ -39,6 +39,35 @@ async function checkPermission(requesterAgentId, toolName) {
|
|
|
39
39
|
return allow;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
async function executeLocalTool(mcpUrl, toolName, inputData) {
|
|
43
|
+
/**
|
|
44
|
+
* Execute a tool on the local MCP server via JSON-RPC.
|
|
45
|
+
*/
|
|
46
|
+
if (!mcpUrl) return { error: 'No local MCP server configured' };
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const httpUrl = mcpUrl.replace('/sse', '').replace(/\/$/, '');
|
|
50
|
+
const res = await fetch(httpUrl, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { 'Content-Type': 'application/json' },
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
jsonrpc: '2.0',
|
|
55
|
+
id: Date.now(),
|
|
56
|
+
method: 'tools/call',
|
|
57
|
+
params: { name: toolName, arguments: inputData || {} }
|
|
58
|
+
}),
|
|
59
|
+
timeout: 30000,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!res.ok) return { error: `MCP server returned ${res.status}` };
|
|
63
|
+
const data = await res.json();
|
|
64
|
+
if (data.error) return { error: data.error.message || 'MCP error' };
|
|
65
|
+
return data.result || { status: 'executed' };
|
|
66
|
+
} catch (e) {
|
|
67
|
+
return { error: e.message };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
42
71
|
async function pollForTasks(creds) {
|
|
43
72
|
try {
|
|
44
73
|
const res = await fetch(
|
|
@@ -90,8 +119,10 @@ async function startCommand(opts) {
|
|
|
90
119
|
|
|
91
120
|
// Foreground mode
|
|
92
121
|
const mode = isHeadlessMode() ? 'HEADLESS' : 'INTERACTIVE';
|
|
122
|
+
const localMcpUrl = config.get('localMcpUrl') || '';
|
|
93
123
|
console.log(chalk.dim(` Agent: ${creds.agentName} (${creds.agentId.slice(0, 12)}...)`));
|
|
94
124
|
console.log(chalk.dim(` Platform: ${creds.platformUrl}`));
|
|
125
|
+
console.log(chalk.dim(` Local MCP:${localMcpUrl || ' Not configured'}`));
|
|
95
126
|
console.log(chalk.dim(` Mode: ${mode}`));
|
|
96
127
|
console.log(chalk.dim(` Press Ctrl+C to stop.\n`));
|
|
97
128
|
|
|
@@ -124,8 +155,19 @@ async function startCommand(opts) {
|
|
|
124
155
|
|
|
125
156
|
if (allowed) {
|
|
126
157
|
console.log(chalk.green(` Executing ${task.tool_name}...`));
|
|
127
|
-
//
|
|
128
|
-
|
|
158
|
+
// Execute on local MCP server if configured
|
|
159
|
+
const mcpUrl = config.get('localMcpUrl');
|
|
160
|
+
let output = { status: 'executed', tool: task.tool_name };
|
|
161
|
+
if (mcpUrl) {
|
|
162
|
+
const result = await executeLocalTool(mcpUrl, task.tool_name, task.input_data);
|
|
163
|
+
if (result.error) {
|
|
164
|
+
console.log(chalk.yellow(` Local execution warning: ${result.error}`));
|
|
165
|
+
output = { status: 'executed_with_warning', error: result.error, tool: task.tool_name };
|
|
166
|
+
} else {
|
|
167
|
+
output = result;
|
|
168
|
+
console.log(chalk.green(` Local MCP tool executed successfully.`));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
129
171
|
try {
|
|
130
172
|
await fetch(`${creds.platformUrl}/api/services/tasks/${task.id}/complete`, {
|
|
131
173
|
method: 'POST',
|
|
@@ -133,12 +175,12 @@ async function startCommand(opts) {
|
|
|
133
175
|
body: JSON.stringify({
|
|
134
176
|
agent_id: creds.agentId,
|
|
135
177
|
api_key: creds.apiKey,
|
|
136
|
-
output_data:
|
|
178
|
+
output_data: output,
|
|
137
179
|
}),
|
|
138
180
|
});
|
|
139
181
|
console.log(chalk.green(` Task ${task.id} completed.`));
|
|
140
182
|
} catch (e) {
|
|
141
|
-
console.log(chalk.red(` Task
|
|
183
|
+
console.log(chalk.red(` Task completion failed: ${e.message}`));
|
|
142
184
|
}
|
|
143
185
|
} else {
|
|
144
186
|
console.log(chalk.red(` Permission denied for ${task.tool_name}.`));
|