utils.ts
1 import type { PermissionRule } from 'src/utils/permissions/PermissionRule.js' 2 import { getSettingsForSource } from 'src/utils/settings/settings.js' 3 import type { SettingsJson } from 'src/utils/settings/types.js' 4 import { BASH_TOOL_NAME } from '../../tools/BashTool/toolName.js' 5 import { SAFE_ENV_VARS } from '../../utils/managedEnvConstants.js' 6 import { getPermissionRulesForSource } from '../../utils/permissions/permissionsLoader.js' 7 8 function hasHooks(settings: SettingsJson | null): boolean { 9 if (settings === null || settings.disableAllHooks) { 10 return false 11 } 12 if (settings.statusLine) { 13 return true 14 } 15 if (settings.fileSuggestion) { 16 return true 17 } 18 if (!settings.hooks) { 19 return false 20 } 21 for (const hookConfig of Object.values(settings.hooks)) { 22 if (hookConfig.length > 0) { 23 return true 24 } 25 } 26 return false 27 } 28 29 export function getHooksSources(): string[] { 30 const sources: string[] = [] 31 32 const projectSettings = getSettingsForSource('projectSettings') 33 if (hasHooks(projectSettings)) { 34 sources.push('.claude/settings.json') 35 } 36 37 const localSettings = getSettingsForSource('localSettings') 38 if (hasHooks(localSettings)) { 39 sources.push('.claude/settings.local.json') 40 } 41 42 return sources 43 } 44 45 function hasBashPermission(rules: PermissionRule[]): boolean { 46 return rules.some( 47 rule => 48 rule.ruleBehavior === 'allow' && 49 (rule.ruleValue.toolName === BASH_TOOL_NAME || 50 rule.ruleValue.toolName.startsWith(BASH_TOOL_NAME + '(')), 51 ) 52 } 53 54 /** 55 * Get which setting sources have bash allow rules. 56 * Returns an array of file paths that have bash permissions. 57 */ 58 export function getBashPermissionSources(): string[] { 59 const sources: string[] = [] 60 61 const projectRules = getPermissionRulesForSource('projectSettings') 62 if (hasBashPermission(projectRules)) { 63 sources.push('.claude/settings.json') 64 } 65 66 const localRules = getPermissionRulesForSource('localSettings') 67 if (hasBashPermission(localRules)) { 68 sources.push('.claude/settings.local.json') 69 } 70 71 return sources 72 } 73 74 /** 75 * Format a list of items with proper "and" conjunction. 76 * @param items - Array of items to format 77 * @param limit - Optional limit for how many items to show before summarizing (ignored if 0) 78 */ 79 export function formatListWithAnd(items: string[], limit?: number): string { 80 if (items.length === 0) return '' 81 82 // Ignore limit if it's 0 83 const effectiveLimit = limit === 0 ? undefined : limit 84 85 // If no limit or items are within limit, use normal formatting 86 if (!effectiveLimit || items.length <= effectiveLimit) { 87 if (items.length === 1) return items[0]! 88 if (items.length === 2) return `${items[0]} and ${items[1]}` 89 90 const lastItem = items[items.length - 1]! 91 const allButLast = items.slice(0, -1) 92 return `${allButLast.join(', ')}, and ${lastItem}` 93 } 94 95 // If we have more items than the limit, show first few and count the rest 96 const shown = items.slice(0, effectiveLimit) 97 const remaining = items.length - effectiveLimit 98 99 if (shown.length === 1) { 100 return `${shown[0]} and ${remaining} more` 101 } 102 103 return `${shown.join(', ')}, and ${remaining} more` 104 } 105 106 /** 107 * Check if settings have otelHeadersHelper configured 108 */ 109 function hasOtelHeadersHelper(settings: SettingsJson | null): boolean { 110 return !!settings?.otelHeadersHelper 111 } 112 113 /** 114 * Get which setting sources have otelHeadersHelper configured. 115 * Returns an array of file paths that have otelHeadersHelper. 116 */ 117 export function getOtelHeadersHelperSources(): string[] { 118 const sources: string[] = [] 119 120 const projectSettings = getSettingsForSource('projectSettings') 121 if (hasOtelHeadersHelper(projectSettings)) { 122 sources.push('.claude/settings.json') 123 } 124 125 const localSettings = getSettingsForSource('localSettings') 126 if (hasOtelHeadersHelper(localSettings)) { 127 sources.push('.claude/settings.local.json') 128 } 129 130 return sources 131 } 132 133 /** 134 * Check if settings have apiKeyHelper configured 135 */ 136 function hasApiKeyHelper(settings: SettingsJson | null): boolean { 137 return !!settings?.apiKeyHelper 138 } 139 140 /** 141 * Get which setting sources have apiKeyHelper configured. 142 * Returns an array of file paths that have apiKeyHelper. 143 */ 144 export function getApiKeyHelperSources(): string[] { 145 const sources: string[] = [] 146 147 const projectSettings = getSettingsForSource('projectSettings') 148 if (hasApiKeyHelper(projectSettings)) { 149 sources.push('.claude/settings.json') 150 } 151 152 const localSettings = getSettingsForSource('localSettings') 153 if (hasApiKeyHelper(localSettings)) { 154 sources.push('.claude/settings.local.json') 155 } 156 157 return sources 158 } 159 160 /** 161 * Check if settings have AWS commands configured 162 */ 163 function hasAwsCommands(settings: SettingsJson | null): boolean { 164 return !!(settings?.awsAuthRefresh || settings?.awsCredentialExport) 165 } 166 167 /** 168 * Get which setting sources have AWS commands configured. 169 * Returns an array of file paths that have awsAuthRefresh or awsCredentialExport. 170 */ 171 export function getAwsCommandsSources(): string[] { 172 const sources: string[] = [] 173 174 const projectSettings = getSettingsForSource('projectSettings') 175 if (hasAwsCommands(projectSettings)) { 176 sources.push('.claude/settings.json') 177 } 178 179 const localSettings = getSettingsForSource('localSettings') 180 if (hasAwsCommands(localSettings)) { 181 sources.push('.claude/settings.local.json') 182 } 183 184 return sources 185 } 186 187 /** 188 * Check if settings have GCP commands configured 189 */ 190 function hasGcpCommands(settings: SettingsJson | null): boolean { 191 return !!settings?.gcpAuthRefresh 192 } 193 194 /** 195 * Get which setting sources have GCP commands configured. 196 * Returns an array of file paths that have gcpAuthRefresh. 197 */ 198 export function getGcpCommandsSources(): string[] { 199 const sources: string[] = [] 200 201 const projectSettings = getSettingsForSource('projectSettings') 202 if (hasGcpCommands(projectSettings)) { 203 sources.push('.claude/settings.json') 204 } 205 206 const localSettings = getSettingsForSource('localSettings') 207 if (hasGcpCommands(localSettings)) { 208 sources.push('.claude/settings.local.json') 209 } 210 211 return sources 212 } 213 214 /** 215 * Check if settings have dangerous environment variables configured. 216 * Any env var NOT in SAFE_ENV_VARS is considered dangerous. 217 */ 218 function hasDangerousEnvVars(settings: SettingsJson | null): boolean { 219 if (!settings?.env) { 220 return false 221 } 222 return Object.keys(settings.env).some( 223 key => !SAFE_ENV_VARS.has(key.toUpperCase()), 224 ) 225 } 226 227 /** 228 * Get which setting sources have dangerous environment variables configured. 229 * Returns an array of file paths that have env vars not in SAFE_ENV_VARS. 230 */ 231 export function getDangerousEnvVarsSources(): string[] { 232 const sources: string[] = [] 233 234 const projectSettings = getSettingsForSource('projectSettings') 235 if (hasDangerousEnvVars(projectSettings)) { 236 sources.push('.claude/settings.json') 237 } 238 239 const localSettings = getSettingsForSource('localSettings') 240 if (hasDangerousEnvVars(localSettings)) { 241 sources.push('.claude/settings.local.json') 242 } 243 244 return sources 245 }