slashdev 0.1.0 → 1.0.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 (70) hide show
  1. package/.gitmodules +3 -0
  2. package/CLAUDE.md +87 -0
  3. package/README.md +158 -21
  4. package/bin/check-setup.js +27 -0
  5. package/claude-skills/agentswarm/SKILL.md +479 -0
  6. package/claude-skills/bug-diagnosis/SKILL.md +34 -0
  7. package/claude-skills/code-review/SKILL.md +26 -0
  8. package/claude-skills/frontend-design/LICENSE.txt +177 -0
  9. package/claude-skills/frontend-design/SKILL.md +42 -0
  10. package/claude-skills/pr-description/SKILL.md +35 -0
  11. package/claude-skills/scope-estimate/SKILL.md +37 -0
  12. package/hooks/post-response.sh +242 -0
  13. package/package.json +11 -3
  14. package/skills/front-end-design/prompts/system.md +37 -0
  15. package/skills/front-end-testing/prompts/system.md +66 -0
  16. package/skills/github-manager/prompts/system.md +79 -0
  17. package/skills/product-expert/prompts/system.md +52 -0
  18. package/skills/server-admin/prompts/system.md +39 -0
  19. package/src/auth/index.js +115 -0
  20. package/src/cli.js +188 -18
  21. package/src/commands/setup-internals.js +137 -0
  22. package/src/commands/setup.js +104 -0
  23. package/src/commands/update.js +60 -0
  24. package/src/connections/index.js +449 -0
  25. package/src/connections/providers/github.js +71 -0
  26. package/src/connections/providers/servers.js +175 -0
  27. package/src/connections/registry.js +21 -0
  28. package/src/core/claude.js +78 -0
  29. package/src/core/codebase.js +119 -0
  30. package/src/core/config.js +110 -0
  31. package/src/index.js +8 -1
  32. package/src/info.js +54 -21
  33. package/src/skills/index.js +252 -0
  34. package/src/utils/ssh-keys.js +67 -0
  35. package/vendor/gstack/.env.example +5 -0
  36. package/vendor/gstack/autoplan/SKILL.md +1116 -0
  37. package/vendor/gstack/browse/SKILL.md +538 -0
  38. package/vendor/gstack/canary/SKILL.md +587 -0
  39. package/vendor/gstack/careful/SKILL.md +59 -0
  40. package/vendor/gstack/codex/SKILL.md +862 -0
  41. package/vendor/gstack/connect-chrome/SKILL.md +549 -0
  42. package/vendor/gstack/cso/ACKNOWLEDGEMENTS.md +14 -0
  43. package/vendor/gstack/cso/SKILL.md +929 -0
  44. package/vendor/gstack/design-consultation/SKILL.md +962 -0
  45. package/vendor/gstack/design-review/SKILL.md +1314 -0
  46. package/vendor/gstack/design-shotgun/SKILL.md +730 -0
  47. package/vendor/gstack/document-release/SKILL.md +718 -0
  48. package/vendor/gstack/freeze/SKILL.md +82 -0
  49. package/vendor/gstack/gstack-upgrade/SKILL.md +232 -0
  50. package/vendor/gstack/guard/SKILL.md +82 -0
  51. package/vendor/gstack/investigate/SKILL.md +504 -0
  52. package/vendor/gstack/land-and-deploy/SKILL.md +1367 -0
  53. package/vendor/gstack/office-hours/SKILL.md +1317 -0
  54. package/vendor/gstack/plan-ceo-review/SKILL.md +1537 -0
  55. package/vendor/gstack/plan-design-review/SKILL.md +1227 -0
  56. package/vendor/gstack/plan-eng-review/SKILL.md +1120 -0
  57. package/vendor/gstack/qa/SKILL.md +1136 -0
  58. package/vendor/gstack/qa/references/issue-taxonomy.md +85 -0
  59. package/vendor/gstack/qa/templates/qa-report-template.md +126 -0
  60. package/vendor/gstack/qa-only/SKILL.md +726 -0
  61. package/vendor/gstack/retro/SKILL.md +1197 -0
  62. package/vendor/gstack/review/SKILL.md +1138 -0
  63. package/vendor/gstack/review/TODOS-format.md +62 -0
  64. package/vendor/gstack/review/checklist.md +220 -0
  65. package/vendor/gstack/review/design-checklist.md +132 -0
  66. package/vendor/gstack/review/greptile-triage.md +220 -0
  67. package/vendor/gstack/setup-browser-cookies/SKILL.md +348 -0
  68. package/vendor/gstack/setup-deploy/SKILL.md +528 -0
  69. package/vendor/gstack/ship/SKILL.md +1931 -0
  70. package/vendor/gstack/unfreeze/SKILL.md +40 -0
@@ -0,0 +1,79 @@
1
+ # GitHub Manager
2
+
3
+ You are a GitHub workflow expert from the Slashdev team. You help engineers work effectively with Git and GitHub.
4
+
5
+ ## Your Expertise
6
+
7
+ - **Pull Requests**: Creating clear, reviewable PRs
8
+ - **Commit Messages**: Writing meaningful commit history
9
+ - **Issues**: Structuring bug reports and feature requests
10
+ - **Releases**: Changelogs, release notes, versioning
11
+ - **Branch Strategy**: Git flow, trunk-based development
12
+ - **Code Review**: Providing constructive feedback
13
+
14
+ ## PR Description Template
15
+
16
+ ```markdown
17
+ ## Summary
18
+ Brief description of what this PR does.
19
+
20
+ ## Changes
21
+ - Change 1
22
+ - Change 2
23
+ - Change 3
24
+
25
+ ## Testing
26
+ How to test these changes.
27
+
28
+ ## Screenshots (if applicable)
29
+ Before/after or relevant UI changes.
30
+
31
+ ## Checklist
32
+ - [ ] Tests pass
33
+ - [ ] Code follows style guidelines
34
+ - [ ] Documentation updated (if needed)
35
+ ```
36
+
37
+ ## Commit Message Format
38
+
39
+ ```
40
+ <type>(<scope>): <subject>
41
+
42
+ <body>
43
+
44
+ <footer>
45
+ ```
46
+
47
+ Types: feat, fix, docs, style, refactor, test, chore
48
+
49
+ ## Release Notes Style
50
+
51
+ ```markdown
52
+ ## [1.2.0] - 2024-01-15
53
+
54
+ ### Added
55
+ - New feature X
56
+
57
+ ### Changed
58
+ - Improved Y
59
+
60
+ ### Fixed
61
+ - Bug in Z
62
+
63
+ ### Security
64
+ - Updated vulnerable dependency
65
+ ```
66
+
67
+ ## Your Approach
68
+
69
+ 1. **Context Matters**: Ask about the changes to provide relevant suggestions
70
+ 2. **Reviewable PRs**: Keep changes focused and well-organized
71
+ 3. **Clear History**: Commits tell a story
72
+ 4. **Conventional Commits**: Follow established conventions
73
+
74
+ ## Response Style
75
+
76
+ - Provide ready-to-use templates
77
+ - Explain GitHub best practices
78
+ - Consider team workflow implications
79
+ - Be practical and actionable
@@ -0,0 +1,52 @@
1
+ # Product Expert
2
+
3
+ You are a senior product manager from the Slashdev team. You help engineers think through product decisions and create clear documentation.
4
+
5
+ ## Your Expertise
6
+
7
+ - **PRDs**: Writing clear, comprehensive Product Requirements Documents
8
+ - **User Stories**: Creating actionable stories with acceptance criteria
9
+ - **Specifications**: Detailed technical and functional specifications
10
+ - **Roadmaps**: Strategic planning and prioritization
11
+ - **Communication**: Stakeholder updates, release notes, announcements
12
+
13
+ ## PRD Structure
14
+
15
+ When writing PRDs, include:
16
+
17
+ 1. **Overview**: What problem are we solving?
18
+ 2. **Goals & Success Metrics**: How will we measure success?
19
+ 3. **User Stories**: Who benefits and how?
20
+ 4. **Requirements**: Must-have vs nice-to-have
21
+ 5. **Technical Considerations**: Constraints and dependencies
22
+ 6. **Timeline**: Milestones and phases
23
+ 7. **Open Questions**: What still needs to be decided?
24
+
25
+ ## User Story Format
26
+
27
+ ```
28
+ As a [type of user],
29
+ I want to [action],
30
+ So that [benefit].
31
+
32
+ Acceptance Criteria:
33
+ - [ ] Criterion 1
34
+ - [ ] Criterion 2
35
+ - [ ] Criterion 3
36
+ ```
37
+
38
+ ## Your Approach
39
+
40
+ 1. **Ask Questions**: Clarify requirements before writing
41
+ 2. **Be Specific**: Avoid vague language
42
+ 3. **Think Edge Cases**: Consider what could go wrong
43
+ 4. **Prioritize**: Help distinguish must-haves from nice-to-haves
44
+ 5. **Stay User-Focused**: Always tie back to user value
45
+
46
+ ## Response Style
47
+
48
+ - Use clear, professional language
49
+ - Structure documents with headers and lists
50
+ - Include examples where helpful
51
+ - Be thorough but not verbose
52
+ - Highlight open questions and assumptions
@@ -0,0 +1,39 @@
1
+ # Server Admin
2
+
3
+ You are an expert Linux server administrator from the Slashdev team. You help engineers manage, troubleshoot, and secure their servers.
4
+
5
+ ## Your Expertise
6
+
7
+ - **System Administration**: Package management, service management, cron jobs, log analysis
8
+ - **Security**: Firewall configuration (ufw, iptables), SSH hardening, fail2ban, user permissions
9
+ - **Web Servers**: Nginx, Apache, Caddy configuration and optimization
10
+ - **Databases**: MySQL/MariaDB, PostgreSQL, MongoDB, Redis administration
11
+ - **Containers**: Docker, Docker Compose, container orchestration
12
+ - **Monitoring**: System metrics, disk usage, process management, log rotation
13
+ - **Networking**: DNS, SSL/TLS certificates (Let's Encrypt), port management, VPN
14
+ - **Deployment**: Git-based deployment, CI/CD pipelines, zero-downtime deploys
15
+ - **Performance**: Server tuning, caching strategies, load balancing
16
+
17
+ ## Your Approach
18
+
19
+ 1. **Safety First**: Always warn about destructive commands. Suggest dry-runs when available
20
+ 2. **Explain Commands**: Provide context for what each command does and why
21
+ 3. **Server Context**: Reference the connected servers by name when relevant
22
+ 4. **Best Practices**: Follow security hardening and performance best practices
23
+ 5. **Step-by-Step**: Break complex procedures into clear, numbered steps
24
+
25
+ ## Command Format
26
+
27
+ When providing server commands, always:
28
+ - Include the full command with flags explained
29
+ - Show expected output when helpful
30
+ - Prefix destructive commands with warnings
31
+ - Suggest backing up before making changes
32
+
33
+ ## Response Style
34
+
35
+ - Provide ready-to-use commands
36
+ - Explain the "why" behind each step
37
+ - Consider the connected server context
38
+ - Warn about common pitfalls
39
+ - Suggest verification steps after changes
@@ -0,0 +1,115 @@
1
+ import inquirer from 'inquirer';
2
+ import ora from 'ora';
3
+ import chalk from 'chalk';
4
+ import { setApiKey, setUser, getApiKey, getUser, clearApiKey, isAuthenticated } from '../core/config.js';
5
+ import { validateApiKey } from '../core/claude.js';
6
+ import { blue } from '../banner.js';
7
+
8
+ export async function login() {
9
+ console.log();
10
+
11
+ if (isAuthenticated()) {
12
+ const user = getUser();
13
+ console.log(chalk.hex('#4d7fff')(` You're already logged in${user ? ` as ${chalk.bold(user)}` : ''}.`));
14
+ console.log(chalk.dim(` Run ${chalk.hex('#215ff6')('slashdev logout')} to sign out first.`));
15
+ console.log();
16
+ return;
17
+ }
18
+
19
+ console.log(chalk.hex('#215ff6').bold(' Slashdev Login'));
20
+ console.log(chalk.hex('#4d7fff')(' ─────────────────────────────────────────'));
21
+ console.log();
22
+ console.log(chalk.dim(' To use Slashdev skills, you need an Anthropic API key.'));
23
+ console.log(chalk.dim(' Get one at: ') + chalk.hex('#215ff6').underline('https://console.anthropic.com/settings/keys'));
24
+ console.log();
25
+
26
+ const answers = await inquirer.prompt([
27
+ {
28
+ type: 'password',
29
+ name: 'apiKey',
30
+ message: chalk.hex('#7a9fff')('Enter your Anthropic API key:'),
31
+ mask: '*',
32
+ validate: (input) => {
33
+ if (!input || input.trim().length === 0) {
34
+ return 'API key is required';
35
+ }
36
+ if (!input.startsWith('sk-ant-')) {
37
+ return 'Invalid API key format. Should start with sk-ant-';
38
+ }
39
+ return true;
40
+ },
41
+ },
42
+ {
43
+ type: 'input',
44
+ name: 'name',
45
+ message: chalk.hex('#7a9fff')('What should we call you? (optional):'),
46
+ default: '',
47
+ },
48
+ ]);
49
+
50
+ const spinner = ora({
51
+ text: chalk.hex('#4d7fff')('Validating API key...'),
52
+ color: 'blue',
53
+ }).start();
54
+
55
+ const isValid = await validateApiKey(answers.apiKey.trim());
56
+
57
+ if (!isValid) {
58
+ spinner.fail(chalk.red('Invalid API key. Please check and try again.'));
59
+ return;
60
+ }
61
+
62
+ setApiKey(answers.apiKey.trim());
63
+ if (answers.name) {
64
+ setUser(answers.name.trim());
65
+ }
66
+
67
+ spinner.succeed(chalk.hex('#4d7fff')('API key validated and saved!'));
68
+ console.log();
69
+ console.log(chalk.hex('#215ff6').bold(` Welcome to Slashdev${answers.name ? `, ${answers.name}` : ''}!`));
70
+ console.log();
71
+ console.log(chalk.dim(' Try running a skill:'));
72
+ console.log(` ${chalk.hex('#4d7fff')('slashdev design')} ${chalk.dim('"review my Button component"')}`);
73
+ console.log(` ${chalk.hex('#4d7fff')('slashdev product')} ${chalk.dim('"write a PRD for user auth"')}`);
74
+ console.log();
75
+ }
76
+
77
+ export async function logout() {
78
+ console.log();
79
+
80
+ if (!isAuthenticated()) {
81
+ console.log(chalk.hex('#7a9fff')(' You\'re not logged in.'));
82
+ console.log();
83
+ return;
84
+ }
85
+
86
+ const user = getUser();
87
+ clearApiKey();
88
+
89
+ console.log(chalk.hex('#4d7fff')(` Logged out${user ? ` ${user}` : ''} successfully.`));
90
+ console.log(chalk.dim(` Run ${chalk.hex('#215ff6')('slashdev login')} to sign in again.`));
91
+ console.log();
92
+ }
93
+
94
+ export function whoami() {
95
+ console.log();
96
+
97
+ if (!isAuthenticated()) {
98
+ console.log(chalk.hex('#7a9fff')(' Not logged in.'));
99
+ console.log(chalk.dim(` Run ${chalk.hex('#215ff6')('slashdev login')} to authenticate.`));
100
+ console.log();
101
+ return;
102
+ }
103
+
104
+ const user = getUser();
105
+ const apiKey = getApiKey();
106
+ const maskedKey = apiKey ? `${apiKey.slice(0, 10)}...${apiKey.slice(-4)}` : 'unknown';
107
+
108
+ console.log(chalk.hex('#215ff6').bold(' Slashdev Account'));
109
+ console.log(chalk.hex('#4d7fff')(' ─────────────────────────────────────────'));
110
+ console.log();
111
+ console.log(` ${chalk.hex('#7a9fff')('User:')} ${user || chalk.dim('(not set)')}`);
112
+ console.log(` ${chalk.hex('#7a9fff')('API Key:')} ${chalk.dim(maskedKey)}`);
113
+ console.log(` ${chalk.hex('#7a9fff')('Status:')} ${chalk.hex('#4d7fff')('● Authenticated')}`);
114
+ console.log();
115
+ }
package/src/cli.js CHANGED
@@ -1,23 +1,193 @@
1
+ import { Command } from 'commander';
1
2
  import { displayBanner } from './banner.js';
2
3
  import { displayInfo, displayVersion, displayHelp } from './info.js';
4
+ import { login, logout, whoami } from './auth/index.js';
5
+ import { listSkills, runSkill, getSkillByCommand } from './skills/index.js';
6
+ import { connect, disconnect, listConnections } from './connections/index.js';
7
+ import { setup } from './commands/setup.js';
8
+ import { update } from './commands/update.js';
9
+ import { readFileSync } from 'fs';
10
+ import { fileURLToPath } from 'url';
11
+ import { dirname, join } from 'path';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
16
+
17
+ const program = new Command();
18
+
19
+ program
20
+ .name('slashdev')
21
+ .description('Skills & workflows for AI agents in the Slashdev ecosystem')
22
+ .version(pkg.version, '-v, --version', 'Show version number');
23
+
24
+ // Claude Code integration commands
25
+ program
26
+ .command('setup')
27
+ .description('Install Claude Code hooks and skills for your Slashdev account')
28
+ .action(async () => {
29
+ displayBanner();
30
+ await setup();
31
+ });
32
+
33
+ program
34
+ .command('update')
35
+ .description('Update Claude Code hooks and skills to the latest version')
36
+ .action(async () => {
37
+ displayBanner();
38
+ await update();
39
+ });
40
+
41
+ // Default action (no command) - show banner
42
+ program
43
+ .action(async () => {
44
+ displayBanner();
45
+ displayInfo();
46
+ });
47
+
48
+ // Authentication commands
49
+ program
50
+ .command('login')
51
+ .description('Authenticate with your Anthropic API key')
52
+ .action(async () => {
53
+ displayBanner();
54
+ await login();
55
+ });
56
+
57
+ program
58
+ .command('logout')
59
+ .description('Clear stored credentials')
60
+ .action(async () => {
61
+ await logout();
62
+ });
63
+
64
+ program
65
+ .command('whoami')
66
+ .description('Show current authentication status')
67
+ .action(() => {
68
+ whoami();
69
+ });
70
+
71
+ // Connection commands
72
+ program
73
+ .command('connect [service]')
74
+ .description('Connect an external service (e.g. github)')
75
+ .action(async (service) => {
76
+ displayBanner();
77
+ await connect(service);
78
+ });
79
+
80
+ program
81
+ .command('disconnect [service]')
82
+ .description('Remove a connected service')
83
+ .action(async (service) => {
84
+ await disconnect(service);
85
+ });
86
+
87
+ program
88
+ .command('connections')
89
+ .description('List all connections and their status')
90
+ .action(() => {
91
+ displayBanner();
92
+ listConnections();
93
+ });
94
+
95
+ // Skills command
96
+ program
97
+ .command('skills')
98
+ .description('List installed skills')
99
+ .action(() => {
100
+ displayBanner();
101
+ listSkills();
102
+ });
103
+
104
+ // Design skill
105
+ program
106
+ .command('design [prompt...]')
107
+ .description('Front-end design assistance')
108
+ .action(async (promptParts) => {
109
+ const prompt = promptParts.join(' ');
110
+ if (!prompt) {
111
+ console.log('\n Usage: slashdev design "your design question"\n');
112
+ console.log(' Examples:');
113
+ console.log(' slashdev design "review my Button component for accessibility"');
114
+ console.log(' slashdev design "create a dark mode color palette"');
115
+ console.log(' slashdev design "improve the mobile layout"\n');
116
+ return;
117
+ }
118
+ await runSkill('front-end-design', prompt);
119
+ });
120
+
121
+ // Product skill
122
+ program
123
+ .command('product [prompt...]')
124
+ .description('Product management assistance')
125
+ .action(async (promptParts) => {
126
+ const prompt = promptParts.join(' ');
127
+ if (!prompt) {
128
+ console.log('\n Usage: slashdev product "your product question"\n');
129
+ console.log(' Examples:');
130
+ console.log(' slashdev product "write a PRD for user authentication"');
131
+ console.log(' slashdev product "create user stories for checkout flow"');
132
+ console.log(' slashdev product "draft release notes for v2.0"\n');
133
+ return;
134
+ }
135
+ await runSkill('product-expert', prompt);
136
+ });
137
+
138
+ // GitHub skill
139
+ program
140
+ .command('github [prompt...]')
141
+ .alias('gh')
142
+ .description('GitHub workflow assistance')
143
+ .action(async (promptParts) => {
144
+ const prompt = promptParts.join(' ');
145
+ if (!prompt) {
146
+ console.log('\n Usage: slashdev github "your github question"\n');
147
+ console.log(' Examples:');
148
+ console.log(' slashdev github "write a PR description for adding auth"');
149
+ console.log(' slashdev github "create release notes for v1.2.0"');
150
+ console.log(' slashdev github "structure an issue for a login bug"\n');
151
+ return;
152
+ }
153
+ await runSkill('github-manager', prompt);
154
+ });
155
+
156
+ // Test skill
157
+ program
158
+ .command('test [prompt...]')
159
+ .description('Front-end testing assistance')
160
+ .action(async (promptParts) => {
161
+ const prompt = promptParts.join(' ');
162
+ if (!prompt) {
163
+ console.log('\n Usage: slashdev test "your testing question"\n');
164
+ console.log(' Examples:');
165
+ console.log(' slashdev test "write unit tests for the Button component"');
166
+ console.log(' slashdev test "create E2E tests for user login flow"');
167
+ console.log(' slashdev test "improve test coverage for the utils"\n');
168
+ return;
169
+ }
170
+ await runSkill('front-end-testing', prompt);
171
+ });
172
+
173
+ // Server admin skill
174
+ program
175
+ .command('server [prompt...]')
176
+ .alias('ssh')
177
+ .description('Server administration assistance')
178
+ .action(async (promptParts) => {
179
+ const prompt = promptParts.join(' ');
180
+ if (!prompt) {
181
+ console.log('\n Usage: slashdev server "your server question"\n');
182
+ console.log(' Examples:');
183
+ console.log(' slashdev server "harden SSH config on prod-web"');
184
+ console.log(' slashdev server "set up nginx reverse proxy"');
185
+ console.log(' slashdev server "check disk usage and clean up"\n');
186
+ return;
187
+ }
188
+ await runSkill('server-admin', prompt);
189
+ });
3
190
 
4
191
  export async function run(args) {
5
- const flags = new Set(args);
6
-
7
- // Handle --version / -v
8
- if (flags.has('--version') || flags.has('-v')) {
9
- displayVersion();
10
- return;
11
- }
12
-
13
- // Handle --help / -h
14
- if (flags.has('--help') || flags.has('-h')) {
15
- await displayBanner();
16
- displayHelp();
17
- return;
18
- }
19
-
20
- // Default: show banner + info
21
- await displayBanner();
22
- displayInfo();
192
+ await program.parseAsync(['node', 'slashdev', ...args]);
23
193
  }
@@ -0,0 +1,137 @@
1
+ // Shared install functions used by both setup and update commands
2
+ import { homedir } from 'os';
3
+ import { join, dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import {
6
+ existsSync,
7
+ mkdirSync,
8
+ readdirSync,
9
+ readFileSync,
10
+ writeFileSync,
11
+ copyFileSync,
12
+ chmodSync,
13
+ } from 'fs';
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ const PACKAGE_ROOT = join(__dirname, '..', '..');
18
+
19
+ const CLAUDE_DIR = join(homedir(), '.claude');
20
+ const CLAUDE_HOOKS_DIR = join(CLAUDE_DIR, 'hooks');
21
+ const CLAUDE_SKILLS_DIR = join(CLAUDE_DIR, 'skills');
22
+ const CLAUDE_SETTINGS_FILE = join(CLAUDE_DIR, 'settings.json');
23
+
24
+ function ensureDir(dir) {
25
+ if (!existsSync(dir)) {
26
+ mkdirSync(dir, { recursive: true });
27
+ }
28
+ }
29
+
30
+ function copyDirRecursive(src, dest) {
31
+ ensureDir(dest);
32
+ const entries = readdirSync(src, { withFileTypes: true });
33
+ for (const entry of entries) {
34
+ const srcPath = join(src, entry.name);
35
+ const destPath = join(dest, entry.name);
36
+ if (entry.isDirectory()) {
37
+ copyDirRecursive(srcPath, destPath);
38
+ } else {
39
+ copyFileSync(srcPath, destPath);
40
+ if (entry.name.endsWith('.sh')) {
41
+ chmodSync(destPath, 0o755);
42
+ }
43
+ }
44
+ }
45
+ }
46
+
47
+ export function installHooks() {
48
+ const hooksSource = join(PACKAGE_ROOT, 'hooks');
49
+ if (!existsSync(hooksSource)) {
50
+ return { installed: 0 };
51
+ }
52
+
53
+ ensureDir(CLAUDE_HOOKS_DIR);
54
+ copyDirRecursive(hooksSource, CLAUDE_HOOKS_DIR);
55
+
56
+ // Register hooks in Claude Code settings.json
57
+ let settings = {};
58
+ if (existsSync(CLAUDE_SETTINGS_FILE)) {
59
+ try {
60
+ settings = JSON.parse(readFileSync(CLAUDE_SETTINGS_FILE, 'utf-8'));
61
+ } catch {
62
+ settings = {};
63
+ }
64
+ }
65
+
66
+ if (!settings.hooks) {
67
+ settings.hooks = {};
68
+ }
69
+
70
+ const hookCommand = join(CLAUDE_HOOKS_DIR, 'post-response.sh');
71
+ const stopHooks = settings.hooks.Stop || [];
72
+
73
+ const alreadyRegistered = stopHooks.some(
74
+ (h) => h.hooks && h.hooks.some((inner) => inner.command && inner.command.includes('post-response.sh'))
75
+ );
76
+
77
+ if (!alreadyRegistered) {
78
+ stopHooks.push({
79
+ matcher: '',
80
+ hooks: [
81
+ {
82
+ type: 'command',
83
+ command: hookCommand,
84
+ },
85
+ ],
86
+ });
87
+ settings.hooks.Stop = stopHooks;
88
+ }
89
+
90
+ writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2), 'utf-8');
91
+
92
+ const hookFiles = readdirSync(hooksSource).filter((f) => f.endsWith('.sh'));
93
+ return { installed: hookFiles.length };
94
+ }
95
+
96
+ export function installSkills() {
97
+ const skillsSource = join(PACKAGE_ROOT, 'claude-skills');
98
+ if (!existsSync(skillsSource)) {
99
+ return { installed: 0 };
100
+ }
101
+
102
+ ensureDir(CLAUDE_SKILLS_DIR);
103
+
104
+ const skillDirs = readdirSync(skillsSource, { withFileTypes: true }).filter((d) => d.isDirectory());
105
+
106
+ for (const dir of skillDirs) {
107
+ const srcPath = join(skillsSource, dir.name);
108
+ const destPath = join(CLAUDE_SKILLS_DIR, dir.name);
109
+ copyDirRecursive(srcPath, destPath);
110
+ }
111
+
112
+ return { installed: skillDirs.length };
113
+ }
114
+
115
+ export function installGstack() {
116
+ const gstackSource = join(PACKAGE_ROOT, 'vendor', 'gstack');
117
+ if (!existsSync(gstackSource)) {
118
+ return { installed: 0, warning: 'gstack submodule not initialized. Run: git submodule update --init vendor/gstack' };
119
+ }
120
+
121
+ const gstackDest = join(CLAUDE_SKILLS_DIR, 'gstack');
122
+ ensureDir(gstackDest);
123
+
124
+ const entries = readdirSync(gstackSource, { withFileTypes: true }).filter((d) => d.isDirectory());
125
+ let installed = 0;
126
+
127
+ for (const dir of entries) {
128
+ const skillMd = join(gstackSource, dir.name, 'SKILL.md');
129
+ if (existsSync(skillMd)) {
130
+ const destPath = join(gstackDest, dir.name);
131
+ copyDirRecursive(join(gstackSource, dir.name), destPath);
132
+ installed++;
133
+ }
134
+ }
135
+
136
+ return { installed };
137
+ }