246 lines
6.8 KiB
TypeScript
246 lines
6.8 KiB
TypeScript
import type { PermissionRule } from 'src/utils/permissions/PermissionRule.js'
|
|
import { getSettingsForSource } from 'src/utils/settings/settings.js'
|
|
import type { SettingsJson } from 'src/utils/settings/types.js'
|
|
import { BASH_TOOL_NAME } from '../../tools/BashTool/toolName.js'
|
|
import { SAFE_ENV_VARS } from '../../utils/managedEnvConstants.js'
|
|
import { getPermissionRulesForSource } from '../../utils/permissions/permissionsLoader.js'
|
|
|
|
function hasHooks(settings: SettingsJson | null): boolean {
|
|
if (settings === null || settings.disableAllHooks) {
|
|
return false
|
|
}
|
|
if (settings.statusLine) {
|
|
return true
|
|
}
|
|
if (settings.fileSuggestion) {
|
|
return true
|
|
}
|
|
if (!settings.hooks) {
|
|
return false
|
|
}
|
|
for (const hookConfig of Object.values(settings.hooks)) {
|
|
if (hookConfig.length > 0) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
export function getHooksSources(): string[] {
|
|
const sources: string[] = []
|
|
|
|
const projectSettings = getSettingsForSource('projectSettings')
|
|
if (hasHooks(projectSettings)) {
|
|
sources.push('.claude/settings.json')
|
|
}
|
|
|
|
const localSettings = getSettingsForSource('localSettings')
|
|
if (hasHooks(localSettings)) {
|
|
sources.push('.claude/settings.local.json')
|
|
}
|
|
|
|
return sources
|
|
}
|
|
|
|
function hasBashPermission(rules: PermissionRule[]): boolean {
|
|
return rules.some(
|
|
rule =>
|
|
rule.ruleBehavior === 'allow' &&
|
|
(rule.ruleValue.toolName === BASH_TOOL_NAME ||
|
|
rule.ruleValue.toolName.startsWith(BASH_TOOL_NAME + '(')),
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Get which setting sources have bash allow rules.
|
|
* Returns an array of file paths that have bash permissions.
|
|
*/
|
|
export function getBashPermissionSources(): string[] {
|
|
const sources: string[] = []
|
|
|
|
const projectRules = getPermissionRulesForSource('projectSettings')
|
|
if (hasBashPermission(projectRules)) {
|
|
sources.push('.claude/settings.json')
|
|
}
|
|
|
|
const localRules = getPermissionRulesForSource('localSettings')
|
|
if (hasBashPermission(localRules)) {
|
|
sources.push('.claude/settings.local.json')
|
|
}
|
|
|
|
return sources
|
|
}
|
|
|
|
/**
|
|
* Format a list of items with proper "and" conjunction.
|
|
* @param items - Array of items to format
|
|
* @param limit - Optional limit for how many items to show before summarizing (ignored if 0)
|
|
*/
|
|
export function formatListWithAnd(items: string[], limit?: number): string {
|
|
if (items.length === 0) return ''
|
|
|
|
// Ignore limit if it's 0
|
|
const effectiveLimit = limit === 0 ? undefined : limit
|
|
|
|
// If no limit or items are within limit, use normal formatting
|
|
if (!effectiveLimit || items.length <= effectiveLimit) {
|
|
if (items.length === 1) return items[0]!
|
|
if (items.length === 2) return `${items[0]} and ${items[1]}`
|
|
|
|
const lastItem = items[items.length - 1]!
|
|
const allButLast = items.slice(0, -1)
|
|
return `${allButLast.join(', ')}, and ${lastItem}`
|
|
}
|
|
|
|
// If we have more items than the limit, show first few and count the rest
|
|
const shown = items.slice(0, effectiveLimit)
|
|
const remaining = items.length - effectiveLimit
|
|
|
|
if (shown.length === 1) {
|
|
return `${shown[0]} and ${remaining} more`
|
|
}
|
|
|
|
return `${shown.join(', ')}, and ${remaining} more`
|
|
}
|
|
|
|
/**
|
|
* Check if settings have otelHeadersHelper configured
|
|
*/
|
|
function hasOtelHeadersHelper(settings: SettingsJson | null): boolean {
|
|
return !!settings?.otelHeadersHelper
|
|
}
|
|
|
|
/**
|
|
* Get which setting sources have otelHeadersHelper configured.
|
|
* Returns an array of file paths that have otelHeadersHelper.
|
|
*/
|
|
export function getOtelHeadersHelperSources(): string[] {
|
|
const sources: string[] = []
|
|
|
|
const projectSettings = getSettingsForSource('projectSettings')
|
|
if (hasOtelHeadersHelper(projectSettings)) {
|
|
sources.push('.claude/settings.json')
|
|
}
|
|
|
|
const localSettings = getSettingsForSource('localSettings')
|
|
if (hasOtelHeadersHelper(localSettings)) {
|
|
sources.push('.claude/settings.local.json')
|
|
}
|
|
|
|
return sources
|
|
}
|
|
|
|
/**
|
|
* Check if settings have apiKeyHelper configured
|
|
*/
|
|
function hasApiKeyHelper(settings: SettingsJson | null): boolean {
|
|
return !!settings?.apiKeyHelper
|
|
}
|
|
|
|
/**
|
|
* Get which setting sources have apiKeyHelper configured.
|
|
* Returns an array of file paths that have apiKeyHelper.
|
|
*/
|
|
export function getApiKeyHelperSources(): string[] {
|
|
const sources: string[] = []
|
|
|
|
const projectSettings = getSettingsForSource('projectSettings')
|
|
if (hasApiKeyHelper(projectSettings)) {
|
|
sources.push('.claude/settings.json')
|
|
}
|
|
|
|
const localSettings = getSettingsForSource('localSettings')
|
|
if (hasApiKeyHelper(localSettings)) {
|
|
sources.push('.claude/settings.local.json')
|
|
}
|
|
|
|
return sources
|
|
}
|
|
|
|
/**
|
|
* Check if settings have AWS commands configured
|
|
*/
|
|
function hasAwsCommands(settings: SettingsJson | null): boolean {
|
|
return !!(settings?.awsAuthRefresh || settings?.awsCredentialExport)
|
|
}
|
|
|
|
/**
|
|
* Get which setting sources have AWS commands configured.
|
|
* Returns an array of file paths that have awsAuthRefresh or awsCredentialExport.
|
|
*/
|
|
export function getAwsCommandsSources(): string[] {
|
|
const sources: string[] = []
|
|
|
|
const projectSettings = getSettingsForSource('projectSettings')
|
|
if (hasAwsCommands(projectSettings)) {
|
|
sources.push('.claude/settings.json')
|
|
}
|
|
|
|
const localSettings = getSettingsForSource('localSettings')
|
|
if (hasAwsCommands(localSettings)) {
|
|
sources.push('.claude/settings.local.json')
|
|
}
|
|
|
|
return sources
|
|
}
|
|
|
|
/**
|
|
* Check if settings have GCP commands configured
|
|
*/
|
|
function hasGcpCommands(settings: SettingsJson | null): boolean {
|
|
return !!settings?.gcpAuthRefresh
|
|
}
|
|
|
|
/**
|
|
* Get which setting sources have GCP commands configured.
|
|
* Returns an array of file paths that have gcpAuthRefresh.
|
|
*/
|
|
export function getGcpCommandsSources(): string[] {
|
|
const sources: string[] = []
|
|
|
|
const projectSettings = getSettingsForSource('projectSettings')
|
|
if (hasGcpCommands(projectSettings)) {
|
|
sources.push('.claude/settings.json')
|
|
}
|
|
|
|
const localSettings = getSettingsForSource('localSettings')
|
|
if (hasGcpCommands(localSettings)) {
|
|
sources.push('.claude/settings.local.json')
|
|
}
|
|
|
|
return sources
|
|
}
|
|
|
|
/**
|
|
* Check if settings have dangerous environment variables configured.
|
|
* Any env var NOT in SAFE_ENV_VARS is considered dangerous.
|
|
*/
|
|
function hasDangerousEnvVars(settings: SettingsJson | null): boolean {
|
|
if (!settings?.env) {
|
|
return false
|
|
}
|
|
return Object.keys(settings.env).some(
|
|
key => !SAFE_ENV_VARS.has(key.toUpperCase()),
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Get which setting sources have dangerous environment variables configured.
|
|
* Returns an array of file paths that have env vars not in SAFE_ENV_VARS.
|
|
*/
|
|
export function getDangerousEnvVarsSources(): string[] {
|
|
const sources: string[] = []
|
|
|
|
const projectSettings = getSettingsForSource('projectSettings')
|
|
if (hasDangerousEnvVars(projectSettings)) {
|
|
sources.push('.claude/settings.json')
|
|
}
|
|
|
|
const localSettings = getSettingsForSource('localSettings')
|
|
if (hasDangerousEnvVars(localSettings)) {
|
|
sources.push('.claude/settings.local.json')
|
|
}
|
|
|
|
return sources
|
|
}
|