UNPKG

@ace-sdk/cli

Version:

ACE CLI - Command-line tool for intelligent pattern learning and playbook management

319 lines 15.4 kB
/** * Whoami Command - Display current user info * v4.5.5: Added --server flag to fetch real-time status from server * @package @ace-sdk/cli */ import chalk from 'chalk'; import { getCurrentUser, isAuthenticated, getTokenType, getEffectiveOrgId, loadUserAuth, createHttpClient } from '@ace-sdk/core'; import { globalOptions } from '../cli.js'; /** * Format token expiration status */ function formatExpirationStatus(expiresAt) { if (!expiresAt) { return { text: 'No expiration info', color: 'yellow' }; } const now = new Date(); const expiry = new Date(expiresAt); const diffMs = expiry.getTime() - now.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMins / 60); const diffDays = Math.floor(diffHours / 24); if (diffMs < 0) { return { text: 'Expired', color: 'red' }; } if (diffMins < 5) { return { text: `Expires in ${diffMins} minutes (will auto-refresh)`, color: 'yellow' }; } if (diffHours < 1) { return { text: `Expires in ${diffMins} minutes`, color: 'green' }; } if (diffDays < 1) { return { text: `Expires in ${diffHours} hours`, color: 'green' }; } return { text: `Expires in ${diffDays} days`, color: 'green' }; } /** * Calculate seconds until expiry (v4.5.3) */ function calculateSecondsUntilExpiry(expiresAt) { if (!expiresAt) return null; const diffMs = new Date(expiresAt).getTime() - Date.now(); return Math.max(0, Math.floor(diffMs / 1000)); } /** * Check if hard cap is approaching (within 24 hours) (v4.5.3) */ function isHardCapApproaching(absoluteExpiresAt) { if (!absoluteExpiresAt) return false; const diffMs = new Date(absoluteExpiresAt).getTime() - Date.now(); return diffMs > 0 && diffMs < 24 * 60 * 60 * 1000; // Less than 24 hours } /** * Calculate hours until hard cap (v4.5.3) */ function calculateHoursUntilHardCap(absoluteExpiresAt) { if (!absoluteExpiresAt) return null; const diffMs = new Date(absoluteExpiresAt).getTime() - Date.now(); if (diffMs < 0) return 0; return Math.floor(diffMs / (60 * 60 * 1000)); } /** * Format hard cap status for text output (v4.5.3) */ function formatHardCapStatus(absoluteExpiresAt) { if (!absoluteExpiresAt) { return { text: 'Not set', color: 'yellow' }; } const hoursRemaining = calculateHoursUntilHardCap(absoluteExpiresAt); if (hoursRemaining === null) return { text: 'Not set', color: 'yellow' }; if (hoursRemaining === 0) return { text: 'Expired - re-login required', color: 'red' }; if (hoursRemaining < 24) return { text: `${hoursRemaining} hours remaining`, color: 'yellow' }; const daysRemaining = Math.floor(hoursRemaining / 24); return { text: `${daysRemaining} days remaining`, color: 'green' }; } /** * Format last_used_at timestamp for display */ function formatLastUsed(lastUsedAt) { if (!lastUsedAt) return 'Never (or not tracked)'; const lastUsed = new Date(lastUsedAt); const now = new Date(); const diffMs = now.getTime() - lastUsed.getTime(); const diffMins = Math.floor(diffMs / 60000); if (diffMins < 1) return 'Just now'; if (diffMins < 60) return `${diffMins} minute${diffMins === 1 ? '' : 's'} ago`; const diffHours = Math.floor(diffMins / 60); if (diffHours < 24) return `${diffHours} hour${diffHours === 1 ? '' : 's'} ago`; const diffDays = Math.floor(diffHours / 24); return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`; } export async function whoamiCommand(options = {}) { // Check if logged in if (!isAuthenticated()) { if (globalOptions.json) { // Exit 0 in JSON mode - command succeeded, result is "not authenticated" console.log(JSON.stringify({ authenticated: false, message: 'Not logged in' })); return; } else { console.log(chalk.yellow('Not logged in')); console.log(chalk.gray('Run'), chalk.white('ace-cli login'), chalk.gray('to authenticate')); process.exit(1); } } // v4.5.5: If --server flag, fetch real-time status from server if (options.server) { await fetchServerStatus(); return; } // Default: show local config status const user = getCurrentUser(); const auth = loadUserAuth(); // v4.5.0: Get full auth for expiration info const tokenType = getTokenType(); const currentOrgId = getEffectiveOrgId(); // Calculate expiration status const tokenStatus = formatExpirationStatus(auth?.expires_at); const refreshStatus = formatExpirationStatus(auth?.refresh_expires_at); // v4.5.3: Calculate sliding window TTL fields const hardCapStatus = formatHardCapStatus(auth?.absolute_expires_at); if (globalOptions.json) { console.log(JSON.stringify({ authenticated: true, source: 'local', // v4.5.5: Indicate this is from local config token_type: tokenType, user: user ? { user_id: user.user_id, email: user.email, name: user.name, authenticated_at: user.authenticated_at, default_org_id: user.default_org_id, organizations: user.organizations } : null, current_org_id: currentOrgId, // v4.5.0: Token expiration info token_expires_at: auth?.expires_at || null, refresh_expires_at: auth?.refresh_expires_at || null, token_status: tokenStatus.text, session_status: refreshStatus.text, // v4.5.3: Sliding window TTL fields token_expires_in: calculateSecondsUntilExpiry(auth?.expires_at), absolute_expires_at: auth?.absolute_expires_at || null, last_used_at: null, // Only available with --server is_hard_cap_approaching: isHardCapApproaching(auth?.absolute_expires_at), hard_cap_hours_remaining: calculateHoursUntilHardCap(auth?.absolute_expires_at) })); } else { if (user) { console.log(); console.log(chalk.cyan('User Account')); console.log(chalk.gray('────────────────────────────────')); console.log(chalk.gray('Email:'), chalk.white(user.email)); if (user.name) { console.log(chalk.gray('Name:'), chalk.white(user.name)); } console.log(chalk.gray('User ID:'), chalk.white(user.user_id)); console.log(chalk.gray('Token Type:'), chalk.white(tokenType)); if (user.authenticated_at) { const date = new Date(user.authenticated_at); console.log(chalk.gray('Authenticated:'), chalk.white(date.toLocaleString())); } // v4.5.0: Token expiration status console.log(); console.log(chalk.cyan('Token Status'), chalk.gray('(local)')); console.log(chalk.gray('────────────────────────────────')); const tokenColor = tokenStatus.color === 'green' ? chalk.green : tokenStatus.color === 'yellow' ? chalk.yellow : chalk.red; const refreshColor = refreshStatus.color === 'green' ? chalk.green : refreshStatus.color === 'yellow' ? chalk.yellow : chalk.red; const hardCapColor = hardCapStatus.color === 'green' ? chalk.green : hardCapStatus.color === 'yellow' ? chalk.yellow : chalk.red; console.log(chalk.gray('Access Token:'), tokenColor(tokenStatus.text)); console.log(chalk.gray('Session:'), refreshColor(refreshStatus.text)); console.log(chalk.gray('Hard Cap (7d):'), hardCapColor(hardCapStatus.text)); console.log(chalk.gray('\nTip:'), chalk.dim('Use --server to see real-time status from server')); if (user.organizations.length > 0) { console.log(); console.log(chalk.cyan('Organizations')); console.log(chalk.gray('────────────────────────────────')); for (const org of user.organizations) { const isDefault = currentOrgId === org.org_id; const marker = isDefault ? chalk.green(' (default)') : ''; console.log(` ${chalk.white(org.name)} ${chalk.gray(`[${org.role}]`)}${marker}`); if (globalOptions.verbose) { console.log(` ${chalk.gray('ID:')} ${org.org_id}`); } } } console.log(); } else { // Deprecated org token (no user info stored) console.log(chalk.yellow('Authenticated with deprecated org token')); console.log(chalk.gray('Token Type:'), chalk.white(tokenType)); if (currentOrgId) { console.log(chalk.gray('Organization:'), chalk.white(currentOrgId)); } console.log(); console.log(chalk.gray('Run'), chalk.white('ace-cli login'), chalk.gray('to upgrade to user authentication')); } } } /** * v4.5.5: Fetch real-time token status from server * This validates the sliding window TTL and shows server-side state */ async function fetchServerStatus() { const tokenType = getTokenType(); const currentOrgId = getEffectiveOrgId(); try { // HttpClient handles auto-refresh automatically const client = createHttpClient({ autoRefresh: true }); const serverData = await client.get('/api/v1/auth/me'); // Calculate status from server data (authoritative) const tokenStatus = formatExpirationStatus(serverData.token_expires_at); const refreshStatus = formatExpirationStatus(serverData.refresh_expires_at); const hardCapStatus = formatHardCapStatus(serverData.absolute_expires_at); if (globalOptions.json) { console.log(JSON.stringify({ authenticated: true, source: 'server', // v4.5.5: Indicate this is from server token_type: tokenType, user: { user_id: serverData.user_id, email: serverData.email, organizations: serverData.organizations }, current_org_id: currentOrgId, // Server-side token status (authoritative) token_expires_at: serverData.token_expires_at || null, token_expires_in: serverData.token_expires_in || null, refresh_expires_at: serverData.refresh_expires_at || null, absolute_expires_at: serverData.absolute_expires_at || null, last_used_at: serverData.last_used_at || null, device_count: serverData.device_count || null, token_status: tokenStatus.text, session_status: refreshStatus.text, is_hard_cap_approaching: isHardCapApproaching(serverData.absolute_expires_at), hard_cap_hours_remaining: calculateHoursUntilHardCap(serverData.absolute_expires_at) })); } else { console.log(); console.log(chalk.cyan('User Account'), chalk.green('(verified by server)')); console.log(chalk.gray('────────────────────────────────')); console.log(chalk.gray('Email:'), chalk.white(serverData.email)); console.log(chalk.gray('User ID:'), chalk.white(serverData.user_id)); console.log(chalk.gray('Token Type:'), chalk.white(tokenType)); console.log(chalk.gray('Devices:'), chalk.white(serverData.device_count ?? 'Unknown')); // v4.5.5: Server-side token status (authoritative) console.log(); console.log(chalk.cyan('Token Status'), chalk.green('(server)')); console.log(chalk.gray('────────────────────────────────')); const tokenColor = tokenStatus.color === 'green' ? chalk.green : tokenStatus.color === 'yellow' ? chalk.yellow : chalk.red; const refreshColor = refreshStatus.color === 'green' ? chalk.green : refreshStatus.color === 'yellow' ? chalk.yellow : chalk.red; const hardCapColor = hardCapStatus.color === 'green' ? chalk.green : hardCapStatus.color === 'yellow' ? chalk.yellow : chalk.red; console.log(chalk.gray('Access Token:'), tokenColor(tokenStatus.text)); console.log(chalk.gray('Session:'), refreshColor(refreshStatus.text)); console.log(chalk.gray('Hard Cap (7d):'), hardCapColor(hardCapStatus.text)); console.log(chalk.gray('Last Used:'), chalk.white(formatLastUsed(serverData.last_used_at))); // Show exact timestamps in verbose mode if (globalOptions.verbose) { console.log(); console.log(chalk.cyan('Raw Timestamps')); console.log(chalk.gray('────────────────────────────────')); console.log(chalk.gray('token_expires_at:'), chalk.dim(serverData.token_expires_at || 'null')); console.log(chalk.gray('refresh_expires_at:'), chalk.dim(serverData.refresh_expires_at || 'null')); console.log(chalk.gray('absolute_expires_at:'), chalk.dim(serverData.absolute_expires_at || 'null')); console.log(chalk.gray('last_used_at:'), chalk.dim(serverData.last_used_at || 'null')); } if (serverData.organizations.length > 0) { console.log(); console.log(chalk.cyan('Organizations')); console.log(chalk.gray('────────────────────────────────')); for (const org of serverData.organizations) { const isDefault = currentOrgId === org.org_id; const marker = isDefault ? chalk.green(' (default)') : ''; console.log(` ${chalk.white(org.name)} ${chalk.gray(`[${org.role}]`)}${marker}`); if (globalOptions.verbose) { console.log(` ${chalk.gray('ID:')} ${org.org_id}`); } } } console.log(); } } catch (error) { if (globalOptions.json) { console.log(JSON.stringify({ authenticated: false, source: 'server', error: error instanceof Error ? error.message : String(error) })); } else { console.log(chalk.red('Failed to fetch server status:'), error instanceof Error ? error.message : String(error)); console.log(chalk.gray('Use'), chalk.white('ce-ace whoami'), chalk.gray('(without --server) to see local status')); } process.exit(1); } } //# sourceMappingURL=whoami.js.map