@ace-sdk/cli
Version:
ACE CLI - Command-line tool for intelligent pattern learning and playbook management
319 lines • 15.4 kB
JavaScript
/**
* 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