get-shit-done-cc 1.9.6 → 1.9.11

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
@@ -8,8 +8,9 @@
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/get-shit-done-cc?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/get-shit-done-cc)
10
10
  [![npm downloads](https://img.shields.io/npm/dm/get-shit-done-cc?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/get-shit-done-cc)
11
- [![License](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](LICENSE)
11
+ [![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/5JJgD5svVS)
12
12
  [![GitHub stars](https://img.shields.io/github/stars/glittercowboy/get-shit-done?style=for-the-badge&logo=github&color=181717)](https://github.com/glittercowboy/get-shit-done)
13
+ [![License](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](LICENSE)
13
14
 
14
15
  <br>
15
16
 
@@ -440,6 +441,7 @@ You're never locked in. The system adapts.
440
441
  | `/gsd:help` | Show all commands and usage guide |
441
442
  | `/gsd:whats-new` | See what changed since your installed version |
442
443
  | `/gsd:update` | Update GSD with changelog preview |
444
+ | `/gsd:join-discord` | Join the GSD Discord community |
443
445
 
444
446
  ### Brownfield
445
447
 
@@ -553,6 +555,22 @@ CLAUDE_CONFIG_DIR=/home/youruser/.claude npx get-shit-done-cc --global
553
555
  ```
554
556
  This ensures absolute paths are used instead of `~` which may not expand correctly in containers.
555
557
 
558
+ ### Uninstalling
559
+
560
+ To remove GSD completely:
561
+
562
+ ```bash
563
+ # Global installs
564
+ npx get-shit-done-cc --claude --global --uninstall
565
+ npx get-shit-done-cc --opencode --global --uninstall
566
+
567
+ # Local installs (current project)
568
+ npx get-shit-done-cc --claude --local --uninstall
569
+ npx get-shit-done-cc --opencode --local --uninstall
570
+ ```
571
+
572
+ This removes all GSD commands, agents, hooks, and settings while preserving your other configurations.
573
+
556
574
  ---
557
575
 
558
576
  ## Community Ports
package/bin/install.js CHANGED
@@ -22,6 +22,7 @@ const hasLocal = args.includes('--local') || args.includes('-l');
22
22
  const hasOpencode = args.includes('--opencode');
23
23
  const hasClaude = args.includes('--claude');
24
24
  const hasBoth = args.includes('--both');
25
+ const hasUninstall = args.includes('--uninstall') || args.includes('-u');
25
26
 
26
27
  // Runtime selection - can be set by flags or interactive prompt
27
28
  let selectedRuntimes = [];
@@ -33,11 +34,60 @@ if (hasBoth) {
33
34
  selectedRuntimes = ['claude'];
34
35
  }
35
36
 
36
- // Helper to get directory name for a runtime
37
+ // Helper to get directory name for a runtime (used for local/project installs)
37
38
  function getDirName(runtime) {
38
39
  return runtime === 'opencode' ? '.opencode' : '.claude';
39
40
  }
40
41
 
42
+ /**
43
+ * Get the global config directory for OpenCode
44
+ * OpenCode follows XDG Base Directory spec and uses ~/.config/opencode/
45
+ * Priority: OPENCODE_CONFIG_DIR > dirname(OPENCODE_CONFIG) > XDG_CONFIG_HOME/opencode > ~/.config/opencode
46
+ */
47
+ function getOpencodeGlobalDir() {
48
+ // 1. Explicit OPENCODE_CONFIG_DIR env var
49
+ if (process.env.OPENCODE_CONFIG_DIR) {
50
+ return expandTilde(process.env.OPENCODE_CONFIG_DIR);
51
+ }
52
+
53
+ // 2. OPENCODE_CONFIG env var (use its directory)
54
+ if (process.env.OPENCODE_CONFIG) {
55
+ return path.dirname(expandTilde(process.env.OPENCODE_CONFIG));
56
+ }
57
+
58
+ // 3. XDG_CONFIG_HOME/opencode
59
+ if (process.env.XDG_CONFIG_HOME) {
60
+ return path.join(expandTilde(process.env.XDG_CONFIG_HOME), 'opencode');
61
+ }
62
+
63
+ // 4. Default: ~/.config/opencode (XDG default)
64
+ return path.join(os.homedir(), '.config', 'opencode');
65
+ }
66
+
67
+ /**
68
+ * Get the global config directory for a runtime
69
+ * @param {string} runtime - 'claude' or 'opencode'
70
+ * @param {string|null} explicitDir - Explicit directory from --config-dir flag
71
+ */
72
+ function getGlobalDir(runtime, explicitDir = null) {
73
+ if (runtime === 'opencode') {
74
+ // For OpenCode, --config-dir overrides env vars
75
+ if (explicitDir) {
76
+ return expandTilde(explicitDir);
77
+ }
78
+ return getOpencodeGlobalDir();
79
+ }
80
+
81
+ // Claude Code: --config-dir > CLAUDE_CONFIG_DIR > ~/.claude
82
+ if (explicitDir) {
83
+ return expandTilde(explicitDir);
84
+ }
85
+ if (process.env.CLAUDE_CONFIG_DIR) {
86
+ return expandTilde(process.env.CLAUDE_CONFIG_DIR);
87
+ }
88
+ return path.join(os.homedir(), '.claude');
89
+ }
90
+
41
91
  const banner = `
42
92
  ${cyan} ██████╗ ███████╗██████╗
43
93
  ██╔════╝ ██╔════╝██╔══██╗
@@ -91,6 +141,7 @@ if (hasHelp) {
91
141
  ${cyan}--claude${reset} Install for Claude Code only
92
142
  ${cyan}--opencode${reset} Install for OpenCode only
93
143
  ${cyan}--both${reset} Install for both Claude Code and OpenCode
144
+ ${cyan}-u, --uninstall${reset} Uninstall GSD (remove all GSD files)
94
145
  ${cyan}-c, --config-dir <path>${reset} Specify custom config directory
95
146
  ${cyan}-h, --help${reset} Show this help message
96
147
  ${cyan}--force-statusline${reset} Replace existing statusline config
@@ -114,6 +165,12 @@ if (hasHelp) {
114
165
  ${dim}# Install to current project only${reset}
115
166
  npx get-shit-done-cc --claude --local
116
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
+
117
174
  ${yellow}Notes:${reset}
118
175
  The --config-dir option is useful when you have multiple Claude Code
119
176
  configurations (e.g., for different subscriptions). It takes priority
@@ -220,10 +277,10 @@ function convertClaudeToOpencodeFrontmatter(content) {
220
277
  convertedContent = convertedContent.replace(/\bAskUserQuestion\b/g, 'question');
221
278
  convertedContent = convertedContent.replace(/\bSlashCommand\b/g, 'skill');
222
279
  convertedContent = convertedContent.replace(/\bTodoWrite\b/g, 'todowrite');
223
- // Replace /gsd:command with /gsd/command for opencode
224
- convertedContent = convertedContent.replace(/\/gsd:/g, '/gsd/');
225
- // Replace ~/.claude with ~/.opencode
226
- convertedContent = convertedContent.replace(/~\/\.claude\b/g, '~/.opencode');
280
+ // Replace /gsd:command with /gsd-command for opencode (flat command structure)
281
+ convertedContent = convertedContent.replace(/\/gsd:/g, '/gsd-');
282
+ // Replace ~/.claude with ~/.config/opencode (OpenCode's correct config location)
283
+ convertedContent = convertedContent.replace(/~\/\.claude\b/g, '~/.config/opencode');
227
284
 
228
285
  // Check if content has frontmatter
229
286
  if (!convertedContent.startsWith('---')) {
@@ -314,6 +371,63 @@ function convertClaudeToOpencodeFrontmatter(content) {
314
371
  return `---\n${newFrontmatter}\n---${body}`;
315
372
  }
316
373
 
374
+ /**
375
+ * Copy commands to a flat structure for OpenCode
376
+ * OpenCode expects: command/gsd-help.md (invoked as /gsd-help)
377
+ * Source structure: commands/gsd/help.md
378
+ *
379
+ * @param {string} srcDir - Source directory (e.g., commands/gsd/)
380
+ * @param {string} destDir - Destination directory (e.g., command/)
381
+ * @param {string} prefix - Prefix for filenames (e.g., 'gsd')
382
+ * @param {string} pathPrefix - Path prefix for file references
383
+ * @param {string} runtime - Target runtime ('claude' or 'opencode')
384
+ */
385
+ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
386
+ if (!fs.existsSync(srcDir)) {
387
+ return;
388
+ }
389
+
390
+ // Remove old gsd-*.md files before copying new ones
391
+ if (fs.existsSync(destDir)) {
392
+ for (const file of fs.readdirSync(destDir)) {
393
+ if (file.startsWith(`${prefix}-`) && file.endsWith('.md')) {
394
+ fs.unlinkSync(path.join(destDir, file));
395
+ }
396
+ }
397
+ } else {
398
+ fs.mkdirSync(destDir, { recursive: true });
399
+ }
400
+
401
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
402
+
403
+ for (const entry of entries) {
404
+ const srcPath = path.join(srcDir, entry.name);
405
+
406
+ if (entry.isDirectory()) {
407
+ // Recurse into subdirectories, adding to prefix
408
+ // e.g., commands/gsd/debug/start.md -> command/gsd-debug-start.md
409
+ copyFlattenedCommands(srcPath, destDir, `${prefix}-${entry.name}`, pathPrefix, runtime);
410
+ } else if (entry.name.endsWith('.md')) {
411
+ // Flatten: help.md -> gsd-help.md
412
+ const baseName = entry.name.replace('.md', '');
413
+ const destName = `${prefix}-${baseName}.md`;
414
+ const destPath = path.join(destDir, destName);
415
+
416
+ // Read, transform, and write
417
+ let content = fs.readFileSync(srcPath, 'utf8');
418
+ // Replace path references
419
+ const claudeDirRegex = /~\/\.claude\//g;
420
+ const opencodeDirRegex = /~\/\.opencode\//g;
421
+ content = content.replace(claudeDirRegex, pathPrefix);
422
+ content = content.replace(opencodeDirRegex, pathPrefix);
423
+ // Convert frontmatter for opencode compatibility
424
+ content = convertClaudeToOpencodeFrontmatter(content);
425
+
426
+ fs.writeFileSync(destPath, content);
427
+ }
428
+ }
429
+ }
430
+
317
431
  /**
318
432
  * Recursively copy directory, replacing paths in .md files
319
433
  * Deletes existing destDir first to remove orphaned files from previous versions
@@ -419,12 +533,214 @@ function cleanupOrphanedHooks(settings) {
419
533
  return settings;
420
534
  }
421
535
 
536
+ /**
537
+ * Uninstall GSD from the specified directory for a specific runtime
538
+ * Removes only GSD-specific files/directories, preserves user content
539
+ * @param {boolean} isGlobal - Whether to uninstall from global or local
540
+ * @param {string} runtime - Target runtime ('claude' or 'opencode')
541
+ */
542
+ function uninstall(isGlobal, runtime = 'claude') {
543
+ const isOpencode = runtime === 'opencode';
544
+ const dirName = getDirName(runtime);
545
+
546
+ // Get the target directory based on runtime and install type
547
+ const targetDir = isGlobal
548
+ ? getGlobalDir(runtime, explicitConfigDir)
549
+ : path.join(process.cwd(), dirName);
550
+
551
+ const locationLabel = isGlobal
552
+ ? targetDir.replace(os.homedir(), '~')
553
+ : targetDir.replace(process.cwd(), '.');
554
+
555
+ const runtimeLabel = isOpencode ? 'OpenCode' : 'Claude Code';
556
+ console.log(` Uninstalling GSD from ${cyan}${runtimeLabel}${reset} at ${cyan}${locationLabel}${reset}\n`);
557
+
558
+ // Check if target directory exists
559
+ if (!fs.existsSync(targetDir)) {
560
+ console.log(` ${yellow}⚠${reset} Directory does not exist: ${locationLabel}`);
561
+ console.log(` Nothing to uninstall.\n`);
562
+ return;
563
+ }
564
+
565
+ let removedCount = 0;
566
+
567
+ // 1. Remove GSD commands directory
568
+ if (isOpencode) {
569
+ // OpenCode: remove command/gsd-*.md files
570
+ const commandDir = path.join(targetDir, 'command');
571
+ if (fs.existsSync(commandDir)) {
572
+ const files = fs.readdirSync(commandDir);
573
+ for (const file of files) {
574
+ if (file.startsWith('gsd-') && file.endsWith('.md')) {
575
+ fs.unlinkSync(path.join(commandDir, file));
576
+ removedCount++;
577
+ }
578
+ }
579
+ console.log(` ${green}✓${reset} Removed GSD commands from command/`);
580
+ }
581
+ } else {
582
+ // Claude Code: remove commands/gsd/ directory
583
+ const gsdCommandsDir = path.join(targetDir, 'commands', 'gsd');
584
+ if (fs.existsSync(gsdCommandsDir)) {
585
+ fs.rmSync(gsdCommandsDir, { recursive: true });
586
+ removedCount++;
587
+ console.log(` ${green}✓${reset} Removed commands/gsd/`);
588
+ }
589
+ }
590
+
591
+ // 2. Remove get-shit-done directory
592
+ const gsdDir = path.join(targetDir, 'get-shit-done');
593
+ if (fs.existsSync(gsdDir)) {
594
+ fs.rmSync(gsdDir, { recursive: true });
595
+ removedCount++;
596
+ console.log(` ${green}✓${reset} Removed get-shit-done/`);
597
+ }
598
+
599
+ // 3. Remove GSD agents (gsd-*.md files only)
600
+ const agentsDir = path.join(targetDir, 'agents');
601
+ if (fs.existsSync(agentsDir)) {
602
+ const files = fs.readdirSync(agentsDir);
603
+ let agentCount = 0;
604
+ for (const file of files) {
605
+ if (file.startsWith('gsd-') && file.endsWith('.md')) {
606
+ fs.unlinkSync(path.join(agentsDir, file));
607
+ agentCount++;
608
+ }
609
+ }
610
+ if (agentCount > 0) {
611
+ removedCount++;
612
+ console.log(` ${green}✓${reset} Removed ${agentCount} GSD agents`);
613
+ }
614
+ }
615
+
616
+ // 4. Remove GSD hooks
617
+ const hooksDir = path.join(targetDir, 'hooks');
618
+ if (fs.existsSync(hooksDir)) {
619
+ const gsdHooks = ['gsd-statusline.js', 'gsd-check-update.js', 'gsd-check-update.sh'];
620
+ let hookCount = 0;
621
+ for (const hook of gsdHooks) {
622
+ const hookPath = path.join(hooksDir, hook);
623
+ if (fs.existsSync(hookPath)) {
624
+ fs.unlinkSync(hookPath);
625
+ hookCount++;
626
+ }
627
+ }
628
+ if (hookCount > 0) {
629
+ removedCount++;
630
+ console.log(` ${green}✓${reset} Removed ${hookCount} GSD hooks`);
631
+ }
632
+ }
633
+
634
+ // 5. Clean up settings.json (remove GSD hooks and statusline)
635
+ const settingsPath = path.join(targetDir, 'settings.json');
636
+ if (fs.existsSync(settingsPath)) {
637
+ let settings = readSettings(settingsPath);
638
+ let settingsModified = false;
639
+
640
+ // Remove GSD statusline if it references our hook
641
+ if (settings.statusLine && settings.statusLine.command &&
642
+ settings.statusLine.command.includes('gsd-statusline')) {
643
+ delete settings.statusLine;
644
+ settingsModified = true;
645
+ console.log(` ${green}✓${reset} Removed GSD statusline from settings`);
646
+ }
647
+
648
+ // Remove GSD hooks from SessionStart
649
+ if (settings.hooks && settings.hooks.SessionStart) {
650
+ const before = settings.hooks.SessionStart.length;
651
+ settings.hooks.SessionStart = settings.hooks.SessionStart.filter(entry => {
652
+ if (entry.hooks && Array.isArray(entry.hooks)) {
653
+ // Filter out GSD hooks
654
+ const hasGsdHook = entry.hooks.some(h =>
655
+ h.command && (h.command.includes('gsd-check-update') || h.command.includes('gsd-statusline'))
656
+ );
657
+ return !hasGsdHook;
658
+ }
659
+ return true;
660
+ });
661
+ if (settings.hooks.SessionStart.length < before) {
662
+ settingsModified = true;
663
+ console.log(` ${green}✓${reset} Removed GSD hooks from settings`);
664
+ }
665
+ // Clean up empty array
666
+ if (settings.hooks.SessionStart.length === 0) {
667
+ delete settings.hooks.SessionStart;
668
+ }
669
+ // Clean up empty hooks object
670
+ if (Object.keys(settings.hooks).length === 0) {
671
+ delete settings.hooks;
672
+ }
673
+ }
674
+
675
+ if (settingsModified) {
676
+ writeSettings(settingsPath, settings);
677
+ removedCount++;
678
+ }
679
+ }
680
+
681
+ // 6. For OpenCode, clean up permissions from opencode.json
682
+ if (isOpencode) {
683
+ const opencodeConfigDir = getOpencodeGlobalDir();
684
+ const configPath = path.join(opencodeConfigDir, 'opencode.json');
685
+ if (fs.existsSync(configPath)) {
686
+ try {
687
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
688
+ let modified = false;
689
+
690
+ // Remove GSD permission entries
691
+ if (config.permission) {
692
+ for (const permType of ['read', 'external_directory']) {
693
+ if (config.permission[permType]) {
694
+ const keys = Object.keys(config.permission[permType]);
695
+ for (const key of keys) {
696
+ if (key.includes('get-shit-done')) {
697
+ delete config.permission[permType][key];
698
+ modified = true;
699
+ }
700
+ }
701
+ // Clean up empty objects
702
+ if (Object.keys(config.permission[permType]).length === 0) {
703
+ delete config.permission[permType];
704
+ }
705
+ }
706
+ }
707
+ if (Object.keys(config.permission).length === 0) {
708
+ delete config.permission;
709
+ }
710
+ }
711
+
712
+ if (modified) {
713
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
714
+ removedCount++;
715
+ console.log(` ${green}✓${reset} Removed GSD permissions from opencode.json`);
716
+ }
717
+ } catch (e) {
718
+ // Ignore JSON parse errors
719
+ }
720
+ }
721
+ }
722
+
723
+ if (removedCount === 0) {
724
+ console.log(` ${yellow}⚠${reset} No GSD files found to remove.`);
725
+ }
726
+
727
+ console.log(`
728
+ ${green}Done!${reset} GSD has been uninstalled from ${runtimeLabel}.
729
+ Your other files and settings have been preserved.
730
+ `);
731
+ }
732
+
422
733
  /**
423
734
  * Configure OpenCode permissions to allow reading GSD reference docs
424
- * This prevents permission prompts when GSD accesses ~/.opencode/get-shit-done/
735
+ * This prevents permission prompts when GSD accesses the get-shit-done directory
425
736
  */
426
737
  function configureOpencodePermissions() {
427
- const configPath = path.join(os.homedir(), '.opencode.json');
738
+ // OpenCode config file is at ~/.config/opencode/opencode.json
739
+ const opencodeConfigDir = getOpencodeGlobalDir();
740
+ const configPath = path.join(opencodeConfigDir, 'opencode.json');
741
+
742
+ // Ensure config directory exists
743
+ fs.mkdirSync(opencodeConfigDir, { recursive: true });
428
744
 
429
745
  // Read existing config or create empty object
430
746
  let config = {};
@@ -433,7 +749,7 @@ function configureOpencodePermissions() {
433
749
  config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
434
750
  } catch (e) {
435
751
  // Invalid JSON - start fresh but warn user
436
- console.log(` ${yellow}⚠${reset} ~/.opencode.json had invalid JSON, recreating`);
752
+ console.log(` ${yellow}⚠${reset} opencode.json had invalid JSON, recreating`);
437
753
  }
438
754
  }
439
755
 
@@ -442,7 +758,13 @@ function configureOpencodePermissions() {
442
758
  config.permission = {};
443
759
  }
444
760
 
445
- const gsdPath = '~/.opencode/get-shit-done/*';
761
+ // Build the GSD path using the actual config directory
762
+ // Use ~ shorthand if it's in the default location, otherwise use full path
763
+ const defaultConfigDir = path.join(os.homedir(), '.config', 'opencode');
764
+ const gsdPath = opencodeConfigDir === defaultConfigDir
765
+ ? '~/.config/opencode/get-shit-done/*'
766
+ : `${opencodeConfigDir}/get-shit-done/*`;
767
+
446
768
  let modified = false;
447
769
 
448
770
  // Configure read permission
@@ -511,24 +833,23 @@ function verifyFileInstalled(filePath, description) {
511
833
  */
512
834
  function install(isGlobal, runtime = 'claude') {
513
835
  const isOpencode = runtime === 'opencode';
514
- const dirName = getDirName(runtime);
836
+ const dirName = getDirName(runtime); // .opencode or .claude (for local installs)
515
837
  const src = path.join(__dirname, '..');
516
838
 
517
- // Priority: explicit --config-dir arg > CLAUDE_CONFIG_DIR env var > default dir
518
- const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.CLAUDE_CONFIG_DIR);
519
- const defaultGlobalDir = configDir || path.join(os.homedir(), dirName);
839
+ // Get the target directory based on runtime and install type
520
840
  const targetDir = isGlobal
521
- ? defaultGlobalDir
841
+ ? getGlobalDir(runtime, explicitConfigDir)
522
842
  : path.join(process.cwd(), dirName);
523
843
 
524
844
  const locationLabel = isGlobal
525
845
  ? targetDir.replace(os.homedir(), '~')
526
846
  : targetDir.replace(process.cwd(), '.');
527
847
 
528
- // Path prefix for file references
529
- // Use actual path when CLAUDE_CONFIG_DIR is set, otherwise use ~ shorthand
848
+ // 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/
530
851
  const pathPrefix = isGlobal
531
- ? (configDir ? `${targetDir}/` : `~/${dirName}/`)
852
+ ? `${targetDir}/`
532
853
  : `./${dirName}/`;
533
854
 
534
855
  const runtimeLabel = isOpencode ? 'OpenCode' : 'Claude Code';
@@ -540,18 +861,35 @@ function install(isGlobal, runtime = 'claude') {
540
861
  // Clean up orphaned files from previous versions
541
862
  cleanupOrphanedFiles(targetDir);
542
863
 
543
- // Create commands directory
544
- const commandsDir = path.join(targetDir, 'commands');
545
- fs.mkdirSync(commandsDir, { recursive: true });
546
-
547
- // Copy commands/gsd with path replacement
548
- const gsdSrc = path.join(src, 'commands', 'gsd');
549
- const gsdDest = path.join(commandsDir, 'gsd');
550
- copyWithPathReplacement(gsdSrc, gsdDest, pathPrefix, runtime);
551
- if (verifyInstalled(gsdDest, 'commands/gsd')) {
552
- console.log(` ${green}✓${reset} Installed commands/gsd`);
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
866
+ if (isOpencode) {
867
+ // OpenCode: flat structure in command/ directory
868
+ const commandDir = path.join(targetDir, 'command');
869
+ fs.mkdirSync(commandDir, { recursive: true });
870
+
871
+ // Copy commands/gsd/*.md as command/gsd-*.md (flatten structure)
872
+ const gsdSrc = path.join(src, 'commands', 'gsd');
873
+ copyFlattenedCommands(gsdSrc, commandDir, 'gsd', pathPrefix, runtime);
874
+ if (verifyInstalled(commandDir, 'command/gsd-*')) {
875
+ const count = fs.readdirSync(commandDir).filter(f => f.startsWith('gsd-')).length;
876
+ console.log(` ${green}✓${reset} Installed ${count} commands to command/`);
877
+ } else {
878
+ failures.push('command/gsd-*');
879
+ }
553
880
  } else {
554
- failures.push('commands/gsd');
881
+ // Claude Code: nested structure in commands/ directory
882
+ const commandsDir = path.join(targetDir, 'commands');
883
+ fs.mkdirSync(commandsDir, { recursive: true });
884
+
885
+ const gsdSrc = path.join(src, 'commands', 'gsd');
886
+ const gsdDest = path.join(commandsDir, 'gsd');
887
+ copyWithPathReplacement(gsdSrc, gsdDest, pathPrefix, runtime);
888
+ if (verifyInstalled(gsdDest, 'commands/gsd')) {
889
+ console.log(` ${green}✓${reset} Installed commands/gsd`);
890
+ } else {
891
+ failures.push('commands/gsd');
892
+ }
555
893
  }
556
894
 
557
895
  // Copy get-shit-done skill with path replacement
@@ -718,9 +1056,11 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
718
1056
  }
719
1057
 
720
1058
  const program = isOpencode ? 'OpenCode' : 'Claude Code';
721
- const command = isOpencode ? '/gsd/help' : '/gsd:help';
1059
+ const command = isOpencode ? '/gsd-help' : '/gsd:help';
722
1060
  console.log(`
723
1061
  ${green}Done!${reset} Launch ${program} and run ${cyan}${command}${reset}.
1062
+
1063
+ ${cyan}Join the community:${reset} https://discord.gg/5JJgD5svVS
724
1064
  `);
725
1065
  }
726
1066
 
@@ -803,7 +1143,7 @@ function promptRuntime(callback) {
803
1143
  console.log(` ${yellow}Which runtime(s) would you like to install for?${reset}
804
1144
 
805
1145
  ${cyan}1${reset}) Claude Code ${dim}(~/.claude)${reset}
806
- ${cyan}2${reset}) OpenCode ${dim}(~/.opencode)${reset} - open source, free models
1146
+ ${cyan}2${reset}) OpenCode ${dim}(~/.config/opencode)${reset} - open source, free models
807
1147
  ${cyan}3${reset}) Both
808
1148
  `);
809
1149
 
@@ -852,10 +1192,9 @@ function promptLocation(runtimes) {
852
1192
  });
853
1193
 
854
1194
  // Show paths for selected runtimes
855
- const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.CLAUDE_CONFIG_DIR);
856
1195
  const pathExamples = runtimes.map(r => {
857
- const dir = getDirName(r);
858
- const globalPath = configDir || path.join(os.homedir(), dir);
1196
+ // Use the proper global directory function for each runtime
1197
+ const globalPath = getGlobalDir(r, explicitConfigDir);
859
1198
  return globalPath.replace(os.homedir(), '~');
860
1199
  }).join(', ');
861
1200
 
@@ -917,6 +1256,17 @@ if (hasGlobal && hasLocal) {
917
1256
  } else if (explicitConfigDir && hasLocal) {
918
1257
  console.error(` ${yellow}Cannot use --config-dir with --local${reset}`);
919
1258
  process.exit(1);
1259
+ } else if (hasUninstall) {
1260
+ // Uninstall mode
1261
+ if (!hasGlobal && !hasLocal) {
1262
+ console.error(` ${yellow}--uninstall requires --global or --local${reset}`);
1263
+ console.error(` Example: npx get-shit-done-cc --claude --global --uninstall`);
1264
+ process.exit(1);
1265
+ }
1266
+ const runtimes = selectedRuntimes.length > 0 ? selectedRuntimes : ['claude'];
1267
+ for (const runtime of runtimes) {
1268
+ uninstall(hasGlobal, runtime);
1269
+ }
920
1270
  } else if (selectedRuntimes.length > 0) {
921
1271
  // Non-interactive: runtime specified via flags
922
1272
  if (!hasGlobal && !hasLocal) {
@@ -352,6 +352,14 @@ Update GSD to latest version with changelog preview.
352
352
 
353
353
  Usage: `/gsd:update`
354
354
 
355
+ **`/gsd:join-discord`**
356
+ Join the GSD Discord community.
357
+
358
+ - Get help, share what you're building, stay updated
359
+ - Connect with other GSD users
360
+
361
+ Usage: `/gsd:join-discord`
362
+
355
363
  ## Files & Structure
356
364
 
357
365
  ```
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: gsd:join-discord
3
+ description: Join the GSD Discord community
4
+ ---
5
+
6
+ <objective>
7
+ Display the Discord invite link for the GSD community server.
8
+ </objective>
9
+
10
+ <output>
11
+ # Join the GSD Discord
12
+
13
+ Connect with other GSD users, get help, share what you're building, and stay updated.
14
+
15
+ **Invite link:** https://discord.gg/5JJgD5svVS
16
+
17
+ Click the link or paste it into your browser to join.
18
+ </output>
@@ -173,7 +173,7 @@ REQUIREMENTS=$(cat .planning/REQUIREMENTS.md 2>/dev/null | grep -A100 "## Requir
173
173
  DECISIONS=$(grep -A20 "### Decisions Made" .planning/STATE.md 2>/dev/null)
174
174
 
175
175
  # Get phase context if exists
176
- PHASE_CONTEXT=$(cat "${PHASE_DIR}/${PHASE}-CONTEXT.md" 2>/dev/null)
176
+ PHASE_CONTEXT=$(cat "${PHASE_DIR}"/*-CONTEXT.md 2>/dev/null)
177
177
  ```
178
178
 
179
179
  Fill research prompt and spawn:
@@ -81,7 +81,7 @@ ls .planning/phases/${PHASE}-*/RESEARCH.md 2>/dev/null
81
81
  ```bash
82
82
  grep -A20 "Phase ${PHASE}:" .planning/ROADMAP.md
83
83
  cat .planning/REQUIREMENTS.md 2>/dev/null
84
- cat .planning/phases/${PHASE}-*/${PHASE}-CONTEXT.md 2>/dev/null
84
+ cat .planning/phases/${PHASE}-*/*-CONTEXT.md 2>/dev/null
85
85
  grep -A30 "### Decisions Made" .planning/STATE.md 2>/dev/null
86
86
  ```
87
87
 
@@ -132,7 +132,7 @@ Check if CONTEXT.md already exists:
132
132
  ```bash
133
133
  # Match both zero-padded (05-*) and unpadded (5-*) folders
134
134
  PADDED_PHASE=$(printf "%02d" ${PHASE})
135
- ls .planning/phases/${PADDED_PHASE}-*/CONTEXT.md .planning/phases/${PADDED_PHASE}-*/${PADDED_PHASE}-CONTEXT.md .planning/phases/${PHASE}-*/CONTEXT.md .planning/phases/${PHASE}-*/${PHASE}-CONTEXT.md 2>/dev/null
135
+ ls .planning/phases/${PADDED_PHASE}-*/*-CONTEXT.md .planning/phases/${PHASE}-*/*-CONTEXT.md 2>/dev/null
136
136
  ```
137
137
 
138
138
  **If exists:**
@@ -197,7 +197,7 @@ What would you like to do?
197
197
  **Note:** When offering phase planning, check for CONTEXT.md existence first:
198
198
 
199
199
  ```bash
200
- ls .planning/phases/XX-name/CONTEXT.md 2>/dev/null
200
+ ls .planning/phases/XX-name/*-CONTEXT.md 2>/dev/null
201
201
  ```
202
202
 
203
203
  If missing, suggest discuss-phase before plan. If exists, offer plan directly.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "get-shit-done-cc",
3
- "version": "1.9.6",
3
+ "version": "1.9.11",
4
4
  "description": "A meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.",
5
5
  "bin": {
6
6
  "get-shit-done-cc": "bin/install.js"