Files
msd-core/bin/install.js
Lex Christopherson 0ef716c094 feat: support CLAUDE_CONFIG_DIR environment variable
Global installs now respect CLAUDE_CONFIG_DIR when set, allowing users
to customize their Claude config location. Handles tilde expansion for
paths like ~/custom-claude.

Closes #10

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 08:59:25 -06:00

154 lines
4.7 KiB
JavaScript
Executable File

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const os = require('os');
const readline = require('readline');
// Colors
const cyan = '\x1b[36m';
const green = '\x1b[32m';
const yellow = '\x1b[33m';
const dim = '\x1b[2m';
const reset = '\x1b[0m';
// Get version from package.json
const pkg = require('../package.json');
const banner = `
${cyan} ██████╗ ███████╗██████╗
██╔════╝ ██╔════╝██╔══██╗
██║ ███╗███████╗██║ ██║
██║ ██║╚════██║██║ ██║
╚██████╔╝███████║██████╔╝
╚═════╝ ╚══════╝╚═════╝${reset}
Get Shit Done ${dim}v${pkg.version}${reset}
A meta-prompting, context engineering and spec-driven
development system for Claude Code by TÂCHES.
`;
// Parse args
const args = process.argv.slice(2);
const hasGlobal = args.includes('--global') || args.includes('-g');
const hasLocal = args.includes('--local') || args.includes('-l');
console.log(banner);
/**
* 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;
}
/**
* Recursively copy directory, replacing paths in .md files
*/
function copyWithPathReplacement(srcDir, destDir, pathPrefix) {
fs.mkdirSync(destDir, { recursive: true });
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(srcDir, entry.name);
const destPath = path.join(destDir, entry.name);
if (entry.isDirectory()) {
copyWithPathReplacement(srcPath, destPath, pathPrefix);
} else if (entry.name.endsWith('.md')) {
// Replace ~/.claude/ with the appropriate prefix in markdown files
let content = fs.readFileSync(srcPath, 'utf8');
content = content.replace(/~\/\.claude\//g, pathPrefix);
fs.writeFileSync(destPath, content);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}
/**
* Install to the specified directory
*/
function install(isGlobal) {
const src = path.join(__dirname, '..');
const configDir = expandTilde(process.env.CLAUDE_CONFIG_DIR);
const defaultGlobalDir = configDir || path.join(os.homedir(), '.claude');
const claudeDir = isGlobal
? defaultGlobalDir
: path.join(process.cwd(), '.claude');
const locationLabel = isGlobal
? claudeDir.replace(os.homedir(), '~')
: claudeDir.replace(process.cwd(), '.');
// Path prefix for file references
// Use actual path when CLAUDE_CONFIG_DIR is set, otherwise use ~ shorthand
const pathPrefix = isGlobal
? (configDir ? `${claudeDir}/` : '~/.claude/')
: './.claude/';
console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
// Create commands directory
const commandsDir = path.join(claudeDir, 'commands');
fs.mkdirSync(commandsDir, { recursive: true });
// Copy commands/gsd with path replacement
const gsdSrc = path.join(src, 'commands', 'gsd');
const gsdDest = path.join(commandsDir, 'gsd');
copyWithPathReplacement(gsdSrc, gsdDest, pathPrefix);
console.log(` ${green}✓${reset} Installed commands/gsd`);
// Copy get-shit-done skill with path replacement
const skillSrc = path.join(src, 'get-shit-done');
const skillDest = path.join(claudeDir, 'get-shit-done');
copyWithPathReplacement(skillSrc, skillDest, pathPrefix);
console.log(` ${green}✓${reset} Installed get-shit-done`);
console.log(`
${green}Done!${reset} Run ${cyan}/gsd:help${reset} to get started.
`);
}
/**
* Prompt for install location
*/
function promptLocation() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const globalPath = expandTilde(process.env.CLAUDE_CONFIG_DIR) || path.join(os.homedir(), '.claude');
const globalLabel = globalPath.replace(os.homedir(), '~');
console.log(` ${yellow}Where would you like to install?${reset}
${cyan}1${reset}) Global ${dim}(${globalLabel})${reset} - available in all projects
${cyan}2${reset}) Local ${dim}(./.claude)${reset} - this project only
`);
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
rl.close();
const choice = answer.trim() || '1';
const isGlobal = choice !== '2';
install(isGlobal);
});
}
// Main
if (hasGlobal && hasLocal) {
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
process.exit(1);
} else if (hasGlobal) {
install(true);
} else if (hasLocal) {
install(false);
} else {
promptLocation();
}