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 +19 -1
- package/bin/install.js +383 -33
- package/commands/gsd/help.md +8 -0
- package/commands/gsd/join-discord.md +18 -0
- package/commands/gsd/plan-phase.md +1 -1
- package/commands/gsd/research-phase.md +1 -1
- package/get-shit-done/workflows/discuss-phase.md +1 -1
- package/get-shit-done/workflows/resume-project.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/get-shit-done-cc)
|
|
10
10
|
[](https://www.npmjs.com/package/get-shit-done-cc)
|
|
11
|
-
[](https://discord.gg/5JJgD5svVS)
|
|
12
12
|
[](https://github.com/glittercowboy/get-shit-done)
|
|
13
|
+
[](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
|
|
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
|
|
735
|
+
* This prevents permission prompts when GSD accesses the get-shit-done directory
|
|
425
736
|
*/
|
|
426
737
|
function configureOpencodePermissions() {
|
|
427
|
-
|
|
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}
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
?
|
|
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
|
-
//
|
|
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
|
-
?
|
|
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
|
-
//
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
858
|
-
const globalPath =
|
|
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) {
|
package/commands/gsd/help.md
CHANGED
|
@@ -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}
|
|
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}
|
|
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}
|
|
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
|
|
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