@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/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('šŸæļø GopherHole CLI - Connect AI agents to the world')
17
- .version('0.1.0');
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('Log in to GopherHole')
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 ${chalk.green(data.user.email)}`);
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('Create a new GopherHole account')
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 ${chalk.green(data.user.email)}`);
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(chalk.green('Logged out successfully'));
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. Run: gopherhole login'));
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
- console.log(`Logged in as ${chalk.green(user.email)} (${user.name})`);
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.command('agents').description('Manage agents');
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
- .action(async () => {
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. Run: gopherhole login'));
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) throw new Error('Failed to fetch agents');
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('No agents yet. Create one with: gopherhole agents create'));
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('\nYour Agents:\n'));
507
+ console.log(chalk.bold('\nšŸæļø Your Agents:\n'));
144
508
  for (const agent of data.agents) {
145
- const status = agent.status === 'active' ? chalk.green('ā—') : chalk.red('ā—');
146
- console.log(` ${status} ${chalk.bold(agent.name)} (${agent.id})`);
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('Create a new agent')
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. Run: gopherhole login'));
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
- { type: 'input', name: 'name', message: 'Agent name:' },
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: ${chalk.green(data.agent.name)}`);
200
- console.log('');
201
- console.log(` ${chalk.bold('Agent ID:')} ${data.agent.id}`);
202
- console.log(` ${chalk.bold('API Key:')} ${chalk.cyan(data.apiKey)}`);
203
- console.log('');
204
- console.log(chalk.gray('Save this API key - it won\'t be shown again!'));
205
- console.log('');
206
- console.log(chalk.bold('Quick start:'));
207
- console.log(chalk.gray(`
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
- const hub = new GopherHole('${data.apiKey}');
211
- await hub.connect();
587
+ console.log(chalk.red('\n āš ļø Save your API key now - it won\'t be shown again!'));
212
588
 
213
- hub.on('message', (msg) => {
214
- console.log('Received:', msg);
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('Delete an agent')
226
- .action(async (agentId) => {
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. Run: gopherhole login'));
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
- const { confirm } = await inquirer.prompt([
234
- {
235
- type: 'confirm',
236
- name: 'confirm',
237
- message: `Are you sure you want to delete agent ${agentId}?`,
238
- default: false,
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
- if (!confirm) {
243
- console.log('Cancelled');
244
- return;
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) throw new Error('Failed to delete agent');
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('Initialize GopherHole in current directory')
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 Init\n'));
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('First, let\'s log you in (or create an account).\n');
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: 'Do you have a GopherHole account?',
683
+ message: 'Choose:',
283
684
  choices: [
284
- { name: 'Yes, log me in', value: 'login' },
285
- { name: 'No, create one', value: 'signup' },
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', `GOPHERHOLE_API_KEY=${data.apiKey}\n`);
372
- console.log(chalk.green('āœ“ Created .env with API key'));
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 hub = new GopherHole(process.env.GOPHERHOLE_API_KEY);
782
+ const client = new GopherHole(process.env.GOPHERHOLE_API_KEY!);
378
783
 
379
784
  async function main() {
380
- await hub.connect();
381
- console.log('šŸæļø Connected to GopherHole!');
785
+ await client.connect();
786
+ console.log('šŸæļø Connected to GopherHole as ${agentName}!');
382
787
 
383
- hub.on('message', async (msg) => {
384
- console.log(\`Message from \${msg.from}:\`, msg.payload);
788
+ // Handle incoming messages
789
+ client.on('message', async (msg) => {
790
+ console.log(\`šŸ“© Message from \${msg.from}:\`, msg.text);
385
791
 
386
- // Reply to the message
387
- await hub.sendText(msg.from, 'Hello back!');
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(chalk.green('āœ“ Created agent.ts example'));
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(' 1. Install the SDK:');
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('Send a message to an agent (for testing)')
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. Run: gopherhole login'));
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('Sending message...').start();
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
- console.log(chalk.gray(JSON.stringify(data, null, 2)));
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();