mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-19 06:45:50 +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,68 +1,96 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from 'src/services/analytics/index.js';
|
||||
import { setupTerminal, shouldOfferTerminalSetup } from '../commands/terminalSetup/terminalSetup.js';
|
||||
import { useExitOnCtrlCDWithKeybindings } from '../hooks/useExitOnCtrlCDWithKeybindings.js';
|
||||
import { Box, Link, Newline, Text, useTheme } from '../ink.js';
|
||||
import { useKeybindings } from '../keybindings/useKeybinding.js';
|
||||
import { isAnthropicAuthEnabled } from '../utils/auth.js';
|
||||
import { normalizeApiKeyForConfig } from '../utils/authPortable.js';
|
||||
import { getCustomApiKeyStatus } from '../utils/config.js';
|
||||
import { env } from '../utils/env.js';
|
||||
import { isRunningOnHomespace } from '../utils/envUtils.js';
|
||||
import { PreflightStep } from '../utils/preflightChecks.js';
|
||||
import type { ThemeSetting } from '../utils/theme.js';
|
||||
import { ApproveApiKey } from './ApproveApiKey.js';
|
||||
import { ConsoleOAuthFlow } from './ConsoleOAuthFlow.js';
|
||||
import { Select } from './CustomSelect/select.js';
|
||||
import { WelcomeV2 } from './LogoV2/WelcomeV2.js';
|
||||
import { PressEnterToContinue } from './PressEnterToContinue.js';
|
||||
import { ThemePicker } from './ThemePicker.js';
|
||||
import { OrderedList } from './ui/OrderedList.js';
|
||||
type StepId = 'preflight' | 'theme' | 'oauth' | 'api-key' | 'security' | 'terminal-setup';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
logEvent,
|
||||
} from 'src/services/analytics/index.js'
|
||||
import {
|
||||
setupTerminal,
|
||||
shouldOfferTerminalSetup,
|
||||
} from '../commands/terminalSetup/terminalSetup.js'
|
||||
import { useExitOnCtrlCDWithKeybindings } from '../hooks/useExitOnCtrlCDWithKeybindings.js'
|
||||
import { Box, Link, Newline, Text, useTheme } from '../ink.js'
|
||||
import { useKeybindings } from '../keybindings/useKeybinding.js'
|
||||
import { isAnthropicAuthEnabled } from '../utils/auth.js'
|
||||
import { normalizeApiKeyForConfig } from '../utils/authPortable.js'
|
||||
import { getCustomApiKeyStatus } from '../utils/config.js'
|
||||
import { env } from '../utils/env.js'
|
||||
import { isRunningOnHomespace } from '../utils/envUtils.js'
|
||||
import { PreflightStep } from '../utils/preflightChecks.js'
|
||||
import type { ThemeSetting } from '../utils/theme.js'
|
||||
import { ApproveApiKey } from './ApproveApiKey.js'
|
||||
import { ConsoleOAuthFlow } from './ConsoleOAuthFlow.js'
|
||||
import { Select } from './CustomSelect/select.js'
|
||||
import { WelcomeV2 } from './LogoV2/WelcomeV2.js'
|
||||
import { PressEnterToContinue } from './PressEnterToContinue.js'
|
||||
import { ThemePicker } from './ThemePicker.js'
|
||||
import { OrderedList } from './ui/OrderedList.js'
|
||||
|
||||
type StepId =
|
||||
| 'preflight'
|
||||
| 'theme'
|
||||
| 'oauth'
|
||||
| 'api-key'
|
||||
| 'security'
|
||||
| 'terminal-setup'
|
||||
|
||||
interface OnboardingStep {
|
||||
id: StepId;
|
||||
component: React.ReactNode;
|
||||
id: StepId
|
||||
component: React.ReactNode
|
||||
}
|
||||
|
||||
type Props = {
|
||||
onDone(): void;
|
||||
};
|
||||
export function Onboarding({
|
||||
onDone
|
||||
}: Props): React.ReactNode {
|
||||
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
||||
const [skipOAuth, setSkipOAuth] = useState(false);
|
||||
const [oauthEnabled] = useState(() => isAnthropicAuthEnabled());
|
||||
const [theme, setTheme] = useTheme();
|
||||
onDone(): void
|
||||
}
|
||||
|
||||
export function Onboarding({ onDone }: Props): React.ReactNode {
|
||||
const [currentStepIndex, setCurrentStepIndex] = useState(0)
|
||||
const [skipOAuth, setSkipOAuth] = useState(false)
|
||||
const [oauthEnabled] = useState(() => isAnthropicAuthEnabled())
|
||||
const [theme, setTheme] = useTheme()
|
||||
|
||||
useEffect(() => {
|
||||
logEvent('tengu_began_setup', {
|
||||
oauthEnabled
|
||||
});
|
||||
}, [oauthEnabled]);
|
||||
oauthEnabled,
|
||||
})
|
||||
}, [oauthEnabled])
|
||||
|
||||
function goToNextStep() {
|
||||
if (currentStepIndex < steps.length - 1) {
|
||||
const nextIndex = currentStepIndex + 1;
|
||||
setCurrentStepIndex(nextIndex);
|
||||
const nextIndex = currentStepIndex + 1
|
||||
setCurrentStepIndex(nextIndex)
|
||||
|
||||
logEvent('tengu_onboarding_step', {
|
||||
oauthEnabled,
|
||||
stepId: steps[nextIndex]?.id as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
||||
});
|
||||
stepId: steps[nextIndex]
|
||||
?.id as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
})
|
||||
} else {
|
||||
onDone();
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
|
||||
function handleThemeSelection(newTheme: ThemeSetting) {
|
||||
setTheme(newTheme);
|
||||
goToNextStep();
|
||||
setTheme(newTheme)
|
||||
goToNextStep()
|
||||
}
|
||||
const exitState = useExitOnCtrlCDWithKeybindings();
|
||||
|
||||
const exitState = useExitOnCtrlCDWithKeybindings()
|
||||
|
||||
// Define all onboarding steps
|
||||
const themeStep = <Box marginX={1}>
|
||||
<ThemePicker onThemeSelect={handleThemeSelection} showIntroText={true} helpText="To change this later, run /theme" hideEscToCancel={true} skipExitHandling={true} // Skip exit handling as Onboarding already handles it
|
||||
/>
|
||||
</Box>;
|
||||
const securityStep = <Box flexDirection="column" gap={1} paddingLeft={1}>
|
||||
const themeStep = (
|
||||
<Box marginX={1}>
|
||||
<ThemePicker
|
||||
onThemeSelect={handleThemeSelection}
|
||||
showIntroText={true}
|
||||
helpText="To change this later, run /theme"
|
||||
hideEscToCancel={true}
|
||||
skipExitHandling={true} // Skip exit handling as Onboarding already handles it
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
||||
const securityStep = (
|
||||
<Box flexDirection="column" gap={1} paddingLeft={1}>
|
||||
<Text bold>Security notes:</Text>
|
||||
<Box flexDirection="column" width={70}>
|
||||
{/**
|
||||
@@ -92,152 +120,182 @@ export function Onboarding({
|
||||
</OrderedList>
|
||||
</Box>
|
||||
<PressEnterToContinue />
|
||||
</Box>;
|
||||
const preflightStep = <PreflightStep onSuccess={goToNextStep} />;
|
||||
</Box>
|
||||
)
|
||||
|
||||
const preflightStep = <PreflightStep onSuccess={goToNextStep} />
|
||||
// Create the steps array - determine which steps to include based on reAuth and oauthEnabled
|
||||
const apiKeyNeedingApproval = useMemo(() => {
|
||||
// Add API key step if needed
|
||||
// On homespace, ANTHROPIC_API_KEY is preserved in process.env for child
|
||||
// processes but ignored by Claude Code itself (see auth.ts).
|
||||
if (!process.env.ANTHROPIC_API_KEY || isRunningOnHomespace()) {
|
||||
return '';
|
||||
return ''
|
||||
}
|
||||
const customApiKeyTruncated = normalizeApiKeyForConfig(process.env.ANTHROPIC_API_KEY);
|
||||
const customApiKeyTruncated = normalizeApiKeyForConfig(
|
||||
process.env.ANTHROPIC_API_KEY,
|
||||
)
|
||||
if (getCustomApiKeyStatus(customApiKeyTruncated) === 'new') {
|
||||
return customApiKeyTruncated;
|
||||
return customApiKeyTruncated
|
||||
}
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
function handleApiKeyDone(approved: boolean) {
|
||||
if (approved) {
|
||||
setSkipOAuth(true);
|
||||
setSkipOAuth(true)
|
||||
}
|
||||
goToNextStep();
|
||||
goToNextStep()
|
||||
}
|
||||
const steps: OnboardingStep[] = [];
|
||||
|
||||
const steps: OnboardingStep[] = []
|
||||
if (oauthEnabled) {
|
||||
steps.push({
|
||||
id: 'preflight',
|
||||
component: preflightStep
|
||||
});
|
||||
steps.push({ id: 'preflight', component: preflightStep })
|
||||
}
|
||||
steps.push({
|
||||
id: 'theme',
|
||||
component: themeStep
|
||||
});
|
||||
steps.push({ id: 'theme', component: themeStep })
|
||||
|
||||
if (apiKeyNeedingApproval) {
|
||||
steps.push({
|
||||
id: 'api-key',
|
||||
component: <ApproveApiKey customApiKeyTruncated={apiKeyNeedingApproval} onDone={handleApiKeyDone} />
|
||||
});
|
||||
component: (
|
||||
<ApproveApiKey
|
||||
customApiKeyTruncated={apiKeyNeedingApproval}
|
||||
onDone={handleApiKeyDone}
|
||||
/>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
if (oauthEnabled) {
|
||||
steps.push({
|
||||
id: 'oauth',
|
||||
component: <SkippableStep skip={skipOAuth} onSkip={goToNextStep}>
|
||||
component: (
|
||||
<SkippableStep skip={skipOAuth} onSkip={goToNextStep}>
|
||||
<ConsoleOAuthFlow onDone={goToNextStep} />
|
||||
</SkippableStep>
|
||||
});
|
||||
),
|
||||
})
|
||||
}
|
||||
steps.push({
|
||||
id: 'security',
|
||||
component: securityStep
|
||||
});
|
||||
|
||||
steps.push({ id: 'security', component: securityStep })
|
||||
|
||||
if (shouldOfferTerminalSetup()) {
|
||||
steps.push({
|
||||
id: 'terminal-setup',
|
||||
component: <Box flexDirection="column" gap={1} paddingLeft={1}>
|
||||
component: (
|
||||
<Box flexDirection="column" gap={1} paddingLeft={1}>
|
||||
<Text bold>Use Claude Code's terminal setup?</Text>
|
||||
<Box flexDirection="column" width={70} gap={1}>
|
||||
<Text>
|
||||
For the optimal coding experience, enable the recommended settings
|
||||
<Newline />
|
||||
for your terminal:{' '}
|
||||
{env.terminal === 'Apple_Terminal' ? 'Option+Enter for newlines and visual bell' : 'Shift+Enter for newlines'}
|
||||
{env.terminal === 'Apple_Terminal'
|
||||
? 'Option+Enter for newlines and visual bell'
|
||||
: 'Shift+Enter for newlines'}
|
||||
</Text>
|
||||
<Select options={[{
|
||||
label: 'Yes, use recommended settings',
|
||||
value: 'install'
|
||||
}, {
|
||||
label: 'No, maybe later with /terminal-setup',
|
||||
value: 'no'
|
||||
}]} onChange={value => {
|
||||
if (value === 'install') {
|
||||
// Errors already logged in setupTerminal, just swallow and proceed
|
||||
void setupTerminal(theme).catch(() => {}).finally(goToNextStep);
|
||||
} else {
|
||||
goToNextStep();
|
||||
}
|
||||
}} onCancel={() => goToNextStep()} />
|
||||
<Select
|
||||
options={[
|
||||
{
|
||||
label: 'Yes, use recommended settings',
|
||||
value: 'install',
|
||||
},
|
||||
{
|
||||
label: 'No, maybe later with /terminal-setup',
|
||||
value: 'no',
|
||||
},
|
||||
]}
|
||||
onChange={value => {
|
||||
if (value === 'install') {
|
||||
// Errors already logged in setupTerminal, just swallow and proceed
|
||||
void setupTerminal(theme)
|
||||
.catch(() => {})
|
||||
.finally(goToNextStep)
|
||||
} else {
|
||||
goToNextStep()
|
||||
}
|
||||
}}
|
||||
onCancel={() => goToNextStep()}
|
||||
/>
|
||||
<Text dimColor>
|
||||
{exitState.pending ? <>Press {exitState.keyName} again to exit</> : <>Enter to confirm · Esc to skip</>}
|
||||
{exitState.pending ? (
|
||||
<>Press {exitState.keyName} again to exit</>
|
||||
) : (
|
||||
<>Enter to confirm · Esc to skip</>
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
});
|
||||
),
|
||||
})
|
||||
}
|
||||
const currentStep = steps[currentStepIndex];
|
||||
|
||||
const currentStep = steps[currentStepIndex]
|
||||
|
||||
// Handle Enter on security step and Escape on terminal-setup step
|
||||
// Dependencies match what goToNextStep uses internally
|
||||
const handleSecurityContinue = useCallback(() => {
|
||||
if (currentStepIndex === steps.length - 1) {
|
||||
onDone();
|
||||
onDone()
|
||||
} else {
|
||||
goToNextStep();
|
||||
goToNextStep()
|
||||
}
|
||||
}, [currentStepIndex, steps.length, oauthEnabled, onDone]);
|
||||
}, [currentStepIndex, steps.length, oauthEnabled, onDone])
|
||||
|
||||
const handleTerminalSetupSkip = useCallback(() => {
|
||||
goToNextStep();
|
||||
}, [currentStepIndex, steps.length, oauthEnabled, onDone]);
|
||||
useKeybindings({
|
||||
'confirm:yes': handleSecurityContinue
|
||||
}, {
|
||||
context: 'Confirmation',
|
||||
isActive: currentStep?.id === 'security'
|
||||
});
|
||||
useKeybindings({
|
||||
'confirm:no': handleTerminalSetupSkip
|
||||
}, {
|
||||
context: 'Confirmation',
|
||||
isActive: currentStep?.id === 'terminal-setup'
|
||||
});
|
||||
return <Box flexDirection="column">
|
||||
goToNextStep()
|
||||
}, [currentStepIndex, steps.length, oauthEnabled, onDone])
|
||||
|
||||
useKeybindings(
|
||||
{
|
||||
'confirm:yes': handleSecurityContinue,
|
||||
},
|
||||
{
|
||||
context: 'Confirmation',
|
||||
isActive: currentStep?.id === 'security',
|
||||
},
|
||||
)
|
||||
|
||||
useKeybindings(
|
||||
{
|
||||
'confirm:no': handleTerminalSetupSkip,
|
||||
},
|
||||
{
|
||||
context: 'Confirmation',
|
||||
isActive: currentStep?.id === 'terminal-setup',
|
||||
},
|
||||
)
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<WelcomeV2 />
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
{currentStep?.component}
|
||||
{exitState.pending && <Box padding={1}>
|
||||
{exitState.pending && (
|
||||
<Box padding={1}>
|
||||
<Text dimColor>Press {exitState.keyName} again to exit</Text>
|
||||
</Box>}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>;
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
export function SkippableStep(t0) {
|
||||
const $ = _c(4);
|
||||
const {
|
||||
skip,
|
||||
onSkip,
|
||||
children
|
||||
} = t0;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== onSkip || $[1] !== skip) {
|
||||
t1 = () => {
|
||||
if (skip) {
|
||||
onSkip();
|
||||
}
|
||||
};
|
||||
t2 = [skip, onSkip];
|
||||
$[0] = onSkip;
|
||||
$[1] = skip;
|
||||
$[2] = t1;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
t2 = $[3];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
|
||||
export function SkippableStep({
|
||||
skip,
|
||||
onSkip,
|
||||
children,
|
||||
}: {
|
||||
skip: boolean
|
||||
onSkip(): void
|
||||
children: React.ReactNode
|
||||
}): React.ReactNode {
|
||||
useEffect(() => {
|
||||
if (skip) {
|
||||
onSkip()
|
||||
}
|
||||
}, [skip, onSkip])
|
||||
if (skip) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
return children;
|
||||
return children
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user