@gopherhole/cli 0.1.0 → 0.1.1

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