mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 14:25:51 +00:00
193 lines
5.5 KiB
TypeScript
193 lines
5.5 KiB
TypeScript
import figures from 'figures'
|
|
import * as React from 'react'
|
|
import { Suspense, use } from 'react'
|
|
import { getSessionId } from '../../bootstrap/state.js'
|
|
import type { LocalJSXCommandContext } from '../../commands.js'
|
|
import { useIsInsideModal } from '../../context/modalContext.js'
|
|
import { Box, Text, useTheme } from '@anthropic/ink'
|
|
import { type AppState, useAppState } from '../../state/AppState.js'
|
|
import { getCwd } from '../../utils/cwd.js'
|
|
import { getCurrentSessionTitle } from '../../utils/sessionStorage.js'
|
|
import {
|
|
buildAccountProperties,
|
|
buildAPIProviderProperties,
|
|
buildIDEProperties,
|
|
buildInstallationDiagnostics,
|
|
buildInstallationHealthDiagnostics,
|
|
buildMcpProperties,
|
|
buildMemoryDiagnostics,
|
|
buildSandboxProperties,
|
|
buildSettingSourcesProperties,
|
|
type Diagnostic,
|
|
getModelDisplayLabel,
|
|
type Property,
|
|
} from '../../utils/status.js'
|
|
import type { ThemeName } from '../../utils/theme.js'
|
|
import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js'
|
|
|
|
type Props = {
|
|
context: LocalJSXCommandContext
|
|
diagnosticsPromise: Promise<Diagnostic[]>
|
|
}
|
|
|
|
function buildPrimarySection(): Property[] {
|
|
const sessionId = getSessionId()
|
|
const customTitle = getCurrentSessionTitle(sessionId)
|
|
const nameValue = customTitle ?? <Text dimColor>/rename to add a name</Text>
|
|
|
|
return [
|
|
{ label: 'Version', value: MACRO.VERSION },
|
|
{ label: 'Session name', value: nameValue },
|
|
{ label: 'Session ID', value: sessionId },
|
|
{ label: 'cwd', value: getCwd() },
|
|
...buildAccountProperties(),
|
|
...buildAPIProviderProperties(),
|
|
]
|
|
}
|
|
|
|
function buildSecondarySection({
|
|
mainLoopModel,
|
|
mcp,
|
|
theme,
|
|
context,
|
|
}: {
|
|
mainLoopModel: AppState['mainLoopModel']
|
|
mcp: AppState['mcp']
|
|
theme: ThemeName
|
|
context: LocalJSXCommandContext
|
|
}): Property[] {
|
|
const modelLabel = getModelDisplayLabel(mainLoopModel)
|
|
|
|
return [
|
|
{ label: 'Model', value: modelLabel },
|
|
...buildIDEProperties(
|
|
mcp.clients,
|
|
context.options.ideInstallationStatus,
|
|
theme,
|
|
),
|
|
...buildMcpProperties(mcp.clients, theme),
|
|
...buildSandboxProperties(),
|
|
...buildSettingSourcesProperties(),
|
|
]
|
|
}
|
|
|
|
export async function buildDiagnostics(): Promise<Diagnostic[]> {
|
|
return [
|
|
...(await buildInstallationDiagnostics()),
|
|
...(await buildInstallationHealthDiagnostics()),
|
|
...(await buildMemoryDiagnostics()),
|
|
]
|
|
}
|
|
|
|
function PropertyValue({
|
|
value,
|
|
}: {
|
|
value: Property['value']
|
|
}): React.ReactNode {
|
|
if (Array.isArray(value)) {
|
|
return (
|
|
<Box flexWrap="wrap" columnGap={1} flexShrink={99}>
|
|
{value.map((item, i) => {
|
|
return (
|
|
<Text key={i}>
|
|
{item}
|
|
{i < value.length - 1 ? ',' : ''}
|
|
</Text>
|
|
)
|
|
})}
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
if (typeof value === 'string') {
|
|
return <Text>{value}</Text>
|
|
}
|
|
|
|
return value
|
|
}
|
|
|
|
export function Status({
|
|
context,
|
|
diagnosticsPromise,
|
|
}: Props): React.ReactNode {
|
|
const mainLoopModel = useAppState(s => s.mainLoopModel)
|
|
const mcp = useAppState(s => s.mcp)
|
|
const [theme] = useTheme()
|
|
|
|
// Sections are synchronous — compute in render so they're never empty.
|
|
// diagnosticsPromise is created once in Settings.tsx so it resolves once
|
|
// per pane invocation instead of re-fetching on every tab switch (Tab
|
|
// unmounts children when not selected, which was causing the flash).
|
|
const sections = React.useMemo(
|
|
() => [
|
|
buildPrimarySection(),
|
|
buildSecondarySection({ mainLoopModel, mcp, theme, context }),
|
|
],
|
|
[mainLoopModel, mcp, theme, context],
|
|
)
|
|
|
|
// flexGrow so the "Esc to cancel" footer pins to the bottom of the
|
|
// Modal's inner ScrollBox when content is short. The ScrollBox content
|
|
// wrapper has flexGrow:1 (fills at least the viewport), so this stretches
|
|
// to match. Without it, short Status content floats at the top and the
|
|
// footer sits mid-modal with 2-3 trailing blank rows below. Outside a
|
|
// Modal (non-fullscreen), leave layout alone — no ScrollBox to fill.
|
|
const grow = useIsInsideModal() ? 1 : undefined
|
|
|
|
return (
|
|
<Box flexDirection="column" flexGrow={grow}>
|
|
<Box flexDirection="column" gap={1} flexGrow={grow}>
|
|
{sections.map(
|
|
(properties, i) =>
|
|
properties.length > 0 && (
|
|
<Box key={i} flexDirection="column">
|
|
{properties.map(({ label, value }, j) => (
|
|
<Box key={j} flexDirection="row" gap={1} flexShrink={0}>
|
|
{label !== undefined && <Text bold>{label}:</Text>}
|
|
<PropertyValue value={value} />
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
),
|
|
)}
|
|
|
|
<Suspense fallback={null}>
|
|
<Diagnostics promise={diagnosticsPromise} />
|
|
</Suspense>
|
|
</Box>
|
|
<Text dimColor>
|
|
<ConfigurableShortcutHint
|
|
action="confirm:no"
|
|
context="Settings"
|
|
fallback="Esc"
|
|
description="cancel"
|
|
/>
|
|
</Text>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
function Diagnostics({
|
|
promise,
|
|
}: {
|
|
promise: Promise<Diagnostic[]>
|
|
}): React.ReactNode {
|
|
const diagnostics = use(promise)
|
|
if (diagnostics.length === 0) return null
|
|
return (
|
|
<Box flexDirection="column" paddingBottom={1}>
|
|
<Text bold>System Diagnostics</Text>
|
|
{diagnostics.map((diagnostic, i) => (
|
|
<Box key={i} flexDirection="row" gap={1} paddingX={1}>
|
|
<Text color="error">{figures.warning}</Text>
|
|
{typeof diagnostic === 'string' ? (
|
|
<Text wrap="wrap">{diagnostic}</Text>
|
|
) : (
|
|
diagnostic
|
|
)}
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
)
|
|
}
|