Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
7.5 kB · 272 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273import { mkdir, open, unlink } from 'fs/promises'import { join } from 'path'import type { SettingSource } from 'src/utils/settings/constants.js'import { getManagedFilePath } from 'src/utils/settings/managedPath.js'import type { AgentMemoryScope } from '../../tools/AgentTool/agentMemory.js'import { type AgentDefinition, isBuiltInAgent, isPluginAgent,} from '../../tools/AgentTool/loadAgentsDir.js'import { getCwd } from '../../utils/cwd.js'import type { EffortValue } from '../../utils/effort.js'import { getClaudeConfigHomeDir } from '../../utils/envUtils.js'import { getErrnoCode } from '../../utils/errors.js'import { AGENT_PATHS } from './types.js'
/** * Formats agent data as markdown file content */export function formatAgentAsMarkdown( agentType: string, whenToUse: string, tools: string[] | undefined, systemPrompt: string, color?: string, model?: string, memory?: AgentMemoryScope, effort?: EffortValue,): string { // For YAML double-quoted strings, we need to escape: // - Backslashes: \ -> \\ // - Double quotes: " -> \" // - Newlines: \n -> \\n (so yaml reads it as literal backslash-n, not newline) const escapedWhenToUse = whenToUse .replace(/\\/g, '\\\\') // Escape backslashes first .replace(/"/g, '\\"') // Escape double quotes .replace(/\n/g, '\\\\n') // Escape newlines as \\n so yaml preserves them as \n
// Omit tools field entirely when tools is undefined or ['*'] (all tools allowed) const isAllTools = tools === undefined || (tools.length === 1 && tools[0] === '*') const toolsLine = isAllTools ? '' : `\ntools: ${tools.join(', ')}` const modelLine = model ? `\nmodel: ${model}` : '' const effortLine = effort !== undefined ? `\neffort: ${effort}` : '' const colorLine = color ? `\ncolor: ${color}` : '' const memoryLine = memory ? `\nmemory: ${memory}` : ''
return `---name: ${agentType}description: "${escapedWhenToUse}"${toolsLine}${modelLine}${effortLine}${colorLine}${memoryLine}---
${systemPrompt}`}
/** * Gets the directory path for an agent location */function getAgentDirectoryPath(location: SettingSource): string { switch (location) { case 'flagSettings': throw new Error(`Cannot get directory path for ${location} agents`) case 'userSettings': return join(getClaudeConfigHomeDir(), AGENT_PATHS.AGENTS_DIR) case 'projectSettings': return join(getCwd(), AGENT_PATHS.FOLDER_NAME, AGENT_PATHS.AGENTS_DIR) case 'policySettings': return join( getManagedFilePath(), AGENT_PATHS.FOLDER_NAME, AGENT_PATHS.AGENTS_DIR, ) case 'localSettings': return join(getCwd(), AGENT_PATHS.FOLDER_NAME, AGENT_PATHS.AGENTS_DIR) }}
function getRelativeAgentDirectoryPath(location: SettingSource): string { switch (location) { case 'projectSettings': return join('.', AGENT_PATHS.FOLDER_NAME, AGENT_PATHS.AGENTS_DIR) default: return getAgentDirectoryPath(location) }}
/** * Gets the file path for a new agent based on its name * Used when creating new agent files */export function getNewAgentFilePath(agent: { source: SettingSource agentType: string}): string { const dirPath = getAgentDirectoryPath(agent.source) return join(dirPath, `${agent.agentType}.md`)}
/** * Gets the actual file path for an agent (handles filename vs agentType mismatch) * Always use this for existing agents to get their real file location */export function getActualAgentFilePath(agent: AgentDefinition): string { if (agent.source === 'built-in') { return 'Built-in' } if (agent.source === 'plugin') { throw new Error('Cannot get file path for plugin agents') }
const dirPath = getAgentDirectoryPath(agent.source) const filename = agent.filename || agent.agentType return join(dirPath, `${filename}.md`)}
/** * Gets the relative file path for a new agent based on its name * Used for displaying where new agent files will be created */export function getNewRelativeAgentFilePath(agent: { source: SettingSource | 'built-in' agentType: string}): string { if (agent.source === 'built-in') { return 'Built-in' } const dirPath = getRelativeAgentDirectoryPath(agent.source) return join(dirPath, `${agent.agentType}.md`)}
/** * Gets the actual relative file path for an agent (handles filename vs agentType mismatch) */export function getActualRelativeAgentFilePath(agent: AgentDefinition): string { if (isBuiltInAgent(agent)) { return 'Built-in' } if (isPluginAgent(agent)) { return `Plugin: ${agent.plugin || 'Unknown'}` } if (agent.source === 'flagSettings') { return 'CLI argument' }
const dirPath = getRelativeAgentDirectoryPath(agent.source) const filename = agent.filename || agent.agentType return join(dirPath, `${filename}.md`)}
/** * Ensures the directory for an agent location exists */async function ensureAgentDirectoryExists( source: SettingSource,): Promise<string> { const dirPath = getAgentDirectoryPath(source) await mkdir(dirPath, { recursive: true }) return dirPath}
/** * Saves an agent to the filesystem * @param checkExists - If true, throws error if file already exists */export async function saveAgentToFile( source: SettingSource | 'built-in', agentType: string, whenToUse: string, tools: string[] | undefined, systemPrompt: string, checkExists = true, color?: string, model?: string, memory?: AgentMemoryScope, effort?: EffortValue,): Promise<void> { if (source === 'built-in') { throw new Error('Cannot save built-in agents') }
await ensureAgentDirectoryExists(source) const filePath = getNewAgentFilePath({ source, agentType })
const content = formatAgentAsMarkdown( agentType, whenToUse, tools, systemPrompt, color, model, memory, effort, ) try { await writeFileAndFlush(filePath, content, checkExists ? 'wx' : 'w') } catch (e: unknown) { if (getErrnoCode(e) === 'EEXIST') { throw new Error(`Agent file already exists: ${filePath}`) } throw e }}
/** * Updates an existing agent file */export async function updateAgentFile( agent: AgentDefinition, newWhenToUse: string, newTools: string[] | undefined, newSystemPrompt: string, newColor?: string, newModel?: string, newMemory?: AgentMemoryScope, newEffort?: EffortValue,): Promise<void> { if (agent.source === 'built-in') { throw new Error('Cannot update built-in agents') }
const filePath = getActualAgentFilePath(agent)
const content = formatAgentAsMarkdown( agent.agentType, newWhenToUse, newTools, newSystemPrompt, newColor, newModel, newMemory, newEffort, )
await writeFileAndFlush(filePath, content)}
/** * Deletes an agent file */export async function deleteAgentFromFile( agent: AgentDefinition,): Promise<void> { if (agent.source === 'built-in') { throw new Error('Cannot delete built-in agents') }
const filePath = getActualAgentFilePath(agent)
try { await unlink(filePath) } catch (e: unknown) { const code = getErrnoCode(e) if (code !== 'ENOENT') { throw e } }}
async function writeFileAndFlush( filePath: string, content: string, flag: 'w' | 'wx' = 'w',): Promise<void> { const handle = await open(filePath, flag) try { await handle.writeFile(content, { encoding: 'utf-8' }) await handle.datasync() } finally { await handle.close() }}