slashdev 0.1.0 → 1.1.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 (73) hide show
  1. package/.gitmodules +3 -0
  2. package/CLAUDE.md +88 -0
  3. package/GETTING-STARTED.md +68 -0
  4. package/README.md +158 -21
  5. package/bin/check-setup.js +27 -0
  6. package/claude-skills/agentswarm/SKILL.md +479 -0
  7. package/claude-skills/bug-diagnosis/SKILL.md +34 -0
  8. package/claude-skills/code-review/SKILL.md +26 -0
  9. package/claude-skills/frontend-design/LICENSE.txt +177 -0
  10. package/claude-skills/frontend-design/SKILL.md +42 -0
  11. package/claude-skills/pr-description/SKILL.md +35 -0
  12. package/claude-skills/scope-estimate/SKILL.md +37 -0
  13. package/claude-skills/secure-deploy/SKILL.md +219 -0
  14. package/hooks/post-response.sh +242 -0
  15. package/hooks/pre-install-guard.sh +88 -0
  16. package/package.json +11 -3
  17. package/skills/front-end-design/prompts/system.md +37 -0
  18. package/skills/front-end-testing/prompts/system.md +66 -0
  19. package/skills/github-manager/prompts/system.md +79 -0
  20. package/skills/product-expert/prompts/system.md +52 -0
  21. package/skills/server-admin/prompts/system.md +39 -0
  22. package/src/auth/index.js +115 -0
  23. package/src/cli.js +188 -18
  24. package/src/commands/setup-internals.js +221 -0
  25. package/src/commands/setup.js +110 -0
  26. package/src/commands/update.js +64 -0
  27. package/src/connections/index.js +449 -0
  28. package/src/connections/providers/github.js +71 -0
  29. package/src/connections/providers/servers.js +175 -0
  30. package/src/connections/registry.js +21 -0
  31. package/src/core/claude.js +78 -0
  32. package/src/core/codebase.js +119 -0
  33. package/src/core/config.js +110 -0
  34. package/src/index.js +8 -1
  35. package/src/info.js +54 -21
  36. package/src/skills/index.js +252 -0
  37. package/src/utils/ssh-keys.js +67 -0
  38. package/vendor/gstack/.env.example +5 -0
  39. package/vendor/gstack/autoplan/SKILL.md +1116 -0
  40. package/vendor/gstack/browse/SKILL.md +538 -0
  41. package/vendor/gstack/canary/SKILL.md +587 -0
  42. package/vendor/gstack/careful/SKILL.md +59 -0
  43. package/vendor/gstack/codex/SKILL.md +862 -0
  44. package/vendor/gstack/connect-chrome/SKILL.md +549 -0
  45. package/vendor/gstack/cso/ACKNOWLEDGEMENTS.md +14 -0
  46. package/vendor/gstack/cso/SKILL.md +929 -0
  47. package/vendor/gstack/design-consultation/SKILL.md +962 -0
  48. package/vendor/gstack/design-review/SKILL.md +1314 -0
  49. package/vendor/gstack/design-shotgun/SKILL.md +730 -0
  50. package/vendor/gstack/document-release/SKILL.md +718 -0
  51. package/vendor/gstack/freeze/SKILL.md +82 -0
  52. package/vendor/gstack/gstack-upgrade/SKILL.md +232 -0
  53. package/vendor/gstack/guard/SKILL.md +82 -0
  54. package/vendor/gstack/investigate/SKILL.md +504 -0
  55. package/vendor/gstack/land-and-deploy/SKILL.md +1367 -0
  56. package/vendor/gstack/office-hours/SKILL.md +1317 -0
  57. package/vendor/gstack/plan-ceo-review/SKILL.md +1537 -0
  58. package/vendor/gstack/plan-design-review/SKILL.md +1227 -0
  59. package/vendor/gstack/plan-eng-review/SKILL.md +1120 -0
  60. package/vendor/gstack/qa/SKILL.md +1136 -0
  61. package/vendor/gstack/qa/references/issue-taxonomy.md +85 -0
  62. package/vendor/gstack/qa/templates/qa-report-template.md +126 -0
  63. package/vendor/gstack/qa-only/SKILL.md +726 -0
  64. package/vendor/gstack/retro/SKILL.md +1197 -0
  65. package/vendor/gstack/review/SKILL.md +1138 -0
  66. package/vendor/gstack/review/TODOS-format.md +62 -0
  67. package/vendor/gstack/review/checklist.md +220 -0
  68. package/vendor/gstack/review/design-checklist.md +132 -0
  69. package/vendor/gstack/review/greptile-triage.md +220 -0
  70. package/vendor/gstack/setup-browser-cookies/SKILL.md +348 -0
  71. package/vendor/gstack/setup-deploy/SKILL.md +528 -0
  72. package/vendor/gstack/ship/SKILL.md +1931 -0
  73. package/vendor/gstack/unfreeze/SKILL.md +40 -0
@@ -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,221 @@
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
+ const CLAUDE_MEMORY_FILE = join(CLAUDE_DIR, 'CLAUDE.md');
24
+
25
+ const SLASHDEV_SECTION_START = '<!-- SLASHDEV:RULES:START -->';
26
+ const SLASHDEV_SECTION_END = '<!-- SLASHDEV:RULES:END -->';
27
+
28
+ const SLASHDEV_RULES_BLOCK = `${SLASHDEV_SECTION_START}
29
+ # Slashdev workspace rules
30
+
31
+ ## Security: always use Socket Firewall for dependency installs
32
+
33
+ Claude must use Socket Firewall (\`sfw\`) for **every** dependency install command.
34
+ This applies to:
35
+
36
+ - **Local development** — never run \`npm install\`, \`pnpm install\`, \`yarn add\`,
37
+ \`bun install\`, \`composer install\`, \`pip install\`, or any equivalent without
38
+ prefixing \`sfw \`. A PreToolUse hook will block bare install commands and tell
39
+ you to retry with \`sfw\`.
40
+ - **Production deployment** — when generating or editing CI/CD config (GitHub Actions,
41
+ GitLab CI, CircleCI, Jenkins), Dockerfiles, deploy scripts (\`deploy.sh\`, \`Procfile\`),
42
+ or \`package.json\` scripts that install dependencies, **always** wrap the install
43
+ step with \`sfw\`. Examples:
44
+ - \`sfw npm ci\` instead of \`npm ci\`
45
+ - \`sfw composer install --no-dev\` instead of \`composer install --no-dev\`
46
+ - \`RUN npm install -g @socketsecurity/cli && sfw npm ci\` in a Dockerfile
47
+ - If \`sfw\` is not yet installed in a CI environment, install it first:
48
+ \`npm install -g @socketsecurity/cli\`. Document the install step inline in the workflow.
49
+ - Use the \`/secure-deploy\` skill to retrofit existing CI/CD configs.
50
+
51
+ Reason: Socket Firewall scans every package against Socket's vulnerability/malware
52
+ database before letting it install. This protects against supply-chain attacks
53
+ (compromised packages, typosquats, dependency confusion).
54
+ ${SLASHDEV_SECTION_END}`;
55
+
56
+ function ensureDir(dir) {
57
+ if (!existsSync(dir)) {
58
+ mkdirSync(dir, { recursive: true });
59
+ }
60
+ }
61
+
62
+ function copyDirRecursive(src, dest) {
63
+ ensureDir(dest);
64
+ const entries = readdirSync(src, { withFileTypes: true });
65
+ for (const entry of entries) {
66
+ const srcPath = join(src, entry.name);
67
+ const destPath = join(dest, entry.name);
68
+ if (entry.isDirectory()) {
69
+ copyDirRecursive(srcPath, destPath);
70
+ } else {
71
+ copyFileSync(srcPath, destPath);
72
+ if (entry.name.endsWith('.sh')) {
73
+ chmodSync(destPath, 0o755);
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ export function installHooks() {
80
+ const hooksSource = join(PACKAGE_ROOT, 'hooks');
81
+ if (!existsSync(hooksSource)) {
82
+ return { installed: 0 };
83
+ }
84
+
85
+ ensureDir(CLAUDE_HOOKS_DIR);
86
+ copyDirRecursive(hooksSource, CLAUDE_HOOKS_DIR);
87
+
88
+ // Register hooks in Claude Code settings.json
89
+ let settings = {};
90
+ if (existsSync(CLAUDE_SETTINGS_FILE)) {
91
+ try {
92
+ settings = JSON.parse(readFileSync(CLAUDE_SETTINGS_FILE, 'utf-8'));
93
+ } catch {
94
+ settings = {};
95
+ }
96
+ }
97
+
98
+ if (!settings.hooks) {
99
+ settings.hooks = {};
100
+ }
101
+
102
+ const hookCommand = join(CLAUDE_HOOKS_DIR, 'post-response.sh');
103
+ const stopHooks = settings.hooks.Stop || [];
104
+
105
+ const alreadyRegistered = stopHooks.some(
106
+ (h) => h.hooks && h.hooks.some((inner) => inner.command && inner.command.includes('post-response.sh'))
107
+ );
108
+
109
+ if (!alreadyRegistered) {
110
+ stopHooks.push({
111
+ matcher: '',
112
+ hooks: [
113
+ {
114
+ type: 'command',
115
+ command: hookCommand,
116
+ },
117
+ ],
118
+ });
119
+ settings.hooks.Stop = stopHooks;
120
+ }
121
+
122
+ // Register PreToolUse hook for the install guard (Bash tool only).
123
+ const guardCommand = join(CLAUDE_HOOKS_DIR, 'pre-install-guard.sh');
124
+ if (existsSync(guardCommand)) {
125
+ const preToolUseHooks = settings.hooks.PreToolUse || [];
126
+ const guardRegistered = preToolUseHooks.some(
127
+ (entry) =>
128
+ entry.matcher === 'Bash' &&
129
+ Array.isArray(entry.hooks) &&
130
+ entry.hooks.some((h) => h.command && h.command.includes('pre-install-guard.sh'))
131
+ );
132
+
133
+ if (!guardRegistered) {
134
+ preToolUseHooks.push({
135
+ matcher: 'Bash',
136
+ hooks: [
137
+ {
138
+ type: 'command',
139
+ command: guardCommand,
140
+ statusMessage: 'Checking install safety…',
141
+ },
142
+ ],
143
+ });
144
+ settings.hooks.PreToolUse = preToolUseHooks;
145
+ }
146
+ }
147
+
148
+ writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2), 'utf-8');
149
+
150
+ const hookFiles = readdirSync(hooksSource).filter((f) => f.endsWith('.sh'));
151
+ return { installed: hookFiles.length };
152
+ }
153
+
154
+ export function writeGlobalClaudeMd() {
155
+ ensureDir(CLAUDE_DIR);
156
+
157
+ let existing = '';
158
+ if (existsSync(CLAUDE_MEMORY_FILE)) {
159
+ existing = readFileSync(CLAUDE_MEMORY_FILE, 'utf-8');
160
+ }
161
+
162
+ let next;
163
+ if (existing.includes(SLASHDEV_SECTION_START) && existing.includes(SLASHDEV_SECTION_END)) {
164
+ // Replace the existing managed section in place, preserving everything around it.
165
+ const before = existing.split(SLASHDEV_SECTION_START)[0];
166
+ const after = existing.split(SLASHDEV_SECTION_END)[1];
167
+ next = `${before}${SLASHDEV_RULES_BLOCK}${after}`;
168
+ } else if (existing.trim().length > 0) {
169
+ // Append after existing user content.
170
+ next = `${existing.trimEnd()}\n\n${SLASHDEV_RULES_BLOCK}\n`;
171
+ } else {
172
+ // Brand new file.
173
+ next = `${SLASHDEV_RULES_BLOCK}\n`;
174
+ }
175
+
176
+ writeFileSync(CLAUDE_MEMORY_FILE, next, 'utf-8');
177
+ return { written: true, path: CLAUDE_MEMORY_FILE };
178
+ }
179
+
180
+ export function installSkills() {
181
+ const skillsSource = join(PACKAGE_ROOT, 'claude-skills');
182
+ if (!existsSync(skillsSource)) {
183
+ return { installed: 0 };
184
+ }
185
+
186
+ ensureDir(CLAUDE_SKILLS_DIR);
187
+
188
+ const skillDirs = readdirSync(skillsSource, { withFileTypes: true }).filter((d) => d.isDirectory());
189
+
190
+ for (const dir of skillDirs) {
191
+ const srcPath = join(skillsSource, dir.name);
192
+ const destPath = join(CLAUDE_SKILLS_DIR, dir.name);
193
+ copyDirRecursive(srcPath, destPath);
194
+ }
195
+
196
+ return { installed: skillDirs.length };
197
+ }
198
+
199
+ export function installGstack() {
200
+ const gstackSource = join(PACKAGE_ROOT, 'vendor', 'gstack');
201
+ if (!existsSync(gstackSource)) {
202
+ return { installed: 0, warning: 'gstack submodule not initialized. Run: git submodule update --init vendor/gstack' };
203
+ }
204
+
205
+ const gstackDest = join(CLAUDE_SKILLS_DIR, 'gstack');
206
+ ensureDir(gstackDest);
207
+
208
+ const entries = readdirSync(gstackSource, { withFileTypes: true }).filter((d) => d.isDirectory());
209
+ let installed = 0;
210
+
211
+ for (const dir of entries) {
212
+ const skillMd = join(gstackSource, dir.name, 'SKILL.md');
213
+ if (existsSync(skillMd)) {
214
+ const destPath = join(gstackDest, dir.name);
215
+ copyDirRecursive(join(gstackSource, dir.name), destPath);
216
+ installed++;
217
+ }
218
+ }
219
+
220
+ return { installed };
221
+ }
@@ -0,0 +1,110 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { join, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { existsSync, readFileSync, mkdirSync } from 'fs';
7
+ import { homedir } from 'os';
8
+ import { getConfig, setConfig, getSlashdevDir } from '../core/config.js';
9
+ import { installHooks, installSkills, installGstack, writeGlobalClaudeMd } from './setup-internals.js';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+ const PACKAGE_ROOT = join(__dirname, '..', '..');
14
+
15
+ const CLAUDE_DIR = join(homedir(), '.claude');
16
+
17
+ const BRAND = chalk.hex('#215ff6');
18
+
19
+ function validateEmail(email) {
20
+ if (!email || !email.endsWith('@slashdev.io')) {
21
+ return 'Email must be a valid @slashdev.io address';
22
+ }
23
+ if (!/^[^\s@]+@slashdev\.io$/.test(email)) {
24
+ return 'Invalid email format';
25
+ }
26
+ return true;
27
+ }
28
+
29
+ export async function setup() {
30
+ console.log(BRAND('\n Slashdev Claude Tools Setup\n'));
31
+
32
+ // Check if already configured
33
+ const existingEngineer = getConfig('engineer');
34
+ if (existingEngineer) {
35
+ const { overwrite } = await inquirer.prompt([
36
+ {
37
+ type: 'confirm',
38
+ name: 'overwrite',
39
+ message: `Already configured for ${chalk.cyan(existingEngineer)}. Overwrite?`,
40
+ default: false,
41
+ },
42
+ ]);
43
+ if (!overwrite) {
44
+ console.log(chalk.yellow('\n Setup cancelled.\n'));
45
+ return;
46
+ }
47
+ }
48
+
49
+ // Prompt for email
50
+ const { email } = await inquirer.prompt([
51
+ {
52
+ type: 'input',
53
+ name: 'email',
54
+ message: 'Enter your Slashdev email (e.g. you@slashdev.io):',
55
+ validate: validateEmail,
56
+ },
57
+ ]);
58
+
59
+ const spinner = ora('Installing Slashdev Claude tools...').start();
60
+
61
+ try {
62
+ // Write engineer config
63
+ setConfig('engineer', email);
64
+
65
+ const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
66
+ setConfig('packageVersion', pkg.version);
67
+
68
+ // Ensure Claude directory exists
69
+ if (!existsSync(CLAUDE_DIR)) {
70
+ mkdirSync(CLAUDE_DIR, { recursive: true });
71
+ }
72
+
73
+ // Install hooks
74
+ spinner.text = 'Installing hooks...';
75
+ const hookResult = installHooks();
76
+
77
+ // Install skills
78
+ spinner.text = 'Installing Claude Code skills...';
79
+ const skillResult = installSkills();
80
+
81
+ // Install gstack skills
82
+ spinner.text = 'Installing gstack skills...';
83
+ const gstackResult = installGstack();
84
+
85
+ // Write global Claude rules (Socket Firewall + workspace policy)
86
+ spinner.text = 'Writing global Claude rules...';
87
+ const rulesResult = writeGlobalClaudeMd();
88
+
89
+ spinner.succeed('Slashdev Claude tools installed');
90
+
91
+ console.log('');
92
+ console.log(` ${chalk.green('✓')} Engineer: ${chalk.cyan(email)}`);
93
+ console.log(` ${chalk.green('✓')} Hooks: ${hookResult.installed} installed`);
94
+ console.log(` ${chalk.green('✓')} Skills: ${skillResult.installed} installed`);
95
+ console.log(` ${chalk.green('✓')} Gstack: ${gstackResult.installed} skills installed`);
96
+ if (gstackResult.warning) {
97
+ console.log(` ${chalk.yellow('!')} ${gstackResult.warning}`);
98
+ }
99
+ console.log(` ${chalk.green('✓')} Rules: ${rulesResult.path}`);
100
+ console.log(` ${chalk.green('✓')} Config: ${join(getSlashdevDir(), 'config.json')}`);
101
+ console.log('');
102
+ console.log(chalk.dim(' Hooks, skills, and global rules have been installed to ~/.claude/'));
103
+ console.log(chalk.dim(' Socket Firewall enforcement is active for all Bash install commands.'));
104
+ console.log(chalk.dim(' Prompt logs will be sent to the Slashdev API for quality review.\n'));
105
+ } catch (err) {
106
+ spinner.fail('Setup failed');
107
+ console.error(chalk.red(`\n Error: ${err.message}\n`));
108
+ process.exit(1);
109
+ }
110
+ }