mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-23 00:35: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,24 +1,29 @@
|
||||
import figures from 'figures';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { CommandResultDisplay } from '../../commands.js';
|
||||
import { Box, color, Link, Text, useTheme } from '../../ink.js';
|
||||
import { useKeybinding } from '../../keybindings/useKeybinding.js';
|
||||
import { AuthenticationCancelledError, performMCPOAuthFlow } from '../../services/mcp/auth.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 { Dialog } from '../design-system/Dialog.js';
|
||||
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js';
|
||||
import { Spinner } from '../Spinner.js';
|
||||
import type { AgentMcpServerInfo } from './types.js';
|
||||
import figures from 'figures'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import type { CommandResultDisplay } from '../../commands.js'
|
||||
import { Box, color, Link, Text, useTheme } from '../../ink.js'
|
||||
import { useKeybinding } from '../../keybindings/useKeybinding.js'
|
||||
import {
|
||||
AuthenticationCancelledError,
|
||||
performMCPOAuthFlow,
|
||||
} from '../../services/mcp/auth.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 { Dialog } from '../design-system/Dialog.js'
|
||||
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js'
|
||||
import { Spinner } from '../Spinner.js'
|
||||
import type { AgentMcpServerInfo } from './types.js'
|
||||
|
||||
type Props = {
|
||||
agentServer: AgentMcpServerInfo;
|
||||
onCancel: () => void;
|
||||
onComplete?: (result?: string, options?: {
|
||||
display?: CommandResultDisplay;
|
||||
}) => void;
|
||||
};
|
||||
agentServer: AgentMcpServerInfo
|
||||
onCancel: () => void
|
||||
onComplete?: (
|
||||
result?: string,
|
||||
options?: { display?: CommandResultDisplay },
|
||||
) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu for agent-specific MCP servers.
|
||||
@@ -28,113 +33,165 @@ type Props = {
|
||||
export function MCPAgentServerMenu({
|
||||
agentServer,
|
||||
onCancel,
|
||||
onComplete
|
||||
onComplete,
|
||||
}: Props): React.ReactNode {
|
||||
const [theme] = useTheme();
|
||||
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [authorizationUrl, setAuthorizationUrl] = useState<string | null>(null);
|
||||
const authAbortControllerRef = useRef<AbortController | null>(null);
|
||||
const [theme] = useTheme()
|
||||
const [isAuthenticating, setIsAuthenticating] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [authorizationUrl, setAuthorizationUrl] = useState<string | null>(null)
|
||||
const authAbortControllerRef = useRef<AbortController | null>(null)
|
||||
|
||||
// Abort OAuth flow on unmount so the callback server is closed even if a
|
||||
// parent component's Esc handler navigates away before ours fires.
|
||||
useEffect(() => () => authAbortControllerRef.current?.abort(), []);
|
||||
useEffect(() => () => authAbortControllerRef.current?.abort(), [])
|
||||
|
||||
// Handle ESC to cancel authentication flow
|
||||
const handleEscCancel = useCallback(() => {
|
||||
if (isAuthenticating) {
|
||||
authAbortControllerRef.current?.abort();
|
||||
authAbortControllerRef.current = null;
|
||||
setIsAuthenticating(false);
|
||||
setAuthorizationUrl(null);
|
||||
authAbortControllerRef.current?.abort()
|
||||
authAbortControllerRef.current = null
|
||||
setIsAuthenticating(false)
|
||||
setAuthorizationUrl(null)
|
||||
}
|
||||
}, [isAuthenticating]);
|
||||
}, [isAuthenticating])
|
||||
|
||||
useKeybinding('confirm:no', handleEscCancel, {
|
||||
context: 'Confirmation',
|
||||
isActive: isAuthenticating
|
||||
});
|
||||
isActive: isAuthenticating,
|
||||
})
|
||||
|
||||
const handleAuthenticate = useCallback(async () => {
|
||||
if (!agentServer.needsAuth || !agentServer.url) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
setIsAuthenticating(true);
|
||||
setError(null);
|
||||
const controller = new AbortController();
|
||||
authAbortControllerRef.current = controller;
|
||||
|
||||
setIsAuthenticating(true)
|
||||
setError(null)
|
||||
|
||||
const controller = new AbortController()
|
||||
authAbortControllerRef.current = controller
|
||||
|
||||
try {
|
||||
// Create a temporary config for OAuth
|
||||
const tempConfig = {
|
||||
type: agentServer.transport as 'http' | 'sse',
|
||||
url: agentServer.url
|
||||
};
|
||||
await performMCPOAuthFlow(agentServer.name, tempConfig, setAuthorizationUrl, controller.signal);
|
||||
onComplete?.(`Authentication successful for ${agentServer.name}. The server will connect when the agent runs.`);
|
||||
url: agentServer.url,
|
||||
}
|
||||
|
||||
await performMCPOAuthFlow(
|
||||
agentServer.name,
|
||||
tempConfig,
|
||||
setAuthorizationUrl,
|
||||
controller.signal,
|
||||
)
|
||||
|
||||
onComplete?.(
|
||||
`Authentication successful for ${agentServer.name}. The server will connect when the agent runs.`,
|
||||
)
|
||||
} catch (err) {
|
||||
// Don't show error if it was a cancellation
|
||||
if (err instanceof Error && !(err instanceof AuthenticationCancelledError)) {
|
||||
setError(err.message);
|
||||
if (
|
||||
err instanceof Error &&
|
||||
!(err instanceof AuthenticationCancelledError)
|
||||
) {
|
||||
setError(err.message)
|
||||
}
|
||||
} finally {
|
||||
setIsAuthenticating(false);
|
||||
authAbortControllerRef.current = null;
|
||||
setIsAuthenticating(false)
|
||||
authAbortControllerRef.current = null
|
||||
}
|
||||
}, [agentServer, onComplete]);
|
||||
const capitalizedServerName = capitalize(String(agentServer.name));
|
||||
}, [agentServer, onComplete])
|
||||
|
||||
const capitalizedServerName = capitalize(String(agentServer.name))
|
||||
|
||||
if (isAuthenticating) {
|
||||
return <Box flexDirection="column" gap={1} padding={1}>
|
||||
return (
|
||||
<Box flexDirection="column" gap={1} padding={1}>
|
||||
<Text color="claude">Authenticating with {agentServer.name}…</Text>
|
||||
<Box>
|
||||
<Spinner />
|
||||
<Text> A browser window will open for authentication</Text>
|
||||
</Box>
|
||||
{authorizationUrl && <Box flexDirection="column">
|
||||
{authorizationUrl && (
|
||||
<Box flexDirection="column">
|
||||
<Text dimColor>
|
||||
If your browser doesn't open automatically, copy this URL
|
||||
manually:
|
||||
</Text>
|
||||
<Link url={authorizationUrl} />
|
||||
</Box>}
|
||||
</Box>
|
||||
)}
|
||||
<Box marginLeft={3}>
|
||||
<Text dimColor>
|
||||
Return here after authenticating in your browser.{' '}
|
||||
<ConfigurableShortcutHint action="confirm:no" context="Confirmation" fallback="Esc" description="go back" />
|
||||
<ConfigurableShortcutHint
|
||||
action="confirm:no"
|
||||
context="Confirmation"
|
||||
fallback="Esc"
|
||||
description="go back"
|
||||
/>
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>;
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
const menuOptions = [];
|
||||
|
||||
const menuOptions = []
|
||||
|
||||
// Only show authenticate option for HTTP/SSE servers
|
||||
if (agentServer.needsAuth) {
|
||||
menuOptions.push({
|
||||
label: agentServer.isAuthenticated ? 'Re-authenticate' : 'Authenticate',
|
||||
value: 'auth'
|
||||
});
|
||||
value: 'auth',
|
||||
})
|
||||
}
|
||||
|
||||
menuOptions.push({
|
||||
label: 'Back',
|
||||
value: 'back'
|
||||
});
|
||||
return <Dialog title={`${capitalizedServerName} MCP Server`} subtitle="agent-only" onCancel={onCancel} inputGuide={exitState => exitState.pending ? <Text>Press {exitState.keyName} again to exit</Text> : <Byline>
|
||||
value: 'back',
|
||||
})
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={`${capitalizedServerName} MCP Server`}
|
||||
subtitle="agent-only"
|
||||
onCancel={onCancel}
|
||||
inputGuide={exitState =>
|
||||
exitState.pending ? (
|
||||
<Text>Press {exitState.keyName} again to exit</Text>
|
||||
) : (
|
||||
<Byline>
|
||||
<KeyboardShortcutHint shortcut="↑↓" action="navigate" />
|
||||
<KeyboardShortcutHint shortcut="Enter" action="confirm" />
|
||||
<ConfigurableShortcutHint action="confirm:no" context="Confirmation" fallback="Esc" description="go back" />
|
||||
</Byline>}>
|
||||
<ConfigurableShortcutHint
|
||||
action="confirm:no"
|
||||
context="Confirmation"
|
||||
fallback="Esc"
|
||||
description="go back"
|
||||
/>
|
||||
</Byline>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Box flexDirection="column" gap={0}>
|
||||
<Box>
|
||||
<Text bold>Type: </Text>
|
||||
<Text dimColor>{agentServer.transport}</Text>
|
||||
</Box>
|
||||
|
||||
{agentServer.url && <Box>
|
||||
{agentServer.url && (
|
||||
<Box>
|
||||
<Text bold>URL: </Text>
|
||||
<Text dimColor>{agentServer.url}</Text>
|
||||
</Box>}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{agentServer.command && <Box>
|
||||
{agentServer.command && (
|
||||
<Box>
|
||||
<Text bold>Command: </Text>
|
||||
<Text dimColor>{agentServer.command}</Text>
|
||||
</Box>}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box>
|
||||
<Text bold>Used by: </Text>
|
||||
@@ -149,34 +206,47 @@ export function MCPAgentServerMenu({
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{agentServer.needsAuth && <Box>
|
||||
{agentServer.needsAuth && (
|
||||
<Box>
|
||||
<Text bold>Auth: </Text>
|
||||
{agentServer.isAuthenticated ? <Text>{color('success', theme)(figures.tick)} authenticated</Text> : <Text>
|
||||
{agentServer.isAuthenticated ? (
|
||||
<Text>{color('success', theme)(figures.tick)} authenticated</Text>
|
||||
) : (
|
||||
<Text>
|
||||
{color('warning', theme)(figures.triangleUpOutline)} may need
|
||||
authentication
|
||||
</Text>}
|
||||
</Box>}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text dimColor>This server connects only when running the agent.</Text>
|
||||
</Box>
|
||||
|
||||
{error && <Box>
|
||||
{error && (
|
||||
<Box>
|
||||
<Text color="error">Error: {error}</Text>
|
||||
</Box>}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box>
|
||||
<Select options={menuOptions} onChange={async value => {
|
||||
switch (value) {
|
||||
case 'auth':
|
||||
await handleAuthenticate();
|
||||
break;
|
||||
case 'back':
|
||||
onCancel();
|
||||
break;
|
||||
}
|
||||
}} onCancel={onCancel} />
|
||||
<Select
|
||||
options={menuOptions}
|
||||
onChange={async value => {
|
||||
switch (value) {
|
||||
case 'auth':
|
||||
await handleAuthenticate()
|
||||
break
|
||||
case 'back':
|
||||
onCancel()
|
||||
break
|
||||
}
|
||||
}}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
</Box>
|
||||
</Dialog>;
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user