jobarbiter 0.3.12 → 0.3.13
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/lib/onboard.js +180 -14
- package/package.json +1 -1
- package/src/lib/onboard.ts +200 -16
package/dist/lib/onboard.js
CHANGED
|
@@ -160,19 +160,68 @@ export async function runOnboardWizard(opts) {
|
|
|
160
160
|
const userType = await selectUserType(prompt);
|
|
161
161
|
state.userType = userType;
|
|
162
162
|
// Step 2: Email & Verification
|
|
163
|
-
const
|
|
164
|
-
state.email = email;
|
|
165
|
-
state.apiKey = apiKey;
|
|
166
|
-
state.userId = userId;
|
|
163
|
+
const verificationResult = await handleEmailVerification(prompt, baseUrl, userType);
|
|
164
|
+
state.email = verificationResult.email;
|
|
165
|
+
state.apiKey = verificationResult.apiKey;
|
|
166
|
+
state.userId = verificationResult.userId;
|
|
167
|
+
// If returning user, override userType from backend
|
|
168
|
+
const effectiveUserType = verificationResult.isReturningUser && verificationResult.userType
|
|
169
|
+
? verificationResult.userType
|
|
170
|
+
: userType;
|
|
171
|
+
state.userType = effectiveUserType;
|
|
167
172
|
// Save config immediately after verification (with step progress)
|
|
168
173
|
saveConfig({
|
|
169
|
-
apiKey,
|
|
174
|
+
apiKey: verificationResult.apiKey,
|
|
170
175
|
baseUrl,
|
|
171
|
-
userType,
|
|
176
|
+
userType: effectiveUserType,
|
|
172
177
|
onboardingStep: 1,
|
|
173
178
|
onboardingComplete: false,
|
|
174
179
|
});
|
|
175
|
-
|
|
180
|
+
// Returning user: show progress and resume from first incomplete step
|
|
181
|
+
if (verificationResult.isReturningUser) {
|
|
182
|
+
const config = {
|
|
183
|
+
apiKey: verificationResult.apiKey,
|
|
184
|
+
baseUrl,
|
|
185
|
+
userType: effectiveUserType,
|
|
186
|
+
};
|
|
187
|
+
const progress = await fetchOnboardingProgress(config);
|
|
188
|
+
const { firstIncomplete } = showReturningUserProgress(progress);
|
|
189
|
+
if (firstIncomplete > (effectiveUserType === "worker" ? 6 : 5)) {
|
|
190
|
+
// Everything done except completion step
|
|
191
|
+
const continueAnyway = await prompt.confirm(`All steps look complete. Re-run onboarding anyway?`, false);
|
|
192
|
+
if (!continueAnyway) {
|
|
193
|
+
saveConfig({ ...config, onboardingComplete: true, onboardingStep: effectiveUserType === "worker" ? 7 : 6 });
|
|
194
|
+
console.log(`\n${sym.check} ${c.success("You're all set!")} Run ${c.highlight("jobarbiter status")} to check your account.\n`);
|
|
195
|
+
prompt.close();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const continueFromStep = await prompt.confirm(`Continue from step ${firstIncomplete}?`);
|
|
201
|
+
if (!continueFromStep) {
|
|
202
|
+
// Let them start from step 2
|
|
203
|
+
console.log(c.dim("Starting from the beginning...\n"));
|
|
204
|
+
if (effectiveUserType === "worker") {
|
|
205
|
+
await runWorkerFlow(prompt, state, 2);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
await runEmployerFlow(prompt, state);
|
|
209
|
+
}
|
|
210
|
+
prompt.close();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Resume from firstIncomplete
|
|
215
|
+
if (effectiveUserType === "worker") {
|
|
216
|
+
await runWorkerFlow(prompt, state, firstIncomplete);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
await runEmployerFlow(prompt, state);
|
|
220
|
+
}
|
|
221
|
+
prompt.close();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (effectiveUserType === "worker") {
|
|
176
225
|
await runWorkerFlow(prompt, state);
|
|
177
226
|
}
|
|
178
227
|
else {
|
|
@@ -233,6 +282,7 @@ async function handleEmailVerification(prompt, baseUrl, userType) {
|
|
|
233
282
|
}
|
|
234
283
|
// Call register API
|
|
235
284
|
console.log(c.dim("\nSending verification code..."));
|
|
285
|
+
let isReturningUser = false;
|
|
236
286
|
try {
|
|
237
287
|
await apiUnauthenticated(baseUrl, "POST", "/v1/auth/register", {
|
|
238
288
|
email,
|
|
@@ -241,18 +291,34 @@ async function handleEmailVerification(prompt, baseUrl, userType) {
|
|
|
241
291
|
}
|
|
242
292
|
catch (err) {
|
|
243
293
|
if (err instanceof ApiError && err.status === 409) {
|
|
244
|
-
// Email already registered
|
|
245
|
-
|
|
294
|
+
// Email already registered — switch to login flow
|
|
295
|
+
isReturningUser = true;
|
|
296
|
+
console.log(`\n${sym.rocket} ${c.bold("Welcome back!")} Sending verification code...`);
|
|
297
|
+
try {
|
|
298
|
+
await apiUnauthenticated(baseUrl, "POST", "/v1/auth/login", { email });
|
|
299
|
+
}
|
|
300
|
+
catch (loginErr) {
|
|
301
|
+
throw new Error("Could not send login verification code. Please try again later.");
|
|
302
|
+
}
|
|
246
303
|
}
|
|
247
|
-
|
|
304
|
+
else {
|
|
305
|
+
throw err;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (!isReturningUser) {
|
|
309
|
+
console.log(`\n${sym.check} Verification code sent to ${c.highlight(email)}`);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
console.log(`${sym.check} Verification code sent to ${c.highlight(email)}`);
|
|
248
313
|
}
|
|
249
|
-
console.log(`\n${sym.check} Verification code sent to ${c.highlight(email)}`);
|
|
250
314
|
console.log(c.dim(" (Check your inbox and spam folder. Code expires in 15 minutes.)\n"));
|
|
251
315
|
// Get verification code
|
|
252
316
|
let apiKey;
|
|
253
317
|
let userId;
|
|
318
|
+
let returnedUserType;
|
|
254
319
|
let attempts = 0;
|
|
255
320
|
const maxAttempts = 5;
|
|
321
|
+
const verifyEndpoint = isReturningUser ? "/v1/auth/verify-login" : "/v1/auth/verify";
|
|
256
322
|
while (attempts < maxAttempts) {
|
|
257
323
|
const code = await prompt.question(`Enter 6-digit code: `);
|
|
258
324
|
if (!code || code.length !== 6) {
|
|
@@ -260,12 +326,15 @@ async function handleEmailVerification(prompt, baseUrl, userType) {
|
|
|
260
326
|
continue;
|
|
261
327
|
}
|
|
262
328
|
try {
|
|
263
|
-
const result = await apiUnauthenticated(baseUrl, "POST",
|
|
329
|
+
const result = await apiUnauthenticated(baseUrl, "POST", verifyEndpoint, {
|
|
264
330
|
email,
|
|
265
331
|
code: code.trim(),
|
|
266
332
|
});
|
|
267
333
|
apiKey = result.apiKey;
|
|
268
334
|
userId = result.id;
|
|
335
|
+
if (isReturningUser) {
|
|
336
|
+
returnedUserType = result.userType;
|
|
337
|
+
}
|
|
269
338
|
break;
|
|
270
339
|
}
|
|
271
340
|
catch (err) {
|
|
@@ -287,8 +356,105 @@ async function handleEmailVerification(prompt, baseUrl, userType) {
|
|
|
287
356
|
if (!apiKey || !userId) {
|
|
288
357
|
throw new Error("Verification failed. Please try again.");
|
|
289
358
|
}
|
|
290
|
-
|
|
291
|
-
|
|
359
|
+
if (isReturningUser) {
|
|
360
|
+
console.log(`\n${sym.check} ${c.success("Welcome back! Logged in successfully.")}\n`);
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
console.log(`\n${sym.check} ${c.success("Email verified! Account created.")}\n`);
|
|
364
|
+
}
|
|
365
|
+
return { email, apiKey, userId, isReturningUser, userType: returnedUserType };
|
|
366
|
+
}
|
|
367
|
+
async function fetchOnboardingProgress(config) {
|
|
368
|
+
const progress = {
|
|
369
|
+
accountCreated: true,
|
|
370
|
+
userType: config.userType,
|
|
371
|
+
toolsDetected: [],
|
|
372
|
+
aiAccountsConnected: false,
|
|
373
|
+
domainsSet: [],
|
|
374
|
+
githubConnected: false,
|
|
375
|
+
linkedinConnected: false,
|
|
376
|
+
};
|
|
377
|
+
// Fetch profile
|
|
378
|
+
try {
|
|
379
|
+
const profile = await api(config, "GET", "/v1/profile");
|
|
380
|
+
if (profile.domains && Array.isArray(profile.domains) && profile.domains.length > 0) {
|
|
381
|
+
progress.domainsSet = profile.domains;
|
|
382
|
+
}
|
|
383
|
+
if (profile.tools && typeof profile.tools === "object") {
|
|
384
|
+
const tools = profile.tools;
|
|
385
|
+
if (tools.primary && Array.isArray(tools.primary) && tools.primary.length > 0) {
|
|
386
|
+
progress.toolsDetected = tools.primary;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (profile.bio) {
|
|
390
|
+
progress.bio = profile.bio;
|
|
391
|
+
}
|
|
392
|
+
if (profile.githubUsername) {
|
|
393
|
+
progress.githubConnected = true;
|
|
394
|
+
progress.githubUsername = profile.githubUsername;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
// Profile doesn't exist yet — that's fine
|
|
399
|
+
}
|
|
400
|
+
// Check AI accounts
|
|
401
|
+
const existingProviders = loadProviderKeys();
|
|
402
|
+
progress.aiAccountsConnected = existingProviders.length > 0;
|
|
403
|
+
// Check verification status (LinkedIn etc.)
|
|
404
|
+
try {
|
|
405
|
+
const verificationStatus = await api(config, "GET", "/v1/verification/status");
|
|
406
|
+
if (verificationStatus.linkedin) {
|
|
407
|
+
progress.linkedinConnected = true;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// Endpoint may not exist — gracefully ignore
|
|
412
|
+
}
|
|
413
|
+
return progress;
|
|
414
|
+
}
|
|
415
|
+
function showReturningUserProgress(progress) {
|
|
416
|
+
const isWorker = progress.userType === "worker";
|
|
417
|
+
const totalSteps = isWorker ? 7 : 6;
|
|
418
|
+
console.log(`${c.bold("Here's your onboarding progress:")}\n`);
|
|
419
|
+
if (isWorker) {
|
|
420
|
+
const steps = [
|
|
421
|
+
{ done: true, label: `Account created (${progress.userType})` },
|
|
422
|
+
{ done: progress.toolsDetected.length > 0, label: progress.toolsDetected.length > 0
|
|
423
|
+
? `AI Tools detected (${progress.toolsDetected.join(", ")})`
|
|
424
|
+
: "AI Tools not detected" },
|
|
425
|
+
{ done: progress.aiAccountsConnected, label: progress.aiAccountsConnected
|
|
426
|
+
? "AI Accounts connected"
|
|
427
|
+
: "AI Accounts not connected" },
|
|
428
|
+
{ done: progress.domainsSet.length > 0, label: progress.domainsSet.length > 0
|
|
429
|
+
? `Domains set (${progress.domainsSet.join(", ")})`
|
|
430
|
+
: "Domains not set" },
|
|
431
|
+
{ done: progress.githubConnected, label: progress.githubConnected
|
|
432
|
+
? `GitHub connected (${progress.githubUsername})`
|
|
433
|
+
: "GitHub not connected" },
|
|
434
|
+
{ done: progress.linkedinConnected, label: progress.linkedinConnected
|
|
435
|
+
? "LinkedIn connected"
|
|
436
|
+
: "LinkedIn not connected" },
|
|
437
|
+
{ done: false, label: "Completion" },
|
|
438
|
+
];
|
|
439
|
+
let firstIncomplete = totalSteps; // default to last
|
|
440
|
+
for (let i = 0; i < steps.length; i++) {
|
|
441
|
+
const step = steps[i];
|
|
442
|
+
const icon = step.done ? sym.check : sym.cross;
|
|
443
|
+
console.log(` ${icon} Step ${i + 1}/${totalSteps} — ${step.label}`);
|
|
444
|
+
if (!step.done && firstIncomplete === totalSteps) {
|
|
445
|
+
firstIncomplete = i + 1;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
console.log();
|
|
449
|
+
return { firstIncomplete };
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
// Employer — simpler progress
|
|
453
|
+
console.log(` ${sym.check} Step 1/6 — Account created (employer)`);
|
|
454
|
+
console.log(` ${sym.cross} Step 2/6 — Remaining setup`);
|
|
455
|
+
console.log();
|
|
456
|
+
return { firstIncomplete: 2 };
|
|
457
|
+
}
|
|
292
458
|
}
|
|
293
459
|
// ── Worker Flow ────────────────────────────────────────────────────────
|
|
294
460
|
async function runWorkerFlow(prompt, state, startStep = 2) {
|
package/package.json
CHANGED
package/src/lib/onboard.ts
CHANGED
|
@@ -215,21 +215,71 @@ export async function runOnboardWizard(opts: { force?: boolean; baseUrl?: string
|
|
|
215
215
|
state.userType = userType;
|
|
216
216
|
|
|
217
217
|
// Step 2: Email & Verification
|
|
218
|
-
const
|
|
219
|
-
state.email = email;
|
|
220
|
-
state.apiKey = apiKey;
|
|
221
|
-
state.userId = userId;
|
|
218
|
+
const verificationResult = await handleEmailVerification(prompt, baseUrl, userType);
|
|
219
|
+
state.email = verificationResult.email;
|
|
220
|
+
state.apiKey = verificationResult.apiKey;
|
|
221
|
+
state.userId = verificationResult.userId;
|
|
222
|
+
|
|
223
|
+
// If returning user, override userType from backend
|
|
224
|
+
const effectiveUserType = verificationResult.isReturningUser && verificationResult.userType
|
|
225
|
+
? (verificationResult.userType as "worker" | "employer")
|
|
226
|
+
: userType;
|
|
227
|
+
state.userType = effectiveUserType;
|
|
222
228
|
|
|
223
229
|
// Save config immediately after verification (with step progress)
|
|
224
230
|
saveConfig({
|
|
225
|
-
apiKey,
|
|
231
|
+
apiKey: verificationResult.apiKey,
|
|
226
232
|
baseUrl,
|
|
227
|
-
userType,
|
|
233
|
+
userType: effectiveUserType,
|
|
228
234
|
onboardingStep: 1,
|
|
229
235
|
onboardingComplete: false,
|
|
230
236
|
});
|
|
231
237
|
|
|
232
|
-
|
|
238
|
+
// Returning user: show progress and resume from first incomplete step
|
|
239
|
+
if (verificationResult.isReturningUser) {
|
|
240
|
+
const config: Config = {
|
|
241
|
+
apiKey: verificationResult.apiKey,
|
|
242
|
+
baseUrl,
|
|
243
|
+
userType: effectiveUserType,
|
|
244
|
+
};
|
|
245
|
+
const progress = await fetchOnboardingProgress(config);
|
|
246
|
+
const { firstIncomplete } = showReturningUserProgress(progress);
|
|
247
|
+
|
|
248
|
+
if (firstIncomplete > (effectiveUserType === "worker" ? 6 : 5)) {
|
|
249
|
+
// Everything done except completion step
|
|
250
|
+
const continueAnyway = await prompt.confirm(`All steps look complete. Re-run onboarding anyway?`, false);
|
|
251
|
+
if (!continueAnyway) {
|
|
252
|
+
saveConfig({ ...config, onboardingComplete: true, onboardingStep: effectiveUserType === "worker" ? 7 : 6 });
|
|
253
|
+
console.log(`\n${sym.check} ${c.success("You're all set!")} Run ${c.highlight("jobarbiter status")} to check your account.\n`);
|
|
254
|
+
prompt.close();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
const continueFromStep = await prompt.confirm(`Continue from step ${firstIncomplete}?`);
|
|
259
|
+
if (!continueFromStep) {
|
|
260
|
+
// Let them start from step 2
|
|
261
|
+
console.log(c.dim("Starting from the beginning...\n"));
|
|
262
|
+
if (effectiveUserType === "worker") {
|
|
263
|
+
await runWorkerFlow(prompt, state as OnboardState, 2);
|
|
264
|
+
} else {
|
|
265
|
+
await runEmployerFlow(prompt, state as OnboardState);
|
|
266
|
+
}
|
|
267
|
+
prompt.close();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Resume from firstIncomplete
|
|
273
|
+
if (effectiveUserType === "worker") {
|
|
274
|
+
await runWorkerFlow(prompt, state as OnboardState, firstIncomplete);
|
|
275
|
+
} else {
|
|
276
|
+
await runEmployerFlow(prompt, state as OnboardState);
|
|
277
|
+
}
|
|
278
|
+
prompt.close();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (effectiveUserType === "worker") {
|
|
233
283
|
await runWorkerFlow(prompt, state as OnboardState);
|
|
234
284
|
} else {
|
|
235
285
|
await runEmployerFlow(prompt, state as OnboardState);
|
|
@@ -282,7 +332,7 @@ async function handleEmailVerification(
|
|
|
282
332
|
prompt: Prompt,
|
|
283
333
|
baseUrl: string,
|
|
284
334
|
userType: "worker" | "employer"
|
|
285
|
-
): Promise<{ email: string; apiKey: string; userId: string }> {
|
|
335
|
+
): Promise<{ email: string; apiKey: string; userId: string; isReturningUser: boolean; userType?: string }> {
|
|
286
336
|
// Workers: 1) Account, 2) Tool Detection, 3) AI Accounts, 4) Domains, 5) GitHub, 6) LinkedIn, 7) Done
|
|
287
337
|
// Employers: 1) Account, 2) (skip verification), 3) Company, 4) Domain, 5) What You Need, 6) Done (stays at 6)
|
|
288
338
|
const totalSteps = userType === "employer" ? 6 : 7;
|
|
@@ -300,6 +350,8 @@ async function handleEmailVerification(
|
|
|
300
350
|
// Call register API
|
|
301
351
|
console.log(c.dim("\nSending verification code..."));
|
|
302
352
|
|
|
353
|
+
let isReturningUser = false;
|
|
354
|
+
|
|
303
355
|
try {
|
|
304
356
|
await apiUnauthenticated(baseUrl, "POST", "/v1/auth/register", {
|
|
305
357
|
email,
|
|
@@ -307,37 +359,53 @@ async function handleEmailVerification(
|
|
|
307
359
|
});
|
|
308
360
|
} catch (err) {
|
|
309
361
|
if (err instanceof ApiError && err.status === 409) {
|
|
310
|
-
// Email already registered
|
|
311
|
-
|
|
362
|
+
// Email already registered — switch to login flow
|
|
363
|
+
isReturningUser = true;
|
|
364
|
+
console.log(`\n${sym.rocket} ${c.bold("Welcome back!")} Sending verification code...`);
|
|
365
|
+
try {
|
|
366
|
+
await apiUnauthenticated(baseUrl, "POST", "/v1/auth/login", { email });
|
|
367
|
+
} catch (loginErr) {
|
|
368
|
+
throw new Error("Could not send login verification code. Please try again later.");
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
throw err;
|
|
312
372
|
}
|
|
313
|
-
throw err;
|
|
314
373
|
}
|
|
315
374
|
|
|
316
|
-
|
|
375
|
+
if (!isReturningUser) {
|
|
376
|
+
console.log(`\n${sym.check} Verification code sent to ${c.highlight(email)}`);
|
|
377
|
+
} else {
|
|
378
|
+
console.log(`${sym.check} Verification code sent to ${c.highlight(email)}`);
|
|
379
|
+
}
|
|
317
380
|
console.log(c.dim(" (Check your inbox and spam folder. Code expires in 15 minutes.)\n"));
|
|
318
381
|
|
|
319
382
|
// Get verification code
|
|
320
383
|
let apiKey: string | undefined;
|
|
321
384
|
let userId: string | undefined;
|
|
385
|
+
let returnedUserType: string | undefined;
|
|
322
386
|
let attempts = 0;
|
|
323
387
|
const maxAttempts = 5;
|
|
388
|
+
const verifyEndpoint = isReturningUser ? "/v1/auth/verify-login" : "/v1/auth/verify";
|
|
324
389
|
|
|
325
390
|
while (attempts < maxAttempts) {
|
|
326
391
|
const code = await prompt.question(`Enter 6-digit code: `);
|
|
327
|
-
|
|
392
|
+
|
|
328
393
|
if (!code || code.length !== 6) {
|
|
329
394
|
console.log(c.error("Code must be 6 digits"));
|
|
330
395
|
continue;
|
|
331
396
|
}
|
|
332
397
|
|
|
333
398
|
try {
|
|
334
|
-
const result = await apiUnauthenticated(baseUrl, "POST",
|
|
399
|
+
const result = await apiUnauthenticated(baseUrl, "POST", verifyEndpoint, {
|
|
335
400
|
email,
|
|
336
401
|
code: code.trim(),
|
|
337
402
|
});
|
|
338
403
|
|
|
339
404
|
apiKey = result.apiKey as string;
|
|
340
405
|
userId = result.id as string;
|
|
406
|
+
if (isReturningUser) {
|
|
407
|
+
returnedUserType = result.userType as string;
|
|
408
|
+
}
|
|
341
409
|
break;
|
|
342
410
|
} catch (err) {
|
|
343
411
|
attempts++;
|
|
@@ -358,9 +426,125 @@ async function handleEmailVerification(
|
|
|
358
426
|
throw new Error("Verification failed. Please try again.");
|
|
359
427
|
}
|
|
360
428
|
|
|
361
|
-
|
|
429
|
+
if (isReturningUser) {
|
|
430
|
+
console.log(`\n${sym.check} ${c.success("Welcome back! Logged in successfully.")}\n`);
|
|
431
|
+
} else {
|
|
432
|
+
console.log(`\n${sym.check} ${c.success("Email verified! Account created.")}\n`);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return { email, apiKey, userId, isReturningUser, userType: returnedUserType };
|
|
436
|
+
}
|
|
362
437
|
|
|
363
|
-
|
|
438
|
+
// ── Returning User Progress ─────────────────────────────────────────────
|
|
439
|
+
|
|
440
|
+
interface OnboardProgress {
|
|
441
|
+
accountCreated: boolean;
|
|
442
|
+
userType: "worker" | "employer";
|
|
443
|
+
toolsDetected: string[];
|
|
444
|
+
aiAccountsConnected: boolean;
|
|
445
|
+
domainsSet: string[];
|
|
446
|
+
githubConnected: boolean;
|
|
447
|
+
linkedinConnected: boolean;
|
|
448
|
+
bio?: string;
|
|
449
|
+
githubUsername?: string;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function fetchOnboardingProgress(config: Config): Promise<OnboardProgress> {
|
|
453
|
+
const progress: OnboardProgress = {
|
|
454
|
+
accountCreated: true,
|
|
455
|
+
userType: config.userType as "worker" | "employer",
|
|
456
|
+
toolsDetected: [],
|
|
457
|
+
aiAccountsConnected: false,
|
|
458
|
+
domainsSet: [],
|
|
459
|
+
githubConnected: false,
|
|
460
|
+
linkedinConnected: false,
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// Fetch profile
|
|
464
|
+
try {
|
|
465
|
+
const profile = await api(config, "GET", "/v1/profile");
|
|
466
|
+
if (profile.domains && Array.isArray(profile.domains) && (profile.domains as string[]).length > 0) {
|
|
467
|
+
progress.domainsSet = profile.domains as string[];
|
|
468
|
+
}
|
|
469
|
+
if (profile.tools && typeof profile.tools === "object") {
|
|
470
|
+
const tools = profile.tools as Record<string, unknown>;
|
|
471
|
+
if (tools.primary && Array.isArray(tools.primary) && (tools.primary as string[]).length > 0) {
|
|
472
|
+
progress.toolsDetected = tools.primary as string[];
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (profile.bio) {
|
|
476
|
+
progress.bio = profile.bio as string;
|
|
477
|
+
}
|
|
478
|
+
if (profile.githubUsername) {
|
|
479
|
+
progress.githubConnected = true;
|
|
480
|
+
progress.githubUsername = profile.githubUsername as string;
|
|
481
|
+
}
|
|
482
|
+
} catch {
|
|
483
|
+
// Profile doesn't exist yet — that's fine
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Check AI accounts
|
|
487
|
+
const existingProviders = loadProviderKeys();
|
|
488
|
+
progress.aiAccountsConnected = existingProviders.length > 0;
|
|
489
|
+
|
|
490
|
+
// Check verification status (LinkedIn etc.)
|
|
491
|
+
try {
|
|
492
|
+
const verificationStatus = await api(config, "GET", "/v1/verification/status");
|
|
493
|
+
if (verificationStatus.linkedin) {
|
|
494
|
+
progress.linkedinConnected = true;
|
|
495
|
+
}
|
|
496
|
+
} catch {
|
|
497
|
+
// Endpoint may not exist — gracefully ignore
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return progress;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function showReturningUserProgress(progress: OnboardProgress): { firstIncomplete: number } {
|
|
504
|
+
const isWorker = progress.userType === "worker";
|
|
505
|
+
const totalSteps = isWorker ? 7 : 6;
|
|
506
|
+
|
|
507
|
+
console.log(`${c.bold("Here's your onboarding progress:")}\n`);
|
|
508
|
+
|
|
509
|
+
if (isWorker) {
|
|
510
|
+
const steps: Array<{ done: boolean; label: string }> = [
|
|
511
|
+
{ done: true, label: `Account created (${progress.userType})` },
|
|
512
|
+
{ done: progress.toolsDetected.length > 0, label: progress.toolsDetected.length > 0
|
|
513
|
+
? `AI Tools detected (${progress.toolsDetected.join(", ")})`
|
|
514
|
+
: "AI Tools not detected" },
|
|
515
|
+
{ done: progress.aiAccountsConnected, label: progress.aiAccountsConnected
|
|
516
|
+
? "AI Accounts connected"
|
|
517
|
+
: "AI Accounts not connected" },
|
|
518
|
+
{ done: progress.domainsSet.length > 0, label: progress.domainsSet.length > 0
|
|
519
|
+
? `Domains set (${progress.domainsSet.join(", ")})`
|
|
520
|
+
: "Domains not set" },
|
|
521
|
+
{ done: progress.githubConnected, label: progress.githubConnected
|
|
522
|
+
? `GitHub connected (${progress.githubUsername})`
|
|
523
|
+
: "GitHub not connected" },
|
|
524
|
+
{ done: progress.linkedinConnected, label: progress.linkedinConnected
|
|
525
|
+
? "LinkedIn connected"
|
|
526
|
+
: "LinkedIn not connected" },
|
|
527
|
+
{ done: false, label: "Completion" },
|
|
528
|
+
];
|
|
529
|
+
|
|
530
|
+
let firstIncomplete = totalSteps; // default to last
|
|
531
|
+
for (let i = 0; i < steps.length; i++) {
|
|
532
|
+
const step = steps[i];
|
|
533
|
+
const icon = step.done ? sym.check : sym.cross;
|
|
534
|
+
console.log(` ${icon} Step ${i + 1}/${totalSteps} — ${step.label}`);
|
|
535
|
+
if (!step.done && firstIncomplete === totalSteps) {
|
|
536
|
+
firstIncomplete = i + 1;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
console.log();
|
|
540
|
+
return { firstIncomplete };
|
|
541
|
+
} else {
|
|
542
|
+
// Employer — simpler progress
|
|
543
|
+
console.log(` ${sym.check} Step 1/6 — Account created (employer)`);
|
|
544
|
+
console.log(` ${sym.cross} Step 2/6 — Remaining setup`);
|
|
545
|
+
console.log();
|
|
546
|
+
return { firstIncomplete: 2 };
|
|
547
|
+
}
|
|
364
548
|
}
|
|
365
549
|
|
|
366
550
|
// ── Worker Flow ────────────────────────────────────────────────────────
|