mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-22 00:05:51 +00:00
更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)
* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-2): 格式化 commands (79 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-5): 格式化 components其余 + hooks + tools (232 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md - README.md: 大幅重写,更详细版本历史和配置示例 - Run.ps1: 新增 Windows 启动脚本 - TODO.md: 新增包完成清单 - V6.md: 删除(架构重构规划已不适用) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复以前的问题 * fix: 修复 login 面板的问题 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,92 +1,116 @@
|
||||
import figures from 'figures';
|
||||
import React, { useState } from 'react';
|
||||
import type { CommandResultDisplay } from '../../commands.js';
|
||||
import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js';
|
||||
import { Box, color, Text, useTheme } from '../../ink.js';
|
||||
import { getMcpConfigByName } from '../../services/mcp/config.js';
|
||||
import { useMcpReconnect, useMcpToggleEnabled } from '../../services/mcp/MCPConnectionManager.js';
|
||||
import { describeMcpConfigFilePath, filterMcpPromptsByServer } from '../../services/mcp/utils.js';
|
||||
import { useAppState } from '../../state/AppState.js';
|
||||
import { errorMessage } from '../../utils/errors.js';
|
||||
import { capitalize } from '../../utils/stringUtils.js';
|
||||
import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js';
|
||||
import { Select } from '../CustomSelect/index.js';
|
||||
import { Byline } from '../design-system/Byline.js';
|
||||
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js';
|
||||
import { Spinner } from '../Spinner.js';
|
||||
import { CapabilitiesSection } from './CapabilitiesSection.js';
|
||||
import type { StdioServerInfo } from './types.js';
|
||||
import { handleReconnectError, handleReconnectResult } from './utils/reconnectHelpers.js';
|
||||
import figures from 'figures'
|
||||
import React, { useState } from 'react'
|
||||
import type { CommandResultDisplay } from '../../commands.js'
|
||||
import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js'
|
||||
import { Box, color, Text, useTheme } from '../../ink.js'
|
||||
import { getMcpConfigByName } from '../../services/mcp/config.js'
|
||||
import {
|
||||
useMcpReconnect,
|
||||
useMcpToggleEnabled,
|
||||
} from '../../services/mcp/MCPConnectionManager.js'
|
||||
import {
|
||||
describeMcpConfigFilePath,
|
||||
filterMcpPromptsByServer,
|
||||
} from '../../services/mcp/utils.js'
|
||||
import { useAppState } from '../../state/AppState.js'
|
||||
import { errorMessage } from '../../utils/errors.js'
|
||||
import { capitalize } from '../../utils/stringUtils.js'
|
||||
import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js'
|
||||
import { Select } from '../CustomSelect/index.js'
|
||||
import { Byline } from '../design-system/Byline.js'
|
||||
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js'
|
||||
import { Spinner } from '../Spinner.js'
|
||||
import { CapabilitiesSection } from './CapabilitiesSection.js'
|
||||
import type { StdioServerInfo } from './types.js'
|
||||
import {
|
||||
handleReconnectError,
|
||||
handleReconnectResult,
|
||||
} from './utils/reconnectHelpers.js'
|
||||
|
||||
type Props = {
|
||||
server: StdioServerInfo;
|
||||
serverToolsCount: number;
|
||||
onViewTools: () => void;
|
||||
onCancel: () => void;
|
||||
onComplete: (result?: string, options?: {
|
||||
display?: CommandResultDisplay;
|
||||
}) => void;
|
||||
borderless?: boolean;
|
||||
};
|
||||
server: StdioServerInfo
|
||||
serverToolsCount: number
|
||||
onViewTools: () => void
|
||||
onCancel: () => void
|
||||
onComplete: (
|
||||
result?: string,
|
||||
options?: { display?: CommandResultDisplay },
|
||||
) => void
|
||||
borderless?: boolean
|
||||
}
|
||||
|
||||
export function MCPStdioServerMenu({
|
||||
server,
|
||||
serverToolsCount,
|
||||
onViewTools,
|
||||
onCancel,
|
||||
onComplete,
|
||||
borderless = false
|
||||
borderless = false,
|
||||
}: Props): React.ReactNode {
|
||||
const [theme] = useTheme();
|
||||
const exitState = useExitOnCtrlCDWithKeybindings();
|
||||
const mcp = useAppState(s => s.mcp);
|
||||
const reconnectMcpServer = useMcpReconnect();
|
||||
const toggleMcpServer = useMcpToggleEnabled();
|
||||
const [isReconnecting, setIsReconnecting] = useState(false);
|
||||
const [theme] = useTheme()
|
||||
const exitState = useExitOnCtrlCDWithKeybindings()
|
||||
const mcp = useAppState(s => s.mcp)
|
||||
const reconnectMcpServer = useMcpReconnect()
|
||||
const toggleMcpServer = useMcpToggleEnabled()
|
||||
const [isReconnecting, setIsReconnecting] = useState(false)
|
||||
|
||||
const handleToggleEnabled = React.useCallback(async () => {
|
||||
const wasEnabled = server.client.type !== 'disabled';
|
||||
const wasEnabled = server.client.type !== 'disabled'
|
||||
|
||||
try {
|
||||
await toggleMcpServer(server.name);
|
||||
await toggleMcpServer(server.name)
|
||||
// Return to the server list so user can continue managing other servers
|
||||
onCancel();
|
||||
onCancel()
|
||||
} catch (err) {
|
||||
const action = wasEnabled ? 'disable' : 'enable';
|
||||
onComplete(`Failed to ${action} MCP server '${server.name}': ${errorMessage(err)}`);
|
||||
const action = wasEnabled ? 'disable' : 'enable'
|
||||
onComplete(
|
||||
`Failed to ${action} MCP server '${server.name}': ${errorMessage(err)}`,
|
||||
)
|
||||
}
|
||||
}, [server.client.type, server.name, toggleMcpServer, onCancel, onComplete]);
|
||||
const capitalizedServerName = capitalize(String(server.name));
|
||||
}, [server.client.type, server.name, toggleMcpServer, onCancel, onComplete])
|
||||
|
||||
const capitalizedServerName = capitalize(String(server.name))
|
||||
|
||||
// Count MCP prompts for this server (skills are shown in /skills, not here)
|
||||
const serverCommandsCount = filterMcpPromptsByServer(mcp.commands, server.name).length;
|
||||
const menuOptions = [];
|
||||
const serverCommandsCount = filterMcpPromptsByServer(
|
||||
mcp.commands,
|
||||
server.name,
|
||||
).length
|
||||
|
||||
const menuOptions = []
|
||||
|
||||
// Only show "View tools" if server is not disabled and has tools
|
||||
if (server.client.type !== 'disabled' && serverToolsCount > 0) {
|
||||
menuOptions.push({
|
||||
label: 'View tools',
|
||||
value: 'tools'
|
||||
});
|
||||
value: 'tools',
|
||||
})
|
||||
}
|
||||
|
||||
// Only show reconnect option if the server is not disabled
|
||||
if (server.client.type !== 'disabled') {
|
||||
menuOptions.push({
|
||||
label: 'Reconnect',
|
||||
value: 'reconnectMcpServer'
|
||||
});
|
||||
value: 'reconnectMcpServer',
|
||||
})
|
||||
}
|
||||
|
||||
menuOptions.push({
|
||||
label: server.client.type !== 'disabled' ? 'Disable' : 'Enable',
|
||||
value: 'toggle-enabled'
|
||||
});
|
||||
value: 'toggle-enabled',
|
||||
})
|
||||
|
||||
// If there are no other options, add a back option so Select handles escape
|
||||
if (menuOptions.length === 0) {
|
||||
menuOptions.push({
|
||||
label: 'Back',
|
||||
value: 'back'
|
||||
});
|
||||
value: 'back',
|
||||
})
|
||||
}
|
||||
|
||||
if (isReconnecting) {
|
||||
return <Box flexDirection="column" gap={1} padding={1}>
|
||||
return (
|
||||
<Box flexDirection="column" gap={1} padding={1}>
|
||||
<Text color="text">
|
||||
Reconnecting to <Text bold>{server.name}</Text>
|
||||
</Text>
|
||||
@@ -95,10 +119,17 @@ export function MCPStdioServerMenu({
|
||||
<Text> Restarting MCP server process</Text>
|
||||
</Box>
|
||||
<Text dimColor>This may take a few moments.</Text>
|
||||
</Box>;
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
return <Box flexDirection="column">
|
||||
<Box flexDirection="column" paddingX={1} borderStyle={borderless ? undefined : 'round'}>
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Box
|
||||
flexDirection="column"
|
||||
paddingX={1}
|
||||
borderStyle={borderless ? undefined : 'round'}
|
||||
>
|
||||
<Box marginBottom={1}>
|
||||
<Text bold>{capitalizedServerName} MCP Server</Text>
|
||||
</Box>
|
||||
@@ -106,10 +137,18 @@ export function MCPStdioServerMenu({
|
||||
<Box flexDirection="column" gap={0}>
|
||||
<Box>
|
||||
<Text bold>Status: </Text>
|
||||
{server.client.type === 'disabled' ? <Text>{color('inactive', theme)(figures.radioOff)} disabled</Text> : server.client.type === 'connected' ? <Text>{color('success', theme)(figures.tick)} connected</Text> : server.client.type === 'pending' ? <>
|
||||
{server.client.type === 'disabled' ? (
|
||||
<Text>{color('inactive', theme)(figures.radioOff)} disabled</Text>
|
||||
) : server.client.type === 'connected' ? (
|
||||
<Text>{color('success', theme)(figures.tick)} connected</Text>
|
||||
) : server.client.type === 'pending' ? (
|
||||
<>
|
||||
<Text dimColor>{figures.radioOff}</Text>
|
||||
<Text> connecting…</Text>
|
||||
</> : <Text>{color('error', theme)(figures.cross)} failed</Text>}
|
||||
</>
|
||||
) : (
|
||||
<Text>{color('error', theme)(figures.cross)} failed</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
@@ -117,60 +156,89 @@ export function MCPStdioServerMenu({
|
||||
<Text dimColor>{server.config.command}</Text>
|
||||
</Box>
|
||||
|
||||
{server.config.args && server.config.args.length > 0 && <Box>
|
||||
{server.config.args && server.config.args.length > 0 && (
|
||||
<Box>
|
||||
<Text bold>Args: </Text>
|
||||
<Text dimColor>{server.config.args.join(' ')}</Text>
|
||||
</Box>}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box>
|
||||
<Text bold>Config location: </Text>
|
||||
<Text dimColor>
|
||||
{describeMcpConfigFilePath(getMcpConfigByName(server.name)?.scope ?? 'dynamic')}
|
||||
{describeMcpConfigFilePath(
|
||||
getMcpConfigByName(server.name)?.scope ?? 'dynamic',
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{server.client.type === 'connected' && <CapabilitiesSection serverToolsCount={serverToolsCount} serverPromptsCount={serverCommandsCount} serverResourcesCount={mcp.resources[server.name]?.length || 0} />}
|
||||
{server.client.type === 'connected' && (
|
||||
<CapabilitiesSection
|
||||
serverToolsCount={serverToolsCount}
|
||||
serverPromptsCount={serverCommandsCount}
|
||||
serverResourcesCount={mcp.resources[server.name]?.length || 0}
|
||||
/>
|
||||
)}
|
||||
|
||||
{server.client.type === 'connected' && serverToolsCount > 0 && <Box>
|
||||
{server.client.type === 'connected' && serverToolsCount > 0 && (
|
||||
<Box>
|
||||
<Text bold>Tools: </Text>
|
||||
<Text dimColor>{serverToolsCount} tools</Text>
|
||||
</Box>}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{menuOptions.length > 0 && <Box marginTop={1}>
|
||||
<Select options={menuOptions} onChange={async value => {
|
||||
if (value === 'tools') {
|
||||
onViewTools();
|
||||
} else if (value === 'reconnectMcpServer') {
|
||||
setIsReconnecting(true);
|
||||
try {
|
||||
const result = await reconnectMcpServer(server.name);
|
||||
const {
|
||||
message
|
||||
} = handleReconnectResult(result, server.name);
|
||||
onComplete?.(message);
|
||||
} catch (err_0) {
|
||||
onComplete?.(handleReconnectError(err_0, server.name));
|
||||
} finally {
|
||||
setIsReconnecting(false);
|
||||
}
|
||||
} else if (value === 'toggle-enabled') {
|
||||
await handleToggleEnabled();
|
||||
} else if (value === 'back') {
|
||||
onCancel();
|
||||
}
|
||||
}} onCancel={onCancel} />
|
||||
</Box>}
|
||||
{menuOptions.length > 0 && (
|
||||
<Box marginTop={1}>
|
||||
<Select
|
||||
options={menuOptions}
|
||||
onChange={async value => {
|
||||
if (value === 'tools') {
|
||||
onViewTools()
|
||||
} else if (value === 'reconnectMcpServer') {
|
||||
setIsReconnecting(true)
|
||||
try {
|
||||
const result = await reconnectMcpServer(server.name)
|
||||
const { message } = handleReconnectResult(
|
||||
result,
|
||||
server.name,
|
||||
)
|
||||
onComplete?.(message)
|
||||
} catch (err) {
|
||||
onComplete?.(handleReconnectError(err, server.name))
|
||||
} finally {
|
||||
setIsReconnecting(false)
|
||||
}
|
||||
} else if (value === 'toggle-enabled') {
|
||||
await handleToggleEnabled()
|
||||
} else if (value === 'back') {
|
||||
onCancel()
|
||||
}
|
||||
}}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box marginTop={1}>
|
||||
<Text dimColor italic>
|
||||
{exitState.pending ? <>Press {exitState.keyName} again to exit</> : <Byline>
|
||||
{exitState.pending ? (
|
||||
<>Press {exitState.keyName} again to exit</>
|
||||
) : (
|
||||
<Byline>
|
||||
<KeyboardShortcutHint shortcut="↑↓" action="navigate" />
|
||||
<KeyboardShortcutHint shortcut="Enter" action="select" />
|
||||
<ConfigurableShortcutHint action="confirm:no" context="Confirmation" fallback="Esc" description="back" />
|
||||
</Byline>}
|
||||
<ConfigurableShortcutHint
|
||||
action="confirm:no"
|
||||
context="Confirmation"
|
||||
fallback="Esc"
|
||||
description="back"
|
||||
/>
|
||||
</Byline>
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>;
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user