gsd-cc 1.5.7 → 1.6.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.
Files changed (66) hide show
  1. package/README.md +77 -4
  2. package/bin/install/args.js +161 -0
  3. package/bin/install/assets.js +230 -0
  4. package/bin/install/cli.js +167 -0
  5. package/bin/install/constants.js +77 -0
  6. package/bin/install/dashboard.js +93 -0
  7. package/bin/install/dependencies.js +150 -0
  8. package/bin/install/fs-utils.js +133 -0
  9. package/bin/install/hooks.js +152 -0
  10. package/bin/install/language-config.js +171 -0
  11. package/bin/install/manifest.js +306 -0
  12. package/bin/install/operations.js +422 -0
  13. package/bin/install/paths.js +65 -0
  14. package/bin/install.js +1 -416
  15. package/checklists/planning-ready.md +7 -1
  16. package/checklists/unify-complete.md +49 -3
  17. package/dashboard/app.js +2929 -0
  18. package/dashboard/index.html +33 -0
  19. package/dashboard/styles.css +2348 -0
  20. package/hooks/gsd-boundary-guard.sh +67 -12
  21. package/hooks/gsd-context-monitor.sh +19 -2
  22. package/hooks/gsd-prompt-guard.sh +25 -0
  23. package/hooks/gsd-statusline.sh +46 -6
  24. package/hooks/gsd-workflow-guard.sh +30 -19
  25. package/package.json +21 -1
  26. package/scripts/dashboard/read-model.js +2457 -0
  27. package/scripts/dashboard/task-plan-parser.js +284 -0
  28. package/scripts/dashboard/watch.js +391 -0
  29. package/scripts/dashboard-server.js +734 -0
  30. package/scripts/task-plan-xml.js +138 -0
  31. package/scripts/validate-plan.js +580 -0
  32. package/skills/apply/SKILL.md +172 -22
  33. package/skills/auto/SKILL.md +80 -5
  34. package/skills/auto/apply-instructions.txt +25 -3
  35. package/skills/auto/auto-loop.sh +331 -147
  36. package/skills/auto/lib/allowlist.sh +171 -0
  37. package/skills/auto/lib/approval.sh +333 -0
  38. package/skills/auto/lib/dispatch.sh +30 -0
  39. package/skills/auto/lib/events.sh +239 -0
  40. package/skills/auto/lib/git.sh +441 -0
  41. package/skills/auto/lib/recovery.sh +294 -0
  42. package/skills/auto/lib/runtime.sh +216 -0
  43. package/skills/auto/lib/state.sh +350 -0
  44. package/skills/auto/lib/task-plan.sh +575 -0
  45. package/skills/auto/plan-instructions.txt +38 -5
  46. package/skills/auto/reassess-instructions.txt +4 -0
  47. package/skills/auto/unify-instructions.txt +148 -8
  48. package/skills/config/SKILL.md +52 -11
  49. package/skills/dashboard/SKILL.md +63 -0
  50. package/skills/discuss/SKILL.md +14 -2
  51. package/skills/gsd-cc/SKILL.md +152 -12
  52. package/skills/help/SKILL.md +10 -1
  53. package/skills/ideate/SKILL.md +24 -21
  54. package/skills/ingest/SKILL.md +56 -20
  55. package/skills/plan/SKILL.md +91 -29
  56. package/skills/seed/SKILL.md +51 -16
  57. package/skills/stack/SKILL.md +32 -5
  58. package/skills/status/SKILL.md +147 -28
  59. package/skills/status/token-usage.py +240 -0
  60. package/skills/tutorial/SKILL.md +18 -5
  61. package/skills/unify/SKILL.md +270 -40
  62. package/skills/update/SKILL.md +30 -9
  63. package/templates/PLAN.xml +7 -2
  64. package/templates/STATE.md +5 -1
  65. package/templates/STATE_MACHINE.json +269 -0
  66. package/templates/UNIFY.md +93 -10
package/README.md CHANGED
@@ -6,13 +6,14 @@ A project management system for AI-powered software development. Structure your
6
6
 
7
7
  Claude Code is the best coding agent available. But without structure, large projects degrade into chaos: context rot, lost decisions, no quality control.
8
8
 
9
- GSD-CC orchestrates Claude Code with native Skills (Markdown) — no API costs, no dependencies, no custom agent.
9
+ GSD-CC orchestrates Claude Code with native Skills (Markdown) — no API costs,
10
+ no build step, no custom agent.
10
11
 
11
12
  | Feature | GSD-CC |
12
13
  |---------|--------|
13
14
  | Runtime | Claude Code (native) |
14
15
  | Cost model | Max Plan (flat rate) |
15
- | Dependencies | Zero (Markdown + Bash) |
16
+ | Dependencies | No build step (Markdown + Bash + CLI tools) |
16
17
  | Quality control | Mandatory UNIFY after every slice |
17
18
  | Boundary enforcement | Explicit DO NOT CHANGE rules per task |
18
19
  | Custom project types | Drop 3 files, done |
@@ -22,9 +23,31 @@ GSD-CC orchestrates Claude Code with native Skills (Markdown) — no API costs,
22
23
  ```bash
23
24
  npx gsd-cc # Install globally (default)
24
25
  npx gsd-cc --local # Install to current project only
25
- npx gsd-cc --uninstall
26
+ npx gsd-cc --global --yes # Install/update without prompts
27
+ npx gsd-cc --local --language Deutsch
28
+ npx gsd-cc --uninstall # Remove detected installs safely
29
+ npx gsd-cc --uninstall --global # Remove only the global install
30
+ npx gsd-cc --uninstall --local # Remove only the local install
31
+ npx gsd-cc dashboard --no-open # Start the local dashboard
26
32
  ```
27
33
 
34
+ GSD-CC tracks installed assets in `~/.claude/gsd-cc/install-manifest.json`
35
+ (global) or `./.claude/gsd-cc/install-manifest.json` (local), removes only
36
+ files it owns during uninstall, and aborts if an existing target file cannot
37
+ be proven safe to overwrite.
38
+
39
+ Installed layout:
40
+ - Hooks: `~/.claude/hooks/gsd-cc/` or `./.claude/hooks/gsd-cc/`
41
+ - Custom types: `~/.claude/skills/seed/types/<your-type>/` or `./.claude/skills/seed/types/<your-type>/`
42
+ - Scope-specific uninstall: `--global` or `--local`
43
+ - Prompt-free installs: `--yes`
44
+ - Explicit UI language: `--language <name>`
45
+
46
+ Reinstall and update runs preserve existing `GSD-CC language` and
47
+ `GSD-CC commit language` settings by default. In non-interactive mode, missing
48
+ UI language defaults to English and missing scope defaults to a global install.
49
+ Commit language defaults to English and can be changed with `/gsd-cc-config`.
50
+
28
51
  ## Usage
29
52
 
30
53
  ```bash
@@ -38,12 +61,62 @@ GSD-CC reads your project state and suggests the next action. The full cycle:
38
61
 
39
62
  Auto-mode runs tasks autonomously via `claude -p` on your Max Plan.
40
63
 
64
+ Artifact convention:
65
+ - Slice overview: `.gsd/S{nn}-PLAN.md`
66
+ - Per-task plans: `.gsd/S{nn}-T{nn}-PLAN.xml`
67
+
68
+ ## Dashboard
69
+
70
+ The dashboard gives you a browser view of the current repository's GSD state:
71
+
72
+ ```bash
73
+ npx gsd-cc dashboard
74
+ npx gsd-cc dashboard --no-open
75
+ npx gsd-cc dashboard --host 127.0.0.1 --port 4766
76
+ ```
77
+
78
+ Inside Claude Code, run `/gsd-cc-dashboard` for the same launcher guidance.
79
+
80
+ The server is local-only by default: it binds to `127.0.0.1`, reads `.gsd/`
81
+ from the current project, and serves dashboard assets from the installed
82
+ package. V1 is read-only. It shows project progress, auto-mode events, token
83
+ costs, and safe `.gsd/` artifact previews, but it does not write files or run
84
+ workflow actions.
85
+
41
86
  ## Requirements
42
87
 
43
88
  - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed
44
89
  - Claude Code **Max Plan** (recommended for auto-mode)
45
90
  - **Git** initialized in your project
46
- - **jq** installed (`brew install jq`) — required for auto-mode
91
+ - **jq** installed (`brew install jq`) — required for hooks-ready and
92
+ auto-ready installs
93
+
94
+ Install still succeeds without `jq`, but jq-dependent hooks stay disabled and
95
+ auto-mode remains unavailable until `jq` is installed. Rerun the installer
96
+ after adding `jq` to activate hooks.
97
+
98
+ ## Testing
99
+
100
+ From this package directory:
101
+
102
+ ```bash
103
+ npm test
104
+ ```
105
+
106
+ For dashboard integration sweeps, also launch the read-only Web App from a
107
+ project that has `.gsd/` state:
108
+
109
+ ```bash
110
+ npx gsd-cc dashboard --no-open
111
+ ```
112
+
113
+ The dashboard is optional for auto-mode. Auto-mode writes `.gsd/events.jsonl`
114
+ and other artifacts directly, so it continues to work when the dashboard
115
+ server is not running.
116
+
117
+ The suite uses temporary homes, projects, fake `claude`/`jq` binaries, and
118
+ temporary Git repositories. It must not touch the developer's real
119
+ `~/.claude`, call the real `claude` CLI, or require network access.
47
120
 
48
121
  ## Documentation
49
122
 
@@ -0,0 +1,161 @@
1
+ function defaultParsed() {
2
+ return {
3
+ command: 'install',
4
+ global: false,
5
+ local: false,
6
+ uninstall: false,
7
+ help: false,
8
+ yes: false,
9
+ language: null,
10
+ dashboard: null,
11
+ interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
12
+ };
13
+ }
14
+
15
+ function parseRequiredValue(rawArgs, index, optionName) {
16
+ const value = rawArgs[index + 1];
17
+ if (value === undefined || value.startsWith('-') || !value.trim()) {
18
+ throw new Error(`${optionName} requires a non-empty value.`);
19
+ }
20
+ return value.trim();
21
+ }
22
+
23
+ function parseDashboardPort(value) {
24
+ const trimmed = String(value).trim();
25
+ if (!/^\d+$/.test(trimmed)) {
26
+ throw new Error('--port requires a number between 1 and 65535.');
27
+ }
28
+
29
+ const port = Number(trimmed);
30
+ if (port < 1 || port > 65535) {
31
+ throw new Error('--port requires a number between 1 and 65535.');
32
+ }
33
+
34
+ return port;
35
+ }
36
+
37
+ function ensureDashboardOptions(parsed) {
38
+ if (!parsed.dashboard) {
39
+ parsed.dashboard = {
40
+ host: null,
41
+ port: null,
42
+ open: true
43
+ };
44
+ }
45
+ }
46
+
47
+ function parseArgs(rawArgs) {
48
+ const parsed = defaultParsed();
49
+
50
+ for (let index = 0; index < rawArgs.length; index += 1) {
51
+ const arg = rawArgs[index];
52
+
53
+ if (index === 0 && arg === 'dashboard') {
54
+ parsed.command = 'dashboard';
55
+ ensureDashboardOptions(parsed);
56
+ continue;
57
+ }
58
+
59
+ if (parsed.command === 'dashboard') {
60
+ if (arg === '--no-open') {
61
+ parsed.dashboard.open = false;
62
+ continue;
63
+ }
64
+
65
+ if (arg === '--host') {
66
+ parsed.dashboard.host = parseRequiredValue(rawArgs, index, '--host');
67
+ index += 1;
68
+ continue;
69
+ }
70
+
71
+ if (arg.startsWith('--host=')) {
72
+ const value = arg.slice('--host='.length).trim();
73
+ if (!value) {
74
+ throw new Error('--host requires a non-empty value.');
75
+ }
76
+ parsed.dashboard.host = value;
77
+ continue;
78
+ }
79
+
80
+ if (arg === '--port') {
81
+ parsed.dashboard.port = parseDashboardPort(
82
+ parseRequiredValue(rawArgs, index, '--port')
83
+ );
84
+ index += 1;
85
+ continue;
86
+ }
87
+
88
+ if (arg.startsWith('--port=')) {
89
+ parsed.dashboard.port = parseDashboardPort(arg.slice('--port='.length));
90
+ continue;
91
+ }
92
+
93
+ if (arg === '--help' || arg === '-h') {
94
+ parsed.help = true;
95
+ continue;
96
+ }
97
+
98
+ if (arg.startsWith('-')) {
99
+ throw new Error(`Unknown dashboard option: ${arg}`);
100
+ }
101
+
102
+ throw new Error(`Unexpected dashboard argument: ${arg}`);
103
+ }
104
+
105
+ if (arg === '--global' || arg === '-g') {
106
+ parsed.global = true;
107
+ continue;
108
+ }
109
+
110
+ if (arg === '--local' || arg === '-l') {
111
+ parsed.local = true;
112
+ continue;
113
+ }
114
+
115
+ if (arg === '--uninstall') {
116
+ parsed.uninstall = true;
117
+ continue;
118
+ }
119
+
120
+ if (arg === '--help' || arg === '-h') {
121
+ parsed.help = true;
122
+ continue;
123
+ }
124
+
125
+ if (arg === '--yes' || arg === '-y') {
126
+ parsed.yes = true;
127
+ continue;
128
+ }
129
+
130
+ if (arg === '--language') {
131
+ parsed.language = parseRequiredValue(rawArgs, index, '--language');
132
+ index += 1;
133
+ continue;
134
+ }
135
+
136
+ if (arg.startsWith('--language=')) {
137
+ const value = arg.slice('--language='.length).trim();
138
+ if (!value) {
139
+ throw new Error('--language requires a non-empty value.');
140
+ }
141
+ parsed.language = value;
142
+ continue;
143
+ }
144
+
145
+ if (arg.startsWith('-')) {
146
+ throw new Error(`Unknown option: ${arg}`);
147
+ }
148
+
149
+ throw new Error(`Unexpected argument: ${arg}`);
150
+ }
151
+
152
+ if (parsed.global && parsed.local) {
153
+ throw new Error('Cannot specify both --global and --local.');
154
+ }
155
+
156
+ return parsed;
157
+ }
158
+
159
+ module.exports = {
160
+ parseArgs
161
+ };
@@ -0,0 +1,230 @@
1
+
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const {
5
+ INSTALL_LAYOUT,
6
+ CURRENT_HOOK_DIR,
7
+ LEGACY_HOOK_DIR
8
+ } = require('./constants');
9
+ const {
10
+ compareFileContents,
11
+ ensureDirectory,
12
+ sortPathsDeepFirst
13
+ } = require('./fs-utils');
14
+ const { formatPath } = require('./paths');
15
+ const { buildHookSpecs } = require('./hooks');
16
+
17
+ function collectRelativeFiles(rootDir, currentDir, files) {
18
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
19
+
20
+ for (const entry of entries) {
21
+ const absolutePath = path.join(currentDir, entry.name);
22
+
23
+ if (entry.isDirectory()) {
24
+ collectRelativeFiles(rootDir, absolutePath, files);
25
+ continue;
26
+ }
27
+
28
+ files.push(path.relative(rootDir, absolutePath));
29
+ }
30
+ }
31
+
32
+ function collectAssets(srcBase, claudeBase) {
33
+ const assets = [];
34
+
35
+ for (const layout of INSTALL_LAYOUT) {
36
+ const sourceRoot = path.join(srcBase, layout.sourceDir);
37
+ if (!fs.existsSync(sourceRoot)) {
38
+ continue;
39
+ }
40
+
41
+ const relativeFiles = [];
42
+ collectRelativeFiles(sourceRoot, sourceRoot, relativeFiles);
43
+
44
+ for (const relativePath of relativeFiles) {
45
+ const targetRelativePath = path.join(layout.targetDir, relativePath);
46
+ assets.push({
47
+ sourcePath: path.join(sourceRoot, relativePath),
48
+ targetPath: path.join(claudeBase, targetRelativePath),
49
+ targetRelativePath,
50
+ });
51
+ }
52
+ }
53
+
54
+ return assets.sort((left, right) => {
55
+ return left.targetRelativePath.localeCompare(right.targetRelativePath);
56
+ });
57
+ }
58
+
59
+ function fileContains(filePath, snippet) {
60
+ if (!fs.existsSync(filePath)) {
61
+ return false;
62
+ }
63
+
64
+ return fs.readFileSync(filePath, 'utf8').includes(snippet);
65
+ }
66
+
67
+ function detectLegacyInstallation(claudeBase) {
68
+ const reasons = [];
69
+ const skillsRoot = path.join(claudeBase, 'skills');
70
+ const routerSkill = path.join(skillsRoot, 'gsd-cc', 'SKILL.md');
71
+
72
+ if (fileContains(routerSkill, 'name: gsd-cc')) {
73
+ reasons.push('skills/gsd-cc/SKILL.md');
74
+ }
75
+
76
+ if (fs.existsSync(skillsRoot)) {
77
+ const entries = fs.readdirSync(skillsRoot, { withFileTypes: true });
78
+ for (const entry of entries) {
79
+ if (!entry.isDirectory()) {
80
+ continue;
81
+ }
82
+ if (entry.name === 'gsd' || entry.name.startsWith('gsd-cc-')) {
83
+ reasons.push(path.join('skills', entry.name));
84
+ }
85
+ }
86
+ }
87
+
88
+ for (const spec of buildHookSpecs(claudeBase, LEGACY_HOOK_DIR)) {
89
+ for (const hook of spec.hooks) {
90
+ if (fs.existsSync(hook.command)) {
91
+ reasons.push(path.relative(claudeBase, hook.command));
92
+ }
93
+ }
94
+ }
95
+
96
+ return {
97
+ detected: reasons.length > 0,
98
+ reasons: [...new Set(reasons)].sort()
99
+ };
100
+ }
101
+
102
+ function collectLegacyPaths(claudeBase, assets) {
103
+ const detection = detectLegacyInstallation(claudeBase);
104
+ const legacyFiles = new Set();
105
+ const legacyDirectories = new Set();
106
+ const warnings = [];
107
+
108
+ if (!detection.detected) {
109
+ return {
110
+ detected: false,
111
+ reasons: [],
112
+ files: [],
113
+ directories: [],
114
+ warnings
115
+ };
116
+ }
117
+
118
+ for (const asset of assets) {
119
+ if (fs.existsSync(asset.targetPath)) {
120
+ legacyFiles.add(asset.targetRelativePath);
121
+ }
122
+ }
123
+
124
+ for (const spec of buildHookSpecs(claudeBase, LEGACY_HOOK_DIR)) {
125
+ for (const command of spec.commands) {
126
+ const absolutePath = path.join(claudeBase, command);
127
+ if (fs.existsSync(absolutePath)) {
128
+ legacyFiles.add(command);
129
+ }
130
+ }
131
+ }
132
+
133
+ const skillsRoot = path.join(claudeBase, 'skills');
134
+ if (fs.existsSync(skillsRoot)) {
135
+ const entries = fs.readdirSync(skillsRoot, { withFileTypes: true });
136
+ for (const entry of entries) {
137
+ if (!entry.isDirectory()) {
138
+ continue;
139
+ }
140
+ if (entry.name === 'gsd' || entry.name.startsWith('gsd-cc-')) {
141
+ legacyDirectories.add(path.join('skills', entry.name));
142
+ }
143
+ }
144
+ }
145
+
146
+ const promptsDir = path.join(claudeBase, 'prompts');
147
+ if (fs.existsSync(promptsDir)) {
148
+ warnings.push(
149
+ `${formatPath(promptsDir)} was left in place because GSD-CC can no ` +
150
+ 'longer prove it owns that directory.'
151
+ );
152
+ }
153
+
154
+ return {
155
+ detected: true,
156
+ reasons: detection.reasons,
157
+ files: [...legacyFiles].sort(),
158
+ directories: sortPathsDeepFirst([...legacyDirectories]),
159
+ warnings
160
+ };
161
+ }
162
+
163
+ function ensureNoConflicts(assets, ownedRelativePaths) {
164
+ const conflicts = [];
165
+
166
+ for (const asset of assets) {
167
+ if (!fs.existsSync(asset.targetPath)) {
168
+ continue;
169
+ }
170
+
171
+ if (ownedRelativePaths.has(asset.targetRelativePath)) {
172
+ continue;
173
+ }
174
+
175
+ const targetStat = fs.lstatSync(asset.targetPath);
176
+ if (!targetStat.isFile()) {
177
+ conflicts.push(asset.targetRelativePath);
178
+ continue;
179
+ }
180
+
181
+ if (compareFileContents(asset.sourcePath, asset.targetPath)) {
182
+ continue;
183
+ }
184
+
185
+ conflicts.push(asset.targetRelativePath);
186
+ }
187
+
188
+ if (conflicts.length === 0) {
189
+ return;
190
+ }
191
+
192
+ const rendered = conflicts.map((relativePath) => {
193
+ const asset = assets.find((candidate) => {
194
+ return candidate.targetRelativePath === relativePath;
195
+ });
196
+ return ` - ${formatPath(asset ? asset.targetPath : relativePath)}`;
197
+ }).join('\n');
198
+
199
+ throw new Error(
200
+ 'Refusing to overwrite files that are not proven to be owned by GSD-CC:\n' +
201
+ `${rendered}\n` +
202
+ 'Remove the conflicting files manually or uninstall the existing tool first.'
203
+ );
204
+ }
205
+
206
+ function getInstallMode(asset) {
207
+ const hookDirPrefix = `${CURRENT_HOOK_DIR}${path.sep}`;
208
+ if (
209
+ asset.targetRelativePath.startsWith(hookDirPrefix) &&
210
+ asset.targetRelativePath.endsWith('.sh')
211
+ ) {
212
+ return 0o755;
213
+ }
214
+
215
+ return fs.statSync(asset.sourcePath).mode & 0o777;
216
+ }
217
+
218
+ function copyAsset(asset) {
219
+ ensureDirectory(path.dirname(asset.targetPath));
220
+ fs.copyFileSync(asset.sourcePath, asset.targetPath);
221
+ fs.chmodSync(asset.targetPath, getInstallMode(asset));
222
+ }
223
+
224
+ module.exports = {
225
+ collectAssets,
226
+ collectLegacyPaths,
227
+ detectLegacyInstallation,
228
+ ensureNoConflicts,
229
+ copyAsset
230
+ };
@@ -0,0 +1,167 @@
1
+ const os = require('os');
2
+ const readline = require('readline');
3
+ const pkg = require('../../package.json');
4
+ const { parseArgs } = require('./args');
5
+ const { COLORS } = require('./constants');
6
+ const { launchDashboard } = require('./dashboard');
7
+ const { getClaudeBase } = require('./paths');
8
+ const { writeLanguageConfig } = require('./language-config');
9
+ const {
10
+ installAndConfigure,
11
+ printDefaultGlobalChoice,
12
+ printLanguageSet,
13
+ uninstall
14
+ } = require('./operations');
15
+
16
+ const { cyan, yellow, red, dim, reset } = COLORS;
17
+
18
+ const banner = `
19
+ ${cyan} ██████╗ ███████╗██████╗ ██████╗ ██████╗
20
+ ██╔════╝ ██╔════╝██╔══██╗ ██╔════╝██╔════╝
21
+ ██║ ███╗███████╗██║ ██║█████╗██║ ██║
22
+ ██║ ██║╚════██║██║ ██║╚════╝██║ ██║
23
+ ╚██████╔╝███████║██████╔╝ ╚██████╗╚██████╗
24
+ ╚═════╝ ╚══════╝╚═════╝ ╚═════╝ ╚═════╝${reset}
25
+
26
+ Get Shit Done on Claude Code ${dim}v${pkg.version}${reset}
27
+ `;
28
+
29
+ function printHelp() {
30
+ console.log(` ${yellow}Usage:${reset} npx gsd-cc [options]
31
+ npx gsd-cc dashboard [options]
32
+
33
+ ${yellow}Commands:${reset}
34
+ ${cyan}dashboard${reset} Start the local dashboard server
35
+
36
+ ${yellow}Options:${reset}
37
+ ${cyan}-g, --global${reset} Install globally to ~/.claude/skills/ ${dim}(default)${reset}
38
+ ${cyan}-l, --local${reset} Install locally to ./.claude/skills/
39
+ ${cyan}--uninstall${reset} Remove GSD-CC safely from detected installs
40
+ ${cyan}-y, --yes${reset} Run without prompts
41
+ ${cyan}--language <name>${reset} Set GSD-CC language non-interactively
42
+ ${cyan}-h, --help${reset} Show this help message
43
+
44
+ ${yellow}Dashboard Options:${reset}
45
+ ${cyan}--host <host>${reset} Host to bind ${dim}(default: 127.0.0.1)${reset}
46
+ ${cyan}--port <number>${reset} Port to bind ${dim}(default: 4766)${reset}
47
+ ${cyan}--no-open${reset} Do not open a browser automatically
48
+
49
+ ${yellow}Examples:${reset}
50
+ ${dim}# Install globally (default)${reset}
51
+ npx gsd-cc
52
+
53
+ ${dim}# Install to current project only${reset}
54
+ npx gsd-cc --local
55
+
56
+ ${dim}# Update or automate without prompts${reset}
57
+ npx gsd-cc --global --yes
58
+
59
+ ${dim}# Remove GSD-CC${reset}
60
+ npx gsd-cc --uninstall
61
+
62
+ ${dim}# Start the dashboard launcher${reset}
63
+ npx gsd-cc dashboard --no-open
64
+ `);
65
+ }
66
+
67
+ function fail(error) {
68
+ const message = error && error.message ? error.message : String(error);
69
+ console.error(` ${red}Error:${reset} ${message}`);
70
+ process.exit(1);
71
+ }
72
+
73
+ function promptLanguage(isGlobal, onDone) {
74
+ const rl = readline.createInterface({
75
+ input: process.stdin,
76
+ output: process.stdout,
77
+ });
78
+
79
+ console.log(`
80
+ ${yellow}Which language should GSD-CC use?${reset}
81
+ ${dim}(e.g. English, Deutsch, Français, Español, ...)${reset}
82
+ ${dim}You can change this anytime with /gsd-cc-config in Claude Code${reset}
83
+ `);
84
+
85
+ rl.question(` Language ${dim}[English]${reset}: `, (answer) => {
86
+ rl.close();
87
+
88
+ try {
89
+ const language = answer.trim() || 'English';
90
+ writeLanguageConfig(isGlobal, language);
91
+ printLanguageSet(language);
92
+ onDone();
93
+ } catch (error) {
94
+ fail(error);
95
+ }
96
+ });
97
+ }
98
+
99
+ function promptLocation(installOptions) {
100
+ const rl = readline.createInterface({
101
+ input: process.stdin,
102
+ output: process.stdout,
103
+ });
104
+
105
+ const globalPath = getClaudeBase(true).replace(os.homedir(), '~');
106
+
107
+ console.log(` ${yellow}Where would you like to install?${reset}
108
+
109
+ ${cyan}1${reset}) Global ${dim}(${globalPath})${reset} — available in all projects
110
+ ${cyan}2${reset}) Local ${dim}(./.claude/)${reset} — this project only
111
+ `);
112
+
113
+ rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
114
+ rl.close();
115
+ console.log();
116
+
117
+ try {
118
+ const isGlobal = (answer.trim() || '1') !== '2';
119
+ installAndConfigure(isGlobal, installOptions, promptLanguage);
120
+ } catch (error) {
121
+ fail(error);
122
+ }
123
+ });
124
+ }
125
+
126
+ function main(rawArgs) {
127
+ let options;
128
+ try {
129
+ options = parseArgs(rawArgs);
130
+ } catch (error) {
131
+ console.log(banner);
132
+ fail(error);
133
+ }
134
+
135
+ const hasGlobal = options.global;
136
+ const hasLocal = options.local;
137
+ const hasUninstall = options.uninstall;
138
+ const hasHelp = options.help;
139
+
140
+ console.log(banner);
141
+
142
+ if (hasHelp) {
143
+ printHelp();
144
+ return;
145
+ }
146
+
147
+ try {
148
+ if (options.command === 'dashboard') {
149
+ launchDashboard(options).catch(fail);
150
+ } else if (hasUninstall) {
151
+ uninstall(options);
152
+ } else if (hasGlobal) {
153
+ installAndConfigure(true, options, promptLanguage);
154
+ } else if (hasLocal) {
155
+ installAndConfigure(false, options, promptLanguage);
156
+ } else if (options.yes || !options.interactive) {
157
+ printDefaultGlobalChoice();
158
+ installAndConfigure(true, options, promptLanguage);
159
+ } else {
160
+ promptLocation(options);
161
+ }
162
+ } catch (error) {
163
+ fail(error);
164
+ }
165
+ }
166
+
167
+ module.exports = { main };