// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered import { Box, Text } from '@anthropic/ink' import * as React from 'react' import { getLargeMemoryFiles, MAX_MEMORY_CHARACTER_COUNT, type MemoryFileInfo, } from './claudemd.js' import figures from 'figures' import { getCwd } from './cwd.js' import { relative } from 'path' import { formatNumber } from './format.js' import type { getGlobalConfig } from './config.js' import { getAnthropicApiKeyWithSource, getApiKeyFromConfigOrMacOSKeychain, getAuthTokenSource, isClaudeAISubscriber, } from './auth.js' import type { AgentDefinitionsResult } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js' import { getAgentDescriptionsTotalTokens, AGENT_DESCRIPTIONS_THRESHOLD, } from './statusNoticeHelpers.js' import { isSupportedJetBrainsTerminal, toIDEDisplayName, getTerminalIdeType, } from './ide.js' import { isJetBrainsPluginInstalledCachedSync } from './jetbrains.js' // Types export type StatusNoticeType = 'warning' | 'info' export type StatusNoticeContext = { config: ReturnType agentDefinitions?: AgentDefinitionsResult memoryFiles: MemoryFileInfo[] } export type StatusNoticeDefinition = { id: string type: StatusNoticeType isActive: (context: StatusNoticeContext) => boolean render: (context: StatusNoticeContext) => React.ReactNode } // Individual notice definitions const largeMemoryFilesNotice: StatusNoticeDefinition = { id: 'large-memory-files', type: 'warning', isActive: ctx => getLargeMemoryFiles(ctx.memoryFiles).length > 0, render: ctx => { const largeMemoryFiles = getLargeMemoryFiles(ctx.memoryFiles) return ( <> {largeMemoryFiles.map(file => { const displayPath = file.path.startsWith(getCwd()) ? relative(getCwd(), file.path) : file.path return ( {figures.warning} Large {displayPath} will impact performance ( {formatNumber(file.content.length)} chars >{' '} {formatNumber(MAX_MEMORY_CHARACTER_COUNT)}) · /memory to edit ) })} ) }, } const claudeAiSubscriberExternalTokenNotice: StatusNoticeDefinition = { id: 'claude-ai-external-token', type: 'warning', isActive: () => { const authTokenInfo = getAuthTokenSource() return ( isClaudeAISubscriber() && (authTokenInfo.source === 'ANTHROPIC_AUTH_TOKEN' || authTokenInfo.source === 'apiKeyHelper') ) }, render: () => { const authTokenInfo = getAuthTokenSource() return ( {figures.warning} Auth conflict: Using {authTokenInfo.source} instead of Claude account subscription token. Either unset {authTokenInfo.source}, or run `claude /logout`. ) }, } const apiKeyConflictNotice: StatusNoticeDefinition = { id: 'api-key-conflict', type: 'warning', isActive: () => { const { source: apiKeySource } = getAnthropicApiKeyWithSource({ skipRetrievingKeyFromApiKeyHelper: true, }) return ( !!getApiKeyFromConfigOrMacOSKeychain() && (apiKeySource === 'ANTHROPIC_API_KEY' || apiKeySource === 'apiKeyHelper') ) }, render: () => { const { source: apiKeySource } = getAnthropicApiKeyWithSource({ skipRetrievingKeyFromApiKeyHelper: true, }) return ( {figures.warning} Auth conflict: Using {apiKeySource} instead of Anthropic Console key. Either unset {apiKeySource}, or run `claude /logout`. ) }, } const bothAuthMethodsNotice: StatusNoticeDefinition = { id: 'both-auth-methods', type: 'warning', isActive: () => { const { source: apiKeySource } = getAnthropicApiKeyWithSource({ skipRetrievingKeyFromApiKeyHelper: true, }) const authTokenInfo = getAuthTokenSource() return ( apiKeySource !== 'none' && authTokenInfo.source !== 'none' && !( apiKeySource === 'apiKeyHelper' && authTokenInfo.source === 'apiKeyHelper' ) ) }, render: () => { const { source: apiKeySource } = getAnthropicApiKeyWithSource({ skipRetrievingKeyFromApiKeyHelper: true, }) const authTokenInfo = getAuthTokenSource() return ( {figures.warning} Auth conflict: Both a token ({authTokenInfo.source}) and an API key ({apiKeySource}) are set. This may lead to unexpected behavior. · Trying to use{' '} {authTokenInfo.source === 'claude.ai' ? 'claude.ai' : authTokenInfo.source} ?{' '} {apiKeySource === 'ANTHROPIC_API_KEY' ? 'Unset the ANTHROPIC_API_KEY environment variable, or claude /logout then say "No" to the API key approval before login.' : apiKeySource === 'apiKeyHelper' ? 'Unset the apiKeyHelper setting.' : 'claude /logout'} · Trying to use {apiKeySource}?{' '} {authTokenInfo.source === 'claude.ai' ? 'claude /logout to sign out of claude.ai.' : `Unset the ${authTokenInfo.source} environment variable.`} ) }, } const largeAgentDescriptionsNotice: StatusNoticeDefinition = { id: 'large-agent-descriptions', type: 'warning', isActive: context => { const totalTokens = getAgentDescriptionsTotalTokens( context.agentDefinitions, ) return totalTokens > AGENT_DESCRIPTIONS_THRESHOLD }, render: context => { const totalTokens = getAgentDescriptionsTotalTokens( context.agentDefinitions, ) return ( {figures.warning} Large cumulative agent descriptions will impact performance (~ {formatNumber(totalTokens)} tokens >{' '} {formatNumber(AGENT_DESCRIPTIONS_THRESHOLD)}) · /agents to manage ) }, } const jetbrainsPluginNotice: StatusNoticeDefinition = { id: 'jetbrains-plugin-install', type: 'info', isActive: context => { // Only show if running in JetBrains built-in terminal if (!isSupportedJetBrainsTerminal()) { return false } // Don't show if auto-install is disabled const shouldAutoInstall = context.config.autoInstallIdeExtension ?? true if (!shouldAutoInstall) { return false } // Check if plugin is already installed (cached to avoid repeated filesystem checks) const ideType = getTerminalIdeType() return ideType !== null && !isJetBrainsPluginInstalledCachedSync(ideType) }, render: () => { const ideType = getTerminalIdeType() const ideName = toIDEDisplayName(ideType) return ( {figures.arrowUp} Install the {ideName} plugin from the JetBrains Marketplace:{' '} https://docs.claude.com/s/claude-code-jetbrains ) }, } // All notice definitions export const statusNoticeDefinitions: StatusNoticeDefinition[] = [ largeMemoryFilesNotice, largeAgentDescriptionsNotice, claudeAiSubscriberExternalTokenNotice, apiKeyConflictNotice, bothAuthMethodsNotice, jetbrainsPluginNotice, ] // Helper functions for external use export function getActiveNotices( context: StatusNoticeContext, ): StatusNoticeDefinition[] { return statusNoticeDefinitions.filter(notice => notice.isActive(context)) }