mirror of
http://10.0.2.1:3031/sauer/claude-code.git
synced 2026-06-30 14:16:58 +10:00
198 lines
30 KiB
TypeScript
198 lines
30 KiB
TypeScript
|
|
// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
|
||
|
|
import { Box, Text } from '../ink.js';
|
||
|
|
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 '../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<typeof getGlobalConfig>;
|
||
|
|
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 <Box key={file.path} flexDirection="row">
|
||
|
|
<Text color="warning">{figures.warning}</Text>
|
||
|
|
<Text color="warning">
|
||
|
|
Large <Text bold>{displayPath}</Text> will impact performance (
|
||
|
|
{formatNumber(file.content.length)} chars >{' '}
|
||
|
|
{formatNumber(MAX_MEMORY_CHARACTER_COUNT)})
|
||
|
|
<Text dimColor> · /memory to edit</Text>
|
||
|
|
</Text>
|
||
|
|
</Box>;
|
||
|
|
})}
|
||
|
|
</>;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
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 <Box flexDirection="row" marginTop={1}>
|
||
|
|
<Text color="warning">{figures.warning}</Text>
|
||
|
|
<Text color="warning">
|
||
|
|
Auth conflict: Using {authTokenInfo.source} instead of Claude account
|
||
|
|
subscription token. Either unset {authTokenInfo.source}, or run
|
||
|
|
`claude /logout`.
|
||
|
|
</Text>
|
||
|
|
</Box>;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
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 <Box flexDirection="row" marginTop={1}>
|
||
|
|
<Text color="warning">{figures.warning}</Text>
|
||
|
|
<Text color="warning">
|
||
|
|
Auth conflict: Using {apiKeySource} instead of Anthropic Console key.
|
||
|
|
Either unset {apiKeySource}, or run `claude /logout`.
|
||
|
|
</Text>
|
||
|
|
</Box>;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
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 <Box flexDirection="column" marginTop={1}>
|
||
|
|
<Box flexDirection="row">
|
||
|
|
<Text color="warning">{figures.warning}</Text>
|
||
|
|
<Text color="warning">
|
||
|
|
Auth conflict: Both a token ({authTokenInfo.source}) and an API key
|
||
|
|
({apiKeySource}) are set. This may lead to unexpected behavior.
|
||
|
|
</Text>
|
||
|
|
</Box>
|
||
|
|
<Box flexDirection="column" marginLeft={3}>
|
||
|
|
<Text color="warning">
|
||
|
|
· 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'}
|
||
|
|
</Text>
|
||
|
|
<Text color="warning">
|
||
|
|
· Trying to use {apiKeySource}?{' '}
|
||
|
|
{authTokenInfo.source === 'claude.ai' ? 'claude /logout to sign out of claude.ai.' : `Unset the ${authTokenInfo.source} environment variable.`}
|
||
|
|
</Text>
|
||
|
|
</Box>
|
||
|
|
</Box>;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
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 <Box flexDirection="row">
|
||
|
|
<Text color="warning">{figures.warning}</Text>
|
||
|
|
<Text color="warning">
|
||
|
|
Large cumulative agent descriptions will impact performance (~
|
||
|
|
{formatNumber(totalTokens)} tokens >{' '}
|
||
|
|
{formatNumber(AGENT_DESCRIPTIONS_THRESHOLD)})
|
||
|
|
<Text dimColor> · /agents to manage</Text>
|
||
|
|
</Text>
|
||
|
|
</Box>;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
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 <Box flexDirection="row" gap={1} marginLeft={1}>
|
||
|
|
<Text color="ide">{figures.arrowUp}</Text>
|
||
|
|
<Text>
|
||
|
|
Install the <Text color="ide">{ideName}</Text> plugin from the
|
||
|
|
JetBrains Marketplace:{' '}
|
||
|
|
<Text bold>https://docs.claude.com/s/claude-code-jetbrains</Text>
|
||
|
|
</Text>
|
||
|
|
</Box>;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// 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));
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJCb3giLCJUZXh0IiwiUmVhY3QiLCJnZXRMYXJnZU1lbW9yeUZpbGVzIiwiTUFYX01FTU9SWV9DSEFSQUNURVJfQ09VTlQiLCJNZW1vcnlGaWxlSW5mbyIsImZpZ3VyZXMiLCJnZXRDd2QiLCJyZWxhdGl2ZSIsImZvcm1hdE51bWJlciIsImdldEdsb2JhbENvbmZpZyIsImdldEFudGhyb3BpY0FwaUtleVdpdGhTb3VyY2UiLCJnZXRBcGlLZXlGcm9tQ29uZmlnT3JNYWNPU0tleWNoYWluIiwiZ2V0QXV0aFRva2VuU291cmNlIiwiaXNDbGF1ZGVBSVN1YnNjcmliZXIiLCJBZ2VudERlZmluaXRpb25zUmVzdWx0IiwiZ2V0QWdlbnREZXNjcmlwdGlvbnNUb3RhbFRva2VucyIsIkFHRU5UX0RFU0NSSVBUSU9OU19USFJFU0hPTEQiLCJpc1N1cHBvcnRlZEpldEJyYWluc1Rlcm1pbmFsIiwidG9JREVEaXNwbGF5TmFtZSIsImdldFRlcm1pbmFsSWRlVHlwZSIsImlzSmV0QnJhaW5zUGx1Z2luSW5zdGFsbGVkQ2FjaGVkU3luYyIsIlN0YXR1c05vdGljZVR5cGUiLCJTdGF0dXNOb3RpY2VDb250ZXh0IiwiY29uZmlnIiwiUmV0dXJuVHlwZSIsImFnZW50RGVmaW5pdGlvbnMiLCJtZW1vcnlGaWxlcyIsIlN0YXR1c05vdGljZURlZmluaXRpb24iLCJpZCIsInR5cGUiLCJpc0FjdGl2ZSIsImNvbnRleHQiLCJyZW5kZXIiLCJSZWFjdE5vZGUiLCJsYXJnZU1lbW9yeUZpbGVzTm90aWNlIiwiY3R4IiwibGVuZ3RoIiwibGFyZ2VNZW1vcnlGaWxlcyIsIm1hcCIsImZpbGUiLCJkaXNwbGF5UGF0aCIsInBhdGgiLCJzdGFydHNXaXRoIiwid2FybmluZyIsImNvbnRlbnQiLCJjbGF1ZGVBaVN1YnNjcmliZXJFeHRlcm5hbFRva2VuTm90aWNlIiwiYXV0aFRva2VuSW5mbyIsInNvdXJjZSIsImFwaUtleUNvbmZsaWN0Tm90aWNlIiwiYXBpS2V5U291cmNlIiwic2tpcFJldHJpZXZpbmdLZXlGcm9tQXBpS2V5SGVscGVyIiwiYm90aEF1dGhNZXRob2RzTm90aWNlIiwibGFyZ2VBZ2VudERlc2NyaXB0aW9uc05vdGljZSIsInRvdGFsVG9rZW5zIiwiamV0YnJhaW5zUGx1Z2luTm90aWNlIiwic2hvdWxkQXV0b0luc3RhbGwiLCJhdXRvSW5zdGFsbElkZUV4dGVuc2lvbiIsImlkZVR5cGUiLCJpZGVOYW1lIiwiYXJyb3dVcCIsInN0YXR1c05vdGljZURlZmluaXRpb25zIiwiZ2V0QWN0aXZlTm90aWNlcyIsImZpbHRlciIsIm5vdGljZSJdLCJzb3VyY2VzIjpbInN0YXR1c05vdGljZURlZmluaXRpb25zLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyBiaW9tZS1pZ25vcmUtYWxsIGFzc2lzdC9zb3VyY2Uvb3JnYW5pemVJbXBvcnRzOiBBTlQtT05MWSBpbXBvcnQgbWFya2VycyBtdXN0IG5vdCBiZSByZW9yZGVyZWRcbmltcG9ydCB7IEJveCwgVGV4dCB9IGZyb20gJy4uL2luay5qcydcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHtcbiAgZ2V0TGFyZ2VNZW1vcnlGaWxlcyxcbiAgTUFYX01FTU9SWV9DSEFSQUNURVJfQ09VTlQsXG4gIHR5cGUgTWVtb3J5RmlsZUluZm8sXG59IGZyb20gJy4vY2xhdWRlbWQuanMnXG5pbXBvcnQgZmlndXJlcyBmcm9tICdmaWd1cmVzJ1xuaW1wb3J0IHsgZ2V0Q3dkIH0gZnJvbSAnLi9jd2QuanMnXG5pbXBvcnQgeyByZWxhdGl2ZSB9IGZyb20gJ3BhdGgnXG5pbXBvcnQgeyBmb3JtYXROdW1iZXIgfSBmcm9tICcuL2Zvcm1hdC5qcydcbmltcG9ydCB0eXBlIHsgZ2V0R2xvYmFsQ29uZmlnIH0gZnJvbSAnLi9jb25maWcuanMnXG5pbXBvcnQge1xuICBnZXRBbnRocm9waWNBcGlLZXlXaXRoU291cmNlLFxuICBnZXRBcGlLZXlGcm9tQ29uZmlnT3JNYWNPU0tleWNoYWluLFxuICBnZXRBdXRoVG9rZW5Tb3VyY2UsXG4gIGlzQ2xhdWRlQUlTdWJzY3JpYmVyLFxufSBmcm9tICcuL2F1dGguanMnXG5pbXBvcnQgdHlwZSB7IEFnZW50RGVmaW5pdGlvbnNSZXN1bHQgfSBmcm9tICcuLi90b29scy9BZ2VudFRvb2wvbG9hZEFnZW50c0Rpci5qcydcbmltcG9ydCB7XG4gIGdldEFnZW50RGVzY3JpcHRpb25zVG90YWxUb2tlbnMsXG4gIEFHRU5UX0RFU0NSSVBUSU9OU19USFJFU0hPTEQsXG59IGZyb20gJy4vc3RhdHVzTm90aWNlSGVscGVycy5qcydcbmltcG9ydCB7XG4gIGlzU3VwcG9ydGVkSmV0QnJhaW5zVGVybWluYWwsXG4gIHRvSURFRGlzcGxheU5hbWUsXG4gIGdldFRlcm1pbmFsSWRlVHlwZSxcbn0gZnJvbSAnLi9pZGUuanMnXG5pbXBvcnQgeyBpc0pldEJyYWluc1BsdWdpbkluc3RhbGxlZENhY2hlZFN5bmMgfSBmcm9tICcuL2pldGJyYWlucy5qcydcblxuLy8gVHlwZXNcbmV4cG9ydCB0eXBlIFN0YXR1c05vdGljZVR5cGUgPSAnd2FybmluZycgfCAnaW5mbydcblxuZXhwb3J0IHR5cGUgU3RhdHVzTm90aWNlQ29udGV4dCA9IHtcbiAgY29uZmlnOiBSZXR1cm5UeXBlPHR5cGVvZiBnZXRHbG9iYWxDb25maWc+XG4gIGFnZW50RGVmaW5pdGlvbnM/OiBBZ2VudERlZmluaXRpb25zUmVzdWx0XG4gIG1lbW9yeUZpbGVzOiBNZW1vcnlGaWxlSW5mb1tdXG59XG5cbmV4cG9ydCB0eXBlIFN0YXR1c05vdGljZURlZmluaXRpb24gPSB7XG4gIGlkOiBzdHJpbmdcbiAgdHlwZTogU3RhdHVzTm90aWNlVHlwZVxuICBpc0FjdGl2ZTogKGNvbnRleHQ6IFN0YXR1c05vdGljZUNvbnRleHQpID0+IGJvb2xlYW5cbiAgcmVuZGVyOiAoY29udGV4dDogU3RhdHVzTm90aWNlQ29udGV4dCkgPT4gUmVhY3QuUmVhY3ROb2RlXG59XG5cbi8vIEluZGl2aWR1YWwgbm90aWNlIGRlZmluaXRpb25zXG5jb25zdCBsYXJnZU1lbW9yeUZpbGVzTm90aWNlOiBTdGF0dXNOb3RpY2VEZWZpbml0aW9uID0ge1xuICBpZDogJ2xhcmdlLW1lbW9yeS1maWxlcycsXG4gIHR5cGU6ICd3YXJuaW5nJyxcbiAgaXNBY3RpdmU6IGN0eCA9PiBnZXRMYXJnZU1lbW9yeUZpbGVzKGN0eC5tZW1vcnlGaWxlcykubGVuZ3RoID4gMCxcbiAgcmVuZGVyOiBjdHggPT4ge1xuICAgIGNvbnN0IGxhcmdlTWVtb3J5RmlsZXMgPSBnZXRMYXJnZU1lbW9yeUZpbGVzKGN0eC5tZW1vcnlGaWxlcylcbiAgICByZXR1cm4gKFxuICAgICAgPD5cbiAgICAgICAge2xhcmdlTWVtb3J5RmlsZXMubWFwKGZpbGUgPT4ge1xuICAgICAgICA
|