feat: add Gemini support to installer (#301)
This commit is contained in:
344
bin/install.js
344
bin/install.js
@@ -21,22 +21,28 @@ const hasGlobal = args.includes('--global') || args.includes('-g');
|
|||||||
const hasLocal = args.includes('--local') || args.includes('-l');
|
const hasLocal = args.includes('--local') || args.includes('-l');
|
||||||
const hasOpencode = args.includes('--opencode');
|
const hasOpencode = args.includes('--opencode');
|
||||||
const hasClaude = args.includes('--claude');
|
const hasClaude = args.includes('--claude');
|
||||||
const hasBoth = args.includes('--both');
|
const hasGemini = args.includes('--gemini');
|
||||||
|
const hasBoth = args.includes('--both'); // Legacy flag, keeps working
|
||||||
|
const hasAll = args.includes('--all');
|
||||||
const hasUninstall = args.includes('--uninstall') || args.includes('-u');
|
const hasUninstall = args.includes('--uninstall') || args.includes('-u');
|
||||||
|
|
||||||
// Runtime selection - can be set by flags or interactive prompt
|
// Runtime selection - can be set by flags or interactive prompt
|
||||||
let selectedRuntimes = [];
|
let selectedRuntimes = [];
|
||||||
if (hasBoth) {
|
if (hasAll) {
|
||||||
|
selectedRuntimes = ['claude', 'opencode', 'gemini'];
|
||||||
|
} else if (hasBoth) {
|
||||||
selectedRuntimes = ['claude', 'opencode'];
|
selectedRuntimes = ['claude', 'opencode'];
|
||||||
} else if (hasOpencode) {
|
} else {
|
||||||
selectedRuntimes = ['opencode'];
|
if (hasOpencode) selectedRuntimes.push('opencode');
|
||||||
} else if (hasClaude) {
|
if (hasClaude) selectedRuntimes.push('claude');
|
||||||
selectedRuntimes = ['claude'];
|
if (hasGemini) selectedRuntimes.push('gemini');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to get directory name for a runtime (used for local/project installs)
|
// Helper to get directory name for a runtime (used for local/project installs)
|
||||||
function getDirName(runtime) {
|
function getDirName(runtime) {
|
||||||
return runtime === 'opencode' ? '.opencode' : '.claude';
|
if (runtime === 'opencode') return '.opencode';
|
||||||
|
if (runtime === 'gemini') return '.gemini';
|
||||||
|
return '.claude';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,7 +72,7 @@ function getOpencodeGlobalDir() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the global config directory for a runtime
|
* Get the global config directory for a runtime
|
||||||
* @param {string} runtime - 'claude' or 'opencode'
|
* @param {string} runtime - 'claude', 'opencode', or 'gemini'
|
||||||
* @param {string|null} explicitDir - Explicit directory from --config-dir flag
|
* @param {string|null} explicitDir - Explicit directory from --config-dir flag
|
||||||
*/
|
*/
|
||||||
function getGlobalDir(runtime, explicitDir = null) {
|
function getGlobalDir(runtime, explicitDir = null) {
|
||||||
@@ -78,6 +84,17 @@ function getGlobalDir(runtime, explicitDir = null) {
|
|||||||
return getOpencodeGlobalDir();
|
return getOpencodeGlobalDir();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
// Claude Code: --config-dir > CLAUDE_CONFIG_DIR > ~/.claude
|
// Claude Code: --config-dir > CLAUDE_CONFIG_DIR > ~/.claude
|
||||||
if (explicitDir) {
|
if (explicitDir) {
|
||||||
return expandTilde(explicitDir);
|
return expandTilde(explicitDir);
|
||||||
@@ -88,18 +105,17 @@ function getGlobalDir(runtime, explicitDir = null) {
|
|||||||
return path.join(os.homedir(), '.claude');
|
return path.join(os.homedir(), '.claude');
|
||||||
}
|
}
|
||||||
|
|
||||||
const banner = `
|
const banner = '\n' +
|
||||||
${cyan} ██████╗ ███████╗██████╗
|
cyan + ' ██████╗ ███████╗██████╗\n' +
|
||||||
██╔════╝ ██╔════╝██╔══██╗
|
' ██╔════╝ ██╔════╝██╔══██╗\n' +
|
||||||
██║ ███╗███████╗██║ ██║
|
' ██║ ███╗███████╗██║ ██║\n' +
|
||||||
██║ ██║╚════██║██║ ██║
|
' ██║ ██║╚════██║██║ ██║\n' +
|
||||||
╚██████╔╝███████║██████╔╝
|
' ╚██████╔╝███████║██████╔╝\n' +
|
||||||
╚═════╝ ╚══════╝╚═════╝${reset}
|
' ╚═════╝ ╚══════╝╚═════╝' + reset + '\n' +
|
||||||
|
'\n' +
|
||||||
Get Shit Done ${dim}v${pkg.version}${reset}
|
' Get Shit Done ' + dim + 'v' + pkg.version + reset + '\n' +
|
||||||
A meta-prompting, context engineering and spec-driven
|
' A meta-prompting, context engineering and spec-driven\n' +
|
||||||
development system for Claude Code (and opencode) by TÂCHES.
|
' development system for Claude Code, OpenCode, and Gemini by TÂCHES.\n';
|
||||||
`;
|
|
||||||
|
|
||||||
// Parse --config-dir argument
|
// Parse --config-dir argument
|
||||||
function parseConfigDirArg() {
|
function parseConfigDirArg() {
|
||||||
@@ -133,49 +149,7 @@ console.log(banner);
|
|||||||
|
|
||||||
// Show help if requested
|
// Show help if requested
|
||||||
if (hasHelp) {
|
if (hasHelp) {
|
||||||
console.log(` ${yellow}Usage:${reset} npx get-shit-done-cc [options]
|
console.log(` ${yellow}Usage:${reset} npx get-shit-done-cc [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall GSD (remove all GSD files)\n ${cyan}-c, --config-dir <path>${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx get-shit-done-cc\n\n ${dim}# Install for Claude Code globally${reset}\n npx get-shit-done-cc --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx get-shit-done-cc --gemini --global\n\n ${dim}# Install for all runtimes globally${reset}\n npx get-shit-done-cc --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx get-shit-done-cc --claude --global --config-dir ~/.claude-bc\n\n ${dim}# Install to current project only${reset}\n npx get-shit-done-cc --claude --local\n\n ${dim}# Uninstall GSD from Claude Code globally${reset}\n npx get-shit-done-cc --claude --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / GEMINI_CONFIG_DIR environment variables.\n`);
|
||||||
|
|
||||||
${yellow}Options:${reset}
|
|
||||||
${cyan}-g, --global${reset} Install globally (to config directory)
|
|
||||||
${cyan}-l, --local${reset} Install locally (to current directory)
|
|
||||||
${cyan}--claude${reset} Install for Claude Code only
|
|
||||||
${cyan}--opencode${reset} Install for OpenCode only
|
|
||||||
${cyan}--both${reset} Install for both Claude Code and OpenCode
|
|
||||||
${cyan}-u, --uninstall${reset} Uninstall GSD (remove all GSD files)
|
|
||||||
${cyan}-c, --config-dir <path>${reset} Specify custom config directory
|
|
||||||
${cyan}-h, --help${reset} Show this help message
|
|
||||||
${cyan}--force-statusline${reset} Replace existing statusline config
|
|
||||||
|
|
||||||
${yellow}Examples:${reset}
|
|
||||||
${dim}# Interactive install (prompts for runtime and location)${reset}
|
|
||||||
npx get-shit-done-cc
|
|
||||||
|
|
||||||
${dim}# Install for Claude Code globally${reset}
|
|
||||||
npx get-shit-done-cc --claude --global
|
|
||||||
|
|
||||||
${dim}# Install for OpenCode globally${reset}
|
|
||||||
npx get-shit-done-cc --opencode --global
|
|
||||||
|
|
||||||
${dim}# Install for both runtimes globally${reset}
|
|
||||||
npx get-shit-done-cc --both --global
|
|
||||||
|
|
||||||
${dim}# Install to custom config directory${reset}
|
|
||||||
npx get-shit-done-cc --claude --global --config-dir ~/.claude-bc
|
|
||||||
|
|
||||||
${dim}# Install to current project only${reset}
|
|
||||||
npx get-shit-done-cc --claude --local
|
|
||||||
|
|
||||||
${dim}# Uninstall GSD from Claude Code globally${reset}
|
|
||||||
npx get-shit-done-cc --claude --global --uninstall
|
|
||||||
|
|
||||||
${dim}# Uninstall GSD from current project${reset}
|
|
||||||
npx get-shit-done-cc --claude --local --uninstall
|
|
||||||
|
|
||||||
${yellow}Notes:${reset}
|
|
||||||
The --config-dir option is useful when you have multiple Claude Code
|
|
||||||
configurations (e.g., for different subscriptions). It takes priority
|
|
||||||
over the CLAUDE_CONFIG_DIR environment variable.
|
|
||||||
`);
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,9 +167,9 @@ function expandTilde(filePath) {
|
|||||||
* Build a hook command path using forward slashes for cross-platform compatibility.
|
* Build a hook command path using forward slashes for cross-platform compatibility.
|
||||||
* On Windows, $HOME is not expanded by cmd.exe/PowerShell, so we use the actual path.
|
* On Windows, $HOME is not expanded by cmd.exe/PowerShell, so we use the actual path.
|
||||||
*/
|
*/
|
||||||
function buildHookCommand(claudeDir, hookName) {
|
function buildHookCommand(configDir, hookName) {
|
||||||
// Use forward slashes for Node.js compatibility on all platforms
|
// Use forward slashes for Node.js compatibility on all platforms
|
||||||
const hooksPath = claudeDir.replace(/\\/g, '/') + '/hooks/' + hookName;
|
const hooksPath = configDir.replace(/\\/g, '/') + '/hooks/' + hookName;
|
||||||
return `node "${hooksPath}"`;
|
return `node "${hooksPath}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,6 +345,47 @@ function convertClaudeToOpencodeFrontmatter(content) {
|
|||||||
return `---\n${newFrontmatter}\n---${body}`;
|
return `---\n${newFrontmatter}\n---${body}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Claude Code markdown command to Gemini TOML format
|
||||||
|
* @param {string} content - Markdown file content with YAML frontmatter
|
||||||
|
* @returns {string} - TOML content
|
||||||
|
*/
|
||||||
|
function convertClaudeToGeminiToml(content) {
|
||||||
|
// Check if content has frontmatter
|
||||||
|
if (!content.startsWith('---')) {
|
||||||
|
return `prompt = ${JSON.stringify(content)}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const endIndex = content.indexOf('---', 3);
|
||||||
|
if (endIndex === -1) {
|
||||||
|
return `prompt = ${JSON.stringify(content)}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const frontmatter = content.substring(3, endIndex).trim();
|
||||||
|
const body = content.substring(endIndex + 3).trim();
|
||||||
|
|
||||||
|
// Extract description from frontmatter
|
||||||
|
let description = '';
|
||||||
|
const lines = frontmatter.split('\n');
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (trimmed.startsWith('description:')) {
|
||||||
|
description = trimmed.substring(12).trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct TOML
|
||||||
|
let toml = '';
|
||||||
|
if (description) {
|
||||||
|
toml += `description = ${JSON.stringify(description)}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
toml += `prompt = ${JSON.stringify(body)}\n`;
|
||||||
|
|
||||||
|
return toml;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy commands to a flat structure for OpenCode
|
* Copy commands to a flat structure for OpenCode
|
||||||
* OpenCode expects: command/gsd-help.md (invoked as /gsd-help)
|
* OpenCode expects: command/gsd-help.md (invoked as /gsd-help)
|
||||||
@@ -410,20 +425,41 @@ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
|
|||||||
} else if (entry.name.endsWith('.md')) {
|
} else if (entry.name.endsWith('.md')) {
|
||||||
// Flatten: help.md -> gsd-help.md
|
// Flatten: help.md -> gsd-help.md
|
||||||
const baseName = entry.name.replace('.md', '');
|
const baseName = entry.name.replace('.md', '');
|
||||||
const destName = `${prefix}-${baseName}.md`;
|
|
||||||
const destPath = path.join(destDir, destName);
|
|
||||||
|
|
||||||
// Read, transform, and write
|
if (runtime === 'gemini') {
|
||||||
let content = fs.readFileSync(srcPath, 'utf8');
|
// Flatten and convert to TOML for Gemini (if we used this function for Gemini, which we don't currently but for consistency)
|
||||||
// Replace path references
|
// Actually Gemini uses nested structure via copyWithPathReplacement, so this block is only for OpenCode currently.
|
||||||
const claudeDirRegex = /~\/\.claude\//g;
|
// Keeping as is for OpenCode.
|
||||||
const opencodeDirRegex = /~\/\.opencode\//g;
|
const destName = `${prefix}-${baseName}.md`;
|
||||||
content = content.replace(claudeDirRegex, pathPrefix);
|
const destPath = path.join(destDir, destName);
|
||||||
content = content.replace(opencodeDirRegex, pathPrefix);
|
|
||||||
// Convert frontmatter for opencode compatibility
|
// Read, transform, and write
|
||||||
content = convertClaudeToOpencodeFrontmatter(content);
|
let content = fs.readFileSync(srcPath, 'utf8');
|
||||||
|
// Replace path references
|
||||||
fs.writeFileSync(destPath, content);
|
const claudeDirRegex = /~\/\.claude\//g;
|
||||||
|
const opencodeDirRegex = /~\/\.opencode\//g;
|
||||||
|
content = content.replace(claudeDirRegex, pathPrefix);
|
||||||
|
content = content.replace(opencodeDirRegex, pathPrefix);
|
||||||
|
// Convert frontmatter for opencode compatibility
|
||||||
|
content = convertClaudeToOpencodeFrontmatter(content);
|
||||||
|
|
||||||
|
fs.writeFileSync(destPath, content);
|
||||||
|
} else {
|
||||||
|
const destName = `${prefix}-${baseName}.md`;
|
||||||
|
const destPath = path.join(destDir, destName);
|
||||||
|
|
||||||
|
// Read, transform, and write
|
||||||
|
let content = fs.readFileSync(srcPath, 'utf8');
|
||||||
|
// Replace path references
|
||||||
|
const claudeDirRegex = /~\/\.claude\//g;
|
||||||
|
const opencodeDirRegex = /~\/\.opencode\//g;
|
||||||
|
content = content.replace(claudeDirRegex, pathPrefix);
|
||||||
|
content = content.replace(opencodeDirRegex, pathPrefix);
|
||||||
|
// Convert frontmatter for opencode compatibility
|
||||||
|
content = convertClaudeToOpencodeFrontmatter(content);
|
||||||
|
|
||||||
|
fs.writeFileSync(destPath, content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -434,7 +470,7 @@ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
|
|||||||
* @param {string} srcDir - Source directory
|
* @param {string} srcDir - Source directory
|
||||||
* @param {string} destDir - Destination directory
|
* @param {string} destDir - Destination directory
|
||||||
* @param {string} pathPrefix - Path prefix for file references
|
* @param {string} pathPrefix - Path prefix for file references
|
||||||
* @param {string} runtime - Target runtime ('claude' or 'opencode')
|
* @param {string} runtime - Target runtime ('claude', 'opencode', 'gemini')
|
||||||
*/
|
*/
|
||||||
function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime) {
|
function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime) {
|
||||||
const isOpencode = runtime === 'opencode';
|
const isOpencode = runtime === 'opencode';
|
||||||
@@ -455,15 +491,24 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime) {
|
|||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
copyWithPathReplacement(srcPath, destPath, pathPrefix, runtime);
|
copyWithPathReplacement(srcPath, destPath, pathPrefix, runtime);
|
||||||
} else if (entry.name.endsWith('.md')) {
|
} else if (entry.name.endsWith('.md')) {
|
||||||
// Replace ~/.claude/ with the appropriate prefix in Markdown files
|
// Always replace ~/.claude/ as it is the source of truth in the repo
|
||||||
let content = fs.readFileSync(srcPath, 'utf8');
|
let content = fs.readFileSync(srcPath, 'utf8');
|
||||||
const claudeDirRegex = new RegExp(`~/${dirName.replace('.', '\\.')}/`, 'g');
|
const claudeDirRegex = /~\/\.claude\//g;
|
||||||
content = content.replace(claudeDirRegex, pathPrefix);
|
content = content.replace(claudeDirRegex, pathPrefix);
|
||||||
|
|
||||||
// Convert frontmatter for opencode compatibility
|
// Convert frontmatter for opencode compatibility
|
||||||
if (isOpencode) {
|
if (isOpencode) {
|
||||||
content = convertClaudeToOpencodeFrontmatter(content);
|
content = convertClaudeToOpencodeFrontmatter(content);
|
||||||
|
fs.writeFileSync(destPath, content);
|
||||||
|
} else if (runtime === 'gemini') {
|
||||||
|
// Convert to TOML for Gemini
|
||||||
|
const tomlContent = convertClaudeToGeminiToml(content);
|
||||||
|
// Replace extension with .toml
|
||||||
|
const tomlPath = destPath.replace(/\.md$/, '.toml');
|
||||||
|
fs.writeFileSync(tomlPath, tomlContent);
|
||||||
|
} else {
|
||||||
|
fs.writeFileSync(destPath, content);
|
||||||
}
|
}
|
||||||
fs.writeFileSync(destPath, content);
|
|
||||||
} else {
|
} else {
|
||||||
fs.copyFileSync(srcPath, destPath);
|
fs.copyFileSync(srcPath, destPath);
|
||||||
}
|
}
|
||||||
@@ -473,14 +518,14 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime) {
|
|||||||
/**
|
/**
|
||||||
* Clean up orphaned files from previous GSD versions
|
* Clean up orphaned files from previous GSD versions
|
||||||
*/
|
*/
|
||||||
function cleanupOrphanedFiles(claudeDir) {
|
function cleanupOrphanedFiles(configDir) {
|
||||||
const orphanedFiles = [
|
const orphanedFiles = [
|
||||||
'hooks/gsd-notify.sh', // Removed in v1.6.x
|
'hooks/gsd-notify.sh', // Removed in v1.6.x
|
||||||
'hooks/statusline.js', // Renamed to gsd-statusline.js in v1.9.0
|
'hooks/statusline.js', // Renamed to gsd-statusline.js in v1.9.0
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const relPath of orphanedFiles) {
|
for (const relPath of orphanedFiles) {
|
||||||
const fullPath = path.join(claudeDir, relPath);
|
const fullPath = path.join(configDir, relPath);
|
||||||
if (fs.existsSync(fullPath)) {
|
if (fs.existsSync(fullPath)) {
|
||||||
fs.unlinkSync(fullPath);
|
fs.unlinkSync(fullPath);
|
||||||
console.log(` ${green}✓${reset} Removed orphaned ${relPath}`);
|
console.log(` ${green}✓${reset} Removed orphaned ${relPath}`);
|
||||||
@@ -537,7 +582,7 @@ function cleanupOrphanedHooks(settings) {
|
|||||||
* Uninstall GSD from the specified directory for a specific runtime
|
* Uninstall GSD from the specified directory for a specific runtime
|
||||||
* Removes only GSD-specific files/directories, preserves user content
|
* Removes only GSD-specific files/directories, preserves user content
|
||||||
* @param {boolean} isGlobal - Whether to uninstall from global or local
|
* @param {boolean} isGlobal - Whether to uninstall from global or local
|
||||||
* @param {string} runtime - Target runtime ('claude' or 'opencode')
|
* @param {string} runtime - Target runtime ('claude', 'opencode', 'gemini')
|
||||||
*/
|
*/
|
||||||
function uninstall(isGlobal, runtime = 'claude') {
|
function uninstall(isGlobal, runtime = 'claude') {
|
||||||
const isOpencode = runtime === 'opencode';
|
const isOpencode = runtime === 'opencode';
|
||||||
@@ -552,7 +597,10 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|||||||
? targetDir.replace(os.homedir(), '~')
|
? targetDir.replace(os.homedir(), '~')
|
||||||
: targetDir.replace(process.cwd(), '.');
|
: targetDir.replace(process.cwd(), '.');
|
||||||
|
|
||||||
const runtimeLabel = isOpencode ? 'OpenCode' : 'Claude Code';
|
let runtimeLabel = 'Claude Code';
|
||||||
|
if (runtime === 'opencode') runtimeLabel = 'OpenCode';
|
||||||
|
if (runtime === 'gemini') runtimeLabel = 'Gemini';
|
||||||
|
|
||||||
console.log(` Uninstalling GSD from ${cyan}${runtimeLabel}${reset} at ${cyan}${locationLabel}${reset}\n`);
|
console.log(` Uninstalling GSD from ${cyan}${runtimeLabel}${reset} at ${cyan}${locationLabel}${reset}\n`);
|
||||||
|
|
||||||
// Check if target directory exists
|
// Check if target directory exists
|
||||||
@@ -579,7 +627,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|||||||
console.log(` ${green}✓${reset} Removed GSD commands from command/`);
|
console.log(` ${green}✓${reset} Removed GSD commands from command/`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Claude Code: remove commands/gsd/ directory
|
// Claude Code & Gemini: remove commands/gsd/ directory
|
||||||
const gsdCommandsDir = path.join(targetDir, 'commands', 'gsd');
|
const gsdCommandsDir = path.join(targetDir, 'commands', 'gsd');
|
||||||
if (fs.existsSync(gsdCommandsDir)) {
|
if (fs.existsSync(gsdCommandsDir)) {
|
||||||
fs.rmSync(gsdCommandsDir, { recursive: true });
|
fs.rmSync(gsdCommandsDir, { recursive: true });
|
||||||
@@ -829,11 +877,12 @@ function verifyFileInstalled(filePath, description) {
|
|||||||
/**
|
/**
|
||||||
* Install to the specified directory for a specific runtime
|
* Install to the specified directory for a specific runtime
|
||||||
* @param {boolean} isGlobal - Whether to install globally or locally
|
* @param {boolean} isGlobal - Whether to install globally or locally
|
||||||
* @param {string} runtime - Target runtime ('claude' or 'opencode')
|
* @param {string} runtime - Target runtime ('claude', 'opencode', 'gemini')
|
||||||
*/
|
*/
|
||||||
function install(isGlobal, runtime = 'claude') {
|
function install(isGlobal, runtime = 'claude') {
|
||||||
const isOpencode = runtime === 'opencode';
|
const isOpencode = runtime === 'opencode';
|
||||||
const dirName = getDirName(runtime); // .opencode or .claude (for local installs)
|
const isGemini = runtime === 'gemini';
|
||||||
|
const dirName = getDirName(runtime);
|
||||||
const src = path.join(__dirname, '..');
|
const src = path.join(__dirname, '..');
|
||||||
|
|
||||||
// Get the target directory based on runtime and install type
|
// Get the target directory based on runtime and install type
|
||||||
@@ -846,13 +895,16 @@ function install(isGlobal, runtime = 'claude') {
|
|||||||
: targetDir.replace(process.cwd(), '.');
|
: targetDir.replace(process.cwd(), '.');
|
||||||
|
|
||||||
// Path prefix for file references in markdown content
|
// Path prefix for file references in markdown content
|
||||||
// For global installs: use full path (necessary when config dir is customized)
|
// For global installs: use full path
|
||||||
// For local installs: use relative ./.opencode/ or ./.claude/
|
// For local installs: use relative
|
||||||
const pathPrefix = isGlobal
|
const pathPrefix = isGlobal
|
||||||
? `${targetDir}/`
|
? `${targetDir}/`
|
||||||
: `./${dirName}/`;
|
: `./${dirName}/`;
|
||||||
|
|
||||||
const runtimeLabel = isOpencode ? 'OpenCode' : 'Claude Code';
|
let runtimeLabel = 'Claude Code';
|
||||||
|
if (isOpencode) runtimeLabel = 'OpenCode';
|
||||||
|
if (isGemini) runtimeLabel = 'Gemini';
|
||||||
|
|
||||||
console.log(` Installing for ${cyan}${runtimeLabel}${reset} to ${cyan}${locationLabel}${reset}\n`);
|
console.log(` Installing for ${cyan}${runtimeLabel}${reset} to ${cyan}${locationLabel}${reset}\n`);
|
||||||
|
|
||||||
// Track installation failures
|
// Track installation failures
|
||||||
@@ -861,8 +913,8 @@ function install(isGlobal, runtime = 'claude') {
|
|||||||
// Clean up orphaned files from previous versions
|
// Clean up orphaned files from previous versions
|
||||||
cleanupOrphanedFiles(targetDir);
|
cleanupOrphanedFiles(targetDir);
|
||||||
|
|
||||||
// OpenCode uses 'command/' (singular) with flat structure: command/gsd-help.md
|
// OpenCode uses 'command/' (singular) with flat structure
|
||||||
// Claude Code uses 'commands/' (plural) with nested structure: commands/gsd/help.md
|
// Claude Code & Gemini use 'commands/' (plural) with nested structure
|
||||||
if (isOpencode) {
|
if (isOpencode) {
|
||||||
// OpenCode: flat structure in command/ directory
|
// OpenCode: flat structure in command/ directory
|
||||||
const commandDir = path.join(targetDir, 'command');
|
const commandDir = path.join(targetDir, 'command');
|
||||||
@@ -878,7 +930,7 @@ function install(isGlobal, runtime = 'claude') {
|
|||||||
failures.push('command/gsd-*');
|
failures.push('command/gsd-*');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Claude Code: nested structure in commands/ directory
|
// Claude Code & Gemini: nested structure in commands/ directory
|
||||||
const commandsDir = path.join(targetDir, 'commands');
|
const commandsDir = path.join(targetDir, 'commands');
|
||||||
fs.mkdirSync(commandsDir, { recursive: true });
|
fs.mkdirSync(commandsDir, { recursive: true });
|
||||||
|
|
||||||
@@ -902,8 +954,7 @@ function install(isGlobal, runtime = 'claude') {
|
|||||||
failures.push('get-shit-done');
|
failures.push('get-shit-done');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy agents to agents directory (subagents must be at root level)
|
// Copy agents to agents directory
|
||||||
// Only delete gsd-*.md files to preserve user's custom agents
|
|
||||||
const agentsSrc = path.join(src, 'agents');
|
const agentsSrc = path.join(src, 'agents');
|
||||||
if (fs.existsSync(agentsSrc)) {
|
if (fs.existsSync(agentsSrc)) {
|
||||||
const agentsDest = path.join(targetDir, 'agents');
|
const agentsDest = path.join(targetDir, 'agents');
|
||||||
@@ -918,12 +969,13 @@ function install(isGlobal, runtime = 'claude') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy new agents (don't use copyWithPathReplacement which would wipe the folder)
|
// Copy new agents
|
||||||
const agentEntries = fs.readdirSync(agentsSrc, { withFileTypes: true });
|
const agentEntries = fs.readdirSync(agentsSrc, { withFileTypes: true });
|
||||||
for (const entry of agentEntries) {
|
for (const entry of agentEntries) {
|
||||||
if (entry.isFile() && entry.name.endsWith('.md')) {
|
if (entry.isFile() && entry.name.endsWith('.md')) {
|
||||||
let content = fs.readFileSync(path.join(agentsSrc, entry.name), 'utf8');
|
let content = fs.readFileSync(path.join(agentsSrc, entry.name), 'utf8');
|
||||||
const dirRegex = new RegExp(`~/${dirName.replace('.', '\\.')}/`, 'g');
|
// Always replace ~/.claude/ as it is the source of truth in the repo
|
||||||
|
const dirRegex = /~\/\.claude\//g;
|
||||||
content = content.replace(dirRegex, pathPrefix);
|
content = content.replace(dirRegex, pathPrefix);
|
||||||
// Convert frontmatter for opencode compatibility
|
// Convert frontmatter for opencode compatibility
|
||||||
if (isOpencode) {
|
if (isOpencode) {
|
||||||
@@ -951,7 +1003,7 @@ function install(isGlobal, runtime = 'claude') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write VERSION file for whats-new command
|
// Write VERSION file
|
||||||
const versionDest = path.join(targetDir, 'get-shit-done', 'VERSION');
|
const versionDest = path.join(targetDir, 'get-shit-done', 'VERSION');
|
||||||
fs.writeFileSync(versionDest, pkg.version);
|
fs.writeFileSync(versionDest, pkg.version);
|
||||||
if (verifyFileInstalled(versionDest, 'VERSION')) {
|
if (verifyFileInstalled(versionDest, 'VERSION')) {
|
||||||
@@ -968,7 +1020,6 @@ function install(isGlobal, runtime = 'claude') {
|
|||||||
const hookEntries = fs.readdirSync(hooksSrc);
|
const hookEntries = fs.readdirSync(hooksSrc);
|
||||||
for (const entry of hookEntries) {
|
for (const entry of hookEntries) {
|
||||||
const srcFile = path.join(hooksSrc, entry);
|
const srcFile = path.join(hooksSrc, entry);
|
||||||
// Only copy files, not directories
|
|
||||||
if (fs.statSync(srcFile).isFile()) {
|
if (fs.statSync(srcFile).isFile()) {
|
||||||
const destFile = path.join(hooksDest, entry);
|
const destFile = path.join(hooksDest, entry);
|
||||||
fs.copyFileSync(srcFile, destFile);
|
fs.copyFileSync(srcFile, destFile);
|
||||||
@@ -981,14 +1032,13 @@ function install(isGlobal, runtime = 'claude') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If critical components failed, exit with error
|
|
||||||
if (failures.length > 0) {
|
if (failures.length > 0) {
|
||||||
console.error(`\n ${yellow}Installation incomplete!${reset} Failed: ${failures.join(', ')}`);
|
console.error(`\n ${yellow}Installation incomplete!${reset} Failed: ${failures.join(', ')}`);
|
||||||
console.error(` Try running directly: node ~/.npm/_npx/*/node_modules/get-shit-done-cc/bin/install.js --global\n`);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure statusline and hooks in settings.json
|
// Configure statusline and hooks in settings.json
|
||||||
|
// Gemini shares same hook system as Claude Code for now
|
||||||
const settingsPath = path.join(targetDir, 'settings.json');
|
const settingsPath = path.join(targetDir, 'settings.json');
|
||||||
const settings = cleanupOrphanedHooks(readSettings(settingsPath));
|
const settings = cleanupOrphanedHooks(readSettings(settingsPath));
|
||||||
const statuslineCommand = isGlobal
|
const statuslineCommand = isGlobal
|
||||||
@@ -998,7 +1048,7 @@ function install(isGlobal, runtime = 'claude') {
|
|||||||
? buildHookCommand(targetDir, 'gsd-check-update.js')
|
? buildHookCommand(targetDir, 'gsd-check-update.js')
|
||||||
: 'node ' + dirName + '/hooks/gsd-check-update.js';
|
: 'node ' + dirName + '/hooks/gsd-check-update.js';
|
||||||
|
|
||||||
// Configure SessionStart hook for update checking (skip for opencode - different hook system)
|
// Configure SessionStart hook for update checking (skip for opencode)
|
||||||
if (!isOpencode) {
|
if (!isOpencode) {
|
||||||
if (!settings.hooks) {
|
if (!settings.hooks) {
|
||||||
settings.hooks = {};
|
settings.hooks = {};
|
||||||
@@ -1007,7 +1057,6 @@ function install(isGlobal, runtime = 'claude') {
|
|||||||
settings.hooks.SessionStart = [];
|
settings.hooks.SessionStart = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if GSD update hook already exists
|
|
||||||
const hasGsdUpdateHook = settings.hooks.SessionStart.some(entry =>
|
const hasGsdUpdateHook = settings.hooks.SessionStart.some(entry =>
|
||||||
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('gsd-check-update'))
|
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('gsd-check-update'))
|
||||||
);
|
);
|
||||||
@@ -1030,11 +1079,6 @@ function install(isGlobal, runtime = 'claude') {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply statusline config, then print completion message
|
* Apply statusline config, then print completion message
|
||||||
* @param {string} settingsPath - Path to settings.json
|
|
||||||
* @param {object} settings - Settings object
|
|
||||||
* @param {string} statuslineCommand - Statusline command
|
|
||||||
* @param {boolean} shouldInstallStatusline - Whether to install statusline
|
|
||||||
* @param {string} runtime - Target runtime ('claude' or 'opencode')
|
|
||||||
*/
|
*/
|
||||||
function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, runtime = 'claude') {
|
function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, runtime = 'claude') {
|
||||||
const isOpencode = runtime === 'opencode';
|
const isOpencode = runtime === 'opencode';
|
||||||
@@ -1047,15 +1091,18 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|||||||
console.log(` ${green}✓${reset} Configured statusline`);
|
console.log(` ${green}✓${reset} Configured statusline`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always write settings (hooks were already configured in install())
|
// Always write settings
|
||||||
writeSettings(settingsPath, settings);
|
writeSettings(settingsPath, settings);
|
||||||
|
|
||||||
// Configure OpenCode permissions if needed
|
// Configure OpenCode permissions
|
||||||
if (isOpencode) {
|
if (isOpencode) {
|
||||||
configureOpencodePermissions();
|
configureOpencodePermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
const program = isOpencode ? 'OpenCode' : 'Claude Code';
|
let program = 'Claude Code';
|
||||||
|
if (runtime === 'opencode') program = 'OpenCode';
|
||||||
|
if (runtime === 'gemini') program = 'Gemini';
|
||||||
|
|
||||||
const command = isOpencode ? '/gsd-help' : '/gsd:help';
|
const command = isOpencode ? '/gsd-help' : '/gsd:help';
|
||||||
console.log(`
|
console.log(`
|
||||||
${green}Done!${reset} Launch ${program} and run ${cyan}${command}${reset}.
|
${green}Done!${reset} Launch ${program} and run ${cyan}${command}${reset}.
|
||||||
@@ -1070,19 +1117,16 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|||||||
function handleStatusline(settings, isInteractive, callback) {
|
function handleStatusline(settings, isInteractive, callback) {
|
||||||
const hasExisting = settings.statusLine != null;
|
const hasExisting = settings.statusLine != null;
|
||||||
|
|
||||||
// No existing statusline - just install it
|
|
||||||
if (!hasExisting) {
|
if (!hasExisting) {
|
||||||
callback(true);
|
callback(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has existing and --force-statusline flag
|
|
||||||
if (forceStatusline) {
|
if (forceStatusline) {
|
||||||
callback(true);
|
callback(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has existing, non-interactive mode - skip
|
|
||||||
if (!isInteractive) {
|
if (!isInteractive) {
|
||||||
console.log(` ${yellow}⚠${reset} Skipping statusline (already configured)`);
|
console.log(` ${yellow}⚠${reset} Skipping statusline (already configured)`);
|
||||||
console.log(` Use ${cyan}--force-statusline${reset} to replace\n`);
|
console.log(` Use ${cyan}--force-statusline${reset} to replace\n`);
|
||||||
@@ -1090,7 +1134,6 @@ function handleStatusline(settings, isInteractive, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has existing, interactive mode - prompt user
|
|
||||||
const existingCmd = settings.statusLine.command || settings.statusLine.url || '(custom)';
|
const existingCmd = settings.statusLine.command || settings.statusLine.url || '(custom)';
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
@@ -1099,8 +1142,7 @@ function handleStatusline(settings, isInteractive, callback) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
console.log(`
|
console.log(`
|
||||||
${yellow}⚠${reset} Existing statusline detected
|
${yellow}⚠${reset} Existing statusline detected\n
|
||||||
|
|
||||||
Your current statusline:
|
Your current statusline:
|
||||||
${dim}command: ${existingCmd}${reset}
|
${dim}command: ${existingCmd}${reset}
|
||||||
|
|
||||||
@@ -1121,8 +1163,7 @@ function handleStatusline(settings, isInteractive, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompt for runtime selection (Claude Code / OpenCode / Both)
|
* Prompt for runtime selection
|
||||||
* @param {function} callback - Called with array of selected runtimes
|
|
||||||
*/
|
*/
|
||||||
function promptRuntime(callback) {
|
function promptRuntime(callback) {
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
@@ -1140,19 +1181,20 @@ function promptRuntime(callback) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(` ${yellow}Which runtime(s) would you like to install for?${reset}
|
console.log(` ${yellow}Which runtime(s) would you like to install for?${reset}\n\n ${cyan}1${reset}) Claude Code ${dim}(~/.claude)${reset}
|
||||||
|
|
||||||
${cyan}1${reset}) Claude Code ${dim}(~/.claude)${reset}
|
|
||||||
${cyan}2${reset}) OpenCode ${dim}(~/.config/opencode)${reset} - open source, free models
|
${cyan}2${reset}) OpenCode ${dim}(~/.config/opencode)${reset} - open source, free models
|
||||||
${cyan}3${reset}) Both
|
${cyan}3${reset}) Gemini ${dim}(~/.gemini)${reset}
|
||||||
|
${cyan}4${reset}) All
|
||||||
`);
|
`);
|
||||||
|
|
||||||
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
||||||
answered = true;
|
answered = true;
|
||||||
rl.close();
|
rl.close();
|
||||||
const choice = answer.trim() || '1';
|
const choice = answer.trim() || '1';
|
||||||
if (choice === '3') {
|
if (choice === '4') {
|
||||||
callback(['claude', 'opencode']);
|
callback(['claude', 'opencode', 'gemini']);
|
||||||
|
} else if (choice === '3') {
|
||||||
|
callback(['gemini']);
|
||||||
} else if (choice === '2') {
|
} else if (choice === '2') {
|
||||||
callback(['opencode']);
|
callback(['opencode']);
|
||||||
} else {
|
} else {
|
||||||
@@ -1163,11 +1205,8 @@ function promptRuntime(callback) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompt for install location
|
* Prompt for install location
|
||||||
* @param {string[]} runtimes - Array of runtimes to install for
|
|
||||||
*/
|
*/
|
||||||
function promptLocation(runtimes) {
|
function promptLocation(runtimes) {
|
||||||
// Check if stdin is a TTY - if not, fall back to global install
|
|
||||||
// This handles npx execution in environments like WSL2 where stdin may not be properly connected
|
|
||||||
if (!process.stdin.isTTY) {
|
if (!process.stdin.isTTY) {
|
||||||
console.log(` ${yellow}Non-interactive terminal detected, defaulting to global install${reset}\n`);
|
console.log(` ${yellow}Non-interactive terminal detected, defaulting to global install${reset}\n`);
|
||||||
installAllRuntimes(runtimes, true, false);
|
installAllRuntimes(runtimes, true, false);
|
||||||
@@ -1179,10 +1218,8 @@ function promptLocation(runtimes) {
|
|||||||
output: process.stdout
|
output: process.stdout
|
||||||
});
|
});
|
||||||
|
|
||||||
// Track whether we've processed the answer to prevent double-execution
|
|
||||||
let answered = false;
|
let answered = false;
|
||||||
|
|
||||||
// Handle readline close event (Ctrl+C, Escape, etc.) - cancel installation
|
|
||||||
rl.on('close', () => {
|
rl.on('close', () => {
|
||||||
if (!answered) {
|
if (!answered) {
|
||||||
answered = true;
|
answered = true;
|
||||||
@@ -1191,18 +1228,14 @@ function promptLocation(runtimes) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show paths for selected runtimes
|
|
||||||
const pathExamples = runtimes.map(r => {
|
const pathExamples = runtimes.map(r => {
|
||||||
// Use the proper global directory function for each runtime
|
|
||||||
const globalPath = getGlobalDir(r, explicitConfigDir);
|
const globalPath = getGlobalDir(r, explicitConfigDir);
|
||||||
return globalPath.replace(os.homedir(), '~');
|
return globalPath.replace(os.homedir(), '~');
|
||||||
}).join(', ');
|
}).join(', ');
|
||||||
|
|
||||||
const localExamples = runtimes.map(r => `./${getDirName(r)}`).join(', ');
|
const localExamples = runtimes.map(r => `./${getDirName(r)}`).join(', ');
|
||||||
|
|
||||||
console.log(` ${yellow}Where would you like to install?${reset}
|
console.log(` ${yellow}Where would you like to install?${reset}\n\n ${cyan}1${reset}) Global ${dim}(${pathExamples})${reset} - available in all projects
|
||||||
|
|
||||||
${cyan}1${reset}) Global ${dim}(${pathExamples})${reset} - available in all projects
|
|
||||||
${cyan}2${reset}) Local ${dim}(${localExamples})${reset} - this project only
|
${cyan}2${reset}) Local ${dim}(${localExamples})${reset} - this project only
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -1217,9 +1250,6 @@ function promptLocation(runtimes) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Install GSD for all selected runtimes
|
* Install GSD for all selected runtimes
|
||||||
* @param {string[]} runtimes - Array of runtimes to install for
|
|
||||||
* @param {boolean} isGlobal - Whether to install globally
|
|
||||||
* @param {boolean} isInteractive - Whether running interactively
|
|
||||||
*/
|
*/
|
||||||
function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
||||||
const results = [];
|
const results = [];
|
||||||
@@ -1229,14 +1259,25 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|||||||
results.push(result);
|
results.push(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle statusline for Claude Code only (OpenCode uses themes)
|
// Handle statusline for Claude & Gemini (OpenCode uses themes)
|
||||||
const claudeResult = results.find(r => r.runtime === 'claude');
|
const claudeResult = results.find(r => r.runtime === 'claude');
|
||||||
|
const geminiResult = results.find(r => r.runtime === 'gemini');
|
||||||
|
|
||||||
if (claudeResult) {
|
// Logic: if both are present, ask once if interactive? Or ask for each?
|
||||||
handleStatusline(claudeResult.settings, isInteractive, (shouldInstallStatusline) => {
|
// Simpler: Ask once and apply to both if applicable.
|
||||||
finishInstall(claudeResult.settingsPath, claudeResult.settings, claudeResult.statuslineCommand, shouldInstallStatusline, 'claude');
|
|
||||||
|
if (claudeResult || geminiResult) {
|
||||||
// Finish OpenCode install if present
|
// Use whichever settings exist to check for existing statusline
|
||||||
|
const primaryResult = claudeResult || geminiResult;
|
||||||
|
|
||||||
|
handleStatusline(primaryResult.settings, isInteractive, (shouldInstallStatusline) => {
|
||||||
|
if (claudeResult) {
|
||||||
|
finishInstall(claudeResult.settingsPath, claudeResult.settings, claudeResult.statuslineCommand, shouldInstallStatusline, 'claude');
|
||||||
|
}
|
||||||
|
if (geminiResult) {
|
||||||
|
finishInstall(geminiResult.settingsPath, geminiResult.settings, geminiResult.statuslineCommand, shouldInstallStatusline, 'gemini');
|
||||||
|
}
|
||||||
|
|
||||||
const opencodeResult = results.find(r => r.runtime === 'opencode');
|
const opencodeResult = results.find(r => r.runtime === 'opencode');
|
||||||
if (opencodeResult) {
|
if (opencodeResult) {
|
||||||
finishInstall(opencodeResult.settingsPath, opencodeResult.settings, opencodeResult.statuslineCommand, false, 'opencode');
|
finishInstall(opencodeResult.settingsPath, opencodeResult.settings, opencodeResult.statuslineCommand, false, 'opencode');
|
||||||
@@ -1249,7 +1290,7 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main
|
// Main logic
|
||||||
if (hasGlobal && hasLocal) {
|
if (hasGlobal && hasLocal) {
|
||||||
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
|
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@@ -1257,10 +1298,8 @@ if (hasGlobal && hasLocal) {
|
|||||||
console.error(` ${yellow}Cannot use --config-dir with --local${reset}`);
|
console.error(` ${yellow}Cannot use --config-dir with --local${reset}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else if (hasUninstall) {
|
} else if (hasUninstall) {
|
||||||
// Uninstall mode
|
|
||||||
if (!hasGlobal && !hasLocal) {
|
if (!hasGlobal && !hasLocal) {
|
||||||
console.error(` ${yellow}--uninstall requires --global or --local${reset}`);
|
console.error(` ${yellow}--uninstall requires --global or --local${reset}`);
|
||||||
console.error(` Example: npx get-shit-done-cc --claude --global --uninstall`);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
const runtimes = selectedRuntimes.length > 0 ? selectedRuntimes : ['claude'];
|
const runtimes = selectedRuntimes.length > 0 ? selectedRuntimes : ['claude'];
|
||||||
@@ -1268,19 +1307,16 @@ if (hasGlobal && hasLocal) {
|
|||||||
uninstall(hasGlobal, runtime);
|
uninstall(hasGlobal, runtime);
|
||||||
}
|
}
|
||||||
} else if (selectedRuntimes.length > 0) {
|
} else if (selectedRuntimes.length > 0) {
|
||||||
// Non-interactive: runtime specified via flags
|
|
||||||
if (!hasGlobal && !hasLocal) {
|
if (!hasGlobal && !hasLocal) {
|
||||||
// Need location but runtime is specified - prompt for location only
|
|
||||||
promptLocation(selectedRuntimes);
|
promptLocation(selectedRuntimes);
|
||||||
} else {
|
} else {
|
||||||
// Both runtime and location specified via flags
|
|
||||||
installAllRuntimes(selectedRuntimes, hasGlobal, false);
|
installAllRuntimes(selectedRuntimes, hasGlobal, false);
|
||||||
}
|
}
|
||||||
} else if (hasGlobal || hasLocal) {
|
} else if (hasGlobal || hasLocal) {
|
||||||
// Location specified but no runtime - default to Claude Code
|
// Default to Claude if no runtime specified but location is
|
||||||
installAllRuntimes(['claude'], hasGlobal, false);
|
installAllRuntimes(['claude'], hasGlobal, false);
|
||||||
} else {
|
} else {
|
||||||
// Fully interactive: prompt for runtime, then location
|
// Interactive
|
||||||
if (!process.stdin.isTTY) {
|
if (!process.stdin.isTTY) {
|
||||||
console.log(` ${yellow}Non-interactive terminal detected, defaulting to Claude Code global install${reset}\n`);
|
console.log(` ${yellow}Non-interactive terminal detected, defaulting to Claude Code global install${reset}\n`);
|
||||||
installAllRuntimes(['claude'], true, false);
|
installAllRuntimes(['claude'], true, false);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "get-shit-done-cc",
|
"name": "get-shit-done-cc",
|
||||||
"version": "1.9.13",
|
"version": "1.9.13",
|
||||||
"description": "A meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.",
|
"description": "A meta-prompting, context engineering and spec-driven development system for Claude Code, OpenCode and Gemini by TÂCHES.",
|
||||||
"bin": {
|
"bin": {
|
||||||
"get-shit-done-cc": "bin/install.js"
|
"get-shit-done-cc": "bin/install.js"
|
||||||
},
|
},
|
||||||
@@ -19,7 +19,9 @@
|
|||||||
"ai",
|
"ai",
|
||||||
"meta-prompting",
|
"meta-prompting",
|
||||||
"context-engineering",
|
"context-engineering",
|
||||||
"spec-driven-development"
|
"spec-driven-development",
|
||||||
|
"gemini",
|
||||||
|
"gemini-cli"
|
||||||
],
|
],
|
||||||
"author": "TÂCHES",
|
"author": "TÂCHES",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
Reference in New Issue
Block a user