Files
claude-code/src/components/permissions/shellPermissionHelpers.tsx
claude-code-best 5b1a52b8e0 更新大量 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>
2026-04-04 23:24:27 +08:00

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
}