/ components / TrustDialog / utils.ts
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  }