@adcp/client 2.4.0 ā 2.5.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/README.md +66 -11
- package/bin/adcp-async-handler.js +273 -0
- package/bin/adcp-config.js +230 -0
- package/bin/adcp.js +267 -42
- package/dist/lib/core/ADCPClient.d.ts.map +1 -1
- package/dist/lib/core/ADCPClient.js +20 -0
- package/dist/lib/core/ADCPClient.js.map +1 -1
- package/dist/lib/core/ADCPMultiAgentClient.d.ts +8 -0
- package/dist/lib/core/ADCPMultiAgentClient.d.ts.map +1 -1
- package/dist/lib/core/ADCPMultiAgentClient.js +14 -0
- package/dist/lib/core/ADCPMultiAgentClient.js.map +1 -1
- package/dist/lib/core/AgentClient.d.ts +18 -2
- package/dist/lib/core/AgentClient.d.ts.map +1 -1
- package/dist/lib/core/AgentClient.js +10 -3
- package/dist/lib/core/AgentClient.js.map +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +4 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/types/schemas.generated.js +1 -1
- package/dist/lib/utils/protocol-detection.d.ts +26 -0
- package/dist/lib/utils/protocol-detection.d.ts.map +1 -0
- package/dist/lib/utils/protocol-detection.js +84 -0
- package/dist/lib/utils/protocol-detection.js.map +1 -0
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -457,26 +457,81 @@ ORDER BY sequence_number;
|
|
|
457
457
|
|
|
458
458
|
## CLI Tool
|
|
459
459
|
|
|
460
|
-
For development and testing, use the included CLI tool to interact with AdCP agents
|
|
460
|
+
For development and testing, use the included CLI tool to interact with AdCP agents.
|
|
461
|
+
|
|
462
|
+
### Quick Start with Aliases
|
|
463
|
+
|
|
464
|
+
Save agents for quick access:
|
|
461
465
|
|
|
462
466
|
```bash
|
|
463
|
-
#
|
|
464
|
-
npx @adcp/client
|
|
467
|
+
# Save an agent with an alias
|
|
468
|
+
npx @adcp/client --save-auth test https://test-agent.adcontextprotocol.org
|
|
465
469
|
|
|
466
|
-
#
|
|
467
|
-
npx @adcp/client
|
|
470
|
+
# Use the alias
|
|
471
|
+
npx @adcp/client test get_products '{"brief":"Coffee brands"}'
|
|
468
472
|
|
|
469
|
-
#
|
|
470
|
-
npx @adcp/client
|
|
473
|
+
# List saved agents
|
|
474
|
+
npx @adcp/client --list-agents
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Direct URL Usage
|
|
478
|
+
|
|
479
|
+
Auto-detect protocol and call directly:
|
|
480
|
+
|
|
481
|
+
```bash
|
|
482
|
+
# Protocol auto-detection (default)
|
|
483
|
+
npx @adcp/client https://test-agent.adcontextprotocol.org get_products '{"brief":"Coffee"}'
|
|
484
|
+
|
|
485
|
+
# Force specific protocol with --protocol flag
|
|
486
|
+
npx @adcp/client https://agent.example.com get_products '{"brief":"Coffee"}' --protocol mcp
|
|
487
|
+
npx @adcp/client https://agent.example.com list_authorized_properties --protocol a2a
|
|
471
488
|
|
|
472
|
-
#
|
|
473
|
-
npx @adcp/client
|
|
489
|
+
# List available tools
|
|
490
|
+
npx @adcp/client https://agent.example.com
|
|
491
|
+
|
|
492
|
+
# Use a file for payload
|
|
493
|
+
npx @adcp/client https://agent.example.com create_media_buy @payload.json
|
|
474
494
|
|
|
475
495
|
# JSON output for scripting
|
|
476
|
-
npx @adcp/client
|
|
496
|
+
npx @adcp/client https://agent.example.com get_products '{"brief":"..."}' --json | jq '.products'
|
|
477
497
|
```
|
|
478
498
|
|
|
479
|
-
|
|
499
|
+
### Authentication
|
|
500
|
+
|
|
501
|
+
Three ways to provide auth tokens (priority order):
|
|
502
|
+
|
|
503
|
+
```bash
|
|
504
|
+
# 1. Explicit flag (highest priority)
|
|
505
|
+
npx @adcp/client test get_products '{"brief":"..."}' --auth your-token
|
|
506
|
+
|
|
507
|
+
# 2. Saved in agent config (recommended)
|
|
508
|
+
npx @adcp/client --save-auth prod https://prod-agent.com
|
|
509
|
+
# Will prompt for auth token securely
|
|
510
|
+
|
|
511
|
+
# 3. Environment variable (fallback)
|
|
512
|
+
export ADCP_AUTH_TOKEN=your-token
|
|
513
|
+
npx @adcp/client test get_products '{"brief":"..."}'
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Agent Management
|
|
517
|
+
|
|
518
|
+
```bash
|
|
519
|
+
# Save agent with auth
|
|
520
|
+
npx @adcp/client --save-auth prod https://prod-agent.com mcp
|
|
521
|
+
|
|
522
|
+
# List all saved agents
|
|
523
|
+
npx @adcp/client --list-agents
|
|
524
|
+
|
|
525
|
+
# Remove an agent
|
|
526
|
+
npx @adcp/client --remove-agent test
|
|
527
|
+
|
|
528
|
+
# Show config file location
|
|
529
|
+
npx @adcp/client --show-config
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
**Protocol Auto-Detection**: The CLI automatically detects whether an endpoint uses MCP or A2A by checking URL patterns and discovery endpoints. Override with `--protocol mcp` or `--protocol a2a` if needed.
|
|
533
|
+
|
|
534
|
+
**Config File**: Agent configurations are saved to `~/.adcp/config.json` with secure file permissions (0600).
|
|
480
535
|
|
|
481
536
|
See [docs/CLI.md](docs/CLI.md) for complete CLI documentation including webhook support for async operations.
|
|
482
537
|
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Async webhook handler for AdCP CLI
|
|
5
|
+
*
|
|
6
|
+
* This module handles async/webhook responses by:
|
|
7
|
+
* 1. Starting a temporary HTTP server for webhooks
|
|
8
|
+
* 2. Using ngrok to expose the server publicly (if available)
|
|
9
|
+
* 3. Waiting for the async response
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const http = require('http');
|
|
13
|
+
const { spawn } = require('child_process');
|
|
14
|
+
const { randomUUID } = require('crypto');
|
|
15
|
+
|
|
16
|
+
class AsyncWebhookHandler {
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.port = options.port || 0; // 0 = random available port
|
|
19
|
+
this.timeout = options.timeout || 300000; // 5 minutes default
|
|
20
|
+
this.debug = options.debug || false;
|
|
21
|
+
this.server = null;
|
|
22
|
+
this.ngrokProcess = null;
|
|
23
|
+
this.webhookUrl = null;
|
|
24
|
+
this.operationId = randomUUID();
|
|
25
|
+
this.responsePromise = null;
|
|
26
|
+
this.responseResolver = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if ngrok is installed
|
|
31
|
+
*/
|
|
32
|
+
static isNgrokAvailable() {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
const check = spawn('which', ['ngrok']);
|
|
35
|
+
check.on('close', (code) => resolve(code === 0));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Start the webhook server and optionally ngrok tunnel
|
|
41
|
+
* @param {boolean} useNgrok - Whether to use ngrok (default: true)
|
|
42
|
+
*/
|
|
43
|
+
async start(useNgrok = true) {
|
|
44
|
+
// Create the promise that will resolve when we get the webhook
|
|
45
|
+
this.responsePromise = new Promise((resolve, reject) => {
|
|
46
|
+
this.responseResolver = resolve;
|
|
47
|
+
this.responseRejector = reject;
|
|
48
|
+
|
|
49
|
+
// Set timeout
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
reject(new Error(`Webhook timeout after ${this.timeout}ms`));
|
|
52
|
+
}, this.timeout);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Start HTTP server
|
|
56
|
+
await this.startServer();
|
|
57
|
+
|
|
58
|
+
if (useNgrok) {
|
|
59
|
+
// Start ngrok tunnel
|
|
60
|
+
const ngrokAvailable = await AsyncWebhookHandler.isNgrokAvailable();
|
|
61
|
+
if (ngrokAvailable) {
|
|
62
|
+
await this.startNgrok();
|
|
63
|
+
} else {
|
|
64
|
+
throw new Error('ngrok is not installed. Install it with: brew install ngrok (Mac) or download from https://ngrok.com');
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
// Use local URL (for local agents)
|
|
68
|
+
this.webhookUrl = `http://localhost:${this.port}`;
|
|
69
|
+
|
|
70
|
+
if (this.debug) {
|
|
71
|
+
console.error(`ā
Local webhook server ready: ${this.webhookUrl}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return this.webhookUrl;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Start the HTTP server
|
|
80
|
+
*/
|
|
81
|
+
startServer() {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
this.server = http.createServer((req, res) => {
|
|
84
|
+
if (req.method === 'POST') {
|
|
85
|
+
let body = '';
|
|
86
|
+
|
|
87
|
+
req.on('data', chunk => {
|
|
88
|
+
body += chunk.toString();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
req.on('end', () => {
|
|
92
|
+
try {
|
|
93
|
+
const payload = JSON.parse(body);
|
|
94
|
+
|
|
95
|
+
if (this.debug) {
|
|
96
|
+
console.error('\nš£ Webhook received:');
|
|
97
|
+
console.error(JSON.stringify(payload, null, 2));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Send 202 Accepted response
|
|
101
|
+
res.writeHead(202, { 'Content-Type': 'application/json' });
|
|
102
|
+
res.end(JSON.stringify({ status: 'accepted' }));
|
|
103
|
+
|
|
104
|
+
// Resolve the promise with the webhook payload
|
|
105
|
+
if (this.responseResolver) {
|
|
106
|
+
this.responseResolver(payload);
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (this.debug) {
|
|
110
|
+
console.error('Error parsing webhook:', error);
|
|
111
|
+
}
|
|
112
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
113
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
} else {
|
|
117
|
+
// Health check endpoint
|
|
118
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
119
|
+
res.end(JSON.stringify({ status: 'ready', operation_id: this.operationId }));
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
this.server.listen(this.port, () => {
|
|
124
|
+
const address = this.server.address();
|
|
125
|
+
this.port = address.port;
|
|
126
|
+
|
|
127
|
+
if (this.debug) {
|
|
128
|
+
console.error(`ā
Webhook server listening on port ${this.port}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
resolve();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
this.server.on('error', reject);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Start ngrok tunnel
|
|
140
|
+
*/
|
|
141
|
+
startNgrok() {
|
|
142
|
+
return new Promise((resolve, reject) => {
|
|
143
|
+
if (this.debug) {
|
|
144
|
+
console.error(`š Starting ngrok tunnel for port ${this.port}...`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Start ngrok with JSON output for easier parsing
|
|
148
|
+
this.ngrokProcess = spawn('ngrok', ['http', String(this.port), '--log=stdout', '--log-format=json']);
|
|
149
|
+
|
|
150
|
+
let ngrokStarted = false;
|
|
151
|
+
let buffer = '';
|
|
152
|
+
|
|
153
|
+
this.ngrokProcess.stdout.on('data', (data) => {
|
|
154
|
+
buffer += data.toString();
|
|
155
|
+
const lines = buffer.split('\n');
|
|
156
|
+
buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
157
|
+
|
|
158
|
+
for (const line of lines) {
|
|
159
|
+
if (!line.trim()) continue;
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const parsed = JSON.parse(line);
|
|
163
|
+
|
|
164
|
+
// Look for the tunnel URL in ngrok's JSON output
|
|
165
|
+
if (parsed.url && parsed.url.startsWith('https://')) {
|
|
166
|
+
this.webhookUrl = parsed.url;
|
|
167
|
+
ngrokStarted = true;
|
|
168
|
+
|
|
169
|
+
if (this.debug) {
|
|
170
|
+
console.error(`ā
ngrok tunnel ready: ${this.webhookUrl}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
resolve();
|
|
174
|
+
}
|
|
175
|
+
} catch (e) {
|
|
176
|
+
// Not JSON, might be plain text output
|
|
177
|
+
// Try to extract URL from plain text
|
|
178
|
+
const urlMatch = line.match(/https:\/\/[a-z0-9-]+\.ngrok(?:-free)?\.(?:app|io)/);
|
|
179
|
+
if (urlMatch && !ngrokStarted) {
|
|
180
|
+
this.webhookUrl = urlMatch[0];
|
|
181
|
+
ngrokStarted = true;
|
|
182
|
+
|
|
183
|
+
if (this.debug) {
|
|
184
|
+
console.error(`ā
ngrok tunnel ready: ${this.webhookUrl}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
resolve();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
this.ngrokProcess.stderr.on('data', (data) => {
|
|
194
|
+
if (this.debug) {
|
|
195
|
+
console.error('ngrok stderr:', data.toString());
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
this.ngrokProcess.on('error', (error) => {
|
|
200
|
+
reject(new Error(`Failed to start ngrok: ${error.message}`));
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
this.ngrokProcess.on('close', (code) => {
|
|
204
|
+
if (!ngrokStarted && code !== 0) {
|
|
205
|
+
reject(new Error(`ngrok exited with code ${code}`));
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Timeout for ngrok startup
|
|
210
|
+
setTimeout(() => {
|
|
211
|
+
if (!ngrokStarted) {
|
|
212
|
+
reject(new Error('ngrok failed to start within 10 seconds'));
|
|
213
|
+
}
|
|
214
|
+
}, 10000);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Wait for the webhook response
|
|
220
|
+
*/
|
|
221
|
+
async waitForResponse() {
|
|
222
|
+
if (this.debug) {
|
|
223
|
+
console.error('\nā³ Waiting for async response...');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const startTime = Date.now();
|
|
227
|
+
const result = await this.responsePromise;
|
|
228
|
+
const duration = Date.now() - startTime;
|
|
229
|
+
|
|
230
|
+
if (this.debug) {
|
|
231
|
+
console.error(`ā
Response received after ${(duration / 1000).toFixed(1)}s\n`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Clean up resources
|
|
239
|
+
*/
|
|
240
|
+
async cleanup() {
|
|
241
|
+
if (this.debug) {
|
|
242
|
+
console.error('š§¹ Cleaning up...');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Close HTTP server
|
|
246
|
+
if (this.server) {
|
|
247
|
+
await new Promise((resolve) => {
|
|
248
|
+
this.server.close(() => resolve());
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Kill ngrok process
|
|
253
|
+
if (this.ngrokProcess) {
|
|
254
|
+
this.ngrokProcess.kill();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (this.debug) {
|
|
258
|
+
console.error('ā
Cleanup complete');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Get the webhook URL with operation ID
|
|
264
|
+
*/
|
|
265
|
+
getWebhookUrl() {
|
|
266
|
+
if (!this.webhookUrl) {
|
|
267
|
+
throw new Error('Webhook server not started');
|
|
268
|
+
}
|
|
269
|
+
return `${this.webhookUrl}?operation_id=${this.operationId}`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = { AsyncWebhookHandler };
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdCP CLI Configuration Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages agent aliases and authentication configuration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
const CONFIG_DIR = path.join(os.homedir(), '.adcp');
|
|
12
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get the config file path
|
|
16
|
+
*/
|
|
17
|
+
function getConfigPath() {
|
|
18
|
+
return CONFIG_FILE;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Ensure config directory exists
|
|
23
|
+
*/
|
|
24
|
+
function ensureConfigDir() {
|
|
25
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
26
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Load configuration from disk
|
|
32
|
+
* @returns {Object} Configuration object
|
|
33
|
+
*/
|
|
34
|
+
function loadConfig() {
|
|
35
|
+
try {
|
|
36
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
37
|
+
return { agents: {}, defaults: {} };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
41
|
+
return JSON.parse(content);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(`Warning: Failed to load config from ${CONFIG_FILE}: ${error.message}`);
|
|
44
|
+
return { agents: {}, defaults: {} };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Save configuration to disk
|
|
50
|
+
* @param {Object} config Configuration object
|
|
51
|
+
*/
|
|
52
|
+
function saveConfig(config) {
|
|
53
|
+
try {
|
|
54
|
+
ensureConfigDir();
|
|
55
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throw new Error(`Failed to save config to ${CONFIG_FILE}: ${error.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get agent configuration by alias
|
|
63
|
+
* @param {string} alias Agent alias
|
|
64
|
+
* @returns {Object|null} Agent config or null if not found
|
|
65
|
+
*/
|
|
66
|
+
function getAgent(alias) {
|
|
67
|
+
const config = loadConfig();
|
|
68
|
+
return config.agents[alias] || null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* List all saved agents
|
|
73
|
+
* @returns {Object} Map of alias to agent config
|
|
74
|
+
*/
|
|
75
|
+
function listAgents() {
|
|
76
|
+
const config = loadConfig();
|
|
77
|
+
return config.agents || {};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Save an agent configuration
|
|
82
|
+
* @param {string} alias Agent alias
|
|
83
|
+
* @param {Object} agentConfig Agent configuration
|
|
84
|
+
*/
|
|
85
|
+
function saveAgent(alias, agentConfig) {
|
|
86
|
+
const config = loadConfig();
|
|
87
|
+
if (!config.agents) {
|
|
88
|
+
config.agents = {};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
config.agents[alias] = agentConfig;
|
|
92
|
+
saveConfig(config);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Remove an agent configuration
|
|
97
|
+
* @param {string} alias Agent alias
|
|
98
|
+
* @returns {boolean} True if agent was removed
|
|
99
|
+
*/
|
|
100
|
+
function removeAgent(alias) {
|
|
101
|
+
const config = loadConfig();
|
|
102
|
+
if (config.agents && config.agents[alias]) {
|
|
103
|
+
delete config.agents[alias];
|
|
104
|
+
saveConfig(config);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if a string is an agent alias
|
|
112
|
+
* @param {string} str String to check
|
|
113
|
+
* @returns {boolean} True if string is a saved alias
|
|
114
|
+
*/
|
|
115
|
+
function isAlias(str) {
|
|
116
|
+
const config = loadConfig();
|
|
117
|
+
return config.agents && config.agents[str] !== undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Prompt for input securely (for passwords/tokens)
|
|
122
|
+
* @param {string} prompt Prompt message
|
|
123
|
+
* @param {boolean} hidden Hide input (for passwords)
|
|
124
|
+
* @returns {Promise<string>} User input
|
|
125
|
+
*/
|
|
126
|
+
async function promptSecure(prompt, hidden = true) {
|
|
127
|
+
const readline = require('readline');
|
|
128
|
+
|
|
129
|
+
return new Promise((resolve) => {
|
|
130
|
+
const rl = readline.createInterface({
|
|
131
|
+
input: process.stdin,
|
|
132
|
+
output: process.stdout
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (hidden) {
|
|
136
|
+
// Disable echo for password input
|
|
137
|
+
const stdin = process.stdin;
|
|
138
|
+
const originalSetRawMode = stdin.setRawMode;
|
|
139
|
+
|
|
140
|
+
// Mute output
|
|
141
|
+
rl.stdoutMuted = true;
|
|
142
|
+
rl._writeToOutput = function _writeToOutput(stringToWrite) {
|
|
143
|
+
if (!rl.stdoutMuted) {
|
|
144
|
+
rl.output.write(stringToWrite);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
rl.question(prompt, (answer) => {
|
|
150
|
+
rl.close();
|
|
151
|
+
if (hidden) {
|
|
152
|
+
console.log(''); // New line after hidden input
|
|
153
|
+
}
|
|
154
|
+
resolve(answer.trim());
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Interactive agent setup
|
|
161
|
+
* @param {string} alias Agent alias
|
|
162
|
+
* @param {string} url Agent URL (optional)
|
|
163
|
+
* @param {string} protocol Protocol (optional)
|
|
164
|
+
* @param {string} authToken Auth token (optional)
|
|
165
|
+
* @param {boolean} nonInteractive Skip prompts if all required args provided
|
|
166
|
+
*/
|
|
167
|
+
async function interactiveSetup(alias, url = null, protocol = null, authToken = null, nonInteractive = false) {
|
|
168
|
+
// Non-interactive mode: if URL is provided, just save it
|
|
169
|
+
if (nonInteractive && url) {
|
|
170
|
+
const agentConfig = { url };
|
|
171
|
+
if (protocol) agentConfig.protocol = protocol;
|
|
172
|
+
if (authToken) agentConfig.auth_token = authToken;
|
|
173
|
+
|
|
174
|
+
saveAgent(alias, agentConfig);
|
|
175
|
+
console.log(`\nā
Agent '${alias}' saved to ${CONFIG_FILE}`);
|
|
176
|
+
console.log(`\nYou can now use: adcp ${alias} <tool> <payload>`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log(`\nš Setting up agent: ${alias}\n`);
|
|
181
|
+
|
|
182
|
+
// Get URL if not provided
|
|
183
|
+
if (!url) {
|
|
184
|
+
url = await promptSecure('Agent URL: ', false);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Get protocol if not provided (optional, can auto-detect)
|
|
188
|
+
if (!protocol) {
|
|
189
|
+
const protocolInput = await promptSecure('Protocol (mcp/a2a, or leave blank to auto-detect): ', false);
|
|
190
|
+
protocol = protocolInput || null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Get auth token if not provided
|
|
194
|
+
if (!authToken) {
|
|
195
|
+
authToken = await promptSecure('Auth token (leave blank if not needed): ', true);
|
|
196
|
+
authToken = authToken || null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Build config
|
|
200
|
+
const agentConfig = {
|
|
201
|
+
url: url
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
if (protocol) {
|
|
205
|
+
agentConfig.protocol = protocol;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (authToken) {
|
|
209
|
+
agentConfig.auth_token = authToken;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Save
|
|
213
|
+
saveAgent(alias, agentConfig);
|
|
214
|
+
|
|
215
|
+
console.log(`\nā
Agent '${alias}' saved to ${CONFIG_FILE}`);
|
|
216
|
+
console.log(`\nYou can now use: adcp ${alias} <tool> <payload>`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
module.exports = {
|
|
220
|
+
getConfigPath,
|
|
221
|
+
loadConfig,
|
|
222
|
+
saveConfig,
|
|
223
|
+
getAgent,
|
|
224
|
+
listAgents,
|
|
225
|
+
saveAgent,
|
|
226
|
+
removeAgent,
|
|
227
|
+
isAlias,
|
|
228
|
+
promptSecure,
|
|
229
|
+
interactiveSetup
|
|
230
|
+
};
|