Something went wrong. Try again.
A simple patch that toggles claude-code thinking traces to be fully expanded by default without having to press ctrl + o
Something went wrong. Try again.
claude-code-patches patch-subagent-models.js
13 kB · 388 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389#!/usr/bin/env node
const fs = require('fs');const path = require('path');const os = require('os');const { execSync } = require('child_process');
// Parse command line argumentsconst args = process.argv.slice(2);const isDryRun = args.includes('--dry-run');const isRestore = args.includes('--restore');const showHelp = args.includes('--help') || args.includes('-h');
// Display helpif (showHelp) { console.log('Claude Code Subagent Model Configuration Patcher v2.0.33'); console.log('=========================================================\n'); console.log('Usage: node patch-subagent-models.js [options]\n'); console.log('Options:'); console.log(' --dry-run Preview changes without applying them'); console.log(' --restore Restore from backup file'); console.log(' --help, -h Show this help message\n'); console.log('Configuration:'); console.log(' Create ~/.claude/subagent-models.json to configure models:\n'); console.log(' {'); console.log(' "Plan": "sonnet",'); console.log(' "Explore": "haiku",'); console.log(' "general-purpose": "sonnet"'); console.log(' }\n'); console.log('Examples:'); console.log(' node patch-subagent-models.js # Apply patches'); console.log(' node patch-subagent-models.js --dry-run # Preview changes'); console.log(' node patch-subagent-models.js --restore # Restore original'); process.exit(0);}
console.log('Claude Code Subagent Model Configuration Patcher v2.0.33');console.log('=========================================================\n');
// Helper function to safely execute shell commandsfunction safeExec(command) { try { return execSync(command, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim(); } catch (error) { return null; }}
// Auto-detect Claude Code installation pathfunction getClaudeCodePath() { const homeDir = os.homedir(); const attemptedPaths = [];
// Helper to check and return path if it exists function checkPath(testPath, method) { if (!testPath) return null;
attemptedPaths.push({ path: testPath, method });
try { if (fs.existsSync(testPath)) { // Resolve symlinks for global npm installs try { const realPath = fs.realpathSync(testPath); return realPath; } catch (e) { return testPath; } } } catch (error) { // Path check failed, continue } return null; }
// PRIORITY 1: Local installations const localPaths = [ path.join(homeDir, '.claude', 'local', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'), path.join(homeDir, '.config', 'claude', 'local', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'), ];
for (const localPath of localPaths) { const found = checkPath(localPath, 'local installation'); if (found) return found; }
// PRIORITY 2: Global npm installation via 'npm root -g' const npmGlobalRoot = safeExec('npm root -g'); if (npmGlobalRoot) { const npmGlobalPath = path.join(npmGlobalRoot, '@anthropic-ai', 'claude-code', 'cli.js'); const found = checkPath(npmGlobalPath, 'npm root -g'); if (found) return found; }
// PRIORITY 3: Derive from process.execPath const nodeDir = path.dirname(process.execPath); const derivedGlobalPath = path.join(nodeDir, '..', 'lib', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'); const found = checkPath(derivedGlobalPath, 'derived from process.execPath'); if (found) return found;
// PRIORITY 4: Unix systems - try 'which claude' to find binary if (process.platform !== 'win32') { const claudeBinary = safeExec('which claude'); if (claudeBinary) { try { const realBinary = fs.realpathSync(claudeBinary); const binDir = path.dirname(realBinary); const nodeModulesPath = path.join(binDir, '..', 'lib', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'); const foundFromBinary = checkPath(nodeModulesPath, 'which claude'); if (foundFromBinary) return foundFromBinary; } catch (e) { // Failed to resolve, continue } } }
// No installation found getClaudeCodePath.attemptedPaths = attemptedPaths; return null;}
// Read subagent-models.json for model configurationfunction getModelConfiguration() { const homeDir = os.homedir(); const configPaths = [ path.join(homeDir, '.claude', 'subagent-models.json'), path.join(homeDir, '.config', 'claude', 'subagent-models.json'), ];
for (const configPath of configPaths) { if (fs.existsSync(configPath)) { try { const configContent = fs.readFileSync(configPath, 'utf8'); const config = JSON.parse(configContent);
console.log(`Found model configuration in: ${configPath}\n`); return config; } catch (error) { console.warn(`Warning: Could not parse ${configPath}: ${error.message}`); } } }
return null;}
const targetPath = getClaudeCodePath();
if (!targetPath) { console.error('❌ Error: Could not find Claude Code installation\n'); console.error('Searched using the following methods:\n');
const attemptedPaths = getClaudeCodePath.attemptedPaths || [];
if (attemptedPaths.length > 0) { const byMethod = {}; attemptedPaths.forEach(({ path, method }) => { if (!byMethod[method]) byMethod[method] = []; byMethod[method].push(path); });
Object.entries(byMethod).forEach(([method, paths]) => { console.error(` [${method}]`); paths.forEach(p => console.error(` - ${p}`)); }); }
console.error('\n💡 Troubleshooting:'); console.error(' 1. Verify Claude Code is installed: claude --version'); console.error(' 2. For local install: Check ~/.claude/local or ~/.config/claude/local'); console.error(' 3. For global install: Ensure "npm install -g @anthropic-ai/claude-code" succeeded'); process.exit(1);}
console.log(`Found Claude Code at: ${targetPath}\n`);
const backupPath = targetPath + '.subagent-models.backup';
// Restore from backupif (isRestore) { if (!fs.existsSync(backupPath)) { console.error('❌ Error: Backup file not found at:', backupPath); console.error('\n💡 Tip: The backup is created when you first apply the patch.'); process.exit(1); }
console.log('Restoring from backup...'); fs.copyFileSync(backupPath, targetPath); console.log('✅ Restored successfully!'); console.log('\nPlease restart Claude Code for changes to take effect.'); process.exit(0);}
// Get model configurationconst modelConfig = getModelConfiguration();
if (!modelConfig) { console.log('ℹ️ No model configuration found\n'); console.log('To configure subagent models, create ~/.claude/subagent-models.json:\n'); console.log('{'); console.log(' "Plan": "sonnet",'); console.log(' "Explore": "haiku",'); console.log(' "general-purpose": "sonnet"'); console.log('}\n'); console.log('Valid model values: "haiku", "sonnet", "opus"\n'); console.log('Run with --help for more information.'); process.exit(0);}
console.log('Model Configuration:');console.log(` Plan: ${modelConfig.Plan || '(not set)'}`);console.log(` Explore: ${modelConfig.Explore || '(not set)'}`);console.log(` general-purpose: ${modelConfig['general-purpose'] || '(not set)'}`);console.log('');
// Read fileconsole.log('Reading cli.js...');if (!fs.existsSync(targetPath)) { console.error('❌ Error: cli.js not found at:', targetPath); process.exit(1);}
let content = fs.readFileSync(targetPath, 'utf8');
// Define patch patterns for v2.0.33const patches = [];
// Patch 1: Plan agent (a3A)if (modelConfig.Plan) { patches.push({ name: 'Plan agent model', searchPattern: 'a3A={agentType:"Plan",whenToUse:Sw.whenToUse,disallowedTools:Sw.disallowedTools,systemPrompt:Sw.systemPrompt,source:"built-in",tools:Sw.tools,baseDir:"built-in",model:"sonnet"}', replacement: `a3A={agentType:"Plan",whenToUse:Sw.whenToUse,disallowedTools:Sw.disallowedTools,systemPrompt:Sw.systemPrompt,source:"built-in",tools:Sw.tools,baseDir:"built-in",model:"${modelConfig.Plan}"}`, currentValue: 'sonnet', newValue: modelConfig.Plan });}
// Patch 2: Explore agent (Sw) - model appears at end of definition before }});if (modelConfig.Explore) { patches.push({ name: 'Explore agent model', searchPattern: 'Complete the user\'s search request efficiently and report your findings clearly.`,source:"built-in",baseDir:"built-in",model:"haiku"}});var a3A;', replacement: 'Complete the user\'s search request efficiently and report your findings clearly.`,source:"built-in",baseDir:"built-in",model:"' + modelConfig.Explore + '"}});var a3A;', currentValue: 'haiku', newValue: modelConfig.Explore });}
// Patch 3: general-purpose agent (Y01) - this one might not have a model property by defaultif (modelConfig['general-purpose']) { patches.push({ name: 'general-purpose agent model', searchPattern: /Y01=\{agentType:"general-purpose"[^}]*\}/, isRegex: true, replacePattern: (match) => { // Check if it already has a model property if (match.includes(',model:"')) { return match.replace(/,model:"[^"]+"/g, `,model:"${modelConfig['general-purpose']}"`); } else { // Add model property before the closing brace return match.replace(/\}$/, `,model:"${modelConfig['general-purpose']}"}`); } }, currentValue: '(inherited)', newValue: modelConfig['general-purpose'] });}
// Check and apply patchesconsole.log('Checking patches...\n');
const patchResults = [];
for (const patch of patches) { console.log(`Patch: ${patch.name}`); console.log(` ${patch.currentValue} → ${patch.newValue}`);
let canApply = false; let alreadyApplied = false;
if (patch.isRegex) { const regex = patch.searchPattern; const match = content.match(regex); if (match) { const replaced = patch.replacePattern(match[0]); if (match[0] !== replaced) { canApply = true; } else { alreadyApplied = true; } } } else if (patch.partialMatch && patch.findPattern) { const match = content.match(patch.findPattern); if (match) { const currentModel = match[1]; if (currentModel !== patch.newValue) { canApply = true; } else { alreadyApplied = true; } } } else { if (content.includes(patch.searchPattern)) { canApply = true; } else if (content.includes(patch.replacement)) { alreadyApplied = true; } }
if (canApply) { console.log(' ✅ Ready to apply'); patchResults.push({ ...patch, status: 'ready' }); } else if (alreadyApplied) { console.log(' ⚠️ Already applied'); patchResults.push({ ...patch, status: 'applied' }); } else { console.log(' ❌ Pattern not found - may need update for newer version'); patchResults.push({ ...patch, status: 'notfound' }); } console.log('');}
// Dry run modeif (isDryRun) { console.log('📋 DRY RUN - No changes will be made\n'); console.log('Summary:'); patchResults.forEach(p => { console.log(`- ${p.name}: ${p.status === 'ready' ? 'WOULD APPLY' : p.status === 'applied' ? 'SKIP (already applied)' : 'SKIP (not found)'}`); });
const wouldApply = patchResults.filter(p => p.status === 'ready'); if (wouldApply.length > 0) { console.log('\nRun without --dry-run to apply patches.'); } process.exit(0);}
// Apply patchesconst toApply = patchResults.filter(p => p.status === 'ready');
if (toApply.length === 0) { console.log('ℹ️ No patches to apply\n'); const applied = patchResults.filter(p => p.status === 'applied'); if (applied.length > 0) { console.log('All configured patches are already applied.'); } else { console.log('No matching patterns found. The Claude Code version may have changed.'); console.log('Run with --dry-run to see details.'); } process.exit(0);}
// Create backup if it doesn't existif (!fs.existsSync(backupPath)) { console.log('Creating backup...'); fs.copyFileSync(targetPath, backupPath); console.log(`✅ Backup created: ${backupPath}\n`);}
console.log('Applying patches...\n');
// Apply each patchlet patchedContent = content;for (const patch of toApply) { if (patch.isRegex) { const regex = patch.searchPattern; patchedContent = patchedContent.replace(regex, patch.replacePattern); } else if (patch.partialMatch && patch.findPattern) { patchedContent = patchedContent.replace(patch.findPattern, patch.replacePattern); } else { patchedContent = patchedContent.replace(patch.searchPattern, patch.replacement); } console.log(`✅ Applied: ${patch.name}`);}
// Write fileconsole.log('\nWriting patched file...');fs.writeFileSync(targetPath, patchedContent, 'utf8');console.log('✅ File written successfully\n');
console.log('Summary:');patchResults.forEach(p => { console.log(`- ${p.name}: ${p.status === 'ready' ? 'APPLIED' : p.status === 'applied' ? 'SKIPPED (already applied)' : 'SKIPPED (not found)'}`);});console.log('\n🎉 Patches applied! Please restart Claude Code for changes to take effect.');console.log('\nTo restore original behavior, run: node patch-subagent-models.js --restore');process.exit(0);