get-shit-done-cc 1.9.13 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # GET SHIT DONE
4
4
 
5
- **A light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code and OpenCode.**
5
+ **A light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code, OpenCode, and Gemini CLI.**
6
6
 
7
7
  **Solves context rot — the quality degradation that happens as Claude fills its context window.**
8
8
 
@@ -77,10 +77,10 @@ npx get-shit-done-cc
77
77
  ```
78
78
 
79
79
  The installer prompts you to choose:
80
- 1. **Runtime** — Claude Code, OpenCode, or both
80
+ 1. **Runtime** — Claude Code, OpenCode, Gemini, or all
81
81
  2. **Location** — Global (all projects) or local (current project only)
82
82
 
83
- Verify with `/gsd:help` inside your Claude Code or OpenCode interface.
83
+ Verify with `/gsd:help` inside your chosen runtime.
84
84
 
85
85
  ### Staying Updated
86
86
 
@@ -99,14 +99,17 @@ npx get-shit-done-cc --claude --global # Install to ~/.claude/
99
99
  npx get-shit-done-cc --claude --local # Install to ./.claude/
100
100
 
101
101
  # OpenCode (open source, free models)
102
- npx get-shit-done-cc --opencode --global # Install to ~/.opencode/
102
+ npx get-shit-done-cc --opencode --global # Install to ~/.config/opencode/
103
103
 
104
- # Both runtimes
105
- npx get-shit-done-cc --both --global # Install to both directories
104
+ # Gemini CLI
105
+ npx get-shit-done-cc --gemini --global # Install to ~/.gemini/
106
+
107
+ # All runtimes
108
+ npx get-shit-done-cc --all --global # Install to all directories
106
109
  ```
107
110
 
108
111
  Use `--global` (`-g`) or `--local` (`-l`) to skip the location prompt.
109
- Use `--claude`, `--opencode`, or `--both` to skip the runtime prompt.
112
+ Use `--claude`, `--opencode`, `--gemini`, or `--all` to skip the runtime prompt.
110
113
 
111
114
  </details>
112
115
 
@@ -568,10 +571,14 @@ This removes all GSD commands, agents, hooks, and settings while preserving your
568
571
 
569
572
  ## Community Ports
570
573
 
574
+ OpenCode and Gemini CLI are now natively supported via `npx get-shit-done-cc`.
575
+
576
+ These community ports pioneered multi-runtime support:
577
+
571
578
  | Project | Platform | Description |
572
579
  |---------|----------|-------------|
573
- | [gsd-opencode](https://github.com/rokicool/gsd-opencode) | OpenCode | GSD adapted for OpenCode CLI |
574
- | [gsd-gemini](https://github.com/uberfuzzy/gsd-gemini) | Gemini CLI | GSD adapted for Google's Gemini CLI |
580
+ | [gsd-opencode](https://github.com/rokicool/gsd-opencode) | OpenCode | Original OpenCode adaptation |
581
+ | [gsd-gemini](https://github.com/uberfuzzy/gsd-gemini) | Gemini CLI | Original Gemini adaptation |
575
582
 
576
583
  ---
577
584
 
package/bin/install.js CHANGED
@@ -21,22 +21,28 @@ const hasGlobal = args.includes('--global') || args.includes('-g');
21
21
  const hasLocal = args.includes('--local') || args.includes('-l');
22
22
  const hasOpencode = args.includes('--opencode');
23
23
  const hasClaude = args.includes('--claude');
24
- const hasBoth = args.includes('--both');
24
+ const hasGemini = args.includes('--gemini');
25
+ const hasBoth = args.includes('--both'); // Legacy flag, keeps working
26
+ const hasAll = args.includes('--all');
25
27
  const hasUninstall = args.includes('--uninstall') || args.includes('-u');
26
28
 
27
29
  // Runtime selection - can be set by flags or interactive prompt
28
30
  let selectedRuntimes = [];
29
- if (hasBoth) {
31
+ if (hasAll) {
32
+ selectedRuntimes = ['claude', 'opencode', 'gemini'];
33
+ } else if (hasBoth) {
30
34
  selectedRuntimes = ['claude', 'opencode'];
31
- } else if (hasOpencode) {
32
- selectedRuntimes = ['opencode'];
33
- } else if (hasClaude) {
34
- selectedRuntimes = ['claude'];
35
+ } else {
36
+ if (hasOpencode) selectedRuntimes.push('opencode');
37
+ if (hasClaude) selectedRuntimes.push('claude');
38
+ if (hasGemini) selectedRuntimes.push('gemini');
35
39
  }
36
40
 
37
41
  // Helper to get directory name for a runtime (used for local/project installs)
38
42
  function getDirName(runtime) {
39
- return runtime === 'opencode' ? '.opencode' : '.claude';
43
+ if (runtime === 'opencode') return '.opencode';
44
+ if (runtime === 'gemini') return '.gemini';
45
+ return '.claude';
40
46
  }
41
47
 
42
48
  /**
@@ -66,7 +72,7 @@ function getOpencodeGlobalDir() {
66
72
 
67
73
  /**
68
74
  * Get the global config directory for a runtime
69
- * @param {string} runtime - 'claude' or 'opencode'
75
+ * @param {string} runtime - 'claude', 'opencode', or 'gemini'
70
76
  * @param {string|null} explicitDir - Explicit directory from --config-dir flag
71
77
  */
72
78
  function getGlobalDir(runtime, explicitDir = null) {
@@ -78,6 +84,17 @@ function getGlobalDir(runtime, explicitDir = null) {
78
84
  return getOpencodeGlobalDir();
79
85
  }
80
86
 
87
+ if (runtime === 'gemini') {
88
+ // Gemini: --config-dir > GEMINI_CONFIG_DIR > ~/.gemini
89
+ if (explicitDir) {
90
+ return expandTilde(explicitDir);
91
+ }
92
+ if (process.env.GEMINI_CONFIG_DIR) {
93
+ return expandTilde(process.env.GEMINI_CONFIG_DIR);
94
+ }
95
+ return path.join(os.homedir(), '.gemini');
96
+ }
97
+
81
98
  // Claude Code: --config-dir > CLAUDE_CONFIG_DIR > ~/.claude
82
99
  if (explicitDir) {
83
100
  return expandTilde(explicitDir);
@@ -88,18 +105,17 @@ function getGlobalDir(runtime, explicitDir = null) {
88
105
  return path.join(os.homedir(), '.claude');
89
106
  }
90
107
 
91
- const banner = `
92
- ${cyan} ██████╗ ███████╗██████╗
93
- ██╔════╝ ██╔════╝██╔══██╗
94
- ██║ ███╗███████╗██║ ██║
95
- ██║ ██║╚════██║██║ ██║
96
- ╚██████╔╝███████║██████╔╝
97
- ╚═════╝ ╚══════╝╚═════╝${reset}
98
-
99
- Get Shit Done ${dim}v${pkg.version}${reset}
100
- A meta-prompting, context engineering and spec-driven
101
- development system for Claude Code (and opencode) by TÂCHES.
102
- `;
108
+ const banner = '\n' +
109
+ cyan + ' ██████╗ ███████╗██████╗\n' +
110
+ ' ██╔════╝ ██╔════╝██╔══██╗\n' +
111
+ ' ██║ ███╗███████╗██║ ██║\n' +
112
+ ' ██║ ██║╚════██║██║ ██║\n' +
113
+ ' ╚██████╔╝███████║██████╔╝\n' +
114
+ ' ╚═════╝ ╚══════╝╚═════╝' + reset + '\n' +
115
+ '\n' +
116
+ ' Get Shit Done ' + dim + 'v' + pkg.version + reset + '\n' +
117
+ ' A meta-prompting, context engineering and spec-driven\n' +
118
+ ' development system for Claude Code, OpenCode, and Gemini by TÂCHES.\n';
103
119
 
104
120
  // Parse --config-dir argument
105
121
  function parseConfigDirArg() {
@@ -133,49 +149,7 @@ console.log(banner);
133
149
 
134
150
  // Show help if requested
135
151
  if (hasHelp) {
136
- console.log(` ${yellow}Usage:${reset} npx get-shit-done-cc [options]
137
-
138
- ${yellow}Options:${reset}
139
- ${cyan}-g, --global${reset} Install globally (to config directory)
140
- ${cyan}-l, --local${reset} Install locally (to current directory)
141
- ${cyan}--claude${reset} Install for Claude Code only
142
- ${cyan}--opencode${reset} Install for OpenCode only
143
- ${cyan}--both${reset} Install for both Claude Code and OpenCode
144
- ${cyan}-u, --uninstall${reset} Uninstall GSD (remove all GSD files)
145
- ${cyan}-c, --config-dir <path>${reset} Specify custom config directory
146
- ${cyan}-h, --help${reset} Show this help message
147
- ${cyan}--force-statusline${reset} Replace existing statusline config
148
-
149
- ${yellow}Examples:${reset}
150
- ${dim}# Interactive install (prompts for runtime and location)${reset}
151
- npx get-shit-done-cc
152
-
153
- ${dim}# Install for Claude Code globally${reset}
154
- npx get-shit-done-cc --claude --global
155
-
156
- ${dim}# Install for OpenCode globally${reset}
157
- npx get-shit-done-cc --opencode --global
158
-
159
- ${dim}# Install for both runtimes globally${reset}
160
- npx get-shit-done-cc --both --global
161
-
162
- ${dim}# Install to custom config directory${reset}
163
- npx get-shit-done-cc --claude --global --config-dir ~/.claude-bc
164
-
165
- ${dim}# Install to current project only${reset}
166
- npx get-shit-done-cc --claude --local
167
-
168
- ${dim}# Uninstall GSD from Claude Code globally${reset}
169
- npx get-shit-done-cc --claude --global --uninstall
170
-
171
- ${dim}# Uninstall GSD from current project${reset}
172
- npx get-shit-done-cc --claude --local --uninstall
173
-
174
- ${yellow}Notes:${reset}
175
- The --config-dir option is useful when you have multiple Claude Code
176
- configurations (e.g., for different subscriptions). It takes priority
177
- over the CLAUDE_CONFIG_DIR environment variable.
178
- `);
152
+ 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`);
179
153
  process.exit(0);
180
154
  }
181
155
 
@@ -193,9 +167,9 @@ function expandTilde(filePath) {
193
167
  * Build a hook command path using forward slashes for cross-platform compatibility.
194
168
  * On Windows, $HOME is not expanded by cmd.exe/PowerShell, so we use the actual path.
195
169
  */
196
- function buildHookCommand(claudeDir, hookName) {
170
+ function buildHookCommand(configDir, hookName) {
197
171
  // Use forward slashes for Node.js compatibility on all platforms
198
- const hooksPath = claudeDir.replace(/\\/g, '/') + '/hooks/' + hookName;
172
+ const hooksPath = configDir.replace(/\\/g, '/') + '/hooks/' + hookName;
199
173
  return `node "${hooksPath}"`;
200
174
  }
201
175
 
@@ -371,6 +345,47 @@ function convertClaudeToOpencodeFrontmatter(content) {
371
345
  return `---\n${newFrontmatter}\n---${body}`;
372
346
  }
373
347
 
348
+ /**
349
+ * Convert Claude Code markdown command to Gemini TOML format
350
+ * @param {string} content - Markdown file content with YAML frontmatter
351
+ * @returns {string} - TOML content
352
+ */
353
+ function convertClaudeToGeminiToml(content) {
354
+ // Check if content has frontmatter
355
+ if (!content.startsWith('---')) {
356
+ return `prompt = ${JSON.stringify(content)}\n`;
357
+ }
358
+
359
+ const endIndex = content.indexOf('---', 3);
360
+ if (endIndex === -1) {
361
+ return `prompt = ${JSON.stringify(content)}\n`;
362
+ }
363
+
364
+ const frontmatter = content.substring(3, endIndex).trim();
365
+ const body = content.substring(endIndex + 3).trim();
366
+
367
+ // Extract description from frontmatter
368
+ let description = '';
369
+ const lines = frontmatter.split('\n');
370
+ for (const line of lines) {
371
+ const trimmed = line.trim();
372
+ if (trimmed.startsWith('description:')) {
373
+ description = trimmed.substring(12).trim();
374
+ break;
375
+ }
376
+ }
377
+
378
+ // Construct TOML
379
+ let toml = '';
380
+ if (description) {
381
+ toml += `description = ${JSON.stringify(description)}\n`;
382
+ }
383
+
384
+ toml += `prompt = ${JSON.stringify(body)}\n`;
385
+
386
+ return toml;
387
+ }
388
+
374
389
  /**
375
390
  * Copy commands to a flat structure for OpenCode
376
391
  * OpenCode expects: command/gsd-help.md (invoked as /gsd-help)
@@ -412,17 +427,14 @@ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
412
427
  const baseName = entry.name.replace('.md', '');
413
428
  const destName = `${prefix}-${baseName}.md`;
414
429
  const destPath = path.join(destDir, destName);
415
-
416
- // Read, transform, and write
430
+
417
431
  let content = fs.readFileSync(srcPath, 'utf8');
418
- // Replace path references
419
432
  const claudeDirRegex = /~\/\.claude\//g;
420
433
  const opencodeDirRegex = /~\/\.opencode\//g;
421
434
  content = content.replace(claudeDirRegex, pathPrefix);
422
435
  content = content.replace(opencodeDirRegex, pathPrefix);
423
- // Convert frontmatter for opencode compatibility
424
436
  content = convertClaudeToOpencodeFrontmatter(content);
425
-
437
+
426
438
  fs.writeFileSync(destPath, content);
427
439
  }
428
440
  }
@@ -434,7 +446,7 @@ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
434
446
  * @param {string} srcDir - Source directory
435
447
  * @param {string} destDir - Destination directory
436
448
  * @param {string} pathPrefix - Path prefix for file references
437
- * @param {string} runtime - Target runtime ('claude' or 'opencode')
449
+ * @param {string} runtime - Target runtime ('claude', 'opencode', 'gemini')
438
450
  */
439
451
  function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime) {
440
452
  const isOpencode = runtime === 'opencode';
@@ -455,15 +467,24 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime) {
455
467
  if (entry.isDirectory()) {
456
468
  copyWithPathReplacement(srcPath, destPath, pathPrefix, runtime);
457
469
  } else if (entry.name.endsWith('.md')) {
458
- // Replace ~/.claude/ with the appropriate prefix in Markdown files
470
+ // Always replace ~/.claude/ as it is the source of truth in the repo
459
471
  let content = fs.readFileSync(srcPath, 'utf8');
460
- const claudeDirRegex = new RegExp(`~/${dirName.replace('.', '\\.')}/`, 'g');
472
+ const claudeDirRegex = /~\/\.claude\//g;
461
473
  content = content.replace(claudeDirRegex, pathPrefix);
474
+
462
475
  // Convert frontmatter for opencode compatibility
463
476
  if (isOpencode) {
464
477
  content = convertClaudeToOpencodeFrontmatter(content);
478
+ fs.writeFileSync(destPath, content);
479
+ } else if (runtime === 'gemini') {
480
+ // Convert to TOML for Gemini
481
+ const tomlContent = convertClaudeToGeminiToml(content);
482
+ // Replace extension with .toml
483
+ const tomlPath = destPath.replace(/\.md$/, '.toml');
484
+ fs.writeFileSync(tomlPath, tomlContent);
485
+ } else {
486
+ fs.writeFileSync(destPath, content);
465
487
  }
466
- fs.writeFileSync(destPath, content);
467
488
  } else {
468
489
  fs.copyFileSync(srcPath, destPath);
469
490
  }
@@ -473,14 +494,14 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime) {
473
494
  /**
474
495
  * Clean up orphaned files from previous GSD versions
475
496
  */
476
- function cleanupOrphanedFiles(claudeDir) {
497
+ function cleanupOrphanedFiles(configDir) {
477
498
  const orphanedFiles = [
478
499
  'hooks/gsd-notify.sh', // Removed in v1.6.x
479
500
  'hooks/statusline.js', // Renamed to gsd-statusline.js in v1.9.0
480
501
  ];
481
502
 
482
503
  for (const relPath of orphanedFiles) {
483
- const fullPath = path.join(claudeDir, relPath);
504
+ const fullPath = path.join(configDir, relPath);
484
505
  if (fs.existsSync(fullPath)) {
485
506
  fs.unlinkSync(fullPath);
486
507
  console.log(` ${green}✓${reset} Removed orphaned ${relPath}`);
@@ -537,7 +558,7 @@ function cleanupOrphanedHooks(settings) {
537
558
  * Uninstall GSD from the specified directory for a specific runtime
538
559
  * Removes only GSD-specific files/directories, preserves user content
539
560
  * @param {boolean} isGlobal - Whether to uninstall from global or local
540
- * @param {string} runtime - Target runtime ('claude' or 'opencode')
561
+ * @param {string} runtime - Target runtime ('claude', 'opencode', 'gemini')
541
562
  */
542
563
  function uninstall(isGlobal, runtime = 'claude') {
543
564
  const isOpencode = runtime === 'opencode';
@@ -552,7 +573,10 @@ function uninstall(isGlobal, runtime = 'claude') {
552
573
  ? targetDir.replace(os.homedir(), '~')
553
574
  : targetDir.replace(process.cwd(), '.');
554
575
 
555
- const runtimeLabel = isOpencode ? 'OpenCode' : 'Claude Code';
576
+ let runtimeLabel = 'Claude Code';
577
+ if (runtime === 'opencode') runtimeLabel = 'OpenCode';
578
+ if (runtime === 'gemini') runtimeLabel = 'Gemini';
579
+
556
580
  console.log(` Uninstalling GSD from ${cyan}${runtimeLabel}${reset} at ${cyan}${locationLabel}${reset}\n`);
557
581
 
558
582
  // Check if target directory exists
@@ -579,7 +603,7 @@ function uninstall(isGlobal, runtime = 'claude') {
579
603
  console.log(` ${green}✓${reset} Removed GSD commands from command/`);
580
604
  }
581
605
  } else {
582
- // Claude Code: remove commands/gsd/ directory
606
+ // Claude Code & Gemini: remove commands/gsd/ directory
583
607
  const gsdCommandsDir = path.join(targetDir, 'commands', 'gsd');
584
608
  if (fs.existsSync(gsdCommandsDir)) {
585
609
  fs.rmSync(gsdCommandsDir, { recursive: true });
@@ -829,11 +853,12 @@ function verifyFileInstalled(filePath, description) {
829
853
  /**
830
854
  * Install to the specified directory for a specific runtime
831
855
  * @param {boolean} isGlobal - Whether to install globally or locally
832
- * @param {string} runtime - Target runtime ('claude' or 'opencode')
856
+ * @param {string} runtime - Target runtime ('claude', 'opencode', 'gemini')
833
857
  */
834
858
  function install(isGlobal, runtime = 'claude') {
835
859
  const isOpencode = runtime === 'opencode';
836
- const dirName = getDirName(runtime); // .opencode or .claude (for local installs)
860
+ const isGemini = runtime === 'gemini';
861
+ const dirName = getDirName(runtime);
837
862
  const src = path.join(__dirname, '..');
838
863
 
839
864
  // Get the target directory based on runtime and install type
@@ -846,13 +871,16 @@ function install(isGlobal, runtime = 'claude') {
846
871
  : targetDir.replace(process.cwd(), '.');
847
872
 
848
873
  // Path prefix for file references in markdown content
849
- // For global installs: use full path (necessary when config dir is customized)
850
- // For local installs: use relative ./.opencode/ or ./.claude/
874
+ // For global installs: use full path
875
+ // For local installs: use relative
851
876
  const pathPrefix = isGlobal
852
877
  ? `${targetDir}/`
853
878
  : `./${dirName}/`;
854
879
 
855
- const runtimeLabel = isOpencode ? 'OpenCode' : 'Claude Code';
880
+ let runtimeLabel = 'Claude Code';
881
+ if (isOpencode) runtimeLabel = 'OpenCode';
882
+ if (isGemini) runtimeLabel = 'Gemini';
883
+
856
884
  console.log(` Installing for ${cyan}${runtimeLabel}${reset} to ${cyan}${locationLabel}${reset}\n`);
857
885
 
858
886
  // Track installation failures
@@ -861,8 +889,8 @@ function install(isGlobal, runtime = 'claude') {
861
889
  // Clean up orphaned files from previous versions
862
890
  cleanupOrphanedFiles(targetDir);
863
891
 
864
- // OpenCode uses 'command/' (singular) with flat structure: command/gsd-help.md
865
- // Claude Code uses 'commands/' (plural) with nested structure: commands/gsd/help.md
892
+ // OpenCode uses 'command/' (singular) with flat structure
893
+ // Claude Code & Gemini use 'commands/' (plural) with nested structure
866
894
  if (isOpencode) {
867
895
  // OpenCode: flat structure in command/ directory
868
896
  const commandDir = path.join(targetDir, 'command');
@@ -878,7 +906,7 @@ function install(isGlobal, runtime = 'claude') {
878
906
  failures.push('command/gsd-*');
879
907
  }
880
908
  } else {
881
- // Claude Code: nested structure in commands/ directory
909
+ // Claude Code & Gemini: nested structure in commands/ directory
882
910
  const commandsDir = path.join(targetDir, 'commands');
883
911
  fs.mkdirSync(commandsDir, { recursive: true });
884
912
 
@@ -902,8 +930,7 @@ function install(isGlobal, runtime = 'claude') {
902
930
  failures.push('get-shit-done');
903
931
  }
904
932
 
905
- // Copy agents to agents directory (subagents must be at root level)
906
- // Only delete gsd-*.md files to preserve user's custom agents
933
+ // Copy agents to agents directory
907
934
  const agentsSrc = path.join(src, 'agents');
908
935
  if (fs.existsSync(agentsSrc)) {
909
936
  const agentsDest = path.join(targetDir, 'agents');
@@ -918,12 +945,13 @@ function install(isGlobal, runtime = 'claude') {
918
945
  }
919
946
  }
920
947
 
921
- // Copy new agents (don't use copyWithPathReplacement which would wipe the folder)
948
+ // Copy new agents
922
949
  const agentEntries = fs.readdirSync(agentsSrc, { withFileTypes: true });
923
950
  for (const entry of agentEntries) {
924
951
  if (entry.isFile() && entry.name.endsWith('.md')) {
925
952
  let content = fs.readFileSync(path.join(agentsSrc, entry.name), 'utf8');
926
- const dirRegex = new RegExp(`~/${dirName.replace('.', '\\.')}/`, 'g');
953
+ // Always replace ~/.claude/ as it is the source of truth in the repo
954
+ const dirRegex = /~\/\.claude\//g;
927
955
  content = content.replace(dirRegex, pathPrefix);
928
956
  // Convert frontmatter for opencode compatibility
929
957
  if (isOpencode) {
@@ -951,7 +979,7 @@ function install(isGlobal, runtime = 'claude') {
951
979
  }
952
980
  }
953
981
 
954
- // Write VERSION file for whats-new command
982
+ // Write VERSION file
955
983
  const versionDest = path.join(targetDir, 'get-shit-done', 'VERSION');
956
984
  fs.writeFileSync(versionDest, pkg.version);
957
985
  if (verifyFileInstalled(versionDest, 'VERSION')) {
@@ -968,7 +996,6 @@ function install(isGlobal, runtime = 'claude') {
968
996
  const hookEntries = fs.readdirSync(hooksSrc);
969
997
  for (const entry of hookEntries) {
970
998
  const srcFile = path.join(hooksSrc, entry);
971
- // Only copy files, not directories
972
999
  if (fs.statSync(srcFile).isFile()) {
973
1000
  const destFile = path.join(hooksDest, entry);
974
1001
  fs.copyFileSync(srcFile, destFile);
@@ -981,14 +1008,13 @@ function install(isGlobal, runtime = 'claude') {
981
1008
  }
982
1009
  }
983
1010
 
984
- // If critical components failed, exit with error
985
1011
  if (failures.length > 0) {
986
1012
  console.error(`\n ${yellow}Installation incomplete!${reset} Failed: ${failures.join(', ')}`);
987
- console.error(` Try running directly: node ~/.npm/_npx/*/node_modules/get-shit-done-cc/bin/install.js --global\n`);
988
1013
  process.exit(1);
989
1014
  }
990
1015
 
991
1016
  // Configure statusline and hooks in settings.json
1017
+ // Gemini shares same hook system as Claude Code for now
992
1018
  const settingsPath = path.join(targetDir, 'settings.json');
993
1019
  const settings = cleanupOrphanedHooks(readSettings(settingsPath));
994
1020
  const statuslineCommand = isGlobal
@@ -998,7 +1024,7 @@ function install(isGlobal, runtime = 'claude') {
998
1024
  ? buildHookCommand(targetDir, 'gsd-check-update.js')
999
1025
  : 'node ' + dirName + '/hooks/gsd-check-update.js';
1000
1026
 
1001
- // Configure SessionStart hook for update checking (skip for opencode - different hook system)
1027
+ // Configure SessionStart hook for update checking (skip for opencode)
1002
1028
  if (!isOpencode) {
1003
1029
  if (!settings.hooks) {
1004
1030
  settings.hooks = {};
@@ -1007,7 +1033,6 @@ function install(isGlobal, runtime = 'claude') {
1007
1033
  settings.hooks.SessionStart = [];
1008
1034
  }
1009
1035
 
1010
- // Check if GSD update hook already exists
1011
1036
  const hasGsdUpdateHook = settings.hooks.SessionStart.some(entry =>
1012
1037
  entry.hooks && entry.hooks.some(h => h.command && h.command.includes('gsd-check-update'))
1013
1038
  );
@@ -1030,11 +1055,6 @@ function install(isGlobal, runtime = 'claude') {
1030
1055
 
1031
1056
  /**
1032
1057
  * Apply statusline config, then print completion message
1033
- * @param {string} settingsPath - Path to settings.json
1034
- * @param {object} settings - Settings object
1035
- * @param {string} statuslineCommand - Statusline command
1036
- * @param {boolean} shouldInstallStatusline - Whether to install statusline
1037
- * @param {string} runtime - Target runtime ('claude' or 'opencode')
1038
1058
  */
1039
1059
  function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, runtime = 'claude') {
1040
1060
  const isOpencode = runtime === 'opencode';
@@ -1047,15 +1067,18 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
1047
1067
  console.log(` ${green}✓${reset} Configured statusline`);
1048
1068
  }
1049
1069
 
1050
- // Always write settings (hooks were already configured in install())
1070
+ // Always write settings
1051
1071
  writeSettings(settingsPath, settings);
1052
1072
 
1053
- // Configure OpenCode permissions if needed
1073
+ // Configure OpenCode permissions
1054
1074
  if (isOpencode) {
1055
1075
  configureOpencodePermissions();
1056
1076
  }
1057
1077
 
1058
- const program = isOpencode ? 'OpenCode' : 'Claude Code';
1078
+ let program = 'Claude Code';
1079
+ if (runtime === 'opencode') program = 'OpenCode';
1080
+ if (runtime === 'gemini') program = 'Gemini';
1081
+
1059
1082
  const command = isOpencode ? '/gsd-help' : '/gsd:help';
1060
1083
  console.log(`
1061
1084
  ${green}Done!${reset} Launch ${program} and run ${cyan}${command}${reset}.
@@ -1070,19 +1093,16 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
1070
1093
  function handleStatusline(settings, isInteractive, callback) {
1071
1094
  const hasExisting = settings.statusLine != null;
1072
1095
 
1073
- // No existing statusline - just install it
1074
1096
  if (!hasExisting) {
1075
1097
  callback(true);
1076
1098
  return;
1077
1099
  }
1078
1100
 
1079
- // Has existing and --force-statusline flag
1080
1101
  if (forceStatusline) {
1081
1102
  callback(true);
1082
1103
  return;
1083
1104
  }
1084
1105
 
1085
- // Has existing, non-interactive mode - skip
1086
1106
  if (!isInteractive) {
1087
1107
  console.log(` ${yellow}⚠${reset} Skipping statusline (already configured)`);
1088
1108
  console.log(` Use ${cyan}--force-statusline${reset} to replace\n`);
@@ -1090,7 +1110,6 @@ function handleStatusline(settings, isInteractive, callback) {
1090
1110
  return;
1091
1111
  }
1092
1112
 
1093
- // Has existing, interactive mode - prompt user
1094
1113
  const existingCmd = settings.statusLine.command || settings.statusLine.url || '(custom)';
1095
1114
 
1096
1115
  const rl = readline.createInterface({
@@ -1099,8 +1118,7 @@ function handleStatusline(settings, isInteractive, callback) {
1099
1118
  });
1100
1119
 
1101
1120
  console.log(`
1102
- ${yellow}⚠${reset} Existing statusline detected
1103
-
1121
+ ${yellow}⚠${reset} Existing statusline detected\n
1104
1122
  Your current statusline:
1105
1123
  ${dim}command: ${existingCmd}${reset}
1106
1124
 
@@ -1121,8 +1139,7 @@ function handleStatusline(settings, isInteractive, callback) {
1121
1139
  }
1122
1140
 
1123
1141
  /**
1124
- * Prompt for runtime selection (Claude Code / OpenCode / Both)
1125
- * @param {function} callback - Called with array of selected runtimes
1142
+ * Prompt for runtime selection
1126
1143
  */
1127
1144
  function promptRuntime(callback) {
1128
1145
  const rl = readline.createInterface({
@@ -1140,19 +1157,20 @@ function promptRuntime(callback) {
1140
1157
  }
1141
1158
  });
1142
1159
 
1143
- console.log(` ${yellow}Which runtime(s) would you like to install for?${reset}
1144
-
1145
- ${cyan}1${reset}) Claude Code ${dim}(~/.claude)${reset}
1160
+ console.log(` ${yellow}Which runtime(s) would you like to install for?${reset}\n\n ${cyan}1${reset}) Claude Code ${dim}(~/.claude)${reset}
1146
1161
  ${cyan}2${reset}) OpenCode ${dim}(~/.config/opencode)${reset} - open source, free models
1147
- ${cyan}3${reset}) Both
1162
+ ${cyan}3${reset}) Gemini ${dim}(~/.gemini)${reset}
1163
+ ${cyan}4${reset}) All
1148
1164
  `);
1149
1165
 
1150
1166
  rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
1151
1167
  answered = true;
1152
1168
  rl.close();
1153
1169
  const choice = answer.trim() || '1';
1154
- if (choice === '3') {
1155
- callback(['claude', 'opencode']);
1170
+ if (choice === '4') {
1171
+ callback(['claude', 'opencode', 'gemini']);
1172
+ } else if (choice === '3') {
1173
+ callback(['gemini']);
1156
1174
  } else if (choice === '2') {
1157
1175
  callback(['opencode']);
1158
1176
  } else {
@@ -1163,11 +1181,8 @@ function promptRuntime(callback) {
1163
1181
 
1164
1182
  /**
1165
1183
  * Prompt for install location
1166
- * @param {string[]} runtimes - Array of runtimes to install for
1167
1184
  */
1168
1185
  function promptLocation(runtimes) {
1169
- // Check if stdin is a TTY - if not, fall back to global install
1170
- // This handles npx execution in environments like WSL2 where stdin may not be properly connected
1171
1186
  if (!process.stdin.isTTY) {
1172
1187
  console.log(` ${yellow}Non-interactive terminal detected, defaulting to global install${reset}\n`);
1173
1188
  installAllRuntimes(runtimes, true, false);
@@ -1179,10 +1194,8 @@ function promptLocation(runtimes) {
1179
1194
  output: process.stdout
1180
1195
  });
1181
1196
 
1182
- // Track whether we've processed the answer to prevent double-execution
1183
1197
  let answered = false;
1184
1198
 
1185
- // Handle readline close event (Ctrl+C, Escape, etc.) - cancel installation
1186
1199
  rl.on('close', () => {
1187
1200
  if (!answered) {
1188
1201
  answered = true;
@@ -1191,18 +1204,14 @@ function promptLocation(runtimes) {
1191
1204
  }
1192
1205
  });
1193
1206
 
1194
- // Show paths for selected runtimes
1195
1207
  const pathExamples = runtimes.map(r => {
1196
- // Use the proper global directory function for each runtime
1197
1208
  const globalPath = getGlobalDir(r, explicitConfigDir);
1198
1209
  return globalPath.replace(os.homedir(), '~');
1199
1210
  }).join(', ');
1200
1211
 
1201
1212
  const localExamples = runtimes.map(r => `./${getDirName(r)}`).join(', ');
1202
1213
 
1203
- console.log(` ${yellow}Where would you like to install?${reset}
1204
-
1205
- ${cyan}1${reset}) Global ${dim}(${pathExamples})${reset} - available in all projects
1214
+ console.log(` ${yellow}Where would you like to install?${reset}\n\n ${cyan}1${reset}) Global ${dim}(${pathExamples})${reset} - available in all projects
1206
1215
  ${cyan}2${reset}) Local ${dim}(${localExamples})${reset} - this project only
1207
1216
  `);
1208
1217
 
@@ -1217,9 +1226,6 @@ function promptLocation(runtimes) {
1217
1226
 
1218
1227
  /**
1219
1228
  * Install GSD for all selected runtimes
1220
- * @param {string[]} runtimes - Array of runtimes to install for
1221
- * @param {boolean} isGlobal - Whether to install globally
1222
- * @param {boolean} isInteractive - Whether running interactively
1223
1229
  */
1224
1230
  function installAllRuntimes(runtimes, isGlobal, isInteractive) {
1225
1231
  const results = [];
@@ -1229,14 +1235,25 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
1229
1235
  results.push(result);
1230
1236
  }
1231
1237
 
1232
- // Handle statusline for Claude Code only (OpenCode uses themes)
1238
+ // Handle statusline for Claude & Gemini (OpenCode uses themes)
1233
1239
  const claudeResult = results.find(r => r.runtime === 'claude');
1240
+ const geminiResult = results.find(r => r.runtime === 'gemini');
1234
1241
 
1235
- if (claudeResult) {
1236
- handleStatusline(claudeResult.settings, isInteractive, (shouldInstallStatusline) => {
1237
- finishInstall(claudeResult.settingsPath, claudeResult.settings, claudeResult.statuslineCommand, shouldInstallStatusline, 'claude');
1238
-
1239
- // Finish OpenCode install if present
1242
+ // Logic: if both are present, ask once if interactive? Or ask for each?
1243
+ // Simpler: Ask once and apply to both if applicable.
1244
+
1245
+ if (claudeResult || geminiResult) {
1246
+ // Use whichever settings exist to check for existing statusline
1247
+ const primaryResult = claudeResult || geminiResult;
1248
+
1249
+ handleStatusline(primaryResult.settings, isInteractive, (shouldInstallStatusline) => {
1250
+ if (claudeResult) {
1251
+ finishInstall(claudeResult.settingsPath, claudeResult.settings, claudeResult.statuslineCommand, shouldInstallStatusline, 'claude');
1252
+ }
1253
+ if (geminiResult) {
1254
+ finishInstall(geminiResult.settingsPath, geminiResult.settings, geminiResult.statuslineCommand, shouldInstallStatusline, 'gemini');
1255
+ }
1256
+
1240
1257
  const opencodeResult = results.find(r => r.runtime === 'opencode');
1241
1258
  if (opencodeResult) {
1242
1259
  finishInstall(opencodeResult.settingsPath, opencodeResult.settings, opencodeResult.statuslineCommand, false, 'opencode');
@@ -1249,7 +1266,7 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
1249
1266
  }
1250
1267
  }
1251
1268
 
1252
- // Main
1269
+ // Main logic
1253
1270
  if (hasGlobal && hasLocal) {
1254
1271
  console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
1255
1272
  process.exit(1);
@@ -1257,10 +1274,8 @@ if (hasGlobal && hasLocal) {
1257
1274
  console.error(` ${yellow}Cannot use --config-dir with --local${reset}`);
1258
1275
  process.exit(1);
1259
1276
  } else if (hasUninstall) {
1260
- // Uninstall mode
1261
1277
  if (!hasGlobal && !hasLocal) {
1262
1278
  console.error(` ${yellow}--uninstall requires --global or --local${reset}`);
1263
- console.error(` Example: npx get-shit-done-cc --claude --global --uninstall`);
1264
1279
  process.exit(1);
1265
1280
  }
1266
1281
  const runtimes = selectedRuntimes.length > 0 ? selectedRuntimes : ['claude'];
@@ -1268,19 +1283,16 @@ if (hasGlobal && hasLocal) {
1268
1283
  uninstall(hasGlobal, runtime);
1269
1284
  }
1270
1285
  } else if (selectedRuntimes.length > 0) {
1271
- // Non-interactive: runtime specified via flags
1272
1286
  if (!hasGlobal && !hasLocal) {
1273
- // Need location but runtime is specified - prompt for location only
1274
1287
  promptLocation(selectedRuntimes);
1275
1288
  } else {
1276
- // Both runtime and location specified via flags
1277
1289
  installAllRuntimes(selectedRuntimes, hasGlobal, false);
1278
1290
  }
1279
1291
  } else if (hasGlobal || hasLocal) {
1280
- // Location specified but no runtime - default to Claude Code
1292
+ // Default to Claude if no runtime specified but location is
1281
1293
  installAllRuntimes(['claude'], hasGlobal, false);
1282
1294
  } else {
1283
- // Fully interactive: prompt for runtime, then location
1295
+ // Interactive
1284
1296
  if (!process.stdin.isTTY) {
1285
1297
  console.log(` ${yellow}Non-interactive terminal detected, defaulting to Claude Code global install${reset}\n`);
1286
1298
  installAllRuntimes(['claude'], true, false);
@@ -18,22 +18,25 @@ process.stdin.on('end', () => {
18
18
  const session = data.session_id || '';
19
19
  const remaining = data.context_window?.remaining_percentage;
20
20
 
21
- // Context window display (shows USED percentage)
21
+ // Context window display (shows USED percentage scaled to 80% limit)
22
+ // Claude Code enforces an 80% context limit, so we scale to show 100% at that point
22
23
  let ctx = '';
23
24
  if (remaining != null) {
24
25
  const rem = Math.round(remaining);
25
- const used = Math.max(0, Math.min(100, 100 - rem));
26
+ const rawUsed = Math.max(0, Math.min(100, 100 - rem));
27
+ // Scale: 80% real usage = 100% displayed
28
+ const used = Math.min(100, Math.round((rawUsed / 80) * 100));
26
29
 
27
30
  // Build progress bar (10 segments)
28
31
  const filled = Math.floor(used / 10);
29
32
  const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
30
33
 
31
- // Color based on usage
32
- if (used < 50) {
34
+ // Color based on scaled usage (thresholds adjusted for new scale)
35
+ if (used < 63) { // ~50% real
33
36
  ctx = ` \x1b[32m${bar} ${used}%\x1b[0m`;
34
- } else if (used < 65) {
37
+ } else if (used < 81) { // ~65% real
35
38
  ctx = ` \x1b[33m${bar} ${used}%\x1b[0m`;
36
- } else if (used < 80) {
39
+ } else if (used < 95) { // ~76% real
37
40
  ctx = ` \x1b[38;5;208m${bar} ${used}%\x1b[0m`;
38
41
  } else {
39
42
  ctx = ` \x1b[5;31m💀 ${bar} ${used}%\x1b[0m`;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "get-shit-done-cc",
3
- "version": "1.9.13",
4
- "description": "A meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.",
3
+ "version": "1.10.0",
4
+ "description": "A meta-prompting, context engineering and spec-driven development system for Claude Code, OpenCode and Gemini by TÂCHES.",
5
5
  "bin": {
6
6
  "get-shit-done-cc": "bin/install.js"
7
7
  },
@@ -19,7 +19,9 @@
19
19
  "ai",
20
20
  "meta-prompting",
21
21
  "context-engineering",
22
- "spec-driven-development"
22
+ "spec-driven-development",
23
+ "gemini",
24
+ "gemini-cli"
23
25
  ],
24
26
  "author": "TÂCHES",
25
27
  "license": "MIT",