@gopherhole/cli 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +125 -97
- package/package.json +1 -1
- package/src/index.ts +136 -98
package/dist/index.js
CHANGED
|
@@ -60,7 +60,7 @@ program
|
|
|
60
60
|
|
|
61
61
|
${chalk_1.default.bold('Quick Start:')}
|
|
62
62
|
$ gopherhole quickstart # Interactive setup wizard
|
|
63
|
-
$ gopherhole
|
|
63
|
+
$ gopherhole login # Log in or create account
|
|
64
64
|
$ gopherhole agents create # Create your first agent
|
|
65
65
|
|
|
66
66
|
${chalk_1.default.bold('Examples:')}
|
|
@@ -106,80 +106,55 @@ ${chalk_1.default.bold('Example:')}
|
|
|
106
106
|
console.log(brand.green(`✓ Already logged in as ${user?.email}\n`));
|
|
107
107
|
}
|
|
108
108
|
else {
|
|
109
|
-
console.log(chalk_1.default.bold('Step 1:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
name: 'action',
|
|
114
|
-
message: 'Do you have a GopherHole account?',
|
|
115
|
-
choices: [
|
|
116
|
-
{ name: 'No, create one for me', value: 'signup' },
|
|
117
|
-
{ name: 'Yes, log me in', value: 'login' },
|
|
118
|
-
],
|
|
119
|
-
},
|
|
109
|
+
console.log(chalk_1.default.bold('Step 1: Sign in\n'));
|
|
110
|
+
// OTP-based auth flow
|
|
111
|
+
const { email } = await inquirer_1.default.prompt([
|
|
112
|
+
{ type: 'input', name: 'email', message: 'Email:', validate: (v) => v.includes('@') || 'Enter a valid email' },
|
|
120
113
|
]);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
body: JSON.stringify({ name, email, password }),
|
|
134
|
-
});
|
|
135
|
-
if (!res.ok) {
|
|
136
|
-
const err = await res.json();
|
|
137
|
-
logError('signup', err);
|
|
138
|
-
throw new Error(err.error || 'Signup failed');
|
|
139
|
-
}
|
|
140
|
-
const data = await res.json();
|
|
141
|
-
config.set('sessionId', data.sessionId);
|
|
142
|
-
config.set('user', data.user);
|
|
143
|
-
config.set('tenant', data.tenant);
|
|
144
|
-
sessionId = data.sessionId;
|
|
145
|
-
spinner.succeed('Account created!');
|
|
146
|
-
log('Session ID stored');
|
|
114
|
+
let spinner = (0, ora_1.default)('Sending verification code...').start();
|
|
115
|
+
log('POST /auth/send-code', { email });
|
|
116
|
+
try {
|
|
117
|
+
const sendRes = await fetch(`${API_URL}/auth/send-code`, {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: { 'Content-Type': 'application/json' },
|
|
120
|
+
body: JSON.stringify({ email }),
|
|
121
|
+
});
|
|
122
|
+
if (!sendRes.ok) {
|
|
123
|
+
const err = await sendRes.json();
|
|
124
|
+
logError('send-code', err);
|
|
125
|
+
throw new Error(err.error || 'Failed to send code');
|
|
147
126
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
127
|
+
const sendData = await sendRes.json();
|
|
128
|
+
spinner.succeed('Code sent! Check your email.');
|
|
129
|
+
if (sendData.isNewUser) {
|
|
130
|
+
console.log(chalk_1.default.gray(' No account found - we\'ll create one for you.\n'));
|
|
151
131
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const { email, password } = await inquirer_1.default.prompt([
|
|
155
|
-
{ type: 'input', name: 'email', message: 'Email:' },
|
|
156
|
-
{ type: 'password', name: 'password', message: 'Password:' },
|
|
132
|
+
const { code } = await inquirer_1.default.prompt([
|
|
133
|
+
{ type: 'input', name: 'code', message: 'Enter 6-digit code:', validate: (v) => /^\d{6}$/.test(v) || 'Enter 6 digits' },
|
|
157
134
|
]);
|
|
158
|
-
|
|
159
|
-
log('POST /auth/
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
throw new Error(err.error || 'Login failed');
|
|
170
|
-
}
|
|
171
|
-
const data = await res.json();
|
|
172
|
-
config.set('sessionId', data.sessionId);
|
|
173
|
-
config.set('user', data.user);
|
|
174
|
-
config.set('tenant', data.tenant);
|
|
175
|
-
sessionId = data.sessionId;
|
|
176
|
-
spinner.succeed('Logged in!');
|
|
177
|
-
log('Session ID stored');
|
|
178
|
-
}
|
|
179
|
-
catch (err) {
|
|
180
|
-
spinner.fail(chalk_1.default.red(err.message));
|
|
181
|
-
process.exit(1);
|
|
135
|
+
spinner = (0, ora_1.default)('Verifying...').start();
|
|
136
|
+
log('POST /auth/verify-code', { email, code });
|
|
137
|
+
const verifyRes = await fetch(`${API_URL}/auth/verify-code`, {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: { 'Content-Type': 'application/json' },
|
|
140
|
+
body: JSON.stringify({ email, code }),
|
|
141
|
+
});
|
|
142
|
+
if (!verifyRes.ok) {
|
|
143
|
+
const err = await verifyRes.json();
|
|
144
|
+
logError('verify-code', err);
|
|
145
|
+
throw new Error(err.error || 'Verification failed');
|
|
182
146
|
}
|
|
147
|
+
const data = await verifyRes.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(data.isNewUser ? 'Account created!' : 'Logged in!');
|
|
153
|
+
log('Session ID stored');
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
157
|
+
process.exit(1);
|
|
183
158
|
}
|
|
184
159
|
}
|
|
185
160
|
// Step 2: Create agent
|
|
@@ -333,52 +308,86 @@ ${chalk_1.default.bold('Example:')}
|
|
|
333
308
|
// ========== AUTH COMMANDS ==========
|
|
334
309
|
program
|
|
335
310
|
.command('login')
|
|
336
|
-
.description(`Log in to GopherHole
|
|
311
|
+
.description(`Log in to GopherHole (creates account if needed)
|
|
337
312
|
|
|
338
313
|
${chalk_1.default.bold('Example:')}
|
|
339
314
|
$ gopherhole login
|
|
340
315
|
`)
|
|
341
316
|
.action(async () => {
|
|
342
|
-
const { email
|
|
343
|
-
{ type: 'input', name: 'email', message: 'Email:' },
|
|
344
|
-
{ type: 'password', name: 'password', message: 'Password:' },
|
|
317
|
+
const { email } = await inquirer_1.default.prompt([
|
|
318
|
+
{ type: 'input', name: 'email', message: 'Email:', validate: (v) => v.includes('@') || 'Enter a valid email' },
|
|
345
319
|
]);
|
|
346
|
-
|
|
347
|
-
log('POST /auth/
|
|
320
|
+
let spinner = (0, ora_1.default)('Sending verification code...').start();
|
|
321
|
+
log('POST /auth/send-code', { email });
|
|
348
322
|
try {
|
|
349
|
-
const
|
|
323
|
+
const sendRes = await fetch(`${API_URL}/auth/send-code`, {
|
|
350
324
|
method: 'POST',
|
|
351
325
|
headers: { 'Content-Type': 'application/json' },
|
|
352
|
-
body: JSON.stringify({ email
|
|
326
|
+
body: JSON.stringify({ email }),
|
|
353
327
|
});
|
|
354
|
-
if (!
|
|
355
|
-
const err = await
|
|
356
|
-
logError('
|
|
357
|
-
throw new Error(err.error || '
|
|
328
|
+
if (!sendRes.ok) {
|
|
329
|
+
const err = await sendRes.json();
|
|
330
|
+
logError('send-code', err);
|
|
331
|
+
throw new Error(err.error || 'Failed to send code');
|
|
332
|
+
}
|
|
333
|
+
const sendData = await sendRes.json();
|
|
334
|
+
spinner.succeed('Code sent! Check your email.');
|
|
335
|
+
if (sendData.isNewUser) {
|
|
336
|
+
console.log(chalk_1.default.gray(' No account found - we\'ll create one for you.\n'));
|
|
337
|
+
}
|
|
338
|
+
const { code } = await inquirer_1.default.prompt([
|
|
339
|
+
{ type: 'input', name: 'code', message: 'Enter 6-digit code:', validate: (v) => /^\d{6}$/.test(v) || 'Enter 6 digits' },
|
|
340
|
+
]);
|
|
341
|
+
spinner = (0, ora_1.default)('Verifying...').start();
|
|
342
|
+
log('POST /auth/verify-code', { email, code });
|
|
343
|
+
const verifyRes = await fetch(`${API_URL}/auth/verify-code`, {
|
|
344
|
+
method: 'POST',
|
|
345
|
+
headers: { 'Content-Type': 'application/json' },
|
|
346
|
+
body: JSON.stringify({ email, code }),
|
|
347
|
+
});
|
|
348
|
+
if (!verifyRes.ok) {
|
|
349
|
+
const err = await verifyRes.json();
|
|
350
|
+
logError('verify-code', err);
|
|
351
|
+
throw new Error(err.error || 'Verification failed');
|
|
358
352
|
}
|
|
359
|
-
const data = await
|
|
353
|
+
const data = await verifyRes.json();
|
|
360
354
|
config.set('sessionId', data.sessionId);
|
|
361
355
|
config.set('user', data.user);
|
|
362
356
|
config.set('tenant', data.tenant);
|
|
363
|
-
|
|
357
|
+
if (data.isNewUser) {
|
|
358
|
+
spinner.succeed(brand.green('Account created!'));
|
|
359
|
+
console.log(`\n${brand.green('✨ Welcome to GopherHole!')}\n`);
|
|
360
|
+
console.log('Next steps:');
|
|
361
|
+
console.log(chalk_1.default.gray(' $ gopherhole agents create # Create your first agent'));
|
|
362
|
+
console.log(chalk_1.default.gray(' $ gopherhole discover search # Find agents to talk to\n'));
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
spinner.succeed(`Logged in as ${brand.green(data.user.email)}`);
|
|
366
|
+
}
|
|
364
367
|
log('Session stored in:', config.path);
|
|
365
368
|
}
|
|
366
369
|
catch (err) {
|
|
367
370
|
spinner.fail(chalk_1.default.red(err.message));
|
|
368
371
|
console.log(chalk_1.default.gray('\nTroubleshooting:'));
|
|
369
|
-
console.log(chalk_1.default.gray(' • Check your email
|
|
370
|
-
console.log(chalk_1.default.gray(' •
|
|
372
|
+
console.log(chalk_1.default.gray(' • Check your email for the code'));
|
|
373
|
+
console.log(chalk_1.default.gray(' • Codes expire after 10 minutes'));
|
|
371
374
|
console.log(chalk_1.default.gray(' • Run with --verbose for more details'));
|
|
372
375
|
process.exit(1);
|
|
373
376
|
}
|
|
374
377
|
});
|
|
378
|
+
// Alias for backward compatibility
|
|
375
379
|
program
|
|
376
380
|
.command('signup')
|
|
377
|
-
.description(`Create a new GopherHole account
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
381
|
+
.description(`Create a new GopherHole account (alias for login)`)
|
|
382
|
+
.action(async () => {
|
|
383
|
+
console.log(chalk_1.default.yellow('Note: signup and login are now unified. Running login...\n'));
|
|
384
|
+
// Re-run login command
|
|
385
|
+
await program.parseAsync(['node', 'gopherhole', 'login']);
|
|
386
|
+
});
|
|
387
|
+
// Legacy signup code removed - kept as hidden command for reference
|
|
388
|
+
program
|
|
389
|
+
.command('signup-legacy', { hidden: true })
|
|
390
|
+
.description(`[DEPRECATED] Create account with password`)
|
|
382
391
|
.action(async () => {
|
|
383
392
|
const { name, email, password } = await inquirer_1.default.prompt([
|
|
384
393
|
{ type: 'input', name: 'name', message: 'Name:' },
|
|
@@ -421,17 +430,36 @@ program
|
|
|
421
430
|
program
|
|
422
431
|
.command('whoami')
|
|
423
432
|
.description('Show current logged in user')
|
|
424
|
-
.action(() => {
|
|
425
|
-
const
|
|
426
|
-
const
|
|
427
|
-
|
|
433
|
+
.action(async () => {
|
|
434
|
+
const sessionId = config.get('sessionId');
|
|
435
|
+
const cachedUser = config.get('user');
|
|
436
|
+
const cachedTenant = config.get('tenant');
|
|
437
|
+
if (!sessionId || !cachedUser) {
|
|
428
438
|
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
429
439
|
console.log(chalk_1.default.gray('\nTo log in: gopherhole login'));
|
|
430
440
|
console.log(chalk_1.default.gray('To sign up: gopherhole signup'));
|
|
431
441
|
process.exit(1);
|
|
432
442
|
}
|
|
433
|
-
|
|
434
|
-
|
|
443
|
+
// Validate session with server
|
|
444
|
+
try {
|
|
445
|
+
const res = await fetch(`${API_URL}/auth/me`, {
|
|
446
|
+
headers: { 'X-Session-ID': sessionId },
|
|
447
|
+
});
|
|
448
|
+
if (!res.ok) {
|
|
449
|
+
const data = await res.json().catch(() => ({}));
|
|
450
|
+
if (data.error === 'Session expired' || res.status === 401) {
|
|
451
|
+
console.log(chalk_1.default.red('Session expired.'));
|
|
452
|
+
console.log(chalk_1.default.gray('\nPlease log in again: gopherhole login'));
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
// Network error - show cached info with warning
|
|
459
|
+
console.log(chalk_1.default.yellow('⚠ Could not verify session (offline?)\n'));
|
|
460
|
+
}
|
|
461
|
+
console.log(`\n ${chalk_1.default.bold('User:')} ${cachedUser.name} <${cachedUser.email}>`);
|
|
462
|
+
console.log(` ${chalk_1.default.bold('Org:')} ${cachedTenant?.name || 'Personal'}`);
|
|
435
463
|
console.log(` ${chalk_1.default.bold('Config:')} ${config.path}\n`);
|
|
436
464
|
});
|
|
437
465
|
// ========== AGENT COMMANDS ==========
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -67,7 +67,7 @@ program
|
|
|
67
67
|
|
|
68
68
|
${chalk.bold('Quick Start:')}
|
|
69
69
|
$ gopherhole quickstart # Interactive setup wizard
|
|
70
|
-
$ gopherhole
|
|
70
|
+
$ gopherhole login # Log in or create account
|
|
71
71
|
$ gopherhole agents create # Create your first agent
|
|
72
72
|
|
|
73
73
|
${chalk.bold('Examples:')}
|
|
@@ -116,87 +116,65 @@ ${chalk.bold('Example:')}
|
|
|
116
116
|
const user = config.get('user') as { email: string } | undefined;
|
|
117
117
|
console.log(brand.green(`✓ Already logged in as ${user?.email}\n`));
|
|
118
118
|
} else {
|
|
119
|
-
console.log(chalk.bold('Step 1:
|
|
119
|
+
console.log(chalk.bold('Step 1: Sign in\n'));
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
name: 'action',
|
|
125
|
-
message: 'Do you have a GopherHole account?',
|
|
126
|
-
choices: [
|
|
127
|
-
{ name: 'No, create one for me', value: 'signup' },
|
|
128
|
-
{ name: 'Yes, log me in', value: 'login' },
|
|
129
|
-
],
|
|
130
|
-
},
|
|
121
|
+
// OTP-based auth flow
|
|
122
|
+
const { email } = await inquirer.prompt([
|
|
123
|
+
{ type: 'input', name: 'email', message: 'Email:', validate: (v: string) => v.includes('@') || 'Enter a valid email' },
|
|
131
124
|
]);
|
|
132
125
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
{ type: 'input', name: 'name', message: 'Your name:' },
|
|
136
|
-
{ type: 'input', name: 'email', message: 'Email:' },
|
|
137
|
-
{ type: 'password', name: 'password', message: 'Password (min 8 chars):' },
|
|
138
|
-
]);
|
|
126
|
+
let spinner = ora('Sending verification code...').start();
|
|
127
|
+
log('POST /auth/send-code', { email });
|
|
139
128
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
headers: { 'Content-Type': 'application/json' },
|
|
147
|
-
body: JSON.stringify({ name, email, password }),
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
if (!res.ok) {
|
|
151
|
-
const err = await res.json();
|
|
152
|
-
logError('signup', err);
|
|
153
|
-
throw new Error(err.error || 'Signup failed');
|
|
154
|
-
}
|
|
129
|
+
try {
|
|
130
|
+
const sendRes = await fetch(`${API_URL}/auth/send-code`, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: { 'Content-Type': 'application/json' },
|
|
133
|
+
body: JSON.stringify({ email }),
|
|
134
|
+
});
|
|
155
135
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
sessionId = data.sessionId;
|
|
161
|
-
spinner.succeed('Account created!');
|
|
162
|
-
log('Session ID stored');
|
|
163
|
-
} catch (err) {
|
|
164
|
-
spinner.fail(chalk.red((err as Error).message));
|
|
165
|
-
process.exit(1);
|
|
136
|
+
if (!sendRes.ok) {
|
|
137
|
+
const err = await sendRes.json();
|
|
138
|
+
logError('send-code', err);
|
|
139
|
+
throw new Error(err.error || 'Failed to send code');
|
|
166
140
|
}
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
141
|
+
|
|
142
|
+
const sendData = await sendRes.json();
|
|
143
|
+
spinner.succeed('Code sent! Check your email.');
|
|
144
|
+
|
|
145
|
+
if (sendData.isNewUser) {
|
|
146
|
+
console.log(chalk.gray(' No account found - we\'ll create one for you.\n'));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const { code } = await inquirer.prompt([
|
|
150
|
+
{ type: 'input', name: 'code', message: 'Enter 6-digit code:', validate: (v: string) => /^\d{6}$/.test(v) || 'Enter 6 digits' },
|
|
171
151
|
]);
|
|
172
152
|
|
|
173
|
-
|
|
174
|
-
log('POST /auth/
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
const res = await fetch(`${API_URL}/auth/login`, {
|
|
178
|
-
method: 'POST',
|
|
179
|
-
headers: { 'Content-Type': 'application/json' },
|
|
180
|
-
body: JSON.stringify({ email, password }),
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
if (!res.ok) {
|
|
184
|
-
const err = await res.json();
|
|
185
|
-
logError('login', err);
|
|
186
|
-
throw new Error(err.error || 'Login failed');
|
|
187
|
-
}
|
|
153
|
+
spinner = ora('Verifying...').start();
|
|
154
|
+
log('POST /auth/verify-code', { email, code });
|
|
188
155
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
156
|
+
const verifyRes = await fetch(`${API_URL}/auth/verify-code`, {
|
|
157
|
+
method: 'POST',
|
|
158
|
+
headers: { 'Content-Type': 'application/json' },
|
|
159
|
+
body: JSON.stringify({ email, code }),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (!verifyRes.ok) {
|
|
163
|
+
const err = await verifyRes.json();
|
|
164
|
+
logError('verify-code', err);
|
|
165
|
+
throw new Error(err.error || 'Verification failed');
|
|
199
166
|
}
|
|
167
|
+
|
|
168
|
+
const data = await verifyRes.json();
|
|
169
|
+
config.set('sessionId', data.sessionId);
|
|
170
|
+
config.set('user', data.user);
|
|
171
|
+
config.set('tenant', data.tenant);
|
|
172
|
+
sessionId = data.sessionId;
|
|
173
|
+
spinner.succeed(data.isNewUser ? 'Account created!' : 'Logged in!');
|
|
174
|
+
log('Session ID stored');
|
|
175
|
+
} catch (err) {
|
|
176
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
177
|
+
process.exit(1);
|
|
200
178
|
}
|
|
201
179
|
}
|
|
202
180
|
|
|
@@ -364,57 +342,97 @@ ${chalk.bold('Example:')}
|
|
|
364
342
|
|
|
365
343
|
program
|
|
366
344
|
.command('login')
|
|
367
|
-
.description(`Log in to GopherHole
|
|
345
|
+
.description(`Log in to GopherHole (creates account if needed)
|
|
368
346
|
|
|
369
347
|
${chalk.bold('Example:')}
|
|
370
348
|
$ gopherhole login
|
|
371
349
|
`)
|
|
372
350
|
.action(async () => {
|
|
373
|
-
const { email
|
|
374
|
-
{ type: 'input', name: 'email', message: 'Email:' },
|
|
375
|
-
{ type: 'password', name: 'password', message: 'Password:' },
|
|
351
|
+
const { email } = await inquirer.prompt([
|
|
352
|
+
{ type: 'input', name: 'email', message: 'Email:', validate: (v: string) => v.includes('@') || 'Enter a valid email' },
|
|
376
353
|
]);
|
|
377
354
|
|
|
378
|
-
|
|
379
|
-
log('POST /auth/
|
|
355
|
+
let spinner = ora('Sending verification code...').start();
|
|
356
|
+
log('POST /auth/send-code', { email });
|
|
380
357
|
|
|
381
358
|
try {
|
|
382
|
-
const
|
|
359
|
+
const sendRes = await fetch(`${API_URL}/auth/send-code`, {
|
|
383
360
|
method: 'POST',
|
|
384
361
|
headers: { 'Content-Type': 'application/json' },
|
|
385
|
-
body: JSON.stringify({ email
|
|
362
|
+
body: JSON.stringify({ email }),
|
|
386
363
|
});
|
|
387
364
|
|
|
388
|
-
if (!
|
|
389
|
-
const err = await
|
|
390
|
-
logError('
|
|
391
|
-
throw new Error(err.error || '
|
|
365
|
+
if (!sendRes.ok) {
|
|
366
|
+
const err = await sendRes.json();
|
|
367
|
+
logError('send-code', err);
|
|
368
|
+
throw new Error(err.error || 'Failed to send code');
|
|
392
369
|
}
|
|
393
370
|
|
|
394
|
-
const
|
|
371
|
+
const sendData = await sendRes.json();
|
|
372
|
+
spinner.succeed('Code sent! Check your email.');
|
|
373
|
+
|
|
374
|
+
if (sendData.isNewUser) {
|
|
375
|
+
console.log(chalk.gray(' No account found - we\'ll create one for you.\n'));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const { code } = await inquirer.prompt([
|
|
379
|
+
{ type: 'input', name: 'code', message: 'Enter 6-digit code:', validate: (v: string) => /^\d{6}$/.test(v) || 'Enter 6 digits' },
|
|
380
|
+
]);
|
|
381
|
+
|
|
382
|
+
spinner = ora('Verifying...').start();
|
|
383
|
+
log('POST /auth/verify-code', { email, code });
|
|
384
|
+
|
|
385
|
+
const verifyRes = await fetch(`${API_URL}/auth/verify-code`, {
|
|
386
|
+
method: 'POST',
|
|
387
|
+
headers: { 'Content-Type': 'application/json' },
|
|
388
|
+
body: JSON.stringify({ email, code }),
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
if (!verifyRes.ok) {
|
|
392
|
+
const err = await verifyRes.json();
|
|
393
|
+
logError('verify-code', err);
|
|
394
|
+
throw new Error(err.error || 'Verification failed');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const data = await verifyRes.json();
|
|
395
398
|
config.set('sessionId', data.sessionId);
|
|
396
399
|
config.set('user', data.user);
|
|
397
400
|
config.set('tenant', data.tenant);
|
|
398
401
|
|
|
399
|
-
|
|
402
|
+
if (data.isNewUser) {
|
|
403
|
+
spinner.succeed(brand.green('Account created!'));
|
|
404
|
+
console.log(`\n${brand.green('✨ Welcome to GopherHole!')}\n`);
|
|
405
|
+
console.log('Next steps:');
|
|
406
|
+
console.log(chalk.gray(' $ gopherhole agents create # Create your first agent'));
|
|
407
|
+
console.log(chalk.gray(' $ gopherhole discover search # Find agents to talk to\n'));
|
|
408
|
+
} else {
|
|
409
|
+
spinner.succeed(`Logged in as ${brand.green(data.user.email)}`);
|
|
410
|
+
}
|
|
400
411
|
log('Session stored in:', config.path);
|
|
401
412
|
} catch (err) {
|
|
402
413
|
spinner.fail(chalk.red((err as Error).message));
|
|
403
414
|
console.log(chalk.gray('\nTroubleshooting:'));
|
|
404
|
-
console.log(chalk.gray(' • Check your email
|
|
405
|
-
console.log(chalk.gray(' •
|
|
415
|
+
console.log(chalk.gray(' • Check your email for the code'));
|
|
416
|
+
console.log(chalk.gray(' • Codes expire after 10 minutes'));
|
|
406
417
|
console.log(chalk.gray(' • Run with --verbose for more details'));
|
|
407
418
|
process.exit(1);
|
|
408
419
|
}
|
|
409
420
|
});
|
|
410
421
|
|
|
422
|
+
// Alias for backward compatibility
|
|
411
423
|
program
|
|
412
424
|
.command('signup')
|
|
413
|
-
.description(`Create a new GopherHole account
|
|
425
|
+
.description(`Create a new GopherHole account (alias for login)`)
|
|
426
|
+
.action(async () => {
|
|
427
|
+
console.log(chalk.yellow('Note: signup and login are now unified. Running login...\n'));
|
|
428
|
+
// Re-run login command
|
|
429
|
+
await program.parseAsync(['node', 'gopherhole', 'login']);
|
|
430
|
+
});
|
|
414
431
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
432
|
+
// Legacy signup code removed - kept as hidden command for reference
|
|
433
|
+
program
|
|
434
|
+
.command('signup-legacy', { hidden: true })
|
|
435
|
+
.description(`[DEPRECATED] Create account with password`)
|
|
418
436
|
.action(async () => {
|
|
419
437
|
const { name, email, password } = await inquirer.prompt([
|
|
420
438
|
{ type: 'input', name: 'name', message: 'Name:' },
|
|
@@ -463,19 +481,39 @@ program
|
|
|
463
481
|
program
|
|
464
482
|
.command('whoami')
|
|
465
483
|
.description('Show current logged in user')
|
|
466
|
-
.action(() => {
|
|
467
|
-
const
|
|
468
|
-
const
|
|
484
|
+
.action(async () => {
|
|
485
|
+
const sessionId = config.get('sessionId') as string | undefined;
|
|
486
|
+
const cachedUser = config.get('user') as { email: string; name: string } | undefined;
|
|
487
|
+
const cachedTenant = config.get('tenant') as { name: string } | undefined;
|
|
469
488
|
|
|
470
|
-
if (!
|
|
489
|
+
if (!sessionId || !cachedUser) {
|
|
471
490
|
console.log(chalk.yellow('Not logged in.'));
|
|
472
491
|
console.log(chalk.gray('\nTo log in: gopherhole login'));
|
|
473
492
|
console.log(chalk.gray('To sign up: gopherhole signup'));
|
|
474
493
|
process.exit(1);
|
|
475
494
|
}
|
|
476
495
|
|
|
477
|
-
|
|
478
|
-
|
|
496
|
+
// Validate session with server
|
|
497
|
+
try {
|
|
498
|
+
const res = await fetch(`${API_URL}/auth/me`, {
|
|
499
|
+
headers: { 'X-Session-ID': sessionId },
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
if (!res.ok) {
|
|
503
|
+
const data = await res.json().catch(() => ({}));
|
|
504
|
+
if (data.error === 'Session expired' || res.status === 401) {
|
|
505
|
+
console.log(chalk.red('Session expired.'));
|
|
506
|
+
console.log(chalk.gray('\nPlease log in again: gopherhole login'));
|
|
507
|
+
process.exit(1);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
} catch {
|
|
511
|
+
// Network error - show cached info with warning
|
|
512
|
+
console.log(chalk.yellow('⚠ Could not verify session (offline?)\n'));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
console.log(`\n ${chalk.bold('User:')} ${cachedUser.name} <${cachedUser.email}>`);
|
|
516
|
+
console.log(` ${chalk.bold('Org:')} ${cachedTenant?.name || 'Personal'}`);
|
|
479
517
|
console.log(` ${chalk.bold('Config:')} ${config.path}\n`);
|
|
480
518
|
});
|
|
481
519
|
|