2026-03-20 01:38:15 -07:00
'use strict' ;
const fs = require ( 'fs' );
const path = require ( 'path' );
const os = require ( 'os' );
2026-04-05 14:31:30 -07:00
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 ],
2026-05-11 16:41:08 -04:00
[ 'marketplaces' , CURRENT_PLUGIN_SLUG ],
2026-04-05 14:31:30 -07:00
[ LEGACY_PLUGIN_SLUG ],
[ LEGACY_PLUGIN_HANDLE ],
2026-05-11 16:41:08 -04:00
[ 'marketplaces' , LEGACY_PLUGIN_SLUG ],
2026-04-05 14:31:30 -07:00
];
2026-03-20 01:38:15 -07:00
/**
* 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
2026-04-05 14:31:30 -07:00
* 3. Known plugin roots under ~/.claude/plugins/ (current + legacy slugs)
* 4. Plugin cache auto-detection — scans ~/.claude/plugins/cache/{ecc,everything-claude-code}/
2026-03-24 23:08:27 -04:00
* 5. Fallback to ~/.claude/ (original behaviour)
2026-03-20 01:38:15 -07:00
*
* @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 ;
}
2026-03-24 23:08:27 -04:00
// Exact legacy plugin install locations. These preserve backwards
// compatibility without scanning arbitrary plugin trees.
2026-04-05 14:31:30 -07:00
const legacyPluginRoots = PLUGIN_ROOT_SEGMENTS . map (( segments ) =>
path . join ( claudeDir , 'plugins' , ... segments )
);
2026-03-24 23:08:27 -04:00
for ( const candidate of legacyPluginRoots ) {
if ( fs . existsSync ( path . join ( candidate , probe ))) {
return candidate ;
}
}
2026-03-20 01:38:15 -07:00
// Plugin cache — Claude Code stores marketplace plugins under
// ~/.claude/plugins/cache/<plugin-name>/<org>/<version>/
try {
2026-04-05 14:31:30 -07:00
for ( const slug of PLUGIN_CACHE_SLUGS ) {
const cacheBase = path . join ( claudeDir , 'plugins' , 'cache' , slug );
const orgDirs = fs . readdirSync ( cacheBase , { withFileTypes : true });
2026-03-20 01:38:15 -07:00
2026-04-05 14:31:30 -07:00
for ( const orgEntry of orgDirs ) {
if ( ! orgEntry . isDirectory ()) continue ;
const orgPath = path . join ( cacheBase , orgEntry . name );
2026-03-20 01:38:15 -07:00
2026-04-05 14:31:30 -07:00
let versionDirs ;
try {
versionDirs = fs . readdirSync ( orgPath , { withFileTypes : true });
} catch {
continue ;
}
2026-03-20 01:38:15 -07:00
2026-04-05 14:31:30 -07:00
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 ;
}
2026-03-20 01:38:15 -07:00
}
}
}
} catch {
// Plugin cache doesn't exist or isn't readable — continue to fallback
}
return claudeDir ;
}
/**
* Compact inline version for embedding in command .md code blocks.
*
* This is the minified form of resolveEccRoot() suitable for use in
* node -e "..." scripts where require() is not available before the
* root is known.
*
* Usage in commands:
* const _r = <paste INLINE_RESOLVE>;
* const sm = require(_r + '/scripts/lib/session-manager');
*/
2026-04-05 14:31:30 -07:00
const INLINE_RESOLVE = `(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var s of ${ JSON . stringify ( PLUGIN_ROOT_SEGMENTS ) } ){var l=p.join(d,'plugins',...s);if(f.existsSync(p.join(l,q)))return l}try{for(var g of ${ JSON . stringify ( PLUGIN_CACHE_SLUGS ) } ){var b=p.join(d,'plugins','cache',g);for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}}catch(x){}return d})()` ;
2026-03-20 01:38:15 -07:00
module . exports = {
resolveEccRoot ,
INLINE_RESOLVE ,
};