@gopherhole/cli 0.1.20 → 0.1.22
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 +137 -144
- package/package.json +1 -1
- package/src/index.ts +150 -144
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:' },
|
|
@@ -886,62 +895,46 @@ ${chalk_1.default.bold('Example:')}
|
|
|
886
895
|
// Check if logged in
|
|
887
896
|
if (!sessionId) {
|
|
888
897
|
console.log(chalk_1.default.yellow('Not logged in yet.\n'));
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
name: 'action',
|
|
893
|
-
message: 'Choose:',
|
|
894
|
-
choices: [
|
|
895
|
-
{ name: 'Create account', value: 'signup' },
|
|
896
|
-
{ name: 'Log in', value: 'login' },
|
|
897
|
-
],
|
|
898
|
-
},
|
|
898
|
+
// OTP-based auth flow
|
|
899
|
+
const { email } = await inquirer_1.default.prompt([
|
|
900
|
+
{ type: 'input', name: 'email', message: 'Email:', validate: (v) => v.includes('@') || 'Enter a valid email' },
|
|
899
901
|
]);
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
body: JSON.stringify({ name, email, password }),
|
|
911
|
-
});
|
|
912
|
-
if (!res.ok) {
|
|
913
|
-
spinner.fail('Signup failed');
|
|
914
|
-
process.exit(1);
|
|
915
|
-
}
|
|
916
|
-
const data = await res.json();
|
|
917
|
-
config.set('sessionId', data.sessionId);
|
|
918
|
-
config.set('user', data.user);
|
|
919
|
-
config.set('tenant', data.tenant);
|
|
920
|
-
sessionId = data.sessionId;
|
|
921
|
-
spinner.succeed('Account created!');
|
|
902
|
+
let spinner = (0, ora_1.default)('Sending verification code...').start();
|
|
903
|
+
const sendRes = await fetch(`${API_URL}/auth/send-code`, {
|
|
904
|
+
method: 'POST',
|
|
905
|
+
headers: { 'Content-Type': 'application/json' },
|
|
906
|
+
body: JSON.stringify({ email }),
|
|
907
|
+
});
|
|
908
|
+
if (!sendRes.ok) {
|
|
909
|
+
const err = await sendRes.json().catch(() => ({}));
|
|
910
|
+
spinner.fail(err.error || 'Failed to send code');
|
|
911
|
+
process.exit(1);
|
|
922
912
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
]);
|
|
928
|
-
const spinner = (0, ora_1.default)('Logging in...').start();
|
|
929
|
-
const res = await fetch(`${API_URL}/auth/login`, {
|
|
930
|
-
method: 'POST',
|
|
931
|
-
headers: { 'Content-Type': 'application/json' },
|
|
932
|
-
body: JSON.stringify({ email, password }),
|
|
933
|
-
});
|
|
934
|
-
if (!res.ok) {
|
|
935
|
-
spinner.fail('Login failed');
|
|
936
|
-
process.exit(1);
|
|
937
|
-
}
|
|
938
|
-
const data = await res.json();
|
|
939
|
-
config.set('sessionId', data.sessionId);
|
|
940
|
-
config.set('user', data.user);
|
|
941
|
-
config.set('tenant', data.tenant);
|
|
942
|
-
sessionId = data.sessionId;
|
|
943
|
-
spinner.succeed('Logged in!');
|
|
913
|
+
const sendData = await sendRes.json();
|
|
914
|
+
spinner.succeed('Code sent! Check your email.');
|
|
915
|
+
if (sendData.isNewUser) {
|
|
916
|
+
console.log(chalk_1.default.gray(' No account found - we\'ll create one for you.\n'));
|
|
944
917
|
}
|
|
918
|
+
const { code } = await inquirer_1.default.prompt([
|
|
919
|
+
{ type: 'input', name: 'code', message: 'Enter 6-digit code:', validate: (v) => /^\d{6}$/.test(v) || 'Enter 6 digits' },
|
|
920
|
+
]);
|
|
921
|
+
spinner = (0, ora_1.default)('Verifying...').start();
|
|
922
|
+
const verifyRes = await fetch(`${API_URL}/auth/verify-code`, {
|
|
923
|
+
method: 'POST',
|
|
924
|
+
headers: { 'Content-Type': 'application/json' },
|
|
925
|
+
body: JSON.stringify({ email, code }),
|
|
926
|
+
});
|
|
927
|
+
if (!verifyRes.ok) {
|
|
928
|
+
const err = await verifyRes.json().catch(() => ({}));
|
|
929
|
+
spinner.fail(err.error || 'Verification failed');
|
|
930
|
+
process.exit(1);
|
|
931
|
+
}
|
|
932
|
+
const data = await verifyRes.json();
|
|
933
|
+
config.set('sessionId', data.sessionId);
|
|
934
|
+
config.set('user', data.user);
|
|
935
|
+
config.set('tenant', data.tenant);
|
|
936
|
+
sessionId = data.sessionId;
|
|
937
|
+
spinner.succeed(data.isNewUser ? 'Account created!' : 'Logged in!');
|
|
945
938
|
}
|
|
946
939
|
console.log('');
|
|
947
940
|
// Create agent
|
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
|
-
} else {
|
|
168
|
-
const { email, password } = await inquirer.prompt([
|
|
169
|
-
{ type: 'input', name: 'email', message: 'Email:' },
|
|
170
|
-
{ type: 'password', name: 'password', message: 'Password:' },
|
|
171
|
-
]);
|
|
172
141
|
|
|
173
|
-
const
|
|
174
|
-
|
|
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
|
+
}
|
|
175
148
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
headers: { 'Content-Type': 'application/json' },
|
|
180
|
-
body: JSON.stringify({ email, password }),
|
|
181
|
-
});
|
|
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' },
|
|
151
|
+
]);
|
|
182
152
|
|
|
183
|
-
|
|
184
|
-
|
|
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:' },
|
|
@@ -981,68 +999,56 @@ ${chalk.bold('Example:')}
|
|
|
981
999
|
if (!sessionId) {
|
|
982
1000
|
console.log(chalk.yellow('Not logged in yet.\n'));
|
|
983
1001
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
name: 'action',
|
|
988
|
-
message: 'Choose:',
|
|
989
|
-
choices: [
|
|
990
|
-
{ name: 'Create account', value: 'signup' },
|
|
991
|
-
{ name: 'Log in', value: 'login' },
|
|
992
|
-
],
|
|
993
|
-
},
|
|
1002
|
+
// OTP-based auth flow
|
|
1003
|
+
const { email } = await inquirer.prompt([
|
|
1004
|
+
{ type: 'input', name: 'email', message: 'Email:', validate: (v: string) => v.includes('@') || 'Enter a valid email' },
|
|
994
1005
|
]);
|
|
995
1006
|
|
|
996
|
-
|
|
997
|
-
const { name, email, password } = await inquirer.prompt([
|
|
998
|
-
{ type: 'input', name: 'name', message: 'Name:' },
|
|
999
|
-
{ type: 'input', name: 'email', message: 'Email:' },
|
|
1000
|
-
{ type: 'password', name: 'password', message: 'Password:' },
|
|
1001
|
-
]);
|
|
1007
|
+
let spinner = ora('Sending verification code...').start();
|
|
1002
1008
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
});
|
|
1009
|
+
const sendRes = await fetch(`${API_URL}/auth/send-code`, {
|
|
1010
|
+
method: 'POST',
|
|
1011
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1012
|
+
body: JSON.stringify({ email }),
|
|
1013
|
+
});
|
|
1009
1014
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1015
|
+
if (!sendRes.ok) {
|
|
1016
|
+
const err = await sendRes.json().catch(() => ({}));
|
|
1017
|
+
spinner.fail(err.error || 'Failed to send code');
|
|
1018
|
+
process.exit(1);
|
|
1019
|
+
}
|
|
1014
1020
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
} else {
|
|
1022
|
-
const { email, password } = await inquirer.prompt([
|
|
1023
|
-
{ type: 'input', name: 'email', message: 'Email:' },
|
|
1024
|
-
{ type: 'password', name: 'password', message: 'Password:' },
|
|
1025
|
-
]);
|
|
1021
|
+
const sendData = await sendRes.json();
|
|
1022
|
+
spinner.succeed('Code sent! Check your email.');
|
|
1023
|
+
|
|
1024
|
+
if (sendData.isNewUser) {
|
|
1025
|
+
console.log(chalk.gray(' No account found - we\'ll create one for you.\n'));
|
|
1026
|
+
}
|
|
1026
1027
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1031
|
-
body: JSON.stringify({ email, password }),
|
|
1032
|
-
});
|
|
1028
|
+
const { code } = await inquirer.prompt([
|
|
1029
|
+
{ type: 'input', name: 'code', message: 'Enter 6-digit code:', validate: (v: string) => /^\d{6}$/.test(v) || 'Enter 6 digits' },
|
|
1030
|
+
]);
|
|
1033
1031
|
|
|
1034
|
-
|
|
1035
|
-
spinner.fail('Login failed');
|
|
1036
|
-
process.exit(1);
|
|
1037
|
-
}
|
|
1032
|
+
spinner = ora('Verifying...').start();
|
|
1038
1033
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1034
|
+
const verifyRes = await fetch(`${API_URL}/auth/verify-code`, {
|
|
1035
|
+
method: 'POST',
|
|
1036
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1037
|
+
body: JSON.stringify({ email, code }),
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
if (!verifyRes.ok) {
|
|
1041
|
+
const err = await verifyRes.json().catch(() => ({}));
|
|
1042
|
+
spinner.fail(err.error || 'Verification failed');
|
|
1043
|
+
process.exit(1);
|
|
1045
1044
|
}
|
|
1045
|
+
|
|
1046
|
+
const data = await verifyRes.json();
|
|
1047
|
+
config.set('sessionId', data.sessionId);
|
|
1048
|
+
config.set('user', data.user);
|
|
1049
|
+
config.set('tenant', data.tenant);
|
|
1050
|
+
sessionId = data.sessionId;
|
|
1051
|
+
spinner.succeed(data.isNewUser ? 'Account created!' : 'Logged in!');
|
|
1046
1052
|
}
|
|
1047
1053
|
|
|
1048
1054
|
console.log('');
|