import chalk from 'chalk'; import figures from 'figures'; import * as React from 'react'; import { color, Text } from '../ink.js'; import type { MCPServerConnection } from '../services/mcp/types.js'; import { getAccountInformation, isClaudeAISubscriber } from './auth.js'; import { getLargeMemoryFiles, getMemoryFiles, MAX_MEMORY_CHARACTER_COUNT } from './claudemd.js'; import { getDoctorDiagnostic } from './doctorDiagnostic.js'; import { getAWSRegion, getDefaultVertexRegion, isEnvTruthy } from './envUtils.js'; import { getDisplayPath } from './file.js'; import { formatNumber } from './format.js'; import { getIdeClientName, type IDEExtensionInstallationStatus, isJetBrainsIde, toIDEDisplayName } from './ide.js'; import { getClaudeAiUserDefaultModelDescription, modelDisplayString } from './model/model.js'; import { getAPIProvider } from './model/providers.js'; import { resolveProviderRequest } from '../services/api/providerConfig.js'; import { getMTLSConfig } from './mtls.js'; import { checkInstall } from './nativeInstaller/index.js'; import { getProxyUrl } from './proxy.js'; import { SandboxManager } from './sandbox/sandbox-adapter.js'; import { getSettingsWithAllErrors } from './settings/allErrors.js'; import { getEnabledSettingSources, getSettingSourceDisplayNameCapitalized } from './settings/constants.js'; import { getManagedFileSettingsPresence, getPolicySettingsOrigin, getSettingsForSource } from './settings/settings.js'; import type { ThemeName } from './theme.js'; import { redactSecretValueForDisplay } from './providerProfile.js'; export type Property = { label?: string; value: React.ReactNode | Array; }; export type Diagnostic = React.ReactNode; export function buildSandboxProperties(): Property[] { if ("external" !== 'ant') { return []; } const isSandboxed = SandboxManager.isSandboxingEnabled(); return [{ label: 'Bash Sandbox', value: isSandboxed ? 'Enabled' : 'Disabled' }]; } export function buildIDEProperties(mcpClients: MCPServerConnection[], ideInstallationStatus: IDEExtensionInstallationStatus | null = null, theme: ThemeName): Property[] { const ideClient = mcpClients?.find(client => client.name === 'ide'); if (ideInstallationStatus) { const ideName = toIDEDisplayName(ideInstallationStatus.ideType); const pluginOrExtension = isJetBrainsIde(ideInstallationStatus.ideType) ? 'plugin' : 'extension'; if (ideInstallationStatus.error) { return [{ label: 'IDE', value: {color('error', theme)(figures.cross)} Error installing {ideName}{' '} {pluginOrExtension}: {ideInstallationStatus.error} {'\n'}Please restart your IDE and try again. }]; } if (ideInstallationStatus.installed) { if (ideClient && ideClient.type === 'connected') { if (ideInstallationStatus.installedVersion !== ideClient.serverInfo?.version) { return [{ label: 'IDE', value: `Connected to ${ideName} ${pluginOrExtension} version ${ideInstallationStatus.installedVersion} (server version: ${ideClient.serverInfo?.version})` }]; } else { return [{ label: 'IDE', value: `Connected to ${ideName} ${pluginOrExtension} version ${ideInstallationStatus.installedVersion}` }]; } } else { return [{ label: 'IDE', value: `Installed ${ideName} ${pluginOrExtension}` }]; } } } else if (ideClient) { const ideName = getIdeClientName(ideClient) ?? 'IDE'; if (ideClient.type === 'connected') { return [{ label: 'IDE', value: `Connected to ${ideName} extension` }]; } else { return [{ label: 'IDE', value: `${color('error', theme)(figures.cross)} Not connected to ${ideName}` }]; } } return []; } export function buildMcpProperties(clients: MCPServerConnection[] = [], theme: ThemeName): Property[] { const servers = clients.filter(client => client.name !== 'ide'); if (!servers.length) { return []; } // Summary instead of a full server list — 20+ servers wrapped onto many // rows, dominating the Status pane. Show counts by state + /mcp hint. const byState = { connected: 0, pending: 0, needsAuth: 0, failed: 0 }; for (const s of servers) { if (s.type === 'connected') byState.connected++;else if (s.type === 'pending') byState.pending++;else if (s.type === 'needs-auth') byState.needsAuth++;else byState.failed++; } const parts: string[] = []; if (byState.connected) parts.push(color('success', theme)(`${byState.connected} connected`)); if (byState.needsAuth) parts.push(color('warning', theme)(`${byState.needsAuth} need auth`)); if (byState.pending) parts.push(color('inactive', theme)(`${byState.pending} pending`)); if (byState.failed) parts.push(color('error', theme)(`${byState.failed} failed`)); return [{ label: 'MCP servers', value: `${parts.join(', ')} ${color('inactive', theme)('· /mcp')}` }]; } export async function buildMemoryDiagnostics(): Promise { const files = await getMemoryFiles(); const largeFiles = getLargeMemoryFiles(files); const diagnostics: Diagnostic[] = []; largeFiles.forEach(file => { const displayPath = getDisplayPath(file.path); diagnostics.push(`Large ${displayPath} will impact performance (${formatNumber(file.content.length)} chars > ${formatNumber(MAX_MEMORY_CHARACTER_COUNT)})`); }); return diagnostics; } export function buildSettingSourcesProperties(): Property[] { const enabledSources = getEnabledSettingSources(); // Filter to only sources that actually have settings loaded const sourcesWithSettings = enabledSources.filter(source => { const settings = getSettingsForSource(source); return settings !== null && Object.keys(settings).length > 0; }); // Map internal names to user-friendly names // For policySettings, distinguish between remote and local (or skip if neither exists) const sourceNames = sourcesWithSettings.map(source => { if (source === 'policySettings') { const origin = getPolicySettingsOrigin(); if (origin === null) { return null; // Skip - no policy settings exist } switch (origin) { case 'remote': return 'Enterprise managed settings (remote)'; case 'plist': return 'Enterprise managed settings (plist)'; case 'hklm': return 'Enterprise managed settings (HKLM)'; case 'file': { const { hasBase, hasDropIns } = getManagedFileSettingsPresence(); if (hasBase && hasDropIns) { return 'Enterprise managed settings (file + drop-ins)'; } if (hasDropIns) { return 'Enterprise managed settings (drop-ins)'; } return 'Enterprise managed settings (file)'; } case 'hkcu': return 'Enterprise managed settings (HKCU)'; } } return getSettingSourceDisplayNameCapitalized(source); }).filter((name): name is string => name !== null); return [{ label: 'Setting sources', value: sourceNames }]; } export async function buildInstallationDiagnostics(): Promise { const installWarnings = await checkInstall(); return installWarnings.map(warning => warning.message); } export async function buildInstallationHealthDiagnostics(): Promise { const diagnostic = await getDoctorDiagnostic(); const items: Diagnostic[] = []; const { errors: validationErrors } = getSettingsWithAllErrors(); if (validationErrors.length > 0) { const invalidFiles = Array.from(new Set(validationErrors.map(error => error.file))); const fileList = invalidFiles.join(', '); items.push(`Found invalid settings files: ${fileList}. They will be ignored.`); } // Add warnings from doctor diagnostic (includes leftover installations, config mismatches, etc.) diagnostic.warnings.forEach(warning => { items.push(warning.issue); }); if (diagnostic.hasUpdatePermissions === false) { items.push('No write permissions for auto-updates (requires sudo)'); } return items; } export function buildAccountProperties(): Property[] { const accountInfo = getAccountInformation(); if (!accountInfo) { return []; } const properties: Property[] = []; if (accountInfo.subscription) { properties.push({ label: 'Login method', value: `${accountInfo.subscription} Account` }); } if (accountInfo.tokenSource) { properties.push({ label: 'Auth token', value: accountInfo.tokenSource }); } if (accountInfo.apiKeySource) { properties.push({ label: 'API key', value: accountInfo.apiKeySource }); } // Hide sensitive account info in demo mode if (accountInfo.organization && !process.env.IS_DEMO) { properties.push({ label: 'Organization', value: accountInfo.organization }); } if (accountInfo.email && !process.env.IS_DEMO) { properties.push({ label: 'Email', value: accountInfo.email }); } return properties; } export function buildAPIProviderProperties(): Property[] { const apiProvider = getAPIProvider(); const properties: Property[] = []; if (apiProvider !== 'firstParty') { const providerLabel = { bedrock: 'AWS Bedrock', vertex: 'Google Vertex AI', foundry: 'Microsoft Foundry', openai: 'OpenAI-compatible', codex: 'Codex', gemini: 'Google Gemini', github: 'GitHub Models', mistral: 'Mistral', }[apiProvider]; properties.push({ label: 'API provider', value: providerLabel }); } if (apiProvider === 'firstParty') { const anthropicBaseUrl = process.env.ANTHROPIC_BASE_URL; if (anthropicBaseUrl) { properties.push({ label: 'Anthropic base URL', value: anthropicBaseUrl }); } } else if (apiProvider === 'bedrock') { const bedrockBaseUrl = process.env.BEDROCK_BASE_URL; if (bedrockBaseUrl) { properties.push({ label: 'Bedrock base URL', value: bedrockBaseUrl }); } properties.push({ label: 'AWS region', value: getAWSRegion() }); if (isEnvTruthy(process.env.CLAUDE_CODE_SKIP_BEDROCK_AUTH)) { properties.push({ value: 'AWS auth skipped' }); } } else if (apiProvider === 'vertex') { const vertexBaseUrl = process.env.VERTEX_BASE_URL; if (vertexBaseUrl) { properties.push({ label: 'Vertex base URL', value: vertexBaseUrl }); } const gcpProject = process.env.ANTHROPIC_VERTEX_PROJECT_ID; if (gcpProject) { properties.push({ label: 'GCP project', value: gcpProject }); } properties.push({ label: 'Default region', value: getDefaultVertexRegion() }); if (isEnvTruthy(process.env.CLAUDE_CODE_SKIP_VERTEX_AUTH)) { properties.push({ value: 'GCP auth skipped' }); } } else if (apiProvider === 'foundry') { const foundryBaseUrl = process.env.ANTHROPIC_FOUNDRY_BASE_URL; if (foundryBaseUrl) { properties.push({ label: 'Microsoft Foundry base URL', value: foundryBaseUrl }); } const foundryResource = process.env.ANTHROPIC_FOUNDRY_RESOURCE; if (foundryResource) { properties.push({ label: 'Microsoft Foundry resource', value: foundryResource }); } if (isEnvTruthy(process.env.CLAUDE_CODE_SKIP_FOUNDRY_AUTH)) { properties.push({ value: 'Microsoft Foundry auth skipped' }); } } else if (apiProvider === 'openai') { const openaiBaseUrl = process.env.OPENAI_BASE_URL; if (openaiBaseUrl) { properties.push({ label: 'OpenAI base URL', value: redactSecretValueForDisplay(openaiBaseUrl, process.env) ?? openaiBaseUrl }); } const openaiModel = process.env.OPENAI_MODEL; if (openaiModel) { // Build display model string with resolved model + reasoning effort let modelDisplay = openaiModel; const resolved = resolveProviderRequest({ model: openaiModel }); const resolvedModel = resolved.resolvedModel; const reasoningEffort = resolved.reasoning?.effort; if (resolvedModel && resolvedModel !== openaiModel.toLowerCase()) { // Show resolved model name modelDisplay = resolvedModel; } if (reasoningEffort) { modelDisplay = `${modelDisplay} (${reasoningEffort})`; } properties.push({ label: 'Model', value: redactSecretValueForDisplay(modelDisplay, process.env) ?? modelDisplay }); } } else if (apiProvider === 'codex') { const codexBaseUrl = process.env.OPENAI_BASE_URL; if (codexBaseUrl) { properties.push({ label: 'Codex base URL', value: redactSecretValueForDisplay(codexBaseUrl, process.env) ?? codexBaseUrl }); } const openaiModel = process.env.OPENAI_MODEL; if (openaiModel) { // Build display model string with resolved model + reasoning effort let modelDisplay = openaiModel; const resolved = resolveProviderRequest({ model: openaiModel }); const resolvedModel = resolved.resolvedModel; const reasoningEffort = resolved.reasoning?.effort; if (resolvedModel && resolvedModel !== openaiModel.toLowerCase()) { // Show resolved model name modelDisplay = resolvedModel; } if (reasoningEffort) { modelDisplay = `${modelDisplay} (${reasoningEffort})`; } properties.push({ label: 'Model', value: redactSecretValueForDisplay(modelDisplay, process.env) ?? modelDisplay }); } } else if (apiProvider === 'gemini') { const geminiBaseUrl = process.env.GEMINI_BASE_URL; if (geminiBaseUrl) { properties.push({ label: 'Gemini base URL', value: redactSecretValueForDisplay(geminiBaseUrl, process.env) ?? geminiBaseUrl }); } const geminiModel = process.env.GEMINI_MODEL; if (geminiModel) { properties.push({ label: 'Model', value: redactSecretValueForDisplay(geminiModel, process.env) ?? geminiModel }); } } else if (apiProvider === 'mistral') { const mistralBaseUrl = process.env.MISTRAL_BASE_URL; if (mistralBaseUrl) { properties.push({ label: 'Mistral base URL', value: redactSecretValueForDisplay(mistralBaseUrl, process.env) ?? mistralBaseUrl }) } const mistralModel = process.env.MISTRAL_MODEL; if (mistralModel) { properties.push({ label: 'Model', value: redactSecretValueForDisplay(mistralModel, process.env) ?? mistralModel }) } } const proxyUrl = getProxyUrl(); if (proxyUrl) { properties.push({ label: 'Proxy', value: proxyUrl }); } const mtlsConfig = getMTLSConfig(); if (process.env.NODE_EXTRA_CA_CERTS) { properties.push({ label: 'Additional CA cert(s)', value: process.env.NODE_EXTRA_CA_CERTS }); } if (mtlsConfig) { if (mtlsConfig.cert && process.env.CLAUDE_CODE_CLIENT_CERT) { properties.push({ label: 'mTLS client cert', value: process.env.CLAUDE_CODE_CLIENT_CERT }); } if (mtlsConfig.key && process.env.CLAUDE_CODE_CLIENT_KEY) { properties.push({ label: 'mTLS client key', value: process.env.CLAUDE_CODE_CLIENT_KEY }); } } return properties; } export function getModelDisplayLabel(mainLoopModel: string | null): string { let modelLabel = modelDisplayString(mainLoopModel); if (mainLoopModel === null && isClaudeAISubscriber()) { const description = getClaudeAiUserDefaultModelDescription(); modelLabel = `${chalk.bold('Default')} ${description}`; } return modelLabel; }