@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.
Files changed (3) hide show
  1. package/dist/index.js +806 -82
  2. package/package.json +3 -2
  3. package/src/index.ts +874 -80
package/dist/index.js CHANGED
@@ -11,21 +11,316 @@ const inquirer_1 = __importDefault(require("inquirer"));
11
11
  const ora_1 = __importDefault(require("ora"));
12
12
  const config = new conf_1.default({ projectName: 'gopherhole' });
13
13
  const API_URL = 'https://gopherhole.ai/api';
14
+ const WS_URL = 'wss://gopherhole.helixdata.workers.dev/ws';
15
+ // Brand colors
16
+ const brand = {
17
+ green: chalk_1.default.hex('#22c55e'), // gopher-500 - primary
18
+ greenBright: chalk_1.default.hex('#4ade80'), // gopher-400 - highlights
19
+ greenDark: chalk_1.default.hex('#16a34a'), // gopher-600 - emphasis
20
+ };
21
+ // Global verbose flag
22
+ let verbose = false;
23
+ function log(...args) {
24
+ if (verbose) {
25
+ console.log(chalk_1.default.gray('[verbose]'), ...args);
26
+ }
27
+ }
28
+ function logError(context, error) {
29
+ if (verbose) {
30
+ console.error(chalk_1.default.red(`[verbose:${context}]`), error);
31
+ }
32
+ }
14
33
  const program = new commander_1.Command();
15
34
  program
16
35
  .name('gopherhole')
17
- .description('šŸæļø GopherHole CLI - Connect AI agents to the world')
18
- .version('0.1.0');
36
+ .description(`šŸæļø GopherHole CLI - Connect AI agents to the world
37
+
38
+ ${chalk_1.default.bold('Quick Start:')}
39
+ $ gopherhole quickstart # Interactive setup wizard
40
+ $ gopherhole signup # Create an account
41
+ $ gopherhole agents create # Create your first agent
42
+
43
+ ${chalk_1.default.bold('Examples:')}
44
+ $ gopherhole discover search "weather"
45
+ $ gopherhole send agent-abc123 "Hello!"
46
+ $ gopherhole agents list
47
+
48
+ ${chalk_1.default.bold('Documentation:')}
49
+ https://gopherhole.ai/docs
50
+ `)
51
+ .version('0.1.0')
52
+ .option('-v, --verbose', 'Enable verbose output for debugging')
53
+ .hook('preAction', (thisCommand) => {
54
+ verbose = thisCommand.opts().verbose || false;
55
+ if (verbose) {
56
+ console.log(chalk_1.default.gray('[verbose] Verbose mode enabled'));
57
+ console.log(chalk_1.default.gray(`[verbose] API: ${API_URL}`));
58
+ console.log(chalk_1.default.gray(`[verbose] WebSocket: ${WS_URL}`));
59
+ }
60
+ });
61
+ // ========== QUICKSTART COMMAND ==========
62
+ program
63
+ .command('quickstart')
64
+ .description(`Interactive setup wizard for new users
65
+
66
+ ${chalk_1.default.bold('What it does:')}
67
+ 1. Creates an account (or logs you in)
68
+ 2. Creates your first agent
69
+ 3. Generates starter code
70
+ 4. Shows next steps
71
+
72
+ ${chalk_1.default.bold('Example:')}
73
+ $ gopherhole quickstart
74
+ `)
75
+ .action(async () => {
76
+ console.log(chalk_1.default.bold('\nšŸæļø Welcome to GopherHole!\n'));
77
+ console.log('This wizard will help you set up your first agent.\n');
78
+ let sessionId = config.get('sessionId');
79
+ // Step 1: Auth
80
+ if (sessionId) {
81
+ const user = config.get('user');
82
+ console.log(brand.green(`āœ“ Already logged in as ${user?.email}\n`));
83
+ }
84
+ else {
85
+ console.log(chalk_1.default.bold('Step 1: Create an account\n'));
86
+ const { action } = await inquirer_1.default.prompt([
87
+ {
88
+ type: 'list',
89
+ name: 'action',
90
+ message: 'Do you have a GopherHole account?',
91
+ choices: [
92
+ { name: 'No, create one for me', value: 'signup' },
93
+ { name: 'Yes, log me in', value: 'login' },
94
+ ],
95
+ },
96
+ ]);
97
+ if (action === 'signup') {
98
+ const { name, email, password } = await inquirer_1.default.prompt([
99
+ { type: 'input', name: 'name', message: 'Your name:' },
100
+ { type: 'input', name: 'email', message: 'Email:' },
101
+ { type: 'password', name: 'password', message: 'Password (min 8 chars):' },
102
+ ]);
103
+ const spinner = (0, ora_1.default)('Creating your account...').start();
104
+ log('POST /auth/signup', { email });
105
+ try {
106
+ const res = await fetch(`${API_URL}/auth/signup`, {
107
+ method: 'POST',
108
+ headers: { 'Content-Type': 'application/json' },
109
+ body: JSON.stringify({ name, email, password }),
110
+ });
111
+ if (!res.ok) {
112
+ const err = await res.json();
113
+ logError('signup', err);
114
+ throw new Error(err.error || 'Signup failed');
115
+ }
116
+ const data = await res.json();
117
+ config.set('sessionId', data.sessionId);
118
+ config.set('user', data.user);
119
+ config.set('tenant', data.tenant);
120
+ sessionId = data.sessionId;
121
+ spinner.succeed('Account created!');
122
+ log('Session ID stored');
123
+ }
124
+ catch (err) {
125
+ spinner.fail(chalk_1.default.red(err.message));
126
+ process.exit(1);
127
+ }
128
+ }
129
+ else {
130
+ const { email, password } = await inquirer_1.default.prompt([
131
+ { type: 'input', name: 'email', message: 'Email:' },
132
+ { type: 'password', name: 'password', message: 'Password:' },
133
+ ]);
134
+ const spinner = (0, ora_1.default)('Logging in...').start();
135
+ log('POST /auth/login', { email });
136
+ try {
137
+ const res = await fetch(`${API_URL}/auth/login`, {
138
+ method: 'POST',
139
+ headers: { 'Content-Type': 'application/json' },
140
+ body: JSON.stringify({ email, password }),
141
+ });
142
+ if (!res.ok) {
143
+ const err = await res.json();
144
+ logError('login', err);
145
+ throw new Error(err.error || 'Login failed');
146
+ }
147
+ const data = await res.json();
148
+ config.set('sessionId', data.sessionId);
149
+ config.set('user', data.user);
150
+ config.set('tenant', data.tenant);
151
+ sessionId = data.sessionId;
152
+ spinner.succeed('Logged in!');
153
+ log('Session ID stored');
154
+ }
155
+ catch (err) {
156
+ spinner.fail(chalk_1.default.red(err.message));
157
+ process.exit(1);
158
+ }
159
+ }
160
+ }
161
+ // Step 2: Create agent
162
+ console.log(chalk_1.default.bold('\nStep 2: Create your agent\n'));
163
+ const { agentName, agentDesc } = await inquirer_1.default.prompt([
164
+ {
165
+ type: 'input',
166
+ name: 'agentName',
167
+ message: 'Agent name:',
168
+ default: 'my-agent',
169
+ validate: (input) => input.length > 0 || 'Name is required',
170
+ },
171
+ {
172
+ type: 'input',
173
+ name: 'agentDesc',
174
+ message: 'What does your agent do? (optional):',
175
+ },
176
+ ]);
177
+ const spinner = (0, ora_1.default)('Creating agent...').start();
178
+ log('POST /agents', { name: agentName });
179
+ try {
180
+ const res = await fetch(`${API_URL}/agents`, {
181
+ method: 'POST',
182
+ headers: {
183
+ 'Content-Type': 'application/json',
184
+ 'X-Session-ID': sessionId,
185
+ },
186
+ body: JSON.stringify({ name: agentName, description: agentDesc }),
187
+ });
188
+ if (!res.ok) {
189
+ const err = await res.json();
190
+ logError('create agent', err);
191
+ throw new Error(err.error || 'Failed to create agent');
192
+ }
193
+ const data = await res.json();
194
+ spinner.succeed('Agent created!');
195
+ // Step 3: Show results
196
+ console.log(chalk_1.default.bold('\n✨ Your agent is ready!\n'));
197
+ console.log(chalk_1.default.bold(' Agent Details:'));
198
+ console.log(` Name: ${brand.green(data.agent.name)}`);
199
+ console.log(` ID: ${chalk_1.default.cyan(data.agent.id)}`);
200
+ console.log(` API Key: ${chalk_1.default.yellow(data.apiKey)}`);
201
+ console.log(chalk_1.default.bold('\n Connection Info:'));
202
+ console.log(` WebSocket: ${chalk_1.default.cyan(WS_URL)}`);
203
+ console.log(` REST API: ${chalk_1.default.cyan('https://gopherhole.ai/a2a')}`);
204
+ console.log(chalk_1.default.red('\n āš ļø Save your API key now - it won\'t be shown again!\n'));
205
+ // Step 4: Generate code
206
+ console.log(chalk_1.default.bold('Step 3: Quick start code\n'));
207
+ const { language } = await inquirer_1.default.prompt([
208
+ {
209
+ type: 'list',
210
+ name: 'language',
211
+ message: 'Which language?',
212
+ choices: [
213
+ { name: 'TypeScript / JavaScript', value: 'ts' },
214
+ { name: 'Python', value: 'py' },
215
+ { name: 'Go', value: 'go' },
216
+ { name: 'Just show me the curl command', value: 'curl' },
217
+ ],
218
+ },
219
+ ]);
220
+ console.log('');
221
+ if (language === 'ts') {
222
+ console.log(chalk_1.default.gray(' # Install the SDK'));
223
+ console.log(chalk_1.default.cyan(' npm install @gopherhole/sdk\n'));
224
+ console.log(chalk_1.default.gray(' # agent.ts'));
225
+ console.log(chalk_1.default.white(` import { GopherHole } from '@gopherhole/sdk';
226
+
227
+ const client = new GopherHole('${data.apiKey}');
228
+
229
+ async function main() {
230
+ await client.connect();
231
+ console.log('šŸæļø Connected to GopherHole!');
232
+
233
+ // Handle incoming messages
234
+ client.on('message', async (msg) => {
235
+ console.log('From:', msg.from, 'Message:', msg.text);
236
+
237
+ // Reply back
238
+ await client.send(msg.from, 'Hello! I got your message.');
239
+ });
240
+ }
241
+
242
+ main();`));
243
+ }
244
+ else if (language === 'py') {
245
+ console.log(chalk_1.default.gray(' # Install the SDK'));
246
+ console.log(chalk_1.default.cyan(' pip install gopherhole\n'));
247
+ console.log(chalk_1.default.gray(' # agent.py'));
248
+ console.log(chalk_1.default.white(` from gopherhole import GopherHole
249
+
250
+ client = GopherHole("${data.apiKey}")
251
+
252
+ @client.on_message
253
+ async def handle_message(msg):
254
+ print(f"From: {msg.from_agent}, Message: {msg.text}")
255
+ await client.send(msg.from_agent, "Hello! I got your message.")
256
+
257
+ if __name__ == "__main__":
258
+ client.run()`));
259
+ }
260
+ else if (language === 'go') {
261
+ console.log(chalk_1.default.gray(' # Install the SDK'));
262
+ console.log(chalk_1.default.cyan(' go get github.com/gopherhole/gopherhole-go\n'));
263
+ console.log(chalk_1.default.gray(' // main.go'));
264
+ console.log(chalk_1.default.white(` package main
265
+
266
+ import (
267
+ "fmt"
268
+ gopherhole "github.com/gopherhole/gopherhole-go"
269
+ )
270
+
271
+ func main() {
272
+ client := gopherhole.New("${data.apiKey}")
273
+
274
+ client.OnMessage(func(msg *gopherhole.Message) {
275
+ fmt.Printf("From: %s, Message: %s\\n", msg.From, msg.Text)
276
+ client.Send(msg.From, "Hello! I got your message.")
277
+ })
278
+
279
+ client.Connect()
280
+ }`));
281
+ }
282
+ else {
283
+ console.log(chalk_1.default.gray(' # Test with curl'));
284
+ console.log(chalk_1.default.white(` curl -X POST https://gopherhole.ai/a2a \\
285
+ -H "Authorization: Bearer ${data.apiKey}" \\
286
+ -H "Content-Type: application/json" \\
287
+ -d '{
288
+ "jsonrpc": "2.0",
289
+ "method": "message/send",
290
+ "params": {
291
+ "message": { "role": "user", "parts": [{ "kind": "text", "text": "Hello!" }] },
292
+ "configuration": { "agentId": "echo" }
293
+ },
294
+ "id": 1
295
+ }'`));
296
+ }
297
+ console.log(chalk_1.default.bold('\nšŸ“š Next steps:\n'));
298
+ console.log(` • Dashboard: ${chalk_1.default.cyan('https://gopherhole.ai/dashboard')}`);
299
+ console.log(` • Docs: ${chalk_1.default.cyan('https://gopherhole.ai/docs')}`);
300
+ console.log(` • Find agents: ${chalk_1.default.cyan('gopherhole discover search')}`);
301
+ console.log(` • List yours: ${chalk_1.default.cyan('gopherhole agents list')}`);
302
+ console.log('');
303
+ }
304
+ catch (err) {
305
+ spinner.fail(chalk_1.default.red(err.message));
306
+ process.exit(1);
307
+ }
308
+ });
19
309
  // ========== AUTH COMMANDS ==========
20
310
  program
21
311
  .command('login')
22
- .description('Log in to GopherHole')
312
+ .description(`Log in to GopherHole
313
+
314
+ ${chalk_1.default.bold('Example:')}
315
+ $ gopherhole login
316
+ `)
23
317
  .action(async () => {
24
318
  const { email, password } = await inquirer_1.default.prompt([
25
319
  { type: 'input', name: 'email', message: 'Email:' },
26
320
  { type: 'password', name: 'password', message: 'Password:' },
27
321
  ]);
28
322
  const spinner = (0, ora_1.default)('Logging in...').start();
323
+ log('POST /auth/login', { email });
29
324
  try {
30
325
  const res = await fetch(`${API_URL}/auth/login`, {
31
326
  method: 'POST',
@@ -34,29 +329,40 @@ program
34
329
  });
35
330
  if (!res.ok) {
36
331
  const err = await res.json();
332
+ logError('login', err);
37
333
  throw new Error(err.error || 'Login failed');
38
334
  }
39
335
  const data = await res.json();
40
336
  config.set('sessionId', data.sessionId);
41
337
  config.set('user', data.user);
42
338
  config.set('tenant', data.tenant);
43
- spinner.succeed(`Logged in as ${chalk_1.default.green(data.user.email)}`);
339
+ spinner.succeed(`Logged in as ${brand.green(data.user.email)}`);
340
+ log('Session stored in:', config.path);
44
341
  }
45
342
  catch (err) {
46
343
  spinner.fail(chalk_1.default.red(err.message));
344
+ console.log(chalk_1.default.gray('\nTroubleshooting:'));
345
+ console.log(chalk_1.default.gray(' • Check your email and password'));
346
+ console.log(chalk_1.default.gray(' • Try: gopherhole signup (to create account)'));
347
+ console.log(chalk_1.default.gray(' • Run with --verbose for more details'));
47
348
  process.exit(1);
48
349
  }
49
350
  });
50
351
  program
51
352
  .command('signup')
52
- .description('Create a new GopherHole account')
353
+ .description(`Create a new GopherHole account
354
+
355
+ ${chalk_1.default.bold('Example:')}
356
+ $ gopherhole signup
357
+ `)
53
358
  .action(async () => {
54
359
  const { name, email, password } = await inquirer_1.default.prompt([
55
360
  { type: 'input', name: 'name', message: 'Name:' },
56
361
  { type: 'input', name: 'email', message: 'Email:' },
57
- { type: 'password', name: 'password', message: 'Password:' },
362
+ { type: 'password', name: 'password', message: 'Password (min 8 chars):' },
58
363
  ]);
59
364
  const spinner = (0, ora_1.default)('Creating account...').start();
365
+ log('POST /auth/signup', { email });
60
366
  try {
61
367
  const res = await fetch(`${API_URL}/auth/signup`, {
62
368
  method: 'POST',
@@ -65,13 +371,15 @@ program
65
371
  });
66
372
  if (!res.ok) {
67
373
  const err = await res.json();
374
+ logError('signup', err);
68
375
  throw new Error(err.error || 'Signup failed');
69
376
  }
70
377
  const data = await res.json();
71
378
  config.set('sessionId', data.sessionId);
72
379
  config.set('user', data.user);
73
380
  config.set('tenant', data.tenant);
74
- spinner.succeed(`Account created! Logged in as ${chalk_1.default.green(data.user.email)}`);
381
+ spinner.succeed(`Account created! Logged in as ${brand.green(data.user.email)}`);
382
+ console.log(chalk_1.default.gray('\nNext: gopherhole agents create'));
75
383
  }
76
384
  catch (err) {
77
385
  spinner.fail(chalk_1.default.red(err.message));
@@ -83,52 +391,79 @@ program
83
391
  .description('Log out of GopherHole')
84
392
  .action(() => {
85
393
  config.clear();
86
- console.log(chalk_1.default.green('Logged out successfully'));
394
+ console.log(brand.green('āœ“ Logged out successfully'));
395
+ log('Config cleared:', config.path);
87
396
  });
88
397
  program
89
398
  .command('whoami')
90
399
  .description('Show current logged in user')
91
400
  .action(() => {
92
401
  const user = config.get('user');
402
+ const tenant = config.get('tenant');
93
403
  if (!user) {
94
- console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
404
+ console.log(chalk_1.default.yellow('Not logged in.'));
405
+ console.log(chalk_1.default.gray('\nTo log in: gopherhole login'));
406
+ console.log(chalk_1.default.gray('To sign up: gopherhole signup'));
95
407
  process.exit(1);
96
408
  }
97
- console.log(`Logged in as ${chalk_1.default.green(user.email)} (${user.name})`);
409
+ console.log(`\n ${chalk_1.default.bold('User:')} ${user.name} <${user.email}>`);
410
+ console.log(` ${chalk_1.default.bold('Org:')} ${tenant?.name || 'Personal'}`);
411
+ console.log(` ${chalk_1.default.bold('Config:')} ${config.path}\n`);
98
412
  });
99
413
  // ========== AGENT COMMANDS ==========
100
- const agents = program.command('agents').description('Manage agents');
414
+ const agents = program
415
+ .command('agents')
416
+ .description(`Manage your agents
417
+
418
+ ${chalk_1.default.bold('Examples:')}
419
+ $ gopherhole agents list
420
+ $ gopherhole agents create --name "my-bot"
421
+ $ gopherhole agents delete agent-abc123
422
+ `);
101
423
  agents
102
424
  .command('list')
103
425
  .description('List your agents')
104
- .action(async () => {
426
+ .option('--json', 'Output as JSON')
427
+ .action(async (options) => {
105
428
  const sessionId = config.get('sessionId');
106
429
  if (!sessionId) {
107
- console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
430
+ console.log(chalk_1.default.yellow('Not logged in.'));
431
+ console.log(chalk_1.default.gray('Run: gopherhole login'));
108
432
  process.exit(1);
109
433
  }
110
434
  const spinner = (0, ora_1.default)('Fetching agents...').start();
435
+ log('GET /agents');
111
436
  try {
112
437
  const res = await fetch(`${API_URL}/agents`, {
113
438
  headers: { 'X-Session-ID': sessionId },
114
439
  });
115
- if (!res.ok)
440
+ if (!res.ok) {
441
+ const err = await res.json();
442
+ logError('list', err);
116
443
  throw new Error('Failed to fetch agents');
444
+ }
117
445
  const data = await res.json();
118
446
  spinner.stop();
447
+ if (options.json) {
448
+ console.log(JSON.stringify(data.agents, null, 2));
449
+ return;
450
+ }
119
451
  if (data.agents.length === 0) {
120
- console.log(chalk_1.default.yellow('No agents yet. Create one with: gopherhole agents create'));
452
+ console.log(chalk_1.default.yellow('\nNo agents yet.\n'));
453
+ console.log(chalk_1.default.gray('Create one: gopherhole agents create'));
454
+ console.log(chalk_1.default.gray('Or try: gopherhole quickstart\n'));
121
455
  return;
122
456
  }
123
- console.log(chalk_1.default.bold('\nYour Agents:\n'));
457
+ console.log(chalk_1.default.bold('\nšŸæļø Your Agents:\n'));
124
458
  for (const agent of data.agents) {
125
- const status = agent.status === 'active' ? chalk_1.default.green('ā—') : chalk_1.default.red('ā—');
126
- console.log(` ${status} ${chalk_1.default.bold(agent.name)} (${agent.id})`);
459
+ const status = agent.status === 'active' ? brand.green('ā—') : chalk_1.default.red('ā—');
460
+ console.log(` ${status} ${chalk_1.default.bold(agent.name)}`);
461
+ console.log(` ID: ${chalk_1.default.cyan(agent.id)}`);
127
462
  if (agent.description) {
128
463
  console.log(` ${chalk_1.default.gray(agent.description)}`);
129
464
  }
465
+ console.log('');
130
466
  }
131
- console.log('');
132
467
  }
133
468
  catch (err) {
134
469
  spinner.fail(chalk_1.default.red(err.message));
@@ -137,25 +472,37 @@ agents
137
472
  });
138
473
  agents
139
474
  .command('create')
140
- .description('Create a new agent')
475
+ .description(`Create a new agent
476
+
477
+ ${chalk_1.default.bold('Examples:')}
478
+ $ gopherhole agents create
479
+ $ gopherhole agents create --name "weather-bot" --description "Gets weather"
480
+ `)
141
481
  .option('-n, --name <name>', 'Agent name')
142
482
  .option('-d, --description <description>', 'Agent description')
143
483
  .action(async (options) => {
144
484
  const sessionId = config.get('sessionId');
145
485
  if (!sessionId) {
146
- console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
486
+ console.log(chalk_1.default.yellow('Not logged in.'));
487
+ console.log(chalk_1.default.gray('Run: gopherhole login'));
147
488
  process.exit(1);
148
489
  }
149
490
  let { name, description } = options;
150
491
  if (!name) {
151
492
  const answers = await inquirer_1.default.prompt([
152
- { type: 'input', name: 'name', message: 'Agent name:' },
493
+ {
494
+ type: 'input',
495
+ name: 'name',
496
+ message: 'Agent name:',
497
+ validate: (input) => input.length > 0 || 'Name is required',
498
+ },
153
499
  { type: 'input', name: 'description', message: 'Description (optional):' },
154
500
  ]);
155
501
  name = answers.name;
156
502
  description = answers.description;
157
503
  }
158
504
  const spinner = (0, ora_1.default)('Creating agent...').start();
505
+ log('POST /agents', { name, description });
159
506
  try {
160
507
  const res = await fetch(`${API_URL}/agents`, {
161
508
  method: 'POST',
@@ -167,27 +514,23 @@ agents
167
514
  });
168
515
  if (!res.ok) {
169
516
  const err = await res.json();
517
+ logError('create', err);
170
518
  throw new Error(err.error || 'Failed to create agent');
171
519
  }
172
520
  const data = await res.json();
173
- spinner.succeed(`Agent created: ${chalk_1.default.green(data.agent.name)}`);
174
- console.log('');
175
- console.log(` ${chalk_1.default.bold('Agent ID:')} ${data.agent.id}`);
176
- console.log(` ${chalk_1.default.bold('API Key:')} ${chalk_1.default.cyan(data.apiKey)}`);
521
+ spinner.succeed(`Agent created: ${brand.green(data.agent.name)}`);
522
+ console.log(chalk_1.default.bold('\n Agent Details:'));
523
+ console.log(` ID: ${chalk_1.default.cyan(data.agent.id)}`);
524
+ console.log(` API Key: ${chalk_1.default.yellow(data.apiKey)}`);
525
+ console.log(chalk_1.default.bold('\n Connection:'));
526
+ console.log(` WebSocket: ${chalk_1.default.cyan(WS_URL)}`);
527
+ console.log(` REST API: ${chalk_1.default.cyan('https://gopherhole.ai/a2a')}`);
528
+ console.log(chalk_1.default.red('\n āš ļø Save your API key now - it won\'t be shown again!'));
529
+ console.log(chalk_1.default.bold('\n Quick test:'));
530
+ console.log(chalk_1.default.white(` curl -H "Authorization: Bearer ${data.apiKey}" \\
531
+ https://gopherhole.ai/a2a -d '{"jsonrpc":"2.0","method":"agent/info","id":1}'`));
532
+ console.log(chalk_1.default.gray('\n Full docs: https://gopherhole.ai/docs'));
177
533
  console.log('');
178
- console.log(chalk_1.default.gray('Save this API key - it won\'t be shown again!'));
179
- console.log('');
180
- console.log(chalk_1.default.bold('Quick start:'));
181
- console.log(chalk_1.default.gray(`
182
- import { GopherHole } from '@gopherhole/sdk';
183
-
184
- const hub = new GopherHole('${data.apiKey}');
185
- await hub.connect();
186
-
187
- hub.on('message', (msg) => {
188
- console.log('Received:', msg);
189
- });
190
- `));
191
534
  }
192
535
  catch (err) {
193
536
  spinner.fail(chalk_1.default.red(err.message));
@@ -196,33 +539,45 @@ agents
196
539
  });
197
540
  agents
198
541
  .command('delete <agentId>')
199
- .description('Delete an agent')
200
- .action(async (agentId) => {
542
+ .description(`Delete an agent
543
+
544
+ ${chalk_1.default.bold('Example:')}
545
+ $ gopherhole agents delete agent-abc123
546
+ `)
547
+ .option('-f, --force', 'Skip confirmation')
548
+ .action(async (agentId, options) => {
201
549
  const sessionId = config.get('sessionId');
202
550
  if (!sessionId) {
203
- console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
551
+ console.log(chalk_1.default.yellow('Not logged in.'));
552
+ console.log(chalk_1.default.gray('Run: gopherhole login'));
204
553
  process.exit(1);
205
554
  }
206
- const { confirm } = await inquirer_1.default.prompt([
207
- {
208
- type: 'confirm',
209
- name: 'confirm',
210
- message: `Are you sure you want to delete agent ${agentId}?`,
211
- default: false,
212
- },
213
- ]);
214
- if (!confirm) {
215
- console.log('Cancelled');
216
- return;
555
+ if (!options.force) {
556
+ const { confirm } = await inquirer_1.default.prompt([
557
+ {
558
+ type: 'confirm',
559
+ name: 'confirm',
560
+ message: `Delete agent ${chalk_1.default.cyan(agentId)}? This cannot be undone.`,
561
+ default: false,
562
+ },
563
+ ]);
564
+ if (!confirm) {
565
+ console.log('Cancelled.');
566
+ return;
567
+ }
217
568
  }
218
569
  const spinner = (0, ora_1.default)('Deleting agent...').start();
570
+ log('DELETE /agents/' + agentId);
219
571
  try {
220
572
  const res = await fetch(`${API_URL}/agents/${agentId}`, {
221
573
  method: 'DELETE',
222
574
  headers: { 'X-Session-ID': sessionId },
223
575
  });
224
- if (!res.ok)
225
- throw new Error('Failed to delete agent');
576
+ if (!res.ok) {
577
+ const err = await res.json();
578
+ logError('delete', err);
579
+ throw new Error(err.error || 'Failed to delete agent');
580
+ }
226
581
  spinner.succeed('Agent deleted');
227
582
  }
228
583
  catch (err) {
@@ -233,21 +588,31 @@ agents
233
588
  // ========== INIT COMMAND ==========
234
589
  program
235
590
  .command('init')
236
- .description('Initialize GopherHole in current directory')
591
+ .description(`Initialize GopherHole in current directory
592
+
593
+ ${chalk_1.default.bold('What it does:')}
594
+ • Creates .env with your API key
595
+ • Generates starter code (agent.ts)
596
+ • Sets up package.json if needed
597
+
598
+ ${chalk_1.default.bold('Example:')}
599
+ $ mkdir my-agent && cd my-agent
600
+ $ gopherhole init
601
+ `)
237
602
  .action(async () => {
238
- console.log(chalk_1.default.bold('\nšŸæļø GopherHole Init\n'));
603
+ console.log(chalk_1.default.bold('\nšŸæļø Initialize GopherHole Project\n'));
239
604
  let sessionId = config.get('sessionId');
240
605
  // Check if logged in
241
606
  if (!sessionId) {
242
- console.log('First, let\'s log you in (or create an account).\n');
607
+ console.log(chalk_1.default.yellow('Not logged in yet.\n'));
243
608
  const { action } = await inquirer_1.default.prompt([
244
609
  {
245
610
  type: 'list',
246
611
  name: 'action',
247
- message: 'Do you have a GopherHole account?',
612
+ message: 'Choose:',
248
613
  choices: [
249
- { name: 'Yes, log me in', value: 'login' },
250
- { name: 'No, create one', value: 'signup' },
614
+ { name: 'Create account', value: 'signup' },
615
+ { name: 'Log in', value: 'login' },
251
616
  ],
252
617
  },
253
618
  ]);
@@ -320,52 +685,90 @@ program
320
685
  spinner.succeed('Agent created!');
321
686
  // Write .env file
322
687
  const fs = await import('fs');
323
- fs.writeFileSync('.env', `GOPHERHOLE_API_KEY=${data.apiKey}\n`);
324
- console.log(chalk_1.default.green('āœ“ Created .env with API key'));
688
+ fs.writeFileSync('.env', `# GopherHole Configuration
689
+ GOPHERHOLE_API_KEY=${data.apiKey}
690
+ GOPHERHOLE_AGENT_ID=${data.agent.id}
691
+ `);
692
+ console.log(brand.green('āœ“ Created .env'));
325
693
  // Write example code
326
694
  const exampleCode = `import { GopherHole } from '@gopherhole/sdk';
695
+ import 'dotenv/config';
327
696
 
328
- const hub = new GopherHole(process.env.GOPHERHOLE_API_KEY);
697
+ const client = new GopherHole(process.env.GOPHERHOLE_API_KEY!);
329
698
 
330
699
  async function main() {
331
- await hub.connect();
332
- console.log('šŸæļø Connected to GopherHole!');
700
+ await client.connect();
701
+ console.log('šŸæļø Connected to GopherHole as ${agentName}!');
333
702
 
334
- hub.on('message', async (msg) => {
335
- console.log(\`Message from \${msg.from}:\`, msg.payload);
703
+ // Handle incoming messages
704
+ client.on('message', async (msg) => {
705
+ console.log(\`šŸ“© Message from \${msg.from}:\`, msg.text);
336
706
 
337
- // Reply to the message
338
- await hub.sendText(msg.from, 'Hello back!');
707
+ // Echo back
708
+ await client.send(msg.from, \`You said: \${msg.text}\`);
709
+ });
710
+
711
+ // Keep alive
712
+ process.on('SIGINT', async () => {
713
+ console.log('\\nšŸ‘‹ Disconnecting...');
714
+ await client.disconnect();
715
+ process.exit(0);
339
716
  });
340
717
  }
341
718
 
342
719
  main().catch(console.error);
343
720
  `;
344
721
  fs.writeFileSync('agent.ts', exampleCode);
345
- console.log(chalk_1.default.green('āœ“ Created agent.ts example'));
346
- console.log('');
722
+ console.log(brand.green('āœ“ Created agent.ts'));
723
+ // Create package.json if it doesn't exist
724
+ if (!fs.existsSync('package.json')) {
725
+ const pkg = {
726
+ name: agentName,
727
+ version: '0.1.0',
728
+ type: 'module',
729
+ scripts: {
730
+ start: 'tsx agent.ts',
731
+ dev: 'tsx watch agent.ts',
732
+ },
733
+ dependencies: {
734
+ '@gopherhole/sdk': '^0.1.0',
735
+ dotenv: '^16.0.0',
736
+ },
737
+ devDependencies: {
738
+ tsx: '^4.0.0',
739
+ typescript: '^5.0.0',
740
+ },
741
+ };
742
+ fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
743
+ console.log(brand.green('āœ“ Created package.json'));
744
+ }
745
+ console.log(chalk_1.default.bold('\nšŸŽ‰ Project initialized!\n'));
347
746
  console.log(chalk_1.default.bold('Next steps:'));
348
- console.log('');
349
- console.log(' 1. Install the SDK:');
350
- console.log(chalk_1.default.cyan(' npm install @gopherhole/sdk'));
351
- console.log('');
352
- console.log(' 2. Run your agent:');
353
- console.log(chalk_1.default.cyan(' npx ts-node agent.ts'));
747
+ console.log(chalk_1.default.cyan(' npm install'));
748
+ console.log(chalk_1.default.cyan(' npm start'));
354
749
  console.log('');
355
750
  console.log(chalk_1.default.gray(`Dashboard: https://gopherhole.ai/dashboard`));
751
+ console.log(chalk_1.default.gray(`Docs: https://gopherhole.ai/docs`));
356
752
  console.log('');
357
753
  });
358
754
  // ========== SEND COMMAND ==========
359
755
  program
360
756
  .command('send <agentId> <message>')
361
- .description('Send a message to an agent (for testing)')
757
+ .description(`Send a message to an agent (for testing)
758
+
759
+ ${chalk_1.default.bold('Examples:')}
760
+ $ gopherhole send echo "Hello!"
761
+ $ gopherhole send agent-abc123 "What's the weather?"
762
+ `)
362
763
  .action(async (agentId, message) => {
363
764
  const sessionId = config.get('sessionId');
364
765
  if (!sessionId) {
365
- console.log(chalk_1.default.yellow('Not logged in. Run: gopherhole login'));
766
+ console.log(chalk_1.default.yellow('Not logged in.'));
767
+ console.log(chalk_1.default.gray('Run: gopherhole login'));
366
768
  process.exit(1);
367
769
  }
368
- const spinner = (0, ora_1.default)('Sending message...').start();
770
+ const spinner = (0, ora_1.default)(`Sending to ${agentId}...`).start();
771
+ log('POST /a2a message/send', { to: agentId, message });
369
772
  try {
370
773
  const res = await fetch(`${API_URL}/../a2a`, {
371
774
  method: 'POST',
@@ -384,13 +787,334 @@ program
384
787
  }),
385
788
  });
386
789
  const data = await res.json();
790
+ if (data.error) {
791
+ logError('send', data.error);
792
+ throw new Error(data.error.message || 'Send failed');
793
+ }
387
794
  spinner.succeed('Message sent!');
388
- console.log(chalk_1.default.gray(JSON.stringify(data, null, 2)));
795
+ if (data.result?.task) {
796
+ console.log(chalk_1.default.gray(`\nTask ID: ${data.result.task.id}`));
797
+ console.log(chalk_1.default.gray(`Status: ${data.result.task.status.state}`));
798
+ }
799
+ if (verbose) {
800
+ console.log(chalk_1.default.gray('\nFull response:'));
801
+ console.log(JSON.stringify(data, null, 2));
802
+ }
803
+ }
804
+ catch (err) {
805
+ spinner.fail(chalk_1.default.red(err.message));
806
+ process.exit(1);
807
+ }
808
+ });
809
+ // ========== DISCOVER COMMANDS ==========
810
+ const discover = program
811
+ .command('discover')
812
+ .description(`Discover public agents
813
+
814
+ ${chalk_1.default.bold('Examples:')}
815
+ $ gopherhole discover search "weather"
816
+ $ gopherhole discover top
817
+ $ gopherhole discover info agent-abc123
818
+ `);
819
+ discover
820
+ .command('search [query]')
821
+ .description(`Search for agents
822
+
823
+ ${chalk_1.default.bold('Examples:')}
824
+ $ gopherhole discover search
825
+ $ gopherhole discover search "email assistant"
826
+ $ gopherhole discover search --category productivity
827
+ $ gopherhole discover search --tag ai --sort popular
828
+ `)
829
+ .option('-c, --category <category>', 'Filter by category')
830
+ .option('-t, --tag <tag>', 'Filter by tag')
831
+ .option('-s, --sort <sort>', 'Sort by: rating, popular, recent', 'rating')
832
+ .option('-l, --limit <limit>', 'Number of results', '20')
833
+ .action(async (query, options) => {
834
+ const spinner = (0, ora_1.default)('Searching agents...').start();
835
+ try {
836
+ const params = new URLSearchParams();
837
+ if (query)
838
+ params.set('q', query);
839
+ if (options.category)
840
+ params.set('category', options.category);
841
+ if (options.tag)
842
+ params.set('tag', options.tag);
843
+ if (options.sort)
844
+ params.set('sort', options.sort);
845
+ if (options.limit)
846
+ params.set('limit', options.limit);
847
+ log('GET /discover/agents?' + params.toString());
848
+ const res = await fetch(`${API_URL}/discover/agents?${params}`);
849
+ const data = await res.json();
850
+ spinner.stop();
851
+ if (data.agents.length === 0) {
852
+ console.log(chalk_1.default.yellow('\nNo agents found.'));
853
+ console.log(chalk_1.default.gray('Try: gopherhole discover search (without query)'));
854
+ return;
855
+ }
856
+ console.log(chalk_1.default.bold(`\nšŸ” Found ${data.agents.length} agents:\n`));
857
+ for (const agent of data.agents) {
858
+ const stars = 'ā˜…'.repeat(Math.round(agent.avgRating)) + 'ā˜†'.repeat(5 - Math.round(agent.avgRating));
859
+ const pricing = agent.pricing === 'free' ? brand.green('FREE') :
860
+ agent.pricing === 'paid' ? chalk_1.default.blue('PAID') : chalk_1.default.gray('CONTACT');
861
+ console.log(` ${chalk_1.default.bold(agent.name)} ${chalk_1.default.yellow(stars)} (${agent.ratingCount})`);
862
+ console.log(` ${chalk_1.default.gray(agent.description || 'No description')}`);
863
+ console.log(` ${chalk_1.default.cyan(agent.id)} | ${pricing} | ${agent.category || 'uncategorized'}`);
864
+ console.log('');
865
+ }
866
+ console.log(chalk_1.default.gray(`Tip: gopherhole discover info <agent-id> for details\n`));
867
+ }
868
+ catch (err) {
869
+ spinner.fail(chalk_1.default.red(err.message));
870
+ process.exit(1);
871
+ }
872
+ });
873
+ discover
874
+ .command('categories')
875
+ .description('List available categories')
876
+ .action(async () => {
877
+ const spinner = (0, ora_1.default)('Fetching categories...').start();
878
+ try {
879
+ const res = await fetch(`${API_URL}/discover/categories`);
880
+ const data = await res.json();
881
+ spinner.stop();
882
+ if (data.categories.length === 0) {
883
+ console.log(chalk_1.default.yellow('No categories yet'));
884
+ return;
885
+ }
886
+ console.log(chalk_1.default.bold('\nšŸ“‚ Categories:\n'));
887
+ for (const cat of data.categories) {
888
+ console.log(` ${chalk_1.default.bold(cat.name)} (${cat.count} agents)`);
889
+ }
890
+ console.log(chalk_1.default.gray('\nSearch by category: gopherhole discover search --category <name>\n'));
891
+ }
892
+ catch (err) {
893
+ spinner.fail(chalk_1.default.red(err.message));
894
+ process.exit(1);
895
+ }
896
+ });
897
+ discover
898
+ .command('featured')
899
+ .description('Show featured agents')
900
+ .action(async () => {
901
+ const spinner = (0, ora_1.default)('Fetching featured agents...').start();
902
+ try {
903
+ const res = await fetch(`${API_URL}/discover/featured`);
904
+ const data = await res.json();
905
+ spinner.stop();
906
+ if (data.featured.length === 0) {
907
+ console.log(chalk_1.default.yellow('No featured agents yet'));
908
+ return;
909
+ }
910
+ console.log(chalk_1.default.bold('\n⭐ Featured Agents:\n'));
911
+ for (const agent of data.featured) {
912
+ const stars = 'ā˜…'.repeat(Math.round(agent.avgRating)) + 'ā˜†'.repeat(5 - Math.round(agent.avgRating));
913
+ console.log(` ${chalk_1.default.bold(agent.name)} ${chalk_1.default.yellow(stars)}`);
914
+ console.log(` ${chalk_1.default.gray(agent.description || 'No description')}`);
915
+ console.log(` ${chalk_1.default.cyan(agent.id)}`);
916
+ console.log('');
917
+ }
389
918
  }
390
919
  catch (err) {
391
920
  spinner.fail(chalk_1.default.red(err.message));
392
921
  process.exit(1);
393
922
  }
394
923
  });
924
+ discover
925
+ .command('info <agentId>')
926
+ .description(`Get detailed info about an agent
927
+
928
+ ${chalk_1.default.bold('Example:')}
929
+ $ gopherhole discover info agent-abc123
930
+ `)
931
+ .action(async (agentId) => {
932
+ const spinner = (0, ora_1.default)('Fetching agent info...').start();
933
+ try {
934
+ log('GET /discover/agents/' + agentId);
935
+ const res = await fetch(`${API_URL}/discover/agents/${agentId}`);
936
+ if (!res.ok) {
937
+ throw new Error('Agent not found');
938
+ }
939
+ const data = await res.json();
940
+ spinner.stop();
941
+ const agent = data.agent;
942
+ const stars = 'ā˜…'.repeat(Math.round(agent.stats.avgRating)) + 'ā˜†'.repeat(5 - Math.round(agent.stats.avgRating));
943
+ console.log(chalk_1.default.bold(`\nšŸæļø ${agent.name}\n`));
944
+ console.log(` ${chalk_1.default.gray(agent.description || 'No description')}`);
945
+ console.log('');
946
+ console.log(` ${chalk_1.default.bold('Rating:')} ${chalk_1.default.yellow(stars)} ${agent.stats.avgRating.toFixed(1)}/5 (${agent.stats.ratingCount} reviews)`);
947
+ console.log(` ${chalk_1.default.bold('Messages:')} ${agent.stats.totalMessages.toLocaleString()}`);
948
+ console.log(` ${chalk_1.default.bold('Success:')} ${(agent.stats.successRate * 100).toFixed(1)}%`);
949
+ console.log(` ${chalk_1.default.bold('Response:')} ${agent.stats.avgResponseTime}ms avg`);
950
+ console.log(` ${chalk_1.default.bold('Pricing:')} ${agent.pricing}`);
951
+ console.log(` ${chalk_1.default.bold('Category:')} ${agent.category || 'None'}`);
952
+ console.log(` ${chalk_1.default.bold('By:')} ${agent.tenantName}`);
953
+ if (agent.tags.length > 0) {
954
+ console.log(` ${chalk_1.default.bold('Tags:')} ${agent.tags.join(', ')}`);
955
+ }
956
+ if (agent.websiteUrl) {
957
+ console.log(` ${chalk_1.default.bold('Website:')} ${chalk_1.default.cyan(agent.websiteUrl)}`);
958
+ }
959
+ if (agent.docsUrl) {
960
+ console.log(` ${chalk_1.default.bold('Docs:')} ${chalk_1.default.cyan(agent.docsUrl)}`);
961
+ }
962
+ if (agent.agentCard?.skills?.length) {
963
+ console.log(chalk_1.default.bold('\n Skills:'));
964
+ for (const skill of agent.agentCard.skills) {
965
+ console.log(` • ${skill.name}${skill.description ? ` - ${chalk_1.default.gray(skill.description)}` : ''}`);
966
+ }
967
+ }
968
+ if (data.reviews.length > 0) {
969
+ console.log(chalk_1.default.bold('\n Recent Reviews:'));
970
+ for (const review of data.reviews.slice(0, 3)) {
971
+ const reviewStars = 'ā˜…'.repeat(review.rating) + 'ā˜†'.repeat(5 - review.rating);
972
+ console.log(` ${chalk_1.default.yellow(reviewStars)} "${review.review}"`);
973
+ console.log(` ${chalk_1.default.gray(`- ${review.reviewer_name}`)}`);
974
+ }
975
+ }
976
+ console.log(chalk_1.default.bold('\n Quick start:'));
977
+ console.log(chalk_1.default.cyan(` await client.send('${agentId}', 'Hello!')`));
978
+ console.log('');
979
+ }
980
+ catch (err) {
981
+ spinner.fail(chalk_1.default.red(err.message));
982
+ process.exit(1);
983
+ }
984
+ });
985
+ discover
986
+ .command('top')
987
+ .description(`Show top-rated agents
988
+
989
+ ${chalk_1.default.bold('Example:')}
990
+ $ gopherhole discover top
991
+ $ gopherhole discover top --limit 5
992
+ `)
993
+ .option('-l, --limit <limit>', 'Number of results', '10')
994
+ .action(async (options) => {
995
+ const spinner = (0, ora_1.default)('Fetching top agents...').start();
996
+ try {
997
+ const res = await fetch(`${API_URL}/discover/agents?sort=rating&limit=${options.limit}`);
998
+ const data = await res.json();
999
+ spinner.stop();
1000
+ console.log(chalk_1.default.bold('\nšŸ† Top Rated Agents:\n'));
1001
+ let rank = 1;
1002
+ for (const agent of data.agents) {
1003
+ const stars = 'ā˜…'.repeat(Math.round(agent.avgRating)) + 'ā˜†'.repeat(5 - Math.round(agent.avgRating));
1004
+ const medal = rank === 1 ? 'šŸ„‡' : rank === 2 ? '🄈' : rank === 3 ? 'šŸ„‰' : `#${rank}`;
1005
+ console.log(` ${medal} ${chalk_1.default.bold(agent.name)} ${chalk_1.default.yellow(stars)} (${agent.ratingCount})`);
1006
+ console.log(` ${chalk_1.default.gray(agent.description?.slice(0, 60) || 'No description')}${(agent.description?.length || 0) > 60 ? '...' : ''}`);
1007
+ console.log(` ${chalk_1.default.cyan(agent.id)}`);
1008
+ console.log('');
1009
+ rank++;
1010
+ }
1011
+ }
1012
+ catch (err) {
1013
+ spinner.fail(chalk_1.default.red(err.message));
1014
+ process.exit(1);
1015
+ }
1016
+ });
1017
+ discover
1018
+ .command('rate <agentId>')
1019
+ .description(`Rate an agent
1020
+
1021
+ ${chalk_1.default.bold('Example:')}
1022
+ $ gopherhole discover rate agent-abc123
1023
+ $ gopherhole discover rate agent-abc123 --rating 5 --message "Great bot!"
1024
+ `)
1025
+ .option('-r, --rating <rating>', 'Rating 1-5')
1026
+ .option('-m, --message <review>', 'Review message')
1027
+ .action(async (agentId, options) => {
1028
+ const sessionId = config.get('sessionId');
1029
+ if (!sessionId) {
1030
+ console.log(chalk_1.default.yellow('Not logged in.'));
1031
+ console.log(chalk_1.default.gray('Run: gopherhole login'));
1032
+ process.exit(1);
1033
+ }
1034
+ let { rating, message: review } = options;
1035
+ if (!rating) {
1036
+ const answers = await inquirer_1.default.prompt([
1037
+ {
1038
+ type: 'list',
1039
+ name: 'rating',
1040
+ message: 'Your rating:',
1041
+ choices: [
1042
+ { name: 'ā˜…ā˜…ā˜…ā˜…ā˜… Excellent', value: 5 },
1043
+ { name: 'ā˜…ā˜…ā˜…ā˜…ā˜† Great', value: 4 },
1044
+ { name: 'ā˜…ā˜…ā˜…ā˜†ā˜† Good', value: 3 },
1045
+ { name: 'ā˜…ā˜…ā˜†ā˜†ā˜† Fair', value: 2 },
1046
+ { name: 'ā˜…ā˜†ā˜†ā˜†ā˜† Poor', value: 1 },
1047
+ ],
1048
+ },
1049
+ {
1050
+ type: 'input',
1051
+ name: 'review',
1052
+ message: 'Review (optional):',
1053
+ },
1054
+ ]);
1055
+ rating = answers.rating;
1056
+ review = answers.review;
1057
+ }
1058
+ const spinner = (0, ora_1.default)('Submitting rating...').start();
1059
+ try {
1060
+ const res = await fetch(`${API_URL}/discover/agents/${agentId}/rate`, {
1061
+ method: 'POST',
1062
+ headers: {
1063
+ 'Content-Type': 'application/json',
1064
+ 'X-Session-ID': sessionId,
1065
+ },
1066
+ body: JSON.stringify({
1067
+ rating: parseInt(rating),
1068
+ review: review || undefined,
1069
+ }),
1070
+ });
1071
+ if (!res.ok) {
1072
+ const err = await res.json();
1073
+ throw new Error(err.error || 'Failed to rate');
1074
+ }
1075
+ const data = await res.json();
1076
+ spinner.succeed(`Rated! New average: ${chalk_1.default.yellow(data.avgRating.toFixed(1))} (${data.ratingCount} reviews)`);
1077
+ }
1078
+ catch (err) {
1079
+ spinner.fail(chalk_1.default.red(err.message));
1080
+ process.exit(1);
1081
+ }
1082
+ });
1083
+ // ========== STATUS COMMAND ==========
1084
+ program
1085
+ .command('status')
1086
+ .description('Show GopherHole service status and your session info')
1087
+ .action(async () => {
1088
+ console.log(chalk_1.default.bold('\nšŸæļø GopherHole Status\n'));
1089
+ // Check API health
1090
+ const spinner = (0, ora_1.default)('Checking API...').start();
1091
+ try {
1092
+ const start = Date.now();
1093
+ const res = await fetch(`${API_URL.replace('/api', '')}/health`);
1094
+ const latency = Date.now() - start;
1095
+ if (res.ok) {
1096
+ spinner.succeed(`API: ${brand.green('Online')} (${latency}ms)`);
1097
+ }
1098
+ else {
1099
+ spinner.fail(`API: ${chalk_1.default.red('Degraded')}`);
1100
+ }
1101
+ }
1102
+ catch {
1103
+ spinner.fail(`API: ${chalk_1.default.red('Offline')}`);
1104
+ }
1105
+ // Check login status
1106
+ const user = config.get('user');
1107
+ if (user) {
1108
+ console.log(` Auth: ${brand.green('Logged in')} as ${user.email}`);
1109
+ }
1110
+ else {
1111
+ console.log(` Auth: ${chalk_1.default.yellow('Not logged in')}`);
1112
+ }
1113
+ console.log(` Config: ${config.path}`);
1114
+ console.log('');
1115
+ console.log(chalk_1.default.gray(' Dashboard: https://gopherhole.ai/dashboard'));
1116
+ console.log(chalk_1.default.gray(' Status: https://status.gopherhole.ai'));
1117
+ console.log('');
1118
+ });
395
1119
  // Parse and run
396
1120
  program.parse();