shellConfig.ts
utils/shellConfig.ts
168
Lines
4737
Bytes
7
Exports
5
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with shell-safety. It contains 168 lines, 5 detected imports, and 7 detected exports.
Important relationships
Detected exports
CLAUDE_ALIAS_REGEXgetShellConfigPathsfilterClaudeAliasesreadFileLineswriteFileLinesfindClaudeAliasfindValidClaudeAlias
Keywords
matchaliasclaudeoptionslineslinehometargethomediraliases
Detected imports
fs/promisesospath./errors.js./localInstaller.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
/**
* Utilities for managing shell configuration files (like .bashrc, .zshrc)
* Used for managing claude aliases and PATH entries
*/
import { open, readFile, stat } from 'fs/promises'
import { homedir as osHomedir } from 'os'
import { join } from 'path'
import { isFsInaccessible } from './errors.js'
import { getLocalClaudePath } from './localInstaller.js'
export const CLAUDE_ALIAS_REGEX = /^\s*alias\s+claude\s*=/
type EnvLike = Record<string, string | undefined>
type ShellConfigOptions = {
env?: EnvLike
homedir?: string
}
/**
* Get the paths to shell configuration files
* Respects ZDOTDIR for zsh users
* @param options Optional overrides for testing (env, homedir)
*/
export function getShellConfigPaths(
options?: ShellConfigOptions,
): Record<string, string> {
const home = options?.homedir ?? osHomedir()
const env = options?.env ?? process.env
const zshConfigDir = env.ZDOTDIR || home
return {
zsh: join(zshConfigDir, '.zshrc'),
bash: join(home, '.bashrc'),
fish: join(home, '.config/fish/config.fish'),
}
}
/**
* Filter out installer-created claude aliases from an array of lines
* Only removes aliases pointing to $HOME/.claude/local/claude
* Preserves custom user aliases that point to other locations
* Returns the filtered lines and whether our default installer alias was found
*/
export function filterClaudeAliases(lines: string[]): {
filtered: string[]
hadAlias: boolean
} {
let hadAlias = false
const filtered = lines.filter(line => {
// Check if this is a claude alias
if (CLAUDE_ALIAS_REGEX.test(line)) {
// Extract the alias target - handle spaces, quotes, and various formats
// First try with quotes
let match = line.match(/alias\s+claude\s*=\s*["']([^"']+)["']/)
if (!match) {
// Try without quotes (capturing until end of line or comment)
match = line.match(/alias\s+claude\s*=\s*([^#\n]+)/)
}
if (match && match[1]) {
const target = match[1].trim()
// Only remove if it points to the installer location
// The installer always creates aliases with the full expanded path
if (target === getLocalClaudePath()) {
hadAlias = true
return false // Remove this line
}
}
// Keep custom aliases that don't point to the installer location
}
return true
})
return { filtered, hadAlias }
}
/**
* Read a file and split it into lines
* Returns null if file doesn't exist or can't be read
*/
export async function readFileLines(
filePath: string,
): Promise<string[] | null> {
try {
const content = await readFile(filePath, { encoding: 'utf8' })
return content.split('\n')
} catch (e: unknown) {
if (isFsInaccessible(e)) return null
throw e
}
}
/**
* Write lines back to a file
*/
export async function writeFileLines(
filePath: string,
lines: string[],
): Promise<void> {
const fh = await open(filePath, 'w')
try {
await fh.writeFile(lines.join('\n'), { encoding: 'utf8' })
await fh.datasync()
} finally {
await fh.close()
}
}
/**
* Check if a claude alias exists in any shell config file
* Returns the alias target if found, null otherwise
* @param options Optional overrides for testing (env, homedir)
*/
export async function findClaudeAlias(
options?: ShellConfigOptions,
): Promise<string | null> {
const configs = getShellConfigPaths(options)
for (const configPath of Object.values(configs)) {
const lines = await readFileLines(configPath)
if (!lines) continue
for (const line of lines) {
if (CLAUDE_ALIAS_REGEX.test(line)) {
// Extract the alias target
const match = line.match(/alias\s+claude=["']?([^"'\s]+)/)
if (match && match[1]) {
return match[1]
}
}
}
}
return null
}
/**
* Check if a claude alias exists and points to a valid executable
* Returns the alias target if valid, null otherwise
* @param options Optional overrides for testing (env, homedir)
*/
export async function findValidClaudeAlias(
options?: ShellConfigOptions,
): Promise<string | null> {
const aliasTarget = await findClaudeAlias(options)
if (!aliasTarget) return null
const home = options?.homedir ?? osHomedir()
// Expand ~ to home directory
const expandedPath = aliasTarget.startsWith('~')
? aliasTarget.replace('~', home)
: aliasTarget
// Check if the target exists and is executable
try {
const stats = await stat(expandedPath)
// Check if it's a file (could be executable or symlink)
if (stats.isFile() || stats.isSymbolicLink()) {
return aliasTarget
}
} catch {
// Target doesn't exist or can't be accessed
}
return null
}