#!/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(); }