mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 22:35:51 +00:00
* 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>
214 lines
5.9 KiB
TypeScript
214 lines
5.9 KiB
TypeScript
import { basename, sep } from 'path'
|
|
import React, { type ReactNode } from 'react'
|
|
import { getOriginalCwd } from '../../bootstrap/state.js'
|
|
import { Text } from '../../ink.js'
|
|
import type { PermissionUpdate } from '../../utils/permissions/PermissionUpdateSchema.js'
|
|
import { permissionRuleExtractPrefix } from '../../utils/permissions/shellRuleMatching.js'
|
|
|
|
function commandListDisplay(commands: string[]): ReactNode {
|
|
switch (commands.length) {
|
|
case 0:
|
|
return ''
|
|
case 1:
|
|
return <Text bold>{commands[0]}</Text>
|
|
case 2:
|
|
return (
|
|
<Text>
|
|
<Text bold>{commands[0]}</Text> and <Text bold>{commands[1]}</Text>
|
|
</Text>
|
|
)
|
|
default:
|
|
return (
|
|
<Text>
|
|
<Text bold>{commands.slice(0, -1).join(', ')}</Text>, and{' '}
|
|
<Text bold>{commands.slice(-1)[0]}</Text>
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
|
|
function commandListDisplayTruncated(commands: string[]): ReactNode {
|
|
// Check if the plain text representation would be too long
|
|
const plainText = commands.join(', ')
|
|
if (plainText.length > 50) {
|
|
return 'similar'
|
|
}
|
|
return commandListDisplay(commands)
|
|
}
|
|
|
|
function formatPathList(paths: string[]): ReactNode {
|
|
if (paths.length === 0) return ''
|
|
|
|
// Extract directory names from paths
|
|
const names = paths.map(p => basename(p) || p)
|
|
|
|
if (names.length === 1) {
|
|
return (
|
|
<Text>
|
|
<Text bold>{names[0]}</Text>
|
|
{sep}
|
|
</Text>
|
|
)
|
|
}
|
|
if (names.length === 2) {
|
|
return (
|
|
<Text>
|
|
<Text bold>{names[0]}</Text>
|
|
{sep} and <Text bold>{names[1]}</Text>
|
|
{sep}
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
// For 3+, show first two with "and N more"
|
|
return (
|
|
<Text>
|
|
<Text bold>{names[0]}</Text>
|
|
{sep}, <Text bold>{names[1]}</Text>
|
|
{sep} and {paths.length - 2} more
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Generate the label for the "Yes, and apply suggestions" option in shell
|
|
* permission dialogs (Bash, PowerShell). Parametrized by the shell tool name
|
|
* and an optional command transform (e.g., Bash strips output redirections so
|
|
* filenames don't show as commands).
|
|
*/
|
|
export function generateShellSuggestionsLabel(
|
|
suggestions: PermissionUpdate[],
|
|
shellToolName: string,
|
|
commandTransform?: (command: string) => string,
|
|
): ReactNode | null {
|
|
// Collect all rules for display
|
|
const allRules = suggestions
|
|
.filter(s => s.type === 'addRules')
|
|
.flatMap(s => s.rules || [])
|
|
|
|
// Separate Read rules from shell rules
|
|
const readRules = allRules.filter(r => r.toolName === 'Read')
|
|
const shellRules = allRules.filter(r => r.toolName === shellToolName)
|
|
|
|
// Get directory info
|
|
const directories = suggestions
|
|
.filter(s => s.type === 'addDirectories')
|
|
.flatMap(s => s.directories || [])
|
|
|
|
// Extract paths from Read rules (keep separate from directories)
|
|
const readPaths = readRules
|
|
.map(r => r.ruleContent?.replace('/**', '') || '')
|
|
.filter(p => p)
|
|
|
|
// Extract shell command prefixes, optionally transforming for display
|
|
const shellCommands = [
|
|
...new Set(
|
|
shellRules.flatMap(rule => {
|
|
if (!rule.ruleContent) return []
|
|
const command =
|
|
permissionRuleExtractPrefix(rule.ruleContent) ?? rule.ruleContent
|
|
return commandTransform ? commandTransform(command) : command
|
|
}),
|
|
),
|
|
]
|
|
|
|
// Check what we have
|
|
const hasDirectories = directories.length > 0
|
|
const hasReadPaths = readPaths.length > 0
|
|
const hasCommands = shellCommands.length > 0
|
|
|
|
// Handle single type cases
|
|
if (hasReadPaths && !hasDirectories && !hasCommands) {
|
|
// Only Read rules - use "reading from" language
|
|
if (readPaths.length === 1) {
|
|
const firstPath = readPaths[0]!
|
|
const dirName = basename(firstPath) || firstPath
|
|
return (
|
|
<Text>
|
|
Yes, allow reading from <Text bold>{dirName}</Text>
|
|
{sep} from this project
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
// Multiple read paths
|
|
return (
|
|
<Text>
|
|
Yes, allow reading from {formatPathList(readPaths)} from this project
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
if (hasDirectories && !hasReadPaths && !hasCommands) {
|
|
// Only directory permissions - use "access to" language
|
|
if (directories.length === 1) {
|
|
const firstDir = directories[0]!
|
|
const dirName = basename(firstDir) || firstDir
|
|
return (
|
|
<Text>
|
|
Yes, and always allow access to <Text bold>{dirName}</Text>
|
|
{sep} from this project
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
// Multiple directories
|
|
return (
|
|
<Text>
|
|
Yes, and always allow access to {formatPathList(directories)} from this
|
|
project
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
if (hasCommands && !hasDirectories && !hasReadPaths) {
|
|
// Only shell command permissions
|
|
return (
|
|
<Text>
|
|
{"Yes, and don't ask again for "}
|
|
{commandListDisplayTruncated(shellCommands)} commands in{' '}
|
|
<Text bold>{getOriginalCwd()}</Text>
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
// Handle mixed cases
|
|
if ((hasDirectories || hasReadPaths) && !hasCommands) {
|
|
// Combine directories and read paths since they're both path access
|
|
const allPaths = [...directories, ...readPaths]
|
|
if (hasDirectories && hasReadPaths) {
|
|
// Mixed - use generic "access to"
|
|
return (
|
|
<Text>
|
|
Yes, and always allow access to {formatPathList(allPaths)} from this
|
|
project
|
|
</Text>
|
|
)
|
|
}
|
|
}
|
|
|
|
if ((hasDirectories || hasReadPaths) && hasCommands) {
|
|
// Build descriptive message for both types
|
|
const allPaths = [...directories, ...readPaths]
|
|
|
|
// Keep it concise but informative
|
|
if (allPaths.length === 1 && shellCommands.length === 1) {
|
|
return (
|
|
<Text>
|
|
Yes, and allow access to {formatPathList(allPaths)} and{' '}
|
|
{commandListDisplayTruncated(shellCommands)} commands
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Text>
|
|
Yes, and allow {formatPathList(allPaths)} access and{' '}
|
|
{commandListDisplayTruncated(shellCommands)} commands
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
return null
|
|
}
|