'use strict'; const fs = require('fs'); const path = require('path'); const os = require('os'); const CURRENT_PLUGIN_SLUG = 'ecc'; const LEGACY_PLUGIN_SLUG = 'everything-claude-code'; const CURRENT_PLUGIN_HANDLE = `${CURRENT_PLUGIN_SLUG}@${CURRENT_PLUGIN_SLUG}`; const LEGACY_PLUGIN_HANDLE = `${LEGACY_PLUGIN_SLUG}@${LEGACY_PLUGIN_SLUG}`; const PLUGIN_CACHE_SLUGS = [CURRENT_PLUGIN_SLUG, LEGACY_PLUGIN_SLUG]; const PLUGIN_ROOT_SEGMENTS = [ [CURRENT_PLUGIN_SLUG], [CURRENT_PLUGIN_HANDLE], ['marketplaces', CURRENT_PLUGIN_SLUG], [LEGACY_PLUGIN_SLUG], [LEGACY_PLUGIN_HANDLE], ['marketplaces', LEGACY_PLUGIN_SLUG], ]; /** * Resolve the ECC source root directory. * * Tries, in order: * 1. CLAUDE_PLUGIN_ROOT env var (set by Claude Code for hooks, or by user) * 2. Standard install location (~/.claude/) — when scripts exist there * 3. Known plugin roots under ~/.claude/plugins/ (current + legacy slugs) * 4. Plugin cache auto-detection — scans ~/.claude/plugins/cache/{ecc,everything-claude-code}/ * 5. Fallback to ~/.claude/ (original behaviour) * * @param {object} [options] * @param {string} [options.homeDir] Override home directory (for testing) * @param {string} [options.envRoot] Override CLAUDE_PLUGIN_ROOT (for testing) * @param {string} [options.probe] Relative path used to verify a candidate root * contains ECC scripts. Default: 'scripts/lib/utils.js' * @returns {string} Resolved ECC root path */ function resolveEccRoot(options = {}) { const envRoot = options.envRoot !== undefined ? options.envRoot : (process.env.CLAUDE_PLUGIN_ROOT || ''); if (envRoot && envRoot.trim()) { return envRoot.trim(); } const homeDir = options.homeDir || os.homedir(); const claudeDir = path.join(homeDir, '.claude'); const probe = options.probe || path.join('scripts', 'lib', 'utils.js'); // Standard install — files are copied directly into ~/.claude/ if (fs.existsSync(path.join(claudeDir, probe))) { return claudeDir; } // Exact legacy plugin install locations. These preserve backwards // compatibility without scanning arbitrary plugin trees. const legacyPluginRoots = PLUGIN_ROOT_SEGMENTS.map((segments) => path.join(claudeDir, 'plugins', ...segments) ); for (const candidate of legacyPluginRoots) { if (fs.existsSync(path.join(candidate, probe))) { return candidate; } } // Plugin cache — Claude Code stores marketplace plugins under // ~/.claude/plugins/cache//// try { for (const slug of PLUGIN_CACHE_SLUGS) { const cacheBase = path.join(claudeDir, 'plugins', 'cache', slug); const orgDirs = fs.readdirSync(cacheBase, { withFileTypes: true }); for (const orgEntry of orgDirs) { if (!orgEntry.isDirectory()) continue; const orgPath = path.join(cacheBase, orgEntry.name); let versionDirs; try { versionDirs = fs.readdirSync(orgPath, { withFileTypes: true }); } catch { continue; } for (const verEntry of versionDirs) { if (!verEntry.isDirectory()) continue; const candidate = path.join(orgPath, verEntry.name); if (fs.existsSync(path.join(candidate, probe))) { return candidate; } } } } } catch { // Plugin cache doesn't exist or isn't readable — continue to fallback } return claudeDir; } /** * Compact inline locator for embedding in hooks.json and command .md code blocks. * * Earlier revisions inlined the *entire* resolveEccRoot() search (~700 chars, * duplicated ~80×). That blob used a spread (`...s`) over nested array literals, * which broke Windows hook execution due to shell quoting (#2368). * * This minified form contains no spread, no nested array literals, and no * escaped double quotes, so it survives `node -e "..."` quoting on every shell. * When CLAUDE_PLUGIN_ROOT is set (as Claude Code does for plugin hooks and * commands) it is used directly. Otherwise the inline probes the same set of * locations resolveEccRoot() knows about — ~/.claude, the exact plugin roots * under ~/.claude/plugins/, and the versioned plugin cache — only far enough to * load the committed resolve-ecc-root module, then delegates the authoritative * decision to resolveEccRoot(). This keeps discovery behaviour identical to the * old inline while centralising the real logic in one tested module. * * Usage in commands: * const _r = ; * const sm = require(_r + '/scripts/lib/session-manager'); */ const INLINE_RESOLVE = `(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i