aios-core 4.2.1 → 4.2.3

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.
@@ -7,8 +7,8 @@
7
7
  # - SHA256 hashes for change detection
8
8
  # - File types for categorization
9
9
  #
10
- version: 4.2.1
11
- generated_at: "2026-02-16T00:12:14.502Z"
10
+ version: 4.2.3
11
+ generated_at: "2026-02-16T00:47:03.699Z"
12
12
  generator: scripts/generate-install-manifest.js
13
13
  file_count: 992
14
14
  files:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aios-core",
3
- "version": "4.2.1",
3
+ "version": "4.2.3",
4
4
  "description": "Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework",
5
5
  "bin": {
6
6
  "aios": "bin/aios.js",
@@ -264,13 +264,18 @@ async function stepLicenseGateCI(options) {
264
264
  /**
265
265
  * Interactive email/password license gate flow.
266
266
  *
267
- * Prompts for email, then checks if account exists to determine signup vs login.
267
+ * New flow (PRO-11 v2):
268
+ * 1. Email → checkEmail API → { isBuyer, hasAccount }
269
+ * 2. NOT buyer → "No Pro access found" → STOP
270
+ * 3. IS buyer + HAS account → Password → Login (with retry) → Activate
271
+ * 4. IS buyer + NO account → Password + Confirm → Signup → Verify email → Login → Activate
268
272
  *
269
273
  * @returns {Promise<Object>} Result with { success, key, activationResult }
270
274
  */
271
275
  async function stepLicenseGateWithEmail() {
272
276
  const inquirer = require('inquirer');
273
277
 
278
+ // Step 1: Get email
274
279
  const { email } = await inquirer.prompt([
275
280
  {
276
281
  type: 'input',
@@ -288,53 +293,147 @@ async function stepLicenseGateWithEmail() {
288
293
  },
289
294
  ]);
290
295
 
291
- const { password } = await inquirer.prompt([
292
- {
293
- type: 'password',
294
- name: 'password',
295
- message: colors.primary('Password:'),
296
- mask: '*',
297
- validate: (input) => {
298
- if (!input || input.length < MIN_PASSWORD_LENGTH) {
299
- return `Password must be at least ${MIN_PASSWORD_LENGTH} characters`;
300
- }
301
- return true;
302
- },
303
- },
304
- ]);
296
+ const trimmedEmail = email.trim();
297
+
298
+ // Step 2: Check buyer status + account existence
299
+ const loader = module.exports._testing ? module.exports._testing.loadLicenseApi : loadLicenseApi;
300
+ const licenseModule = loader();
301
+
302
+ if (!licenseModule) {
303
+ return {
304
+ success: false,
305
+ error: 'Pro license module not available. Ensure @aios-fullstack/pro is installed.',
306
+ };
307
+ }
308
+
309
+ const { LicenseApiClient } = licenseModule;
310
+ const client = new LicenseApiClient();
311
+
312
+ // Check connectivity
313
+ const online = await client.isOnline();
314
+ if (!online) {
315
+ return {
316
+ success: false,
317
+ error: 'License server is unreachable. Check your internet connection and try again.',
318
+ };
319
+ }
320
+
321
+ const checkSpinner = createSpinner('Verifying your access...');
322
+ checkSpinner.start();
323
+
324
+ let checkResult;
325
+ try {
326
+ checkResult = await client.checkEmail(trimmedEmail);
327
+ } catch (checkError) {
328
+ checkSpinner.fail(`Verification failed: ${checkError.message}`);
329
+ return { success: false, error: checkError.message };
330
+ }
331
+
332
+ // Step 2a: NOT a buyer → stop
333
+ if (!checkResult.isBuyer) {
334
+ checkSpinner.fail('No AIOS Pro access found for this email.');
335
+ console.log('');
336
+ showInfo('If you believe this is an error, please contact support:');
337
+ showInfo(' Email: support@synkra.ai');
338
+ showInfo(' Purchase Pro: https://pro.synkra.ai');
339
+ return { success: false, error: 'Email not found in Pro buyers list.' };
340
+ }
341
+
342
+ // Step 2b: IS a buyer
343
+ if (checkResult.hasAccount) {
344
+ checkSpinner.succeed('Pro access confirmed! Account found.');
345
+ // Flow 3: Existing account → Login with password (retry loop)
346
+ return loginWithRetry(client, trimmedEmail);
347
+ }
305
348
 
306
- return authenticateWithEmail(email.trim(), password);
349
+ checkSpinner.succeed('Pro access confirmed! Let\'s create your account.');
350
+ // Flow 4: New account → Create account flow
351
+ return createAccountFlow(client, trimmedEmail);
307
352
  }
308
353
 
309
354
  /**
310
- * Prompt user to create a new account interactively.
311
- *
312
- * Asks for confirmation, then password with confirmation.
313
- * Calls signup API and logs in to get session token.
355
+ * Login flow with password retry (max 3 attempts).
314
356
  *
315
357
  * @param {object} client - LicenseApiClient instance
316
- * @param {string} email - User email
317
- * @returns {Promise<Object>} Result with { success, sessionToken } or { success: false, error }
358
+ * @param {string} email - Verified buyer email
359
+ * @returns {Promise<Object>} Result with { success, key, activationResult }
318
360
  */
319
- async function promptCreateAccount(client, email) {
361
+ async function loginWithRetry(client, email) {
320
362
  const inquirer = require('inquirer');
321
363
 
322
- console.log('');
323
- showInfo(`No account found for ${email}.`);
364
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
365
+ const { password } = await inquirer.prompt([
366
+ {
367
+ type: 'password',
368
+ name: 'password',
369
+ message: colors.primary('Password:'),
370
+ mask: '*',
371
+ validate: (input) => {
372
+ if (!input || input.length < MIN_PASSWORD_LENGTH) {
373
+ return `Password must be at least ${MIN_PASSWORD_LENGTH} characters`;
374
+ }
375
+ return true;
376
+ },
377
+ },
378
+ ]);
324
379
 
325
- const { wantCreate } = await inquirer.prompt([
326
- {
327
- type: 'confirm',
328
- name: 'wantCreate',
329
- message: colors.primary('Would you like to create an account?'),
330
- default: true,
331
- },
332
- ]);
380
+ const spinner = createSpinner('Authenticating...');
381
+ spinner.start();
382
+
383
+ try {
384
+ const loginResult = await client.login(email, password);
385
+ spinner.succeed('Authenticated successfully.');
386
+
387
+ // Wait for email verification if needed
388
+ if (!loginResult.emailVerified) {
389
+ const verifyResult = await waitForEmailVerification(client, loginResult.sessionToken);
390
+ if (!verifyResult.success) {
391
+ return verifyResult;
392
+ }
393
+ }
333
394
 
334
- if (!wantCreate) {
335
- return { success: false, error: 'Account creation cancelled.' };
395
+ // Activate Pro
396
+ return activateProByAuth(client, loginResult.sessionToken);
397
+ } catch (loginError) {
398
+ if (loginError.code === 'INVALID_CREDENTIALS') {
399
+ const remaining = MAX_RETRIES - attempt;
400
+ if (remaining > 0) {
401
+ spinner.fail(`Incorrect password. ${remaining} attempt${remaining > 1 ? 's' : ''} remaining.`);
402
+ showInfo('Forgot your password? Visit https://pro.synkra.ai/reset-password');
403
+ } else {
404
+ spinner.fail('Maximum login attempts reached.');
405
+ showInfo('Forgot your password? Visit https://pro.synkra.ai/reset-password');
406
+ showInfo('Or contact support: support@synkra.ai');
407
+ return { success: false, error: 'Maximum login attempts reached.' };
408
+ }
409
+ } else if (loginError.code === 'AUTH_RATE_LIMITED') {
410
+ spinner.fail(loginError.message);
411
+ return { success: false, error: loginError.message };
412
+ } else {
413
+ spinner.fail(`Authentication failed: ${loginError.message}`);
414
+ return { success: false, error: loginError.message };
415
+ }
416
+ }
336
417
  }
337
418
 
419
+ return { success: false, error: 'Maximum login attempts reached.' };
420
+ }
421
+
422
+ /**
423
+ * Create account flow for new buyers.
424
+ *
425
+ * Asks for password, creates account, waits for email verification.
426
+ *
427
+ * @param {object} client - LicenseApiClient instance
428
+ * @param {string} email - Verified buyer email
429
+ * @returns {Promise<Object>} Result with { success, key, activationResult }
430
+ */
431
+ async function createAccountFlow(client, email) {
432
+ const inquirer = require('inquirer');
433
+
434
+ console.log('');
435
+ showInfo('Create your AIOS Pro account to get started.');
436
+
338
437
  // Ask for password with confirmation
339
438
  const { newPassword } = await inquirer.prompt([
340
439
  {
@@ -370,29 +469,79 @@ async function promptCreateAccount(client, email) {
370
469
  const spinner = createSpinner('Creating account...');
371
470
  spinner.start();
372
471
 
472
+ let sessionToken;
373
473
  try {
374
474
  await client.signup(email, confirmPassword);
375
475
  spinner.succeed('Account created! Verification email sent.');
376
-
377
- // Login to get session token
378
- const loginResult = await client.login(email, confirmPassword);
379
- return { success: true, sessionToken: loginResult.sessionToken };
380
476
  } catch (signupError) {
381
477
  if (signupError.code === 'EMAIL_ALREADY_REGISTERED') {
382
- spinner.fail('An account already exists with this email but the password is incorrect.');
383
- showInfo('Forgot your password? Visit https://pro.synkra.ai/reset-password or contact support@synkra.ai');
384
- return { success: false, error: signupError.message };
478
+ spinner.info('Account already exists. Switching to login...');
479
+ return loginWithRetry(client, email);
385
480
  }
386
481
  spinner.fail(`Account creation failed: ${signupError.message}`);
387
482
  return { success: false, error: signupError.message };
388
483
  }
484
+
485
+ // Wait for email verification
486
+ console.log('');
487
+ showInfo('Please check your email and click the verification link.');
488
+
489
+ // Login after signup to get session token
490
+ try {
491
+ const loginResult = await client.login(email, confirmPassword);
492
+ sessionToken = loginResult.sessionToken;
493
+ } catch {
494
+ // Login might fail if email not verified yet — that's OK, we'll poll
495
+ }
496
+
497
+ if (sessionToken) {
498
+ const verifyResult = await waitForEmailVerification(client, sessionToken);
499
+ if (!verifyResult.success) {
500
+ return verifyResult;
501
+ }
502
+ } else {
503
+ // Need to wait for verification then login
504
+ showInfo('Waiting for email verification...');
505
+ showInfo('After verifying, the installation will continue automatically.');
506
+
507
+ // Poll by trying to login periodically
508
+ const startTime = Date.now();
509
+ while (Date.now() - startTime < VERIFY_POLL_TIMEOUT_MS) {
510
+ await new Promise((resolve) => setTimeout(resolve, VERIFY_POLL_INTERVAL_MS));
511
+ try {
512
+ const loginResult = await client.login(email, confirmPassword);
513
+ sessionToken = loginResult.sessionToken;
514
+ if (loginResult.emailVerified) {
515
+ showSuccess('Email verified!');
516
+ break;
517
+ }
518
+ // Got session but not verified yet — use the verification polling
519
+ const verifyResult = await waitForEmailVerification(client, sessionToken);
520
+ if (!verifyResult.success) {
521
+ return verifyResult;
522
+ }
523
+ break;
524
+ } catch {
525
+ // Still waiting for verification
526
+ }
527
+ }
528
+
529
+ if (!sessionToken) {
530
+ showError('Email verification timed out after 10 minutes.');
531
+ showInfo('Run the installer again to retry.');
532
+ return { success: false, error: 'Email verification timed out.' };
533
+ }
534
+ }
535
+
536
+ // Activate Pro
537
+ return activateProByAuth(client, sessionToken);
389
538
  }
390
539
 
391
540
  /**
392
- * Authenticate with email and password.
541
+ * Authenticate with email and password (CI mode / pre-provided credentials).
393
542
  *
394
- * Tries login first. If user doesn't exist, offers to create account.
395
- * Handles email verification polling for new signups.
543
+ * For interactive mode, use stepLicenseGateWithEmail() instead (buyer-first flow).
544
+ * This function is used when credentials are pre-provided (CI, CLI args).
396
545
  *
397
546
  * @param {string} email - User email
398
547
  * @param {string} password - User password
@@ -421,7 +570,22 @@ async function authenticateWithEmail(email, password) {
421
570
  };
422
571
  }
423
572
 
424
- // Try login first
573
+ // CI mode: check buyer first, then try login or auto-signup
574
+ const checkSpinner = createSpinner('Verifying access...');
575
+ checkSpinner.start();
576
+
577
+ try {
578
+ const checkResult = await client.checkEmail(email);
579
+ if (!checkResult.isBuyer) {
580
+ checkSpinner.fail('No AIOS Pro access found for this email.');
581
+ return { success: false, error: 'Email not found in Pro buyers list.' };
582
+ }
583
+ checkSpinner.succeed('Pro access confirmed.');
584
+ } catch {
585
+ checkSpinner.info('Buyer check unavailable, proceeding with login...');
586
+ }
587
+
588
+ // Try login
425
589
  const spinner = createSpinner('Authenticating...');
426
590
  spinner.start();
427
591
 
@@ -434,44 +598,31 @@ async function authenticateWithEmail(email, password) {
434
598
  emailVerified = loginResult.emailVerified;
435
599
  spinner.succeed('Authenticated successfully.');
436
600
  } catch (loginError) {
437
- // If invalid credentials, offer to create account
438
601
  if (loginError.code === 'INVALID_CREDENTIALS') {
439
- spinner.info('No account found for this email.');
440
-
441
- // In CI mode, auto-create without prompting
442
- if (isCIEnvironment()) {
443
- try {
444
- await client.signup(email, password);
445
- showSuccess('Account created. Verification email sent!');
446
- emailVerified = false;
447
- const loginAfterSignup = await client.login(email, password);
448
- sessionToken = loginAfterSignup.sessionToken;
449
- } catch (signupError) {
450
- if (signupError.code === 'EMAIL_ALREADY_REGISTERED') {
451
- showError('An account exists with this email but the password is incorrect.');
452
- showInfo('Forgot your password? Visit https://pro.synkra.ai/reset-password or contact support@synkra.ai');
453
- return { success: false, error: signupError.message };
454
- }
455
- return { success: false, error: signupError.message };
456
- }
457
- } else {
458
- // Interactive: ask user if they want to create account
459
- const signupResult = await promptCreateAccount(client, email);
460
- if (!signupResult.success) {
461
- return signupResult;
462
- }
463
- sessionToken = signupResult.sessionToken;
602
+ spinner.info('Login failed, attempting signup...');
603
+ try {
604
+ await client.signup(email, password);
605
+ showSuccess('Account created. Verification email sent!');
464
606
  emailVerified = false;
607
+ const loginAfterSignup = await client.login(email, password);
608
+ sessionToken = loginAfterSignup.sessionToken;
609
+ } catch (signupError) {
610
+ if (signupError.code === 'EMAIL_ALREADY_REGISTERED') {
611
+ showError('Account exists but the password is incorrect.');
612
+ return { success: false, error: 'Invalid password.' };
613
+ }
614
+ return { success: false, error: signupError.message };
465
615
  }
466
- } else if (loginError.code === 'AUTH_RATE_LIMITED') {
467
- spinner.fail(loginError.message);
468
- return { success: false, error: loginError.message };
469
616
  } else {
470
617
  spinner.fail(`Authentication failed: ${loginError.message}`);
471
618
  return { success: false, error: loginError.message };
472
619
  }
473
620
  }
474
621
 
622
+ if (!sessionToken) {
623
+ return { success: false, error: 'Authentication failed.' };
624
+ }
625
+
475
626
  // Wait for email verification if needed
476
627
  if (!emailVerified) {
477
628
  const verifyResult = await waitForEmailVerification(client, sessionToken);
@@ -1005,11 +1156,12 @@ module.exports = {
1005
1156
  authenticateWithEmail,
1006
1157
  waitForEmailVerification,
1007
1158
  activateProByAuth,
1159
+ loginWithRetry,
1160
+ createAccountFlow,
1008
1161
  stepLicenseGateCI,
1009
1162
  stepLicenseGateWithKey,
1010
1163
  stepLicenseGateWithKeyInteractive,
1011
1164
  stepLicenseGateWithEmail,
1012
- promptCreateAccount,
1013
1165
  loadLicenseApi,
1014
1166
  loadFeatureGate,
1015
1167
  loadProScaffolder,