mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-23 08: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,50 +1,61 @@
|
||||
import { relative } from 'path';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useDiffInIDE } from '../../../hooks/useDiffInIDE.js';
|
||||
import { Box, Text } from '../../../ink.js';
|
||||
import type { ToolUseContext } from '../../../Tool.js';
|
||||
import { getLanguageName } from '../../../utils/cliHighlight.js';
|
||||
import { getCwd } from '../../../utils/cwd.js';
|
||||
import { getFsImplementation, safeResolvePath } from '../../../utils/fsOperations.js';
|
||||
import { expandPath } from '../../../utils/path.js';
|
||||
import type { CompletionType } from '../../../utils/unaryLogging.js';
|
||||
import { Select } from '../../CustomSelect/index.js';
|
||||
import { ShowInIDEPrompt } from '../../ShowInIDEPrompt.js';
|
||||
import { usePermissionRequestLogging } from '../hooks.js';
|
||||
import { PermissionDialog } from '../PermissionDialog.js';
|
||||
import type { ToolUseConfirm } from '../PermissionRequest.js';
|
||||
import type { WorkerBadgeProps } from '../WorkerBadge.js';
|
||||
import type { IDEDiffSupport } from './ideDiffConfig.js';
|
||||
import type { FileOperationType, PermissionOption } from './permissionOptions.js';
|
||||
import { type ToolInput, useFilePermissionDialog } from './useFilePermissionDialog.js';
|
||||
import { relative } from 'path'
|
||||
import React, { useMemo } from 'react'
|
||||
import { useDiffInIDE } from '../../../hooks/useDiffInIDE.js'
|
||||
import { Box, Text } from '../../../ink.js'
|
||||
import type { ToolUseContext } from '../../../Tool.js'
|
||||
import { getLanguageName } from '../../../utils/cliHighlight.js'
|
||||
import { getCwd } from '../../../utils/cwd.js'
|
||||
import {
|
||||
getFsImplementation,
|
||||
safeResolvePath,
|
||||
} from '../../../utils/fsOperations.js'
|
||||
import { expandPath } from '../../../utils/path.js'
|
||||
import type { CompletionType } from '../../../utils/unaryLogging.js'
|
||||
import { Select } from '../../CustomSelect/index.js'
|
||||
import { ShowInIDEPrompt } from '../../ShowInIDEPrompt.js'
|
||||
import { usePermissionRequestLogging } from '../hooks.js'
|
||||
import { PermissionDialog } from '../PermissionDialog.js'
|
||||
import type { ToolUseConfirm } from '../PermissionRequest.js'
|
||||
import type { WorkerBadgeProps } from '../WorkerBadge.js'
|
||||
import type { IDEDiffSupport } from './ideDiffConfig.js'
|
||||
import type {
|
||||
FileOperationType,
|
||||
PermissionOption,
|
||||
} from './permissionOptions.js'
|
||||
import {
|
||||
type ToolInput,
|
||||
useFilePermissionDialog,
|
||||
} from './useFilePermissionDialog.js'
|
||||
|
||||
export type FilePermissionDialogProps<T extends ToolInput = ToolInput> = {
|
||||
// Required props from PermissionRequestProps
|
||||
toolUseConfirm: ToolUseConfirm;
|
||||
toolUseContext: ToolUseContext;
|
||||
onDone: () => void;
|
||||
onReject: () => void;
|
||||
toolUseConfirm: ToolUseConfirm
|
||||
toolUseContext: ToolUseContext
|
||||
onDone: () => void
|
||||
onReject: () => void
|
||||
|
||||
// Dialog customization
|
||||
title: string;
|
||||
subtitle?: React.ReactNode;
|
||||
question?: string | React.ReactNode;
|
||||
content?: React.ReactNode; // Can be general content or diff component
|
||||
title: string
|
||||
subtitle?: React.ReactNode
|
||||
question?: string | React.ReactNode
|
||||
content?: React.ReactNode // Can be general content or diff component
|
||||
|
||||
// Logging
|
||||
completionType?: CompletionType;
|
||||
languageName?: string; // override — derived from path when omitted
|
||||
completionType?: CompletionType
|
||||
languageName?: string // override — derived from path when omitted
|
||||
|
||||
// File/directory operations
|
||||
path: string | null;
|
||||
parseInput: (input: unknown) => T;
|
||||
operationType?: FileOperationType;
|
||||
path: string | null
|
||||
parseInput: (input: unknown) => T
|
||||
operationType?: FileOperationType
|
||||
|
||||
// IDE diff support
|
||||
ideDiffSupport?: IDEDiffSupport<T>;
|
||||
ideDiffSupport?: IDEDiffSupport<T>
|
||||
|
||||
// Worker badge for teammate permission requests
|
||||
workerBadge: WorkerBadgeProps | undefined;
|
||||
};
|
||||
workerBadge: WorkerBadgeProps | undefined
|
||||
}
|
||||
|
||||
export function FilePermissionDialog<T extends ToolInput = ToolInput>({
|
||||
toolUseConfirm,
|
||||
toolUseContext,
|
||||
@@ -60,33 +71,38 @@ export function FilePermissionDialog<T extends ToolInput = ToolInput>({
|
||||
operationType = 'write',
|
||||
ideDiffSupport,
|
||||
workerBadge,
|
||||
languageName: languageNameOverride
|
||||
languageName: languageNameOverride,
|
||||
}: FilePermissionDialogProps<T>): React.ReactNode {
|
||||
// Derive from path unless caller provided an explicit override (NotebookEdit
|
||||
// passes 'python'/'markdown' from cell_type). getLanguageName is async;
|
||||
// downstream UnaryEvent.language_name and logPermissionEvent already accept
|
||||
// Promise<string>. useMemo keeps the promise stable across renders.
|
||||
const languageName = useMemo(() => languageNameOverride ?? (path ? getLanguageName(path) : 'none'), [languageNameOverride, path]);
|
||||
const unaryEvent = useMemo(() => ({
|
||||
completion_type: completionType,
|
||||
language_name: languageName
|
||||
}), [completionType, languageName]);
|
||||
usePermissionRequestLogging(toolUseConfirm, unaryEvent);
|
||||
const languageName = useMemo(
|
||||
() => languageNameOverride ?? (path ? getLanguageName(path) : 'none'),
|
||||
[languageNameOverride, path],
|
||||
)
|
||||
const unaryEvent = useMemo(
|
||||
() => ({
|
||||
completion_type: completionType,
|
||||
language_name: languageName,
|
||||
}),
|
||||
[completionType, languageName],
|
||||
)
|
||||
usePermissionRequestLogging(toolUseConfirm, unaryEvent)
|
||||
|
||||
const symlinkTarget = useMemo(() => {
|
||||
if (!path || operationType === 'read') {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
const expandedPath = expandPath(path);
|
||||
const fs = getFsImplementation();
|
||||
const {
|
||||
resolvedPath,
|
||||
isSymlink
|
||||
} = safeResolvePath(fs, expandedPath);
|
||||
const expandedPath = expandPath(path)
|
||||
const fs = getFsImplementation()
|
||||
const { resolvedPath, isSymlink } = safeResolvePath(fs, expandedPath)
|
||||
if (isSymlink) {
|
||||
return resolvedPath;
|
||||
return resolvedPath
|
||||
}
|
||||
return null;
|
||||
}, [path, operationType]);
|
||||
return null
|
||||
}, [path, operationType])
|
||||
|
||||
const fileDialogResult = useFilePermissionDialog({
|
||||
filePath: path || '',
|
||||
completionType,
|
||||
@@ -95,8 +111,8 @@ export function FilePermissionDialog<T extends ToolInput = ToolInput>({
|
||||
onDone,
|
||||
onReject,
|
||||
parseInput,
|
||||
operationType
|
||||
});
|
||||
operationType,
|
||||
})
|
||||
|
||||
// Use file dialog results for options
|
||||
const {
|
||||
@@ -107,97 +123,150 @@ export function FilePermissionDialog<T extends ToolInput = ToolInput>({
|
||||
handleInputModeToggle,
|
||||
focusedOption,
|
||||
yesInputMode,
|
||||
noInputMode
|
||||
} = fileDialogResult;
|
||||
noInputMode,
|
||||
} = fileDialogResult
|
||||
|
||||
// Parse input using the provided parser
|
||||
const parsedInput = parseInput(toolUseConfirm.input);
|
||||
const parsedInput = parseInput(toolUseConfirm.input)
|
||||
|
||||
// Set up IDE diff support if enabled. Memoized: getConfig may do disk I/O
|
||||
// (FileWrite's getConfig calls readFileSync for the old-content diff).
|
||||
// Keyed on the raw input — parseInput is a pure Zod parse whose result
|
||||
// depends only on toolUseConfirm.input.
|
||||
const ideDiffConfig = useMemo(() => ideDiffSupport ? ideDiffSupport.getConfig(parseInput(toolUseConfirm.input)) : null, [ideDiffSupport, toolUseConfirm.input]);
|
||||
const ideDiffConfig = useMemo(
|
||||
() =>
|
||||
ideDiffSupport
|
||||
? ideDiffSupport.getConfig(parseInput(toolUseConfirm.input))
|
||||
: null,
|
||||
[ideDiffSupport, toolUseConfirm.input],
|
||||
)
|
||||
|
||||
// Create diff params based on whether IDE diff is available
|
||||
const diffParams = ideDiffConfig ? {
|
||||
onChange: (option: PermissionOption, input: {
|
||||
file_path: string;
|
||||
edits: Array<{
|
||||
old_string: string;
|
||||
new_string: string;
|
||||
replace_all?: boolean;
|
||||
}>;
|
||||
}) => {
|
||||
const transformedInput = ideDiffSupport!.applyChanges(parsedInput, input.edits);
|
||||
fileDialogResult.onChange(option, transformedInput);
|
||||
},
|
||||
toolUseContext,
|
||||
filePath: ideDiffConfig.filePath,
|
||||
edits: (ideDiffConfig.edits || []).map(e => ({
|
||||
old_string: e.old_string,
|
||||
new_string: e.new_string,
|
||||
replace_all: e.replace_all || false
|
||||
})),
|
||||
editMode: ideDiffConfig.editMode || 'single'
|
||||
} : {
|
||||
onChange: () => {},
|
||||
toolUseContext,
|
||||
filePath: '',
|
||||
edits: [],
|
||||
editMode: 'single' as const
|
||||
};
|
||||
const {
|
||||
closeTabInIDE,
|
||||
showingDiffInIDE,
|
||||
ideName
|
||||
} = useDiffInIDE(diffParams);
|
||||
const onChange = (option_0: PermissionOption, feedback?: string) => {
|
||||
closeTabInIDE?.();
|
||||
fileDialogResult.onChange(option_0, parsedInput, feedback?.trim());
|
||||
};
|
||||
if (showingDiffInIDE && ideDiffConfig && path) {
|
||||
return <ShowInIDEPrompt onChange={(option_1: PermissionOption, _input, feedback_0?: string) => onChange(option_1, feedback_0)} options={options} filePath={path} input={parsedInput} ideName={ideName} symlinkTarget={symlinkTarget} rejectFeedback={rejectFeedback} acceptFeedback={acceptFeedback} setFocusedOption={setFocusedOption} onInputModeToggle={handleInputModeToggle} focusedOption={focusedOption} yesInputMode={yesInputMode} noInputMode={noInputMode} />;
|
||||
const diffParams = ideDiffConfig
|
||||
? {
|
||||
onChange: (
|
||||
option: PermissionOption,
|
||||
input: {
|
||||
file_path: string
|
||||
edits: Array<{
|
||||
old_string: string
|
||||
new_string: string
|
||||
replace_all?: boolean
|
||||
}>
|
||||
},
|
||||
) => {
|
||||
const transformedInput = ideDiffSupport!.applyChanges(
|
||||
parsedInput,
|
||||
input.edits,
|
||||
)
|
||||
fileDialogResult.onChange(option, transformedInput)
|
||||
},
|
||||
toolUseContext,
|
||||
filePath: ideDiffConfig.filePath,
|
||||
edits: (ideDiffConfig.edits || []).map(e => ({
|
||||
old_string: e.old_string,
|
||||
new_string: e.new_string,
|
||||
replace_all: e.replace_all || false,
|
||||
})),
|
||||
editMode: ideDiffConfig.editMode || 'single',
|
||||
}
|
||||
: {
|
||||
onChange: () => {},
|
||||
toolUseContext,
|
||||
filePath: '',
|
||||
edits: [],
|
||||
editMode: 'single' as const,
|
||||
}
|
||||
|
||||
const { closeTabInIDE, showingDiffInIDE, ideName } = useDiffInIDE(diffParams)
|
||||
|
||||
const onChange = (option: PermissionOption, feedback?: string) => {
|
||||
closeTabInIDE?.()
|
||||
fileDialogResult.onChange(option, parsedInput, feedback?.trim())
|
||||
}
|
||||
const isSymlinkOutsideCwd = symlinkTarget != null && relative(getCwd(), symlinkTarget).startsWith('..');
|
||||
const symlinkWarning = symlinkTarget ? <Box paddingX={1} marginBottom={1}>
|
||||
|
||||
if (showingDiffInIDE && ideDiffConfig && path) {
|
||||
return (
|
||||
<ShowInIDEPrompt
|
||||
onChange={(option: PermissionOption, _input, feedback?: string) =>
|
||||
onChange(option, feedback)
|
||||
}
|
||||
options={options}
|
||||
filePath={path}
|
||||
input={parsedInput}
|
||||
ideName={ideName}
|
||||
symlinkTarget={symlinkTarget}
|
||||
rejectFeedback={rejectFeedback}
|
||||
acceptFeedback={acceptFeedback}
|
||||
setFocusedOption={setFocusedOption}
|
||||
onInputModeToggle={handleInputModeToggle}
|
||||
focusedOption={focusedOption}
|
||||
yesInputMode={yesInputMode}
|
||||
noInputMode={noInputMode}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const isSymlinkOutsideCwd =
|
||||
symlinkTarget != null && relative(getCwd(), symlinkTarget).startsWith('..')
|
||||
|
||||
const symlinkWarning = symlinkTarget ? (
|
||||
<Box paddingX={1} marginBottom={1}>
|
||||
<Text color="warning">
|
||||
{isSymlinkOutsideCwd ? `This will modify ${symlinkTarget} (outside working directory) via a symlink` : `Symlink target: ${symlinkTarget}`}
|
||||
{isSymlinkOutsideCwd
|
||||
? `This will modify ${symlinkTarget} (outside working directory) via a symlink`
|
||||
: `Symlink target: ${symlinkTarget}`}
|
||||
</Text>
|
||||
</Box> : null;
|
||||
return <>
|
||||
<PermissionDialog title={title} subtitle={subtitle} innerPaddingX={0} workerBadge={workerBadge}>
|
||||
</Box>
|
||||
) : null
|
||||
|
||||
return (
|
||||
<>
|
||||
<PermissionDialog
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
innerPaddingX={0}
|
||||
workerBadge={workerBadge}
|
||||
>
|
||||
{symlinkWarning}
|
||||
{content}
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
{typeof question === 'string' ? <Text>{question}</Text> : question}
|
||||
<Select options={options} inlineDescriptions onChange={value => {
|
||||
const selected = options.find(opt => opt.value === value);
|
||||
if (selected) {
|
||||
// For reject option
|
||||
if (selected.option.type === 'reject') {
|
||||
const trimmedFeedback = rejectFeedback.trim();
|
||||
onChange(selected.option, trimmedFeedback || undefined);
|
||||
return;
|
||||
}
|
||||
// For accept-once option, pass accept feedback if present
|
||||
if (selected.option.type === 'accept-once') {
|
||||
const trimmedFeedback_0 = acceptFeedback.trim();
|
||||
onChange(selected.option, trimmedFeedback_0 || undefined);
|
||||
return;
|
||||
}
|
||||
onChange(selected.option);
|
||||
}
|
||||
}} onCancel={() => onChange({
|
||||
type: 'reject'
|
||||
})} onFocus={value_0 => setFocusedOption(value_0)} onInputModeToggle={handleInputModeToggle} />
|
||||
<Select
|
||||
options={options}
|
||||
inlineDescriptions
|
||||
onChange={value => {
|
||||
const selected = options.find(opt => opt.value === value)
|
||||
if (selected) {
|
||||
// For reject option
|
||||
if (selected.option.type === 'reject') {
|
||||
const trimmedFeedback = rejectFeedback.trim()
|
||||
onChange(selected.option, trimmedFeedback || undefined)
|
||||
return
|
||||
}
|
||||
// For accept-once option, pass accept feedback if present
|
||||
if (selected.option.type === 'accept-once') {
|
||||
const trimmedFeedback = acceptFeedback.trim()
|
||||
onChange(selected.option, trimmedFeedback || undefined)
|
||||
return
|
||||
}
|
||||
onChange(selected.option)
|
||||
}
|
||||
}}
|
||||
onCancel={() => onChange({ type: 'reject' })}
|
||||
onFocus={value => setFocusedOption(value)}
|
||||
onInputModeToggle={handleInputModeToggle}
|
||||
/>
|
||||
</Box>
|
||||
</PermissionDialog>
|
||||
<Box paddingX={1} marginTop={1}>
|
||||
<Text dimColor>
|
||||
Esc to cancel
|
||||
{(focusedOption === 'yes' && !yesInputMode || focusedOption === 'no' && !noInputMode) && ' · Tab to amend'}
|
||||
{((focusedOption === 'yes' && !yesInputMode) ||
|
||||
(focusedOption === 'no' && !noInputMode)) &&
|
||||
' · Tab to amend'}
|
||||
</Text>
|
||||
</Box>
|
||||
</>;
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user