* refactor(#56): retire legacy runtime directory helpers into runtime-homes projection Consolidate per-runtime global config-dir resolution onto the single canonical projection runtime-homes:getGlobalConfigDir. Extend it with the explicitDir override (CLI --config-dir) and the opencode/kilo OPENCODE_CONFIG/KILO_CONFIG file-path precedence the installer helpers had, making it byte-for-behavior equivalent to the old getGlobalDir across all 15 install runtimes. Delete bin/install.js's getGlobalDir/getOpencodeGlobalDir/getKiloGlobalDir (and the orphaned local expandTilde), repoint all 9 call-sites, and remove getGlobalDir from module.exports (net -242 lines in the installer). Migrate the 5 test importers to the canonical projection; harden default/XDG assertions against ambient *_CONFIG env vars. getAgentsDir now respects OPENCODE_CONFIG/KILO_CONFIG consistently with the installer (intentional convergence). Update CONTEXT.md Installer Module entry. Completes the installer-refactor chain #58 -> #60 -> #56. Closes #56 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#56): add changeset for runtime directory helper retirement Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
242
bin/install.js
242
bin/install.js
@@ -30,6 +30,7 @@ const {
|
||||
} = require(path.join(__dirname, '..', 'scripts', 'fix-slash-commands.cjs'));
|
||||
const {
|
||||
resolveAntigravityGlobalDir,
|
||||
getGlobalConfigDir,
|
||||
} = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
||||
const {
|
||||
applyWorktreeBaseRef,
|
||||
@@ -408,218 +409,6 @@ function getConfigDirFromHome(runtime, isGlobal) {
|
||||
return "'.claude'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global config directory for OpenCode
|
||||
* OpenCode follows XDG Base Directory spec and uses ~/.config/opencode/
|
||||
* Priority: OPENCODE_CONFIG_DIR > dirname(OPENCODE_CONFIG) > XDG_CONFIG_HOME/opencode > ~/.config/opencode
|
||||
*/
|
||||
function getOpencodeGlobalDir() {
|
||||
// 1. Explicit OPENCODE_CONFIG_DIR env var
|
||||
if (process.env.OPENCODE_CONFIG_DIR) {
|
||||
return expandTilde(process.env.OPENCODE_CONFIG_DIR);
|
||||
}
|
||||
|
||||
// 2. OPENCODE_CONFIG env var (use its directory)
|
||||
if (process.env.OPENCODE_CONFIG) {
|
||||
return path.dirname(expandTilde(process.env.OPENCODE_CONFIG));
|
||||
}
|
||||
|
||||
// 3. XDG_CONFIG_HOME/opencode
|
||||
if (process.env.XDG_CONFIG_HOME) {
|
||||
return path.join(expandTilde(process.env.XDG_CONFIG_HOME), 'opencode');
|
||||
}
|
||||
|
||||
// 4. Default: ~/.config/opencode (XDG default)
|
||||
return path.join(os.homedir(), '.config', 'opencode');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global config directory for Kilo
|
||||
* Kilo follows XDG Base Directory spec and uses ~/.config/kilo/
|
||||
* Priority: KILO_CONFIG_DIR > dirname(KILO_CONFIG) > XDG_CONFIG_HOME/kilo > ~/.config/kilo
|
||||
*/
|
||||
function getKiloGlobalDir() {
|
||||
// 1. Explicit KILO_CONFIG_DIR env var
|
||||
if (process.env.KILO_CONFIG_DIR) {
|
||||
return expandTilde(process.env.KILO_CONFIG_DIR);
|
||||
}
|
||||
|
||||
// 2. KILO_CONFIG env var (use its directory)
|
||||
if (process.env.KILO_CONFIG) {
|
||||
return path.dirname(expandTilde(process.env.KILO_CONFIG));
|
||||
}
|
||||
|
||||
// 3. XDG_CONFIG_HOME/kilo
|
||||
if (process.env.XDG_CONFIG_HOME) {
|
||||
return path.join(expandTilde(process.env.XDG_CONFIG_HOME), 'kilo');
|
||||
}
|
||||
|
||||
// 4. Default: ~/.config/kilo (XDG default)
|
||||
return path.join(os.homedir(), '.config', 'kilo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global config directory for a runtime
|
||||
* @param {string} runtime - 'claude', 'opencode', 'gemini', 'codex', or 'copilot'
|
||||
* @param {string|null} explicitDir - Explicit directory from --config-dir flag
|
||||
*/
|
||||
function getGlobalDir(runtime, explicitDir = null) {
|
||||
if (runtime === 'opencode') {
|
||||
// For OpenCode, --config-dir overrides env vars
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
return getOpencodeGlobalDir();
|
||||
}
|
||||
|
||||
if (runtime === 'kilo') {
|
||||
// For Kilo, --config-dir overrides env vars
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
return getKiloGlobalDir();
|
||||
}
|
||||
|
||||
if (runtime === 'gemini') {
|
||||
// Gemini: --config-dir > GEMINI_CONFIG_DIR > ~/.gemini
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
if (process.env.GEMINI_CONFIG_DIR) {
|
||||
return expandTilde(process.env.GEMINI_CONFIG_DIR);
|
||||
}
|
||||
return path.join(os.homedir(), '.gemini');
|
||||
}
|
||||
|
||||
if (runtime === 'codex') {
|
||||
// Codex: --config-dir > CODEX_HOME > ~/.codex
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
if (process.env.CODEX_HOME) {
|
||||
return expandTilde(process.env.CODEX_HOME);
|
||||
}
|
||||
return path.join(os.homedir(), '.codex');
|
||||
}
|
||||
|
||||
if (runtime === 'copilot') {
|
||||
// Copilot: --config-dir > COPILOT_CONFIG_DIR > ~/.copilot
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
if (process.env.COPILOT_CONFIG_DIR) {
|
||||
return expandTilde(process.env.COPILOT_CONFIG_DIR);
|
||||
}
|
||||
return path.join(os.homedir(), '.copilot');
|
||||
}
|
||||
|
||||
if (runtime === 'antigravity') {
|
||||
// Antigravity: --config-dir > ANTIGRAVITY_CONFIG_DIR > auto-detected
|
||||
// ~/.gemini/{antigravity,antigravity-ide,antigravity-cli}
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
return resolveAntigravityGlobalDir();
|
||||
}
|
||||
|
||||
if (runtime === 'cursor') {
|
||||
// Cursor: --config-dir > CURSOR_CONFIG_DIR > ~/.cursor
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
if (process.env.CURSOR_CONFIG_DIR) {
|
||||
return expandTilde(process.env.CURSOR_CONFIG_DIR);
|
||||
}
|
||||
return path.join(os.homedir(), '.cursor');
|
||||
}
|
||||
|
||||
if (runtime === 'windsurf') {
|
||||
// Windsurf: --config-dir > WINDSURF_CONFIG_DIR > ~/.codeium/windsurf
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
if (process.env.WINDSURF_CONFIG_DIR) {
|
||||
return expandTilde(process.env.WINDSURF_CONFIG_DIR);
|
||||
}
|
||||
return path.join(os.homedir(), '.codeium', 'windsurf');
|
||||
}
|
||||
|
||||
if (runtime === 'augment') {
|
||||
// Augment: --config-dir > AUGMENT_CONFIG_DIR > ~/.augment
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
if (process.env.AUGMENT_CONFIG_DIR) {
|
||||
return expandTilde(process.env.AUGMENT_CONFIG_DIR);
|
||||
}
|
||||
return path.join(os.homedir(), '.augment');
|
||||
}
|
||||
if (runtime === 'trae') {
|
||||
// Trae: --config-dir > TRAE_CONFIG_DIR > ~/.trae
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
if (process.env.TRAE_CONFIG_DIR) {
|
||||
return expandTilde(process.env.TRAE_CONFIG_DIR);
|
||||
}
|
||||
return path.join(os.homedir(), '.trae');
|
||||
}
|
||||
|
||||
if (runtime === 'qwen') {
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
if (process.env.QWEN_CONFIG_DIR) {
|
||||
return expandTilde(process.env.QWEN_CONFIG_DIR);
|
||||
}
|
||||
return path.join(os.homedir(), '.qwen');
|
||||
}
|
||||
|
||||
if (runtime === 'hermes') {
|
||||
// Hermes Agent: --config-dir > HERMES_HOME > ~/.hermes
|
||||
// Honors HERMES_HOME which Hermes users set for profile mode / Docker
|
||||
// deploys (docs: https://hermes-agent.nousresearch.com/docs).
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
if (process.env.HERMES_HOME) {
|
||||
return expandTilde(process.env.HERMES_HOME);
|
||||
}
|
||||
return path.join(os.homedir(), '.hermes');
|
||||
}
|
||||
|
||||
if (runtime === 'codebuddy') {
|
||||
// CodeBuddy: --config-dir > CODEBUDDY_CONFIG_DIR > ~/.codebuddy
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
if (process.env.CODEBUDDY_CONFIG_DIR) {
|
||||
return expandTilde(process.env.CODEBUDDY_CONFIG_DIR);
|
||||
}
|
||||
return path.join(os.homedir(), '.codebuddy');
|
||||
}
|
||||
|
||||
if (runtime === 'cline') {
|
||||
// Cline: --config-dir > CLINE_CONFIG_DIR > ~/.cline
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
if (process.env.CLINE_CONFIG_DIR) {
|
||||
return expandTilde(process.env.CLINE_CONFIG_DIR);
|
||||
}
|
||||
return path.join(os.homedir(), '.cline');
|
||||
}
|
||||
|
||||
// Claude Code: --config-dir > CLAUDE_CONFIG_DIR > ~/.claude
|
||||
if (explicitDir) {
|
||||
return expandTilde(explicitDir);
|
||||
}
|
||||
if (process.env.CLAUDE_CONFIG_DIR) {
|
||||
return expandTilde(process.env.CLAUDE_CONFIG_DIR);
|
||||
}
|
||||
return path.join(os.homedir(), '.claude');
|
||||
}
|
||||
|
||||
const banner = '\n' +
|
||||
cyan + ' ██████╗ ███████╗██████╗\n' +
|
||||
' ██╔════╝ ██╔════╝██╔══██╗\n' +
|
||||
@@ -692,16 +481,6 @@ if (hasHelp) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand ~ to home directory (shell doesn't expand in env vars passed to node)
|
||||
*/
|
||||
function expandTilde(filePath) {
|
||||
if (filePath && filePath.startsWith('~/')) {
|
||||
return path.join(os.homedir(), filePath.slice(2));
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the path prefix used for `@file` references in installed command/skill
|
||||
* markdown. For global installs into a runtime config dir under $HOME, we
|
||||
@@ -1774,11 +1553,11 @@ function getCommitAttribution(runtime) {
|
||||
const resolveConfigPath = runtime === 'opencode'
|
||||
? resolveOpencodeConfigPath
|
||||
: resolveKiloConfigPath;
|
||||
const config = readSettings(resolveConfigPath(getGlobalDir(runtime, null)));
|
||||
const config = readSettings(resolveConfigPath(getGlobalConfigDir(runtime, null)));
|
||||
result = (config && config.disable_ai_attribution === true) ? null : undefined;
|
||||
} else if (runtime === 'gemini') {
|
||||
// Gemini: check gemini settings.json for attribution config
|
||||
const settings = readSettings(path.join(getGlobalDir('gemini', explicitConfigDir), 'settings.json'));
|
||||
const settings = readSettings(path.join(getGlobalConfigDir('gemini', explicitConfigDir), 'settings.json'));
|
||||
if (!settings || !settings.attribution || settings.attribution.commit === undefined) {
|
||||
result = undefined;
|
||||
} else if (settings.attribution.commit === '') {
|
||||
@@ -1788,7 +1567,7 @@ function getCommitAttribution(runtime) {
|
||||
}
|
||||
} else if (runtime === 'claude') {
|
||||
// Claude Code
|
||||
const settings = readSettings(path.join(getGlobalDir('claude', explicitConfigDir), 'settings.json'));
|
||||
const settings = readSettings(path.join(getGlobalConfigDir('claude', explicitConfigDir), 'settings.json'));
|
||||
if (!settings || !settings.attribution || settings.attribution.commit === undefined) {
|
||||
result = undefined;
|
||||
} else if (settings.attribution.commit === '') {
|
||||
@@ -7019,7 +6798,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
||||
|
||||
// Get the target directory based on runtime and install type
|
||||
const targetDir = isGlobal
|
||||
? getGlobalDir(runtime, explicitConfigDir)
|
||||
? getGlobalConfigDir(runtime, explicitConfigDir)
|
||||
: path.join(process.cwd(), dirName);
|
||||
|
||||
const locationLabel = isGlobal
|
||||
@@ -7532,7 +7311,7 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
|
||||
// For local installs, use ./.opencode/
|
||||
// For global installs, use ~/.config/opencode/
|
||||
const opencodeConfigDir = configDir || (isGlobal
|
||||
? getGlobalDir('opencode', explicitConfigDir)
|
||||
? getGlobalConfigDir('opencode', explicitConfigDir)
|
||||
: path.join(process.cwd(), '.opencode'));
|
||||
// Ensure config directory exists
|
||||
fs.mkdirSync(opencodeConfigDir, { recursive: true });
|
||||
@@ -7612,7 +7391,7 @@ function configureKiloPermissions(isGlobal = true, configDir = null) {
|
||||
// For local installs, use ./.kilo/
|
||||
// For global installs, use ~/.config/kilo/
|
||||
const kiloConfigDir = configDir || (isGlobal
|
||||
? getGlobalDir('kilo', explicitConfigDir)
|
||||
? getGlobalConfigDir('kilo', explicitConfigDir)
|
||||
: path.join(process.cwd(), '.kilo'));
|
||||
// Ensure config directory exists
|
||||
fs.mkdirSync(kiloConfigDir, { recursive: true });
|
||||
@@ -8293,7 +8072,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
// Cline local installs write to the project root (like Claude Code) — .clinerules
|
||||
// lives at the root, not inside a .cline/ subdirectory.
|
||||
const targetDir = isGlobal
|
||||
? getGlobalDir(runtime, explicitConfigDir)
|
||||
? getGlobalConfigDir(runtime, explicitConfigDir)
|
||||
: isCline
|
||||
? process.cwd()
|
||||
: path.join(process.cwd(), dirName);
|
||||
@@ -10614,7 +10393,7 @@ function promptLocation(runtimes) {
|
||||
});
|
||||
|
||||
const pathExamples = runtimes.map(r => {
|
||||
const globalPath = getGlobalDir(r, explicitConfigDir);
|
||||
const globalPath = getGlobalConfigDir(r, explicitConfigDir);
|
||||
return globalPath.replace(os.homedir(), '~');
|
||||
}).join(', ');
|
||||
|
||||
@@ -11023,7 +10802,6 @@ module.exports = {
|
||||
GSD_CODEX_MARKER,
|
||||
CODEX_AGENT_SANDBOX,
|
||||
getDirName,
|
||||
getGlobalDir,
|
||||
getConfigDirFromHome,
|
||||
resolveKiloConfigPath,
|
||||
configureKiloPermissions,
|
||||
@@ -11114,7 +10892,7 @@ if (require.main === module && !process.env.GSD_TEST_MODE) {
|
||||
console.error('Usage: node install.js --skills-root <runtime>');
|
||||
process.exit(1);
|
||||
}
|
||||
const globalDir = getGlobalDir(runtimeArg, null);
|
||||
const globalDir = getGlobalConfigDir(runtimeArg, null);
|
||||
// Hermes nests GSD skills under skills/gsd/ as a single category (#2841).
|
||||
// Other runtimes use a flat skills/ root.
|
||||
const skillsRoot = runtimeArg === 'hermes'
|
||||
|
||||
Reference in New Issue
Block a user