UNPKG

alnilam-cli

Version:

Git-native AI career coach that converts multi-year ambitions into weekly execution

634 lines (633 loc) 23.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.getCurrentAuthToken = exports.authCommand = void 0; const commander_1 = require("commander"); const readline = __importStar(require("readline")); const readModule = __importStar(require("read")); const auth_js_1 = require("../lib/auth.js"); // Constants const MIN_PASSWORD_LENGTH = 6; const authCommand = new commander_1.Command('auth'); exports.authCommand = authCommand; authCommand.description('Authentication commands - login with email/password or service role key'); // Create auth manager instance const authManager = new auth_js_1.AuthManager(); // Helper function to prompt for password securely (hidden input) async function promptForPassword(message = 'Enter your password: ') { try { const password = await readModule.read({ prompt: message, silent: true, replace: '*' }); return password.trim(); } catch (error) { throw new Error(`Failed to read password: ${error}`); } } // Helper function to prompt for email async function promptForEmail() { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve) => { rl.question('Enter your email address: ', (answer) => { rl.close(); resolve(answer.trim()); }); }); } // Helper function to validate email format function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } // Register command const registerCommand = new commander_1.Command('register'); registerCommand .description('Register a new account with email and password') .option('--email <email>', 'Email address for registration') .option('--password [password]', 'Password for registration (will prompt if not provided)') .option('--json', 'Output as JSON') .action(async (options) => { try { let email = options.email; let password = options.password; // Get email if not provided if (!email) { email = await promptForEmail(); } // Validate email format if (!isValidEmail(email)) { if (options.json) { console.log(JSON.stringify({ error: 'Invalid email format' })); } else { console.error('❌ Invalid email format'); } process.exit(1); } // Get password if not provided if (password === undefined) { password = await promptForPassword(`Enter your password (min ${MIN_PASSWORD_LENGTH} characters): `); } else if (password === true) { password = await promptForPassword(`Enter your password (min ${MIN_PASSWORD_LENGTH} characters): `); } // Validate password length if (password.length < MIN_PASSWORD_LENGTH) { if (options.json) { console.log(JSON.stringify({ error: `Password must be at least ${MIN_PASSWORD_LENGTH} characters long` })); } else { console.error(`❌ Password must be at least ${MIN_PASSWORD_LENGTH} characters long`); } process.exit(1); } // Register with Supabase const { data, error } = await authManager.register(email, password); if (error) { if (options.json) { console.log(JSON.stringify({ error: error.message })); } else { console.error(`❌ Registration failed: ${error.message}`); if (error.message.includes('already registered')) { console.log('💡 User already exists. Try: alnl auth login'); } } process.exit(1); } if (!data.user) { if (options.json) { console.log(JSON.stringify({ error: 'Registration failed - no user data returned' })); } else { console.error('❌ Registration failed - no user data returned'); } process.exit(1); } // Success - Supabase automatically handles session storage if (options.json) { console.log(JSON.stringify({ success: true, message: data.session ? 'Registration successful and logged in' : 'Registration successful - please check email for confirmation', user: { id: data.user.id, email: data.user.email }, session: !!data.session })); } else { console.log('✅ Registration successful!'); if (data.session) { console.log(`👤 Logged in as: ${data.user.email}`); console.log('🚀 You can now use all Alnilam commands'); } else { console.log('📧 Please check your email for a confirmation link'); console.log('💡 After confirming, run: alnl auth login'); } } } catch (error) { if (options.json) { console.log(JSON.stringify({ error: error.message })); } else { console.error(`❌ Registration error: ${error.message}`); } process.exit(1); } }); // Login command const loginCommand = new commander_1.Command('login'); loginCommand .description('Authenticate with Alnilam using email/password or service role key') .option('--email <email>', 'Email address for authentication') .option('--password [password]', 'Password for authentication (will prompt if not provided)') .option('--service-role-key <key>', 'Service role key for automation/CI') .option('--json', 'Output as JSON') .action(async (options) => { try { // Service role key authentication (for automation/CI) if (options.serviceRoleKey) { const { success } = await authManager.setServiceRoleSession(options.serviceRoleKey); if (options.json) { console.log(JSON.stringify({ success: true, message: 'Service role authentication successful', auth_type: 'service_role' })); } else { console.log('✅ Service role authentication successful!'); console.log('🔑 Using service role permissions'); } return; } // Email/password authentication let email = options.email; let password = options.password; // Get email if not provided if (!email) { email = await promptForEmail(); } // Validate email format if (!isValidEmail(email)) { if (options.json) { console.log(JSON.stringify({ error: 'Invalid email format' })); } else { console.error('❌ Invalid email format'); } process.exit(1); } // Get password if not provided if (password === undefined) { password = await promptForPassword(); } else if (password === true) { password = await promptForPassword(); } // Login with Supabase const { data, error } = await authManager.login(email, password); if (error || !data.session) { if (options.json) { console.log(JSON.stringify({ error: error?.message || 'Authentication failed', suggestion: 'Check your email and password, or try registering: alnl auth register' })); } else { console.error(`❌ Authentication failed: ${error?.message || 'Invalid credentials'}`); console.log('💡 Try registering first with: alnl auth register'); console.log('💡 Or check your email and password'); } process.exit(1); } // Success - Supabase automatically stores the session if (options.json) { console.log(JSON.stringify({ success: true, message: 'Authentication successful', user: { id: data.user.id, email: data.user.email }, expires_at: data.session.expires_at })); } else { console.log('✅ Authentication successful!'); console.log(`👤 Logged in as: ${data.user.email}`); if (data.session.expires_at) { const expiresDate = new Date(data.session.expires_at * 1000); console.log(`⏰ Session expires: ${expiresDate.toLocaleString()}`); } } } catch (error) { if (options.json) { console.log(JSON.stringify({ error: error.message })); } else { console.error(`❌ Login error: ${error.message}`); } process.exit(1); } }); // Verify command (check current authentication status) const verifyCommand = new commander_1.Command('verify'); verifyCommand .description('Verify current authentication status') .option('--json', 'Output as JSON') .action(async (options) => { try { const { user, error } = await authManager.getCurrentUser(); if (error || !user) { if (options.json) { console.log(JSON.stringify({ authenticated: false, error: error?.message || 'Not authenticated', suggestion: 'Run "alnl auth login" or "alnl auth register" to authenticate' })); } else { console.log('❌ Not authenticated'); console.log('💡 Run "alnl auth login" or "alnl auth register" to authenticate'); } process.exit(1); } // Get session info const { session } = await authManager.getSession(); if (options.json) { console.log(JSON.stringify({ authenticated: true, user: { id: user.id, email: user.email }, expires_at: session?.expires_at, expires_in: session?.expires_at ? session.expires_at - Math.floor(Date.now() / 1000) : null })); } else { console.log('✅ Authentication verified'); console.log(`👤 Logged in as: ${user.email}`); if (session?.expires_at) { const expiresDate = new Date(session.expires_at * 1000); const expiresIn = session.expires_at - Math.floor(Date.now() / 1000); const expiresInHours = Math.floor(expiresIn / 3600); console.log(`⏰ Token expires: ${expiresDate.toLocaleString()} (in ${expiresInHours} hours)`); } } } catch (error) { if (options.json) { console.log(JSON.stringify({ authenticated: false, error: error.message })); } else { console.error(`❌ Verification error: ${error.message}`); } process.exit(1); } }); // Status command (alias for verify) const statusCommand = new commander_1.Command('status'); statusCommand .description('Check authentication status') .option('--json', 'Output as JSON') .action(async (options) => { // Reuse verify command logic await verifyCommand.parseAsync(['verify', ...(options.json ? ['--json'] : [])], { from: 'user' }); }); // Refresh command const refreshCommand = new commander_1.Command('refresh'); refreshCommand .description('Refresh authentication session') .option('--json', 'Output as JSON') .action(async (options) => { try { const { data, error } = await authManager.refreshSession(); if (error || !data.session) { if (options.json) { console.log(JSON.stringify({ error: error?.message || 'Session refresh failed', suggestion: 'Run "alnl auth login" to re-authenticate' })); } else { console.log('❌ Session refresh failed'); console.log('💡 Run "alnl auth login" to re-authenticate'); if (error?.message) { console.log(`🔍 Details: ${error.message}`); } } process.exit(1); } if (options.json) { console.log(JSON.stringify({ success: true, message: 'Session refreshed successfully', expires_at: data.session.expires_at })); } else { console.log('✅ Session refreshed successfully'); if (data.session.expires_at) { const expiresDate = new Date(data.session.expires_at * 1000); console.log(`⏰ New expiration: ${expiresDate.toLocaleString()}`); } } } catch (error) { if (options.json) { console.log(JSON.stringify({ error: error.message, suggestion: 'Run "alnl auth login" to re-authenticate' })); } else { console.error(`❌ Refresh error: ${error.message}`); console.log('💡 Run "alnl auth login" to re-authenticate'); } process.exit(1); } }); // Update password command const updatePasswordCommand = new commander_1.Command('update-password'); updatePasswordCommand .description('Update password for currently authenticated user') .option('--password [password]', 'New password (will prompt if not provided)') .option('--json', 'Output as JSON') .action(async (options) => { try { // Check if user is authenticated const { user } = await authManager.getCurrentUser(); if (!user) { if (options.json) { console.log(JSON.stringify({ error: 'Not authenticated' })); } else { console.error('❌ Not authenticated'); console.log('💡 Run "alnl auth login" to authenticate first'); } process.exit(1); } let password = options.password; // Get password if not provided if (password === undefined) { password = await promptForPassword('Enter your new password (min 6 characters): '); } else if (password === true) { password = await promptForPassword('Enter your new password (min 6 characters): '); } // Validate password length if (password.length < 6) { if (options.json) { console.log(JSON.stringify({ error: 'Password must be at least 6 characters long' })); } else { console.error('❌ Password must be at least 6 characters long'); } process.exit(1); } // Update password const { data, error } = await authManager.updatePassword(password); if (error) { if (options.json) { console.log(JSON.stringify({ error: error.message })); } else { console.error(`❌ Password update failed: ${error.message}`); } process.exit(1); } if (options.json) { console.log(JSON.stringify({ success: true, message: 'Password updated successfully', user: { id: data.user?.id, email: data.user?.email } })); } else { console.log('✅ Password updated successfully!'); console.log(`👤 Account: ${data.user?.email}`); console.log('🔒 Your new password is now active'); console.log('💡 You can now login with: alnl auth login'); } } catch (error) { if (options.json) { console.log(JSON.stringify({ error: error.message })); } else { console.error(`❌ Update password error: ${error.message}`); } process.exit(1); } }); // Set session command (for handling password reset callbacks) const setSessionCommand = new commander_1.Command('set-session'); setSessionCommand .description('Set authentication session from tokens (used after password reset)') .requiredOption('--access-token <token>', 'Access token from password reset callback') .requiredOption('--refresh-token <token>', 'Refresh token from password reset callback') .option('--json', 'Output as JSON') .action(async (options) => { try { // Set session from tokens const { data, error } = await authManager.setSession(options.accessToken, options.refreshToken); if (error) { if (options.json) { console.log(JSON.stringify({ error: error.message })); } else { console.error(`❌ Failed to set session: ${error.message}`); } process.exit(1); } if (!data.user) { if (options.json) { console.log(JSON.stringify({ error: 'No user data in session' })); } else { console.error('❌ Failed to set session: No user data'); } process.exit(1); } if (options.json) { console.log(JSON.stringify({ success: true, message: 'Session set successfully', user: { id: data.user.id, email: data.user.email } })); } else { console.log('✅ Session set successfully!'); console.log(`👤 Logged in as: ${data.user.email}`); console.log('🔄 You can now set a new password with: alnl auth update-password'); } } catch (error) { if (options.json) { console.log(JSON.stringify({ error: error.message })); } else { console.error(`❌ Set session error: ${error.message}`); } process.exit(1); } }); // Reset password command const resetPasswordCommand = new commander_1.Command('reset-password'); resetPasswordCommand .description('Send a password reset email') .option('--email <email>', 'Email address to send reset link to') .option('--json', 'Output as JSON') .action(async (options) => { try { let email = options.email; // Get email if not provided if (!email) { email = await promptForEmail(); } // Validate email format if (!isValidEmail(email)) { if (options.json) { console.log(JSON.stringify({ error: 'Invalid email format' })); } else { console.error('❌ Invalid email format'); } process.exit(1); } // Send password reset email with CLI-friendly redirect const { data, error } = await authManager.resetPassword(email); if (error) { if (options.json) { console.log(JSON.stringify({ error: error.message })); } else { console.error(`❌ Password reset failed: ${error.message}`); } process.exit(1); } if (options.json) { console.log(JSON.stringify({ success: true, message: 'Password reset email sent', email: email })); } else { console.log('✅ Password reset email sent!'); console.log(`📧 Check your email at: ${email}`); console.log('🔗 Click the reset link to set a new password'); console.log('💡 After resetting, use: alnl auth login'); } } catch (error) { if (options.json) { console.log(JSON.stringify({ error: error.message })); } else { console.error(`❌ Reset password error: ${error.message}`); } process.exit(1); } }); // Logout command const logoutCommand = new commander_1.Command('logout'); logoutCommand .description('Log out and clear stored credentials') .option('--json', 'Output as JSON') .action(async (options) => { try { const { error } = await authManager.logout(); if (error) { if (options.json) { console.log(JSON.stringify({ error: error.message })); } else { console.error(`❌ Logout error: ${error.message}`); } process.exit(1); } if (options.json) { console.log(JSON.stringify({ success: true, message: 'Logged out successfully' })); } else { console.log('✅ Logged out successfully'); console.log('🔐 All stored credentials have been cleared'); } } catch (error) { if (options.json) { console.log(JSON.stringify({ error: error.message })); } else { console.error(`❌ Logout error: ${error.message}`); } process.exit(1); } }); // Add subcommands authCommand.addCommand(loginCommand); authCommand.addCommand(registerCommand); authCommand.addCommand(verifyCommand); authCommand.addCommand(statusCommand); authCommand.addCommand(refreshCommand); authCommand.addCommand(resetPasswordCommand); authCommand.addCommand(setSessionCommand); authCommand.addCommand(updatePasswordCommand); authCommand.addCommand(logoutCommand); // Export the getCurrentAuthToken function for backward compatibility var auth_js_2 = require("../lib/auth.js"); Object.defineProperty(exports, "getCurrentAuthToken", { enumerable: true, get: function () { return auth_js_2.getCurrentAuthToken; } });