@gopherhole/cli 0.1.0 ā 0.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/dist/index.js +806 -82
- package/package.json +3 -2
- package/src/index.ts +874 -80
package/src/index.ts
CHANGED
|
@@ -8,19 +8,339 @@ import open from 'open';
|
|
|
8
8
|
|
|
9
9
|
const config = new Conf({ projectName: 'gopherhole' });
|
|
10
10
|
const API_URL = 'https://gopherhole.ai/api';
|
|
11
|
+
const WS_URL = 'wss://gopherhole.helixdata.workers.dev/ws';
|
|
12
|
+
|
|
13
|
+
// Brand colors
|
|
14
|
+
const brand = {
|
|
15
|
+
green: chalk.hex('#22c55e'), // gopher-500 - primary
|
|
16
|
+
greenBright: chalk.hex('#4ade80'), // gopher-400 - highlights
|
|
17
|
+
greenDark: chalk.hex('#16a34a'), // gopher-600 - emphasis
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Global verbose flag
|
|
21
|
+
let verbose = false;
|
|
22
|
+
|
|
23
|
+
function log(...args: unknown[]) {
|
|
24
|
+
if (verbose) {
|
|
25
|
+
console.log(chalk.gray('[verbose]'), ...args);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function logError(context: string, error: unknown) {
|
|
30
|
+
if (verbose) {
|
|
31
|
+
console.error(chalk.red(`[verbose:${context}]`), error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
11
34
|
|
|
12
35
|
const program = new Command();
|
|
13
36
|
|
|
14
37
|
program
|
|
15
38
|
.name('gopherhole')
|
|
16
|
-
.description(
|
|
17
|
-
|
|
39
|
+
.description(`šæļø GopherHole CLI - Connect AI agents to the world
|
|
40
|
+
|
|
41
|
+
${chalk.bold('Quick Start:')}
|
|
42
|
+
$ gopherhole quickstart # Interactive setup wizard
|
|
43
|
+
$ gopherhole signup # Create an account
|
|
44
|
+
$ gopherhole agents create # Create your first agent
|
|
45
|
+
|
|
46
|
+
${chalk.bold('Examples:')}
|
|
47
|
+
$ gopherhole discover search "weather"
|
|
48
|
+
$ gopherhole send agent-abc123 "Hello!"
|
|
49
|
+
$ gopherhole agents list
|
|
50
|
+
|
|
51
|
+
${chalk.bold('Documentation:')}
|
|
52
|
+
https://gopherhole.ai/docs
|
|
53
|
+
`)
|
|
54
|
+
.version('0.1.0')
|
|
55
|
+
.option('-v, --verbose', 'Enable verbose output for debugging')
|
|
56
|
+
.hook('preAction', (thisCommand) => {
|
|
57
|
+
verbose = thisCommand.opts().verbose || false;
|
|
58
|
+
if (verbose) {
|
|
59
|
+
console.log(chalk.gray('[verbose] Verbose mode enabled'));
|
|
60
|
+
console.log(chalk.gray(`[verbose] API: ${API_URL}`));
|
|
61
|
+
console.log(chalk.gray(`[verbose] WebSocket: ${WS_URL}`));
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ========== QUICKSTART COMMAND ==========
|
|
66
|
+
|
|
67
|
+
program
|
|
68
|
+
.command('quickstart')
|
|
69
|
+
.description(`Interactive setup wizard for new users
|
|
70
|
+
|
|
71
|
+
${chalk.bold('What it does:')}
|
|
72
|
+
1. Creates an account (or logs you in)
|
|
73
|
+
2. Creates your first agent
|
|
74
|
+
3. Generates starter code
|
|
75
|
+
4. Shows next steps
|
|
76
|
+
|
|
77
|
+
${chalk.bold('Example:')}
|
|
78
|
+
$ gopherhole quickstart
|
|
79
|
+
`)
|
|
80
|
+
.action(async () => {
|
|
81
|
+
console.log(chalk.bold('\nšæļø Welcome to GopherHole!\n'));
|
|
82
|
+
console.log('This wizard will help you set up your first agent.\n');
|
|
83
|
+
|
|
84
|
+
let sessionId = config.get('sessionId') as string;
|
|
85
|
+
|
|
86
|
+
// Step 1: Auth
|
|
87
|
+
if (sessionId) {
|
|
88
|
+
const user = config.get('user') as { email: string } | undefined;
|
|
89
|
+
console.log(brand.green(`ā Already logged in as ${user?.email}\n`));
|
|
90
|
+
} else {
|
|
91
|
+
console.log(chalk.bold('Step 1: Create an account\n'));
|
|
92
|
+
|
|
93
|
+
const { action } = await inquirer.prompt([
|
|
94
|
+
{
|
|
95
|
+
type: 'list',
|
|
96
|
+
name: 'action',
|
|
97
|
+
message: 'Do you have a GopherHole account?',
|
|
98
|
+
choices: [
|
|
99
|
+
{ name: 'No, create one for me', value: 'signup' },
|
|
100
|
+
{ name: 'Yes, log me in', value: 'login' },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
if (action === 'signup') {
|
|
106
|
+
const { name, email, password } = await inquirer.prompt([
|
|
107
|
+
{ type: 'input', name: 'name', message: 'Your name:' },
|
|
108
|
+
{ type: 'input', name: 'email', message: 'Email:' },
|
|
109
|
+
{ type: 'password', name: 'password', message: 'Password (min 8 chars):' },
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
const spinner = ora('Creating your account...').start();
|
|
113
|
+
log('POST /auth/signup', { email });
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const res = await fetch(`${API_URL}/auth/signup`, {
|
|
117
|
+
method: 'POST',
|
|
118
|
+
headers: { 'Content-Type': 'application/json' },
|
|
119
|
+
body: JSON.stringify({ name, email, password }),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (!res.ok) {
|
|
123
|
+
const err = await res.json();
|
|
124
|
+
logError('signup', err);
|
|
125
|
+
throw new Error(err.error || 'Signup failed');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const data = await res.json();
|
|
129
|
+
config.set('sessionId', data.sessionId);
|
|
130
|
+
config.set('user', data.user);
|
|
131
|
+
config.set('tenant', data.tenant);
|
|
132
|
+
sessionId = data.sessionId;
|
|
133
|
+
spinner.succeed('Account created!');
|
|
134
|
+
log('Session ID stored');
|
|
135
|
+
} catch (err) {
|
|
136
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
const { email, password } = await inquirer.prompt([
|
|
141
|
+
{ type: 'input', name: 'email', message: 'Email:' },
|
|
142
|
+
{ type: 'password', name: 'password', message: 'Password:' },
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
const spinner = ora('Logging in...').start();
|
|
146
|
+
log('POST /auth/login', { email });
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const res = await fetch(`${API_URL}/auth/login`, {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
headers: { 'Content-Type': 'application/json' },
|
|
152
|
+
body: JSON.stringify({ email, password }),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (!res.ok) {
|
|
156
|
+
const err = await res.json();
|
|
157
|
+
logError('login', err);
|
|
158
|
+
throw new Error(err.error || 'Login failed');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const data = await res.json();
|
|
162
|
+
config.set('sessionId', data.sessionId);
|
|
163
|
+
config.set('user', data.user);
|
|
164
|
+
config.set('tenant', data.tenant);
|
|
165
|
+
sessionId = data.sessionId;
|
|
166
|
+
spinner.succeed('Logged in!');
|
|
167
|
+
log('Session ID stored');
|
|
168
|
+
} catch (err) {
|
|
169
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Step 2: Create agent
|
|
176
|
+
console.log(chalk.bold('\nStep 2: Create your agent\n'));
|
|
177
|
+
|
|
178
|
+
const { agentName, agentDesc } = await inquirer.prompt([
|
|
179
|
+
{
|
|
180
|
+
type: 'input',
|
|
181
|
+
name: 'agentName',
|
|
182
|
+
message: 'Agent name:',
|
|
183
|
+
default: 'my-agent',
|
|
184
|
+
validate: (input) => input.length > 0 || 'Name is required',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
type: 'input',
|
|
188
|
+
name: 'agentDesc',
|
|
189
|
+
message: 'What does your agent do? (optional):',
|
|
190
|
+
},
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
const spinner = ora('Creating agent...').start();
|
|
194
|
+
log('POST /agents', { name: agentName });
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const res = await fetch(`${API_URL}/agents`, {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
headers: {
|
|
200
|
+
'Content-Type': 'application/json',
|
|
201
|
+
'X-Session-ID': sessionId,
|
|
202
|
+
},
|
|
203
|
+
body: JSON.stringify({ name: agentName, description: agentDesc }),
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (!res.ok) {
|
|
207
|
+
const err = await res.json();
|
|
208
|
+
logError('create agent', err);
|
|
209
|
+
throw new Error(err.error || 'Failed to create agent');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const data = await res.json();
|
|
213
|
+
spinner.succeed('Agent created!');
|
|
214
|
+
|
|
215
|
+
// Step 3: Show results
|
|
216
|
+
console.log(chalk.bold('\n⨠Your agent is ready!\n'));
|
|
217
|
+
|
|
218
|
+
console.log(chalk.bold(' Agent Details:'));
|
|
219
|
+
console.log(` Name: ${brand.green(data.agent.name)}`);
|
|
220
|
+
console.log(` ID: ${chalk.cyan(data.agent.id)}`);
|
|
221
|
+
console.log(` API Key: ${chalk.yellow(data.apiKey)}`);
|
|
222
|
+
|
|
223
|
+
console.log(chalk.bold('\n Connection Info:'));
|
|
224
|
+
console.log(` WebSocket: ${chalk.cyan(WS_URL)}`);
|
|
225
|
+
console.log(` REST API: ${chalk.cyan('https://gopherhole.ai/a2a')}`);
|
|
226
|
+
|
|
227
|
+
console.log(chalk.red('\n ā ļø Save your API key now - it won\'t be shown again!\n'));
|
|
228
|
+
|
|
229
|
+
// Step 4: Generate code
|
|
230
|
+
console.log(chalk.bold('Step 3: Quick start code\n'));
|
|
231
|
+
|
|
232
|
+
const { language } = await inquirer.prompt([
|
|
233
|
+
{
|
|
234
|
+
type: 'list',
|
|
235
|
+
name: 'language',
|
|
236
|
+
message: 'Which language?',
|
|
237
|
+
choices: [
|
|
238
|
+
{ name: 'TypeScript / JavaScript', value: 'ts' },
|
|
239
|
+
{ name: 'Python', value: 'py' },
|
|
240
|
+
{ name: 'Go', value: 'go' },
|
|
241
|
+
{ name: 'Just show me the curl command', value: 'curl' },
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
console.log('');
|
|
247
|
+
|
|
248
|
+
if (language === 'ts') {
|
|
249
|
+
console.log(chalk.gray(' # Install the SDK'));
|
|
250
|
+
console.log(chalk.cyan(' npm install @gopherhole/sdk\n'));
|
|
251
|
+
console.log(chalk.gray(' # agent.ts'));
|
|
252
|
+
console.log(chalk.white(` import { GopherHole } from '@gopherhole/sdk';
|
|
253
|
+
|
|
254
|
+
const client = new GopherHole('${data.apiKey}');
|
|
255
|
+
|
|
256
|
+
async function main() {
|
|
257
|
+
await client.connect();
|
|
258
|
+
console.log('šæļø Connected to GopherHole!');
|
|
259
|
+
|
|
260
|
+
// Handle incoming messages
|
|
261
|
+
client.on('message', async (msg) => {
|
|
262
|
+
console.log('From:', msg.from, 'Message:', msg.text);
|
|
263
|
+
|
|
264
|
+
// Reply back
|
|
265
|
+
await client.send(msg.from, 'Hello! I got your message.');
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
main();`));
|
|
270
|
+
} else if (language === 'py') {
|
|
271
|
+
console.log(chalk.gray(' # Install the SDK'));
|
|
272
|
+
console.log(chalk.cyan(' pip install gopherhole\n'));
|
|
273
|
+
console.log(chalk.gray(' # agent.py'));
|
|
274
|
+
console.log(chalk.white(` from gopherhole import GopherHole
|
|
275
|
+
|
|
276
|
+
client = GopherHole("${data.apiKey}")
|
|
277
|
+
|
|
278
|
+
@client.on_message
|
|
279
|
+
async def handle_message(msg):
|
|
280
|
+
print(f"From: {msg.from_agent}, Message: {msg.text}")
|
|
281
|
+
await client.send(msg.from_agent, "Hello! I got your message.")
|
|
282
|
+
|
|
283
|
+
if __name__ == "__main__":
|
|
284
|
+
client.run()`));
|
|
285
|
+
} else if (language === 'go') {
|
|
286
|
+
console.log(chalk.gray(' # Install the SDK'));
|
|
287
|
+
console.log(chalk.cyan(' go get github.com/gopherhole/gopherhole-go\n'));
|
|
288
|
+
console.log(chalk.gray(' // main.go'));
|
|
289
|
+
console.log(chalk.white(` package main
|
|
290
|
+
|
|
291
|
+
import (
|
|
292
|
+
"fmt"
|
|
293
|
+
gopherhole "github.com/gopherhole/gopherhole-go"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
func main() {
|
|
297
|
+
client := gopherhole.New("${data.apiKey}")
|
|
298
|
+
|
|
299
|
+
client.OnMessage(func(msg *gopherhole.Message) {
|
|
300
|
+
fmt.Printf("From: %s, Message: %s\\n", msg.From, msg.Text)
|
|
301
|
+
client.Send(msg.From, "Hello! I got your message.")
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
client.Connect()
|
|
305
|
+
}`));
|
|
306
|
+
} else {
|
|
307
|
+
console.log(chalk.gray(' # Test with curl'));
|
|
308
|
+
console.log(chalk.white(` curl -X POST https://gopherhole.ai/a2a \\
|
|
309
|
+
-H "Authorization: Bearer ${data.apiKey}" \\
|
|
310
|
+
-H "Content-Type: application/json" \\
|
|
311
|
+
-d '{
|
|
312
|
+
"jsonrpc": "2.0",
|
|
313
|
+
"method": "message/send",
|
|
314
|
+
"params": {
|
|
315
|
+
"message": { "role": "user", "parts": [{ "kind": "text", "text": "Hello!" }] },
|
|
316
|
+
"configuration": { "agentId": "echo" }
|
|
317
|
+
},
|
|
318
|
+
"id": 1
|
|
319
|
+
}'`));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
console.log(chalk.bold('\nš Next steps:\n'));
|
|
323
|
+
console.log(` ⢠Dashboard: ${chalk.cyan('https://gopherhole.ai/dashboard')}`);
|
|
324
|
+
console.log(` ⢠Docs: ${chalk.cyan('https://gopherhole.ai/docs')}`);
|
|
325
|
+
console.log(` ⢠Find agents: ${chalk.cyan('gopherhole discover search')}`);
|
|
326
|
+
console.log(` ⢠List yours: ${chalk.cyan('gopherhole agents list')}`);
|
|
327
|
+
console.log('');
|
|
328
|
+
|
|
329
|
+
} catch (err) {
|
|
330
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
18
334
|
|
|
19
335
|
// ========== AUTH COMMANDS ==========
|
|
20
336
|
|
|
21
337
|
program
|
|
22
338
|
.command('login')
|
|
23
|
-
.description(
|
|
339
|
+
.description(`Log in to GopherHole
|
|
340
|
+
|
|
341
|
+
${chalk.bold('Example:')}
|
|
342
|
+
$ gopherhole login
|
|
343
|
+
`)
|
|
24
344
|
.action(async () => {
|
|
25
345
|
const { email, password } = await inquirer.prompt([
|
|
26
346
|
{ type: 'input', name: 'email', message: 'Email:' },
|
|
@@ -28,6 +348,7 @@ program
|
|
|
28
348
|
]);
|
|
29
349
|
|
|
30
350
|
const spinner = ora('Logging in...').start();
|
|
351
|
+
log('POST /auth/login', { email });
|
|
31
352
|
|
|
32
353
|
try {
|
|
33
354
|
const res = await fetch(`${API_URL}/auth/login`, {
|
|
@@ -38,6 +359,7 @@ program
|
|
|
38
359
|
|
|
39
360
|
if (!res.ok) {
|
|
40
361
|
const err = await res.json();
|
|
362
|
+
logError('login', err);
|
|
41
363
|
throw new Error(err.error || 'Login failed');
|
|
42
364
|
}
|
|
43
365
|
|
|
@@ -46,24 +368,34 @@ program
|
|
|
46
368
|
config.set('user', data.user);
|
|
47
369
|
config.set('tenant', data.tenant);
|
|
48
370
|
|
|
49
|
-
spinner.succeed(`Logged in as ${
|
|
371
|
+
spinner.succeed(`Logged in as ${brand.green(data.user.email)}`);
|
|
372
|
+
log('Session stored in:', config.path);
|
|
50
373
|
} catch (err) {
|
|
51
374
|
spinner.fail(chalk.red((err as Error).message));
|
|
375
|
+
console.log(chalk.gray('\nTroubleshooting:'));
|
|
376
|
+
console.log(chalk.gray(' ⢠Check your email and password'));
|
|
377
|
+
console.log(chalk.gray(' ⢠Try: gopherhole signup (to create account)'));
|
|
378
|
+
console.log(chalk.gray(' ⢠Run with --verbose for more details'));
|
|
52
379
|
process.exit(1);
|
|
53
380
|
}
|
|
54
381
|
});
|
|
55
382
|
|
|
56
383
|
program
|
|
57
384
|
.command('signup')
|
|
58
|
-
.description(
|
|
385
|
+
.description(`Create a new GopherHole account
|
|
386
|
+
|
|
387
|
+
${chalk.bold('Example:')}
|
|
388
|
+
$ gopherhole signup
|
|
389
|
+
`)
|
|
59
390
|
.action(async () => {
|
|
60
391
|
const { name, email, password } = await inquirer.prompt([
|
|
61
392
|
{ type: 'input', name: 'name', message: 'Name:' },
|
|
62
393
|
{ type: 'input', name: 'email', message: 'Email:' },
|
|
63
|
-
{ type: 'password', name: 'password', message: 'Password:' },
|
|
394
|
+
{ type: 'password', name: 'password', message: 'Password (min 8 chars):' },
|
|
64
395
|
]);
|
|
65
396
|
|
|
66
397
|
const spinner = ora('Creating account...').start();
|
|
398
|
+
log('POST /auth/signup', { email });
|
|
67
399
|
|
|
68
400
|
try {
|
|
69
401
|
const res = await fetch(`${API_URL}/auth/signup`, {
|
|
@@ -74,6 +406,7 @@ program
|
|
|
74
406
|
|
|
75
407
|
if (!res.ok) {
|
|
76
408
|
const err = await res.json();
|
|
409
|
+
logError('signup', err);
|
|
77
410
|
throw new Error(err.error || 'Signup failed');
|
|
78
411
|
}
|
|
79
412
|
|
|
@@ -82,7 +415,8 @@ program
|
|
|
82
415
|
config.set('user', data.user);
|
|
83
416
|
config.set('tenant', data.tenant);
|
|
84
417
|
|
|
85
|
-
spinner.succeed(`Account created! Logged in as ${
|
|
418
|
+
spinner.succeed(`Account created! Logged in as ${brand.green(data.user.email)}`);
|
|
419
|
+
console.log(chalk.gray('\nNext: gopherhole agents create'));
|
|
86
420
|
} catch (err) {
|
|
87
421
|
spinner.fail(chalk.red((err as Error).message));
|
|
88
422
|
process.exit(1);
|
|
@@ -94,7 +428,8 @@ program
|
|
|
94
428
|
.description('Log out of GopherHole')
|
|
95
429
|
.action(() => {
|
|
96
430
|
config.clear();
|
|
97
|
-
console.log(
|
|
431
|
+
console.log(brand.green('ā Logged out successfully'));
|
|
432
|
+
log('Config cleared:', config.path);
|
|
98
433
|
});
|
|
99
434
|
|
|
100
435
|
program
|
|
@@ -102,53 +437,83 @@ program
|
|
|
102
437
|
.description('Show current logged in user')
|
|
103
438
|
.action(() => {
|
|
104
439
|
const user = config.get('user') as { email: string; name: string } | undefined;
|
|
440
|
+
const tenant = config.get('tenant') as { name: string } | undefined;
|
|
441
|
+
|
|
105
442
|
if (!user) {
|
|
106
|
-
console.log(chalk.yellow('Not logged in.
|
|
443
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
444
|
+
console.log(chalk.gray('\nTo log in: gopherhole login'));
|
|
445
|
+
console.log(chalk.gray('To sign up: gopherhole signup'));
|
|
107
446
|
process.exit(1);
|
|
108
447
|
}
|
|
109
|
-
|
|
448
|
+
|
|
449
|
+
console.log(`\n ${chalk.bold('User:')} ${user.name} <${user.email}>`);
|
|
450
|
+
console.log(` ${chalk.bold('Org:')} ${tenant?.name || 'Personal'}`);
|
|
451
|
+
console.log(` ${chalk.bold('Config:')} ${config.path}\n`);
|
|
110
452
|
});
|
|
111
453
|
|
|
112
454
|
// ========== AGENT COMMANDS ==========
|
|
113
455
|
|
|
114
|
-
const agents = program
|
|
456
|
+
const agents = program
|
|
457
|
+
.command('agents')
|
|
458
|
+
.description(`Manage your agents
|
|
459
|
+
|
|
460
|
+
${chalk.bold('Examples:')}
|
|
461
|
+
$ gopherhole agents list
|
|
462
|
+
$ gopherhole agents create --name "my-bot"
|
|
463
|
+
$ gopherhole agents delete agent-abc123
|
|
464
|
+
`);
|
|
115
465
|
|
|
116
466
|
agents
|
|
117
467
|
.command('list')
|
|
118
468
|
.description('List your agents')
|
|
119
|
-
.
|
|
469
|
+
.option('--json', 'Output as JSON')
|
|
470
|
+
.action(async (options) => {
|
|
120
471
|
const sessionId = config.get('sessionId') as string;
|
|
121
472
|
if (!sessionId) {
|
|
122
|
-
console.log(chalk.yellow('Not logged in.
|
|
473
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
474
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
123
475
|
process.exit(1);
|
|
124
476
|
}
|
|
125
477
|
|
|
126
478
|
const spinner = ora('Fetching agents...').start();
|
|
479
|
+
log('GET /agents');
|
|
127
480
|
|
|
128
481
|
try {
|
|
129
482
|
const res = await fetch(`${API_URL}/agents`, {
|
|
130
483
|
headers: { 'X-Session-ID': sessionId },
|
|
131
484
|
});
|
|
132
485
|
|
|
133
|
-
if (!res.ok)
|
|
486
|
+
if (!res.ok) {
|
|
487
|
+
const err = await res.json();
|
|
488
|
+
logError('list', err);
|
|
489
|
+
throw new Error('Failed to fetch agents');
|
|
490
|
+
}
|
|
134
491
|
|
|
135
492
|
const data = await res.json();
|
|
136
493
|
spinner.stop();
|
|
137
494
|
|
|
495
|
+
if (options.json) {
|
|
496
|
+
console.log(JSON.stringify(data.agents, null, 2));
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
138
500
|
if (data.agents.length === 0) {
|
|
139
|
-
console.log(chalk.yellow('
|
|
501
|
+
console.log(chalk.yellow('\nNo agents yet.\n'));
|
|
502
|
+
console.log(chalk.gray('Create one: gopherhole agents create'));
|
|
503
|
+
console.log(chalk.gray('Or try: gopherhole quickstart\n'));
|
|
140
504
|
return;
|
|
141
505
|
}
|
|
142
506
|
|
|
143
|
-
console.log(chalk.bold('\
|
|
507
|
+
console.log(chalk.bold('\nšæļø Your Agents:\n'));
|
|
144
508
|
for (const agent of data.agents) {
|
|
145
|
-
const status = agent.status === 'active' ?
|
|
146
|
-
console.log(` ${status} ${chalk.bold(agent.name)}
|
|
509
|
+
const status = agent.status === 'active' ? brand.green('ā') : chalk.red('ā');
|
|
510
|
+
console.log(` ${status} ${chalk.bold(agent.name)}`);
|
|
511
|
+
console.log(` ID: ${chalk.cyan(agent.id)}`);
|
|
147
512
|
if (agent.description) {
|
|
148
513
|
console.log(` ${chalk.gray(agent.description)}`);
|
|
149
514
|
}
|
|
515
|
+
console.log('');
|
|
150
516
|
}
|
|
151
|
-
console.log('');
|
|
152
517
|
} catch (err) {
|
|
153
518
|
spinner.fail(chalk.red((err as Error).message));
|
|
154
519
|
process.exit(1);
|
|
@@ -157,13 +522,19 @@ agents
|
|
|
157
522
|
|
|
158
523
|
agents
|
|
159
524
|
.command('create')
|
|
160
|
-
.description(
|
|
525
|
+
.description(`Create a new agent
|
|
526
|
+
|
|
527
|
+
${chalk.bold('Examples:')}
|
|
528
|
+
$ gopherhole agents create
|
|
529
|
+
$ gopherhole agents create --name "weather-bot" --description "Gets weather"
|
|
530
|
+
`)
|
|
161
531
|
.option('-n, --name <name>', 'Agent name')
|
|
162
532
|
.option('-d, --description <description>', 'Agent description')
|
|
163
533
|
.action(async (options) => {
|
|
164
534
|
const sessionId = config.get('sessionId') as string;
|
|
165
535
|
if (!sessionId) {
|
|
166
|
-
console.log(chalk.yellow('Not logged in.
|
|
536
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
537
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
167
538
|
process.exit(1);
|
|
168
539
|
}
|
|
169
540
|
|
|
@@ -171,7 +542,12 @@ agents
|
|
|
171
542
|
|
|
172
543
|
if (!name) {
|
|
173
544
|
const answers = await inquirer.prompt([
|
|
174
|
-
{
|
|
545
|
+
{
|
|
546
|
+
type: 'input',
|
|
547
|
+
name: 'name',
|
|
548
|
+
message: 'Agent name:',
|
|
549
|
+
validate: (input) => input.length > 0 || 'Name is required',
|
|
550
|
+
},
|
|
175
551
|
{ type: 'input', name: 'description', message: 'Description (optional):' },
|
|
176
552
|
]);
|
|
177
553
|
name = answers.name;
|
|
@@ -179,6 +555,7 @@ agents
|
|
|
179
555
|
}
|
|
180
556
|
|
|
181
557
|
const spinner = ora('Creating agent...').start();
|
|
558
|
+
log('POST /agents', { name, description });
|
|
182
559
|
|
|
183
560
|
try {
|
|
184
561
|
const res = await fetch(`${API_URL}/agents`, {
|
|
@@ -192,28 +569,29 @@ agents
|
|
|
192
569
|
|
|
193
570
|
if (!res.ok) {
|
|
194
571
|
const err = await res.json();
|
|
572
|
+
logError('create', err);
|
|
195
573
|
throw new Error(err.error || 'Failed to create agent');
|
|
196
574
|
}
|
|
197
575
|
|
|
198
576
|
const data = await res.json();
|
|
199
|
-
spinner.succeed(`Agent created: ${
|
|
200
|
-
|
|
201
|
-
console.log(
|
|
202
|
-
console.log(`
|
|
203
|
-
console.log(
|
|
204
|
-
|
|
205
|
-
console.log('');
|
|
206
|
-
console.log(chalk.
|
|
207
|
-
console.log(chalk.
|
|
208
|
-
import { GopherHole } from '@gopherhole/sdk';
|
|
577
|
+
spinner.succeed(`Agent created: ${brand.green(data.agent.name)}`);
|
|
578
|
+
|
|
579
|
+
console.log(chalk.bold('\n Agent Details:'));
|
|
580
|
+
console.log(` ID: ${chalk.cyan(data.agent.id)}`);
|
|
581
|
+
console.log(` API Key: ${chalk.yellow(data.apiKey)}`);
|
|
582
|
+
|
|
583
|
+
console.log(chalk.bold('\n Connection:'));
|
|
584
|
+
console.log(` WebSocket: ${chalk.cyan(WS_URL)}`);
|
|
585
|
+
console.log(` REST API: ${chalk.cyan('https://gopherhole.ai/a2a')}`);
|
|
209
586
|
|
|
210
|
-
|
|
211
|
-
await hub.connect();
|
|
587
|
+
console.log(chalk.red('\n ā ļø Save your API key now - it won\'t be shown again!'));
|
|
212
588
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
589
|
+
console.log(chalk.bold('\n Quick test:'));
|
|
590
|
+
console.log(chalk.white(` curl -H "Authorization: Bearer ${data.apiKey}" \\
|
|
591
|
+
https://gopherhole.ai/a2a -d '{"jsonrpc":"2.0","method":"agent/info","id":1}'`));
|
|
592
|
+
|
|
593
|
+
console.log(chalk.gray('\n Full docs: https://gopherhole.ai/docs'));
|
|
594
|
+
console.log('');
|
|
217
595
|
} catch (err) {
|
|
218
596
|
spinner.fail(chalk.red((err as Error).message));
|
|
219
597
|
process.exit(1);
|
|
@@ -222,29 +600,38 @@ agents
|
|
|
222
600
|
|
|
223
601
|
agents
|
|
224
602
|
.command('delete <agentId>')
|
|
225
|
-
.description(
|
|
226
|
-
|
|
603
|
+
.description(`Delete an agent
|
|
604
|
+
|
|
605
|
+
${chalk.bold('Example:')}
|
|
606
|
+
$ gopherhole agents delete agent-abc123
|
|
607
|
+
`)
|
|
608
|
+
.option('-f, --force', 'Skip confirmation')
|
|
609
|
+
.action(async (agentId, options) => {
|
|
227
610
|
const sessionId = config.get('sessionId') as string;
|
|
228
611
|
if (!sessionId) {
|
|
229
|
-
console.log(chalk.yellow('Not logged in.
|
|
612
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
613
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
230
614
|
process.exit(1);
|
|
231
615
|
}
|
|
232
616
|
|
|
233
|
-
|
|
234
|
-
{
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
617
|
+
if (!options.force) {
|
|
618
|
+
const { confirm } = await inquirer.prompt([
|
|
619
|
+
{
|
|
620
|
+
type: 'confirm',
|
|
621
|
+
name: 'confirm',
|
|
622
|
+
message: `Delete agent ${chalk.cyan(agentId)}? This cannot be undone.`,
|
|
623
|
+
default: false,
|
|
624
|
+
},
|
|
625
|
+
]);
|
|
241
626
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
627
|
+
if (!confirm) {
|
|
628
|
+
console.log('Cancelled.');
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
245
631
|
}
|
|
246
632
|
|
|
247
633
|
const spinner = ora('Deleting agent...').start();
|
|
634
|
+
log('DELETE /agents/' + agentId);
|
|
248
635
|
|
|
249
636
|
try {
|
|
250
637
|
const res = await fetch(`${API_URL}/agents/${agentId}`, {
|
|
@@ -252,7 +639,11 @@ agents
|
|
|
252
639
|
headers: { 'X-Session-ID': sessionId },
|
|
253
640
|
});
|
|
254
641
|
|
|
255
|
-
if (!res.ok)
|
|
642
|
+
if (!res.ok) {
|
|
643
|
+
const err = await res.json();
|
|
644
|
+
logError('delete', err);
|
|
645
|
+
throw new Error(err.error || 'Failed to delete agent');
|
|
646
|
+
}
|
|
256
647
|
|
|
257
648
|
spinner.succeed('Agent deleted');
|
|
258
649
|
} catch (err) {
|
|
@@ -265,24 +656,34 @@ agents
|
|
|
265
656
|
|
|
266
657
|
program
|
|
267
658
|
.command('init')
|
|
268
|
-
.description(
|
|
659
|
+
.description(`Initialize GopherHole in current directory
|
|
660
|
+
|
|
661
|
+
${chalk.bold('What it does:')}
|
|
662
|
+
⢠Creates .env with your API key
|
|
663
|
+
⢠Generates starter code (agent.ts)
|
|
664
|
+
⢠Sets up package.json if needed
|
|
665
|
+
|
|
666
|
+
${chalk.bold('Example:')}
|
|
667
|
+
$ mkdir my-agent && cd my-agent
|
|
668
|
+
$ gopherhole init
|
|
669
|
+
`)
|
|
269
670
|
.action(async () => {
|
|
270
|
-
console.log(chalk.bold('\nšæļø GopherHole
|
|
671
|
+
console.log(chalk.bold('\nšæļø Initialize GopherHole Project\n'));
|
|
271
672
|
|
|
272
673
|
let sessionId = config.get('sessionId') as string;
|
|
273
674
|
|
|
274
675
|
// Check if logged in
|
|
275
676
|
if (!sessionId) {
|
|
276
|
-
console.log('
|
|
677
|
+
console.log(chalk.yellow('Not logged in yet.\n'));
|
|
277
678
|
|
|
278
679
|
const { action } = await inquirer.prompt([
|
|
279
680
|
{
|
|
280
681
|
type: 'list',
|
|
281
682
|
name: 'action',
|
|
282
|
-
message: '
|
|
683
|
+
message: 'Choose:',
|
|
283
684
|
choices: [
|
|
284
|
-
{ name: '
|
|
285
|
-
{ name: '
|
|
685
|
+
{ name: 'Create account', value: 'signup' },
|
|
686
|
+
{ name: 'Log in', value: 'login' },
|
|
286
687
|
],
|
|
287
688
|
},
|
|
288
689
|
]);
|
|
@@ -368,41 +769,73 @@ program
|
|
|
368
769
|
|
|
369
770
|
// Write .env file
|
|
370
771
|
const fs = await import('fs');
|
|
371
|
-
fs.writeFileSync('.env',
|
|
372
|
-
|
|
772
|
+
fs.writeFileSync('.env', `# GopherHole Configuration
|
|
773
|
+
GOPHERHOLE_API_KEY=${data.apiKey}
|
|
774
|
+
GOPHERHOLE_AGENT_ID=${data.agent.id}
|
|
775
|
+
`);
|
|
776
|
+
console.log(brand.green('ā Created .env'));
|
|
373
777
|
|
|
374
778
|
// Write example code
|
|
375
779
|
const exampleCode = `import { GopherHole } from '@gopherhole/sdk';
|
|
780
|
+
import 'dotenv/config';
|
|
376
781
|
|
|
377
|
-
const
|
|
782
|
+
const client = new GopherHole(process.env.GOPHERHOLE_API_KEY!);
|
|
378
783
|
|
|
379
784
|
async function main() {
|
|
380
|
-
await
|
|
381
|
-
console.log('šæļø Connected to GopherHole!');
|
|
785
|
+
await client.connect();
|
|
786
|
+
console.log('šæļø Connected to GopherHole as ${agentName}!');
|
|
382
787
|
|
|
383
|
-
|
|
384
|
-
|
|
788
|
+
// Handle incoming messages
|
|
789
|
+
client.on('message', async (msg) => {
|
|
790
|
+
console.log(\`š© Message from \${msg.from}:\`, msg.text);
|
|
385
791
|
|
|
386
|
-
//
|
|
387
|
-
await
|
|
792
|
+
// Echo back
|
|
793
|
+
await client.send(msg.from, \`You said: \${msg.text}\`);
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
// Keep alive
|
|
797
|
+
process.on('SIGINT', async () => {
|
|
798
|
+
console.log('\\nš Disconnecting...');
|
|
799
|
+
await client.disconnect();
|
|
800
|
+
process.exit(0);
|
|
388
801
|
});
|
|
389
802
|
}
|
|
390
803
|
|
|
391
804
|
main().catch(console.error);
|
|
392
805
|
`;
|
|
393
806
|
fs.writeFileSync('agent.ts', exampleCode);
|
|
394
|
-
console.log(
|
|
807
|
+
console.log(brand.green('ā Created agent.ts'));
|
|
808
|
+
|
|
809
|
+
// Create package.json if it doesn't exist
|
|
810
|
+
if (!fs.existsSync('package.json')) {
|
|
811
|
+
const pkg = {
|
|
812
|
+
name: agentName,
|
|
813
|
+
version: '0.1.0',
|
|
814
|
+
type: 'module',
|
|
815
|
+
scripts: {
|
|
816
|
+
start: 'tsx agent.ts',
|
|
817
|
+
dev: 'tsx watch agent.ts',
|
|
818
|
+
},
|
|
819
|
+
dependencies: {
|
|
820
|
+
'@gopherhole/sdk': '^0.1.0',
|
|
821
|
+
dotenv: '^16.0.0',
|
|
822
|
+
},
|
|
823
|
+
devDependencies: {
|
|
824
|
+
tsx: '^4.0.0',
|
|
825
|
+
typescript: '^5.0.0',
|
|
826
|
+
},
|
|
827
|
+
};
|
|
828
|
+
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
|
829
|
+
console.log(brand.green('ā Created package.json'));
|
|
830
|
+
}
|
|
395
831
|
|
|
396
|
-
console.log('');
|
|
832
|
+
console.log(chalk.bold('\nš Project initialized!\n'));
|
|
397
833
|
console.log(chalk.bold('Next steps:'));
|
|
398
|
-
console.log('');
|
|
399
|
-
console.log('
|
|
400
|
-
console.log(chalk.cyan(' npm install @gopherhole/sdk'));
|
|
401
|
-
console.log('');
|
|
402
|
-
console.log(' 2. Run your agent:');
|
|
403
|
-
console.log(chalk.cyan(' npx ts-node agent.ts'));
|
|
834
|
+
console.log(chalk.cyan(' npm install'));
|
|
835
|
+
console.log(chalk.cyan(' npm start'));
|
|
404
836
|
console.log('');
|
|
405
837
|
console.log(chalk.gray(`Dashboard: https://gopherhole.ai/dashboard`));
|
|
838
|
+
console.log(chalk.gray(`Docs: https://gopherhole.ai/docs`));
|
|
406
839
|
console.log('');
|
|
407
840
|
});
|
|
408
841
|
|
|
@@ -410,15 +843,22 @@ main().catch(console.error);
|
|
|
410
843
|
|
|
411
844
|
program
|
|
412
845
|
.command('send <agentId> <message>')
|
|
413
|
-
.description(
|
|
846
|
+
.description(`Send a message to an agent (for testing)
|
|
847
|
+
|
|
848
|
+
${chalk.bold('Examples:')}
|
|
849
|
+
$ gopherhole send echo "Hello!"
|
|
850
|
+
$ gopherhole send agent-abc123 "What's the weather?"
|
|
851
|
+
`)
|
|
414
852
|
.action(async (agentId, message) => {
|
|
415
853
|
const sessionId = config.get('sessionId') as string;
|
|
416
854
|
if (!sessionId) {
|
|
417
|
-
console.log(chalk.yellow('Not logged in.
|
|
855
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
856
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
418
857
|
process.exit(1);
|
|
419
858
|
}
|
|
420
859
|
|
|
421
|
-
const spinner = ora(
|
|
860
|
+
const spinner = ora(`Sending to ${agentId}...`).start();
|
|
861
|
+
log('POST /a2a message/send', { to: agentId, message });
|
|
422
862
|
|
|
423
863
|
try {
|
|
424
864
|
const res = await fetch(`${API_URL}/../a2a`, {
|
|
@@ -439,13 +879,367 @@ program
|
|
|
439
879
|
});
|
|
440
880
|
|
|
441
881
|
const data = await res.json();
|
|
882
|
+
|
|
883
|
+
if (data.error) {
|
|
884
|
+
logError('send', data.error);
|
|
885
|
+
throw new Error(data.error.message || 'Send failed');
|
|
886
|
+
}
|
|
887
|
+
|
|
442
888
|
spinner.succeed('Message sent!');
|
|
443
|
-
|
|
889
|
+
|
|
890
|
+
if (data.result?.task) {
|
|
891
|
+
console.log(chalk.gray(`\nTask ID: ${data.result.task.id}`));
|
|
892
|
+
console.log(chalk.gray(`Status: ${data.result.task.status.state}`));
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (verbose) {
|
|
896
|
+
console.log(chalk.gray('\nFull response:'));
|
|
897
|
+
console.log(JSON.stringify(data, null, 2));
|
|
898
|
+
}
|
|
444
899
|
} catch (err) {
|
|
445
900
|
spinner.fail(chalk.red((err as Error).message));
|
|
446
901
|
process.exit(1);
|
|
447
902
|
}
|
|
448
903
|
});
|
|
449
904
|
|
|
905
|
+
// ========== DISCOVER COMMANDS ==========
|
|
906
|
+
|
|
907
|
+
const discover = program
|
|
908
|
+
.command('discover')
|
|
909
|
+
.description(`Discover public agents
|
|
910
|
+
|
|
911
|
+
${chalk.bold('Examples:')}
|
|
912
|
+
$ gopherhole discover search "weather"
|
|
913
|
+
$ gopherhole discover top
|
|
914
|
+
$ gopherhole discover info agent-abc123
|
|
915
|
+
`);
|
|
916
|
+
|
|
917
|
+
discover
|
|
918
|
+
.command('search [query]')
|
|
919
|
+
.description(`Search for agents
|
|
920
|
+
|
|
921
|
+
${chalk.bold('Examples:')}
|
|
922
|
+
$ gopherhole discover search
|
|
923
|
+
$ gopherhole discover search "email assistant"
|
|
924
|
+
$ gopherhole discover search --category productivity
|
|
925
|
+
$ gopherhole discover search --tag ai --sort popular
|
|
926
|
+
`)
|
|
927
|
+
.option('-c, --category <category>', 'Filter by category')
|
|
928
|
+
.option('-t, --tag <tag>', 'Filter by tag')
|
|
929
|
+
.option('-s, --sort <sort>', 'Sort by: rating, popular, recent', 'rating')
|
|
930
|
+
.option('-l, --limit <limit>', 'Number of results', '20')
|
|
931
|
+
.action(async (query, options) => {
|
|
932
|
+
const spinner = ora('Searching agents...').start();
|
|
933
|
+
|
|
934
|
+
try {
|
|
935
|
+
const params = new URLSearchParams();
|
|
936
|
+
if (query) params.set('q', query);
|
|
937
|
+
if (options.category) params.set('category', options.category);
|
|
938
|
+
if (options.tag) params.set('tag', options.tag);
|
|
939
|
+
if (options.sort) params.set('sort', options.sort);
|
|
940
|
+
if (options.limit) params.set('limit', options.limit);
|
|
941
|
+
|
|
942
|
+
log('GET /discover/agents?' + params.toString());
|
|
943
|
+
const res = await fetch(`${API_URL}/discover/agents?${params}`);
|
|
944
|
+
const data = await res.json();
|
|
945
|
+
spinner.stop();
|
|
946
|
+
|
|
947
|
+
if (data.agents.length === 0) {
|
|
948
|
+
console.log(chalk.yellow('\nNo agents found.'));
|
|
949
|
+
console.log(chalk.gray('Try: gopherhole discover search (without query)'));
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
console.log(chalk.bold(`\nš Found ${data.agents.length} agents:\n`));
|
|
954
|
+
|
|
955
|
+
for (const agent of data.agents) {
|
|
956
|
+
const stars = 'ā
'.repeat(Math.round(agent.avgRating)) + 'ā'.repeat(5 - Math.round(agent.avgRating));
|
|
957
|
+
const pricing = agent.pricing === 'free' ? brand.green('FREE') :
|
|
958
|
+
agent.pricing === 'paid' ? chalk.blue('PAID') : chalk.gray('CONTACT');
|
|
959
|
+
|
|
960
|
+
console.log(` ${chalk.bold(agent.name)} ${chalk.yellow(stars)} (${agent.ratingCount})`);
|
|
961
|
+
console.log(` ${chalk.gray(agent.description || 'No description')}`);
|
|
962
|
+
console.log(` ${chalk.cyan(agent.id)} | ${pricing} | ${agent.category || 'uncategorized'}`);
|
|
963
|
+
console.log('');
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
console.log(chalk.gray(`Tip: gopherhole discover info <agent-id> for details\n`));
|
|
967
|
+
} catch (err) {
|
|
968
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
969
|
+
process.exit(1);
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
discover
|
|
974
|
+
.command('categories')
|
|
975
|
+
.description('List available categories')
|
|
976
|
+
.action(async () => {
|
|
977
|
+
const spinner = ora('Fetching categories...').start();
|
|
978
|
+
|
|
979
|
+
try {
|
|
980
|
+
const res = await fetch(`${API_URL}/discover/categories`);
|
|
981
|
+
const data = await res.json();
|
|
982
|
+
spinner.stop();
|
|
983
|
+
|
|
984
|
+
if (data.categories.length === 0) {
|
|
985
|
+
console.log(chalk.yellow('No categories yet'));
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
console.log(chalk.bold('\nš Categories:\n'));
|
|
990
|
+
for (const cat of data.categories) {
|
|
991
|
+
console.log(` ${chalk.bold(cat.name)} (${cat.count} agents)`);
|
|
992
|
+
}
|
|
993
|
+
console.log(chalk.gray('\nSearch by category: gopherhole discover search --category <name>\n'));
|
|
994
|
+
} catch (err) {
|
|
995
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
996
|
+
process.exit(1);
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
discover
|
|
1001
|
+
.command('featured')
|
|
1002
|
+
.description('Show featured agents')
|
|
1003
|
+
.action(async () => {
|
|
1004
|
+
const spinner = ora('Fetching featured agents...').start();
|
|
1005
|
+
|
|
1006
|
+
try {
|
|
1007
|
+
const res = await fetch(`${API_URL}/discover/featured`);
|
|
1008
|
+
const data = await res.json();
|
|
1009
|
+
spinner.stop();
|
|
1010
|
+
|
|
1011
|
+
if (data.featured.length === 0) {
|
|
1012
|
+
console.log(chalk.yellow('No featured agents yet'));
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
console.log(chalk.bold('\nā Featured Agents:\n'));
|
|
1017
|
+
for (const agent of data.featured) {
|
|
1018
|
+
const stars = 'ā
'.repeat(Math.round(agent.avgRating)) + 'ā'.repeat(5 - Math.round(agent.avgRating));
|
|
1019
|
+
console.log(` ${chalk.bold(agent.name)} ${chalk.yellow(stars)}`);
|
|
1020
|
+
console.log(` ${chalk.gray(agent.description || 'No description')}`);
|
|
1021
|
+
console.log(` ${chalk.cyan(agent.id)}`);
|
|
1022
|
+
console.log('');
|
|
1023
|
+
}
|
|
1024
|
+
} catch (err) {
|
|
1025
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
1026
|
+
process.exit(1);
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
discover
|
|
1031
|
+
.command('info <agentId>')
|
|
1032
|
+
.description(`Get detailed info about an agent
|
|
1033
|
+
|
|
1034
|
+
${chalk.bold('Example:')}
|
|
1035
|
+
$ gopherhole discover info agent-abc123
|
|
1036
|
+
`)
|
|
1037
|
+
.action(async (agentId) => {
|
|
1038
|
+
const spinner = ora('Fetching agent info...').start();
|
|
1039
|
+
|
|
1040
|
+
try {
|
|
1041
|
+
log('GET /discover/agents/' + agentId);
|
|
1042
|
+
const res = await fetch(`${API_URL}/discover/agents/${agentId}`);
|
|
1043
|
+
if (!res.ok) {
|
|
1044
|
+
throw new Error('Agent not found');
|
|
1045
|
+
}
|
|
1046
|
+
const data = await res.json();
|
|
1047
|
+
spinner.stop();
|
|
1048
|
+
|
|
1049
|
+
const agent = data.agent;
|
|
1050
|
+
const stars = 'ā
'.repeat(Math.round(agent.stats.avgRating)) + 'ā'.repeat(5 - Math.round(agent.stats.avgRating));
|
|
1051
|
+
|
|
1052
|
+
console.log(chalk.bold(`\nšæļø ${agent.name}\n`));
|
|
1053
|
+
console.log(` ${chalk.gray(agent.description || 'No description')}`);
|
|
1054
|
+
console.log('');
|
|
1055
|
+
console.log(` ${chalk.bold('Rating:')} ${chalk.yellow(stars)} ${agent.stats.avgRating.toFixed(1)}/5 (${agent.stats.ratingCount} reviews)`);
|
|
1056
|
+
console.log(` ${chalk.bold('Messages:')} ${agent.stats.totalMessages.toLocaleString()}`);
|
|
1057
|
+
console.log(` ${chalk.bold('Success:')} ${(agent.stats.successRate * 100).toFixed(1)}%`);
|
|
1058
|
+
console.log(` ${chalk.bold('Response:')} ${agent.stats.avgResponseTime}ms avg`);
|
|
1059
|
+
console.log(` ${chalk.bold('Pricing:')} ${agent.pricing}`);
|
|
1060
|
+
console.log(` ${chalk.bold('Category:')} ${agent.category || 'None'}`);
|
|
1061
|
+
console.log(` ${chalk.bold('By:')} ${agent.tenantName}`);
|
|
1062
|
+
|
|
1063
|
+
if (agent.tags.length > 0) {
|
|
1064
|
+
console.log(` ${chalk.bold('Tags:')} ${agent.tags.join(', ')}`);
|
|
1065
|
+
}
|
|
1066
|
+
if (agent.websiteUrl) {
|
|
1067
|
+
console.log(` ${chalk.bold('Website:')} ${chalk.cyan(agent.websiteUrl)}`);
|
|
1068
|
+
}
|
|
1069
|
+
if (agent.docsUrl) {
|
|
1070
|
+
console.log(` ${chalk.bold('Docs:')} ${chalk.cyan(agent.docsUrl)}`);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
if (agent.agentCard?.skills?.length) {
|
|
1074
|
+
console.log(chalk.bold('\n Skills:'));
|
|
1075
|
+
for (const skill of agent.agentCard.skills) {
|
|
1076
|
+
console.log(` ⢠${skill.name}${skill.description ? ` - ${chalk.gray(skill.description)}` : ''}`);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
if (data.reviews.length > 0) {
|
|
1081
|
+
console.log(chalk.bold('\n Recent Reviews:'));
|
|
1082
|
+
for (const review of data.reviews.slice(0, 3)) {
|
|
1083
|
+
const reviewStars = 'ā
'.repeat(review.rating) + 'ā'.repeat(5 - review.rating);
|
|
1084
|
+
console.log(` ${chalk.yellow(reviewStars)} "${review.review}"`);
|
|
1085
|
+
console.log(` ${chalk.gray(`- ${review.reviewer_name}`)}`);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
console.log(chalk.bold('\n Quick start:'));
|
|
1090
|
+
console.log(chalk.cyan(` await client.send('${agentId}', 'Hello!')`));
|
|
1091
|
+
console.log('');
|
|
1092
|
+
} catch (err) {
|
|
1093
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
1094
|
+
process.exit(1);
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
discover
|
|
1099
|
+
.command('top')
|
|
1100
|
+
.description(`Show top-rated agents
|
|
1101
|
+
|
|
1102
|
+
${chalk.bold('Example:')}
|
|
1103
|
+
$ gopherhole discover top
|
|
1104
|
+
$ gopherhole discover top --limit 5
|
|
1105
|
+
`)
|
|
1106
|
+
.option('-l, --limit <limit>', 'Number of results', '10')
|
|
1107
|
+
.action(async (options) => {
|
|
1108
|
+
const spinner = ora('Fetching top agents...').start();
|
|
1109
|
+
|
|
1110
|
+
try {
|
|
1111
|
+
const res = await fetch(`${API_URL}/discover/agents?sort=rating&limit=${options.limit}`);
|
|
1112
|
+
const data = await res.json();
|
|
1113
|
+
spinner.stop();
|
|
1114
|
+
|
|
1115
|
+
console.log(chalk.bold('\nš Top Rated Agents:\n'));
|
|
1116
|
+
|
|
1117
|
+
let rank = 1;
|
|
1118
|
+
for (const agent of data.agents) {
|
|
1119
|
+
const stars = 'ā
'.repeat(Math.round(agent.avgRating)) + 'ā'.repeat(5 - Math.round(agent.avgRating));
|
|
1120
|
+
const medal = rank === 1 ? 'š„' : rank === 2 ? 'š„' : rank === 3 ? 'š„' : `#${rank}`;
|
|
1121
|
+
console.log(` ${medal} ${chalk.bold(agent.name)} ${chalk.yellow(stars)} (${agent.ratingCount})`);
|
|
1122
|
+
console.log(` ${chalk.gray(agent.description?.slice(0, 60) || 'No description')}${(agent.description?.length || 0) > 60 ? '...' : ''}`);
|
|
1123
|
+
console.log(` ${chalk.cyan(agent.id)}`);
|
|
1124
|
+
console.log('');
|
|
1125
|
+
rank++;
|
|
1126
|
+
}
|
|
1127
|
+
} catch (err) {
|
|
1128
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
1129
|
+
process.exit(1);
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1133
|
+
discover
|
|
1134
|
+
.command('rate <agentId>')
|
|
1135
|
+
.description(`Rate an agent
|
|
1136
|
+
|
|
1137
|
+
${chalk.bold('Example:')}
|
|
1138
|
+
$ gopherhole discover rate agent-abc123
|
|
1139
|
+
$ gopherhole discover rate agent-abc123 --rating 5 --message "Great bot!"
|
|
1140
|
+
`)
|
|
1141
|
+
.option('-r, --rating <rating>', 'Rating 1-5')
|
|
1142
|
+
.option('-m, --message <review>', 'Review message')
|
|
1143
|
+
.action(async (agentId, options) => {
|
|
1144
|
+
const sessionId = config.get('sessionId') as string;
|
|
1145
|
+
if (!sessionId) {
|
|
1146
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
1147
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
1148
|
+
process.exit(1);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
let { rating, message: review } = options;
|
|
1152
|
+
|
|
1153
|
+
if (!rating) {
|
|
1154
|
+
const answers = await inquirer.prompt([
|
|
1155
|
+
{
|
|
1156
|
+
type: 'list',
|
|
1157
|
+
name: 'rating',
|
|
1158
|
+
message: 'Your rating:',
|
|
1159
|
+
choices: [
|
|
1160
|
+
{ name: 'ā
ā
ā
ā
ā
Excellent', value: 5 },
|
|
1161
|
+
{ name: 'ā
ā
ā
ā
ā Great', value: 4 },
|
|
1162
|
+
{ name: 'ā
ā
ā
āā Good', value: 3 },
|
|
1163
|
+
{ name: 'ā
ā
āāā Fair', value: 2 },
|
|
1164
|
+
{ name: 'ā
āāāā Poor', value: 1 },
|
|
1165
|
+
],
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
type: 'input',
|
|
1169
|
+
name: 'review',
|
|
1170
|
+
message: 'Review (optional):',
|
|
1171
|
+
},
|
|
1172
|
+
]);
|
|
1173
|
+
rating = answers.rating;
|
|
1174
|
+
review = answers.review;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
const spinner = ora('Submitting rating...').start();
|
|
1178
|
+
|
|
1179
|
+
try {
|
|
1180
|
+
const res = await fetch(`${API_URL}/discover/agents/${agentId}/rate`, {
|
|
1181
|
+
method: 'POST',
|
|
1182
|
+
headers: {
|
|
1183
|
+
'Content-Type': 'application/json',
|
|
1184
|
+
'X-Session-ID': sessionId,
|
|
1185
|
+
},
|
|
1186
|
+
body: JSON.stringify({
|
|
1187
|
+
rating: parseInt(rating),
|
|
1188
|
+
review: review || undefined,
|
|
1189
|
+
}),
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1192
|
+
if (!res.ok) {
|
|
1193
|
+
const err = await res.json();
|
|
1194
|
+
throw new Error(err.error || 'Failed to rate');
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const data = await res.json();
|
|
1198
|
+
spinner.succeed(`Rated! New average: ${chalk.yellow(data.avgRating.toFixed(1))} (${data.ratingCount} reviews)`);
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
1201
|
+
process.exit(1);
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
// ========== STATUS COMMAND ==========
|
|
1206
|
+
|
|
1207
|
+
program
|
|
1208
|
+
.command('status')
|
|
1209
|
+
.description('Show GopherHole service status and your session info')
|
|
1210
|
+
.action(async () => {
|
|
1211
|
+
console.log(chalk.bold('\nšæļø GopherHole Status\n'));
|
|
1212
|
+
|
|
1213
|
+
// Check API health
|
|
1214
|
+
const spinner = ora('Checking API...').start();
|
|
1215
|
+
try {
|
|
1216
|
+
const start = Date.now();
|
|
1217
|
+
const res = await fetch(`${API_URL.replace('/api', '')}/health`);
|
|
1218
|
+
const latency = Date.now() - start;
|
|
1219
|
+
|
|
1220
|
+
if (res.ok) {
|
|
1221
|
+
spinner.succeed(`API: ${brand.green('Online')} (${latency}ms)`);
|
|
1222
|
+
} else {
|
|
1223
|
+
spinner.fail(`API: ${chalk.red('Degraded')}`);
|
|
1224
|
+
}
|
|
1225
|
+
} catch {
|
|
1226
|
+
spinner.fail(`API: ${chalk.red('Offline')}`);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Check login status
|
|
1230
|
+
const user = config.get('user') as { email: string } | undefined;
|
|
1231
|
+
if (user) {
|
|
1232
|
+
console.log(` Auth: ${brand.green('Logged in')} as ${user.email}`);
|
|
1233
|
+
} else {
|
|
1234
|
+
console.log(` Auth: ${chalk.yellow('Not logged in')}`);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
console.log(` Config: ${config.path}`);
|
|
1238
|
+
console.log('');
|
|
1239
|
+
console.log(chalk.gray(' Dashboard: https://gopherhole.ai/dashboard'));
|
|
1240
|
+
console.log(chalk.gray(' Status: https://status.gopherhole.ai'));
|
|
1241
|
+
console.log('');
|
|
1242
|
+
});
|
|
1243
|
+
|
|
450
1244
|
// Parse and run
|
|
451
1245
|
program.parse();
|