Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
12 kB · 389 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390import { posix } from 'path'import type { ToolPermissionContext } from '../../Tool.js'// Types extracted to src/types/permissions.ts to break import cyclesimport type { AdditionalWorkingDirectory, WorkingDirectorySource,} from '../../types/permissions.js'import { logForDebugging } from '../debug.js'import type { EditableSettingSource } from '../settings/constants.js'import { getSettingsForSource, updateSettingsForSource,} from '../settings/settings.js'import { jsonStringify } from '../slowOperations.js'import { toPosixPath } from './filesystem.js'import type { PermissionRuleValue } from './PermissionRule.js'import type { PermissionUpdate, PermissionUpdateDestination,} from './PermissionUpdateSchema.js'import { permissionRuleValueFromString, permissionRuleValueToString,} from './permissionRuleParser.js'import { addPermissionRulesToSettings } from './permissionsLoader.js'
// Re-export for backwards compatibilityexport type { AdditionalWorkingDirectory, WorkingDirectorySource }
export function extractRules( updates: PermissionUpdate[] | undefined,): PermissionRuleValue[] { if (!updates) return []
return updates.flatMap(update => { switch (update.type) { case 'addRules': return update.rules default: return [] } })}
export function hasRules(updates: PermissionUpdate[] | undefined): boolean { return extractRules(updates).length > 0}
/** * Applies a single permission update to the context and returns the updated context * @param context The current permission context * @param update The permission update to apply * @returns The updated permission context */export function applyPermissionUpdate( context: ToolPermissionContext, update: PermissionUpdate,): ToolPermissionContext { switch (update.type) { case 'setMode': logForDebugging( `Applying permission update: Setting mode to '${update.mode}'`, ) return { ...context, mode: update.mode, }
case 'addRules': { const ruleStrings = update.rules.map(rule => permissionRuleValueToString(rule), ) logForDebugging( `Applying permission update: Adding ${update.rules.length} ${update.behavior} rule(s) to destination '${update.destination}': ${jsonStringify(ruleStrings)}`, )
// Determine which collection to update based on behavior const ruleKind = update.behavior === 'allow' ? 'alwaysAllowRules' : update.behavior === 'deny' ? 'alwaysDenyRules' : 'alwaysAskRules'
return { ...context, [ruleKind]: { ...context[ruleKind], [update.destination]: [ ...(context[ruleKind][update.destination] || []), ...ruleStrings, ], }, } }
case 'replaceRules': { const ruleStrings = update.rules.map(rule => permissionRuleValueToString(rule), ) logForDebugging( `Replacing all ${update.behavior} rules for destination '${update.destination}' with ${update.rules.length} rule(s): ${jsonStringify(ruleStrings)}`, )
// Determine which collection to update based on behavior const ruleKind = update.behavior === 'allow' ? 'alwaysAllowRules' : update.behavior === 'deny' ? 'alwaysDenyRules' : 'alwaysAskRules'
return { ...context, [ruleKind]: { ...context[ruleKind], [update.destination]: ruleStrings, // Replace all rules for this source }, } }
case 'addDirectories': { logForDebugging( `Applying permission update: Adding ${update.directories.length} director${update.directories.length === 1 ? 'y' : 'ies'} with destination '${update.destination}': ${jsonStringify(update.directories)}`, ) const newAdditionalDirs = new Map(context.additionalWorkingDirectories) for (const directory of update.directories) { newAdditionalDirs.set(directory, { path: directory, source: update.destination, }) } return { ...context, additionalWorkingDirectories: newAdditionalDirs, } }
case 'removeRules': { const ruleStrings = update.rules.map(rule => permissionRuleValueToString(rule), ) logForDebugging( `Applying permission update: Removing ${update.rules.length} ${update.behavior} rule(s) from source '${update.destination}': ${jsonStringify(ruleStrings)}`, )
// Determine which collection to update based on behavior const ruleKind = update.behavior === 'allow' ? 'alwaysAllowRules' : update.behavior === 'deny' ? 'alwaysDenyRules' : 'alwaysAskRules'
// Filter out the rules to be removed const existingRules = context[ruleKind][update.destination] || [] const rulesToRemove = new Set(ruleStrings) const filteredRules = existingRules.filter( rule => !rulesToRemove.has(rule), )
return { ...context, [ruleKind]: { ...context[ruleKind], [update.destination]: filteredRules, }, } }
case 'removeDirectories': { logForDebugging( `Applying permission update: Removing ${update.directories.length} director${update.directories.length === 1 ? 'y' : 'ies'}: ${jsonStringify(update.directories)}`, ) const newAdditionalDirs = new Map(context.additionalWorkingDirectories) for (const directory of update.directories) { newAdditionalDirs.delete(directory) } return { ...context, additionalWorkingDirectories: newAdditionalDirs, } }
default: return context }}
/** * Applies multiple permission updates to the context and returns the updated context * @param context The current permission context * @param updates The permission updates to apply * @returns The updated permission context */export function applyPermissionUpdates( context: ToolPermissionContext, updates: PermissionUpdate[],): ToolPermissionContext { let updatedContext = context for (const update of updates) { updatedContext = applyPermissionUpdate(updatedContext, update) }
return updatedContext}
export function supportsPersistence( destination: PermissionUpdateDestination,): destination is EditableSettingSource { return ( destination === 'localSettings' || destination === 'userSettings' || destination === 'projectSettings' )}
/** * Persists a permission update to the appropriate settings source * @param update The permission update to persist */export function persistPermissionUpdate(update: PermissionUpdate): void { if (!supportsPersistence(update.destination)) return
logForDebugging( `Persisting permission update: ${update.type} to source '${update.destination}'`, )
switch (update.type) { case 'addRules': { logForDebugging( `Persisting ${update.rules.length} ${update.behavior} rule(s) to ${update.destination}`, ) addPermissionRulesToSettings( { ruleValues: update.rules, ruleBehavior: update.behavior, }, update.destination, ) break }
case 'addDirectories': { logForDebugging( `Persisting ${update.directories.length} director${update.directories.length === 1 ? 'y' : 'ies'} to ${update.destination}`, ) const existingSettings = getSettingsForSource(update.destination) const existingDirs = existingSettings?.permissions?.additionalDirectories || []
// Add new directories, avoiding duplicates const dirsToAdd = update.directories.filter( dir => !existingDirs.includes(dir), )
if (dirsToAdd.length > 0) { const updatedDirs = [...existingDirs, ...dirsToAdd] updateSettingsForSource(update.destination, { permissions: { additionalDirectories: updatedDirs, }, }) } break }
case 'removeRules': { // Handle rule removal logForDebugging( `Removing ${update.rules.length} ${update.behavior} rule(s) from ${update.destination}`, ) const existingSettings = getSettingsForSource(update.destination) const existingPermissions = existingSettings?.permissions || {} const existingRules = existingPermissions[update.behavior] || []
// Convert rules to normalized strings for comparison // Normalize via parse→serialize roundtrip so "Bash(*)" and "Bash" match const rulesToRemove = new Set( update.rules.map(permissionRuleValueToString), ) const filteredRules = existingRules.filter(rule => { const normalized = permissionRuleValueToString( permissionRuleValueFromString(rule), ) return !rulesToRemove.has(normalized) })
updateSettingsForSource(update.destination, { permissions: { [update.behavior]: filteredRules, }, }) break }
case 'removeDirectories': { logForDebugging( `Removing ${update.directories.length} director${update.directories.length === 1 ? 'y' : 'ies'} from ${update.destination}`, ) const existingSettings = getSettingsForSource(update.destination) const existingDirs = existingSettings?.permissions?.additionalDirectories || []
// Remove specified directories const dirsToRemove = new Set(update.directories) const filteredDirs = existingDirs.filter(dir => !dirsToRemove.has(dir))
updateSettingsForSource(update.destination, { permissions: { additionalDirectories: filteredDirs, }, }) break }
case 'setMode': { logForDebugging( `Persisting mode '${update.mode}' to ${update.destination}`, ) updateSettingsForSource(update.destination, { permissions: { defaultMode: update.mode, }, }) break }
case 'replaceRules': { logForDebugging( `Replacing all ${update.behavior} rules in ${update.destination} with ${update.rules.length} rule(s)`, ) const ruleStrings = update.rules.map(permissionRuleValueToString) updateSettingsForSource(update.destination, { permissions: { [update.behavior]: ruleStrings, }, }) break } }}
/** * Persists multiple permission updates to the appropriate settings sources * Only persists updates with persistable sources * @param updates The permission updates to persist */export function persistPermissionUpdates(updates: PermissionUpdate[]): void { for (const update of updates) { persistPermissionUpdate(update) }}
/** * Creates a Read rule suggestion for a directory. * @param dirPath The directory path to create a rule for * @param destination The destination for the permission rule (defaults to 'session') * @returns A PermissionUpdate for a Read rule, or undefined for the root directory */export function createReadRuleSuggestion( dirPath: string, destination: PermissionUpdateDestination = 'session',): PermissionUpdate | undefined { // Convert to POSIX format for pattern matching (handles Windows internally) const pathForPattern = toPosixPath(dirPath)
// Root directory is too broad to be a reasonable permission target if (pathForPattern === '/') { return undefined }
// For absolute paths, prepend an extra / to create //path/** pattern const ruleContent = posix.isAbsolute(pathForPattern) ? `/${pathForPattern}/**` : `${pathForPattern}/**`
return { type: 'addRules', rules: [ { toolName: 'Read', ruleContent, }, ], behavior: 'allow', destination, }}