@hustle-together/api-dev-tools 3.11.1 → 3.12.2
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/.claude/agents/code-reviewer.md +170 -0
- package/.claude/agents/docs-generator.md +80 -0
- package/.claude/agents/implementation-reviewer.md +119 -0
- package/.claude/agents/parallel-researcher.md +52 -0
- package/.claude/agents/research-validator.md +116 -0
- package/.claude/agents/schema-generator.md +70 -0
- package/.claude/agents/test-writer.md +104 -0
- package/.claude/api-dev-state.json +305 -56
- package/.claude/commands/README.md +21 -10
- package/.claude/commands/add-command.md +8 -5
- package/.claude/commands/api-create.md +36 -25
- package/.claude/commands/api-env.md +1 -0
- package/.claude/commands/api-interview.md +32 -19
- package/.claude/commands/api-research.md +47 -21
- package/.claude/commands/api-status.md +21 -1
- package/.claude/commands/api-verify.md +14 -13
- package/.claude/commands/beepboop.md +4 -5
- package/.claude/commands/busycommit.md +2 -3
- package/.claude/commands/commit.md +2 -3
- package/.claude/commands/cycle.md +2 -7
- package/.claude/commands/gap.md +2 -3
- package/.claude/commands/green.md +2 -7
- package/.claude/commands/issue.md +3 -8
- package/.claude/commands/ntfy-setup.md +91 -0
- package/.claude/commands/ntfy-test.md +74 -0
- package/.claude/commands/plan.md +2 -3
- package/.claude/commands/pr.md +2 -3
- package/.claude/commands/publish.md +40 -0
- package/.claude/commands/red.md +2 -7
- package/.claude/commands/refactor.md +2 -7
- package/.claude/commands/spike.md +2 -7
- package/.claude/commands/summarize.md +2 -3
- package/.claude/commands/tdd.md +2 -7
- package/.claude/commands/worktree-add.md +208 -216
- package/.claude/commands/worktree-cleanup.md +172 -178
- package/.claude/settings.json +63 -12
- package/.claude/settings.local.json +2 -1
- package/.claude-plugin/marketplace.json +2 -11
- package/.skills/README.md +55 -53
- package/.skills/_shared/settings.json +1 -1
- package/.skills/add-command/SKILL.md +10 -5
- package/.skills/api-create/SKILL.md +146 -35
- package/.skills/api-env/SKILL.md +1 -0
- package/.skills/api-interview/SKILL.md +32 -19
- package/.skills/api-research/SKILL.md +47 -21
- package/.skills/api-status/SKILL.md +21 -1
- package/.skills/api-verify/SKILL.md +14 -13
- package/.skills/beepboop/SKILL.md +6 -5
- package/.skills/busycommit/SKILL.md +4 -3
- package/.skills/commit/SKILL.md +4 -3
- package/.skills/cycle/SKILL.md +4 -7
- package/.skills/gap/SKILL.md +4 -3
- package/.skills/green/SKILL.md +4 -7
- package/.skills/issue/SKILL.md +5 -8
- package/.skills/plan/SKILL.md +4 -3
- package/.skills/pr/SKILL.md +4 -3
- package/.skills/publish/SKILL.md +160 -0
- package/.skills/red/SKILL.md +4 -7
- package/.skills/refactor/SKILL.md +4 -7
- package/.skills/spike/SKILL.md +4 -7
- package/.skills/summarize/SKILL.md +4 -3
- package/.skills/tdd/SKILL.md +4 -7
- package/.skills/update-todos/SKILL.md +22 -0
- package/.skills/worktree-add/SKILL.md +210 -216
- package/.skills/worktree-cleanup/SKILL.md +183 -187
- package/CHANGELOG.md +97 -79
- package/README.md +161 -7142
- package/bin/cli.js +448 -805
- package/commands/README.md +66 -31
- package/commands/add-command.md +8 -5
- package/commands/beepboop.md +4 -5
- package/commands/busycommit.md +2 -3
- package/commands/commit.md +2 -3
- package/commands/cycle.md +2 -7
- package/commands/gap.md +2 -3
- package/commands/green.md +2 -7
- package/commands/hustle-api-continue.md +8 -5
- package/commands/hustle-api-create.md +70 -29
- package/commands/hustle-api-env.md +1 -0
- package/commands/hustle-api-interview.md +32 -19
- package/commands/hustle-api-research.md +47 -21
- package/commands/hustle-api-sessions.md +8 -7
- package/commands/hustle-api-status.md +21 -1
- package/commands/hustle-api-verify.md +14 -13
- package/commands/hustle-combine.md +488 -241
- package/commands/hustle-ui-create-page.md +113 -50
- package/commands/hustle-ui-create.md +179 -26
- package/commands/issue.md +3 -8
- package/commands/plan.md +2 -3
- package/commands/pr.md +2 -3
- package/commands/red.md +2 -7
- package/commands/refactor.md +2 -7
- package/commands/spike.md +2 -7
- package/commands/summarize.md +2 -3
- package/commands/tdd.md +2 -7
- package/commands/worktree-add.md +208 -216
- package/commands/worktree-cleanup.md +172 -178
- package/hooks/api-workflow-check.py +5 -3
- package/hooks/enforce-component-type-confirm.py +97 -0
- package/hooks/lib/__init__.py +1 -0
- package/hooks/lib/greptile.py +355 -0
- package/hooks/lib/ntfy.py +209 -0
- package/hooks/notify-input-needed.py +73 -0
- package/hooks/notify-phase-complete.py +90 -0
- package/hooks/run-code-review.py +246 -0
- package/hooks/track-token-usage.py +121 -0
- package/package.json +13 -3
- package/scripts/collect-test-results.ts +102 -77
- package/scripts/extract-parameters.ts +112 -70
- package/scripts/generate-test-manifest.ts +118 -77
- package/templates/.env.example +57 -0
- package/templates/BRAND_GUIDE.md +92 -52
- package/templates/CLAUDE-SECTION.md +40 -37
- package/templates/SPEC.json +186 -38
- package/templates/api-dev-state.json +33 -4
- package/templates/api-showcase/_components/APICard.tsx +22 -18
- package/templates/api-showcase/_components/APIModal.tsx +110 -64
- package/templates/api-showcase/_components/APIShowcase.tsx +53 -35
- package/templates/api-showcase/_components/APITester.tsx +128 -67
- package/templates/api-showcase/page.tsx +4 -4
- package/templates/api-test/page.tsx +51 -30
- package/templates/api-test/test-structure/route.ts +43 -34
- package/templates/component/Component.stories.tsx +41 -39
- package/templates/component/Component.test.tsx +96 -78
- package/templates/component/Component.tsx +63 -52
- package/templates/component/Component.types.ts +10 -6
- package/templates/component/Component.visual.spec.ts +170 -0
- package/templates/component/index.ts +2 -2
- package/templates/dev-tools/_components/DevToolsLanding.tsx +8 -8
- package/templates/dev-tools/page.tsx +4 -3
- package/templates/mcp-servers.json +30 -2
- package/templates/page/page.e2e.test.ts +56 -48
- package/templates/page/page.tsx +3 -3
- package/templates/shared/HeroHeader.tsx +16 -15
- package/templates/shared/index.ts +1 -1
- package/templates/ui-showcase/_components/PreviewCard.tsx +20 -20
- package/templates/ui-showcase/_components/PreviewModal.tsx +149 -108
- package/templates/ui-showcase/_components/UIShowcase.tsx +43 -35
- package/templates/ui-showcase/page.tsx +4 -4
package/bin/cli.js
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const fs = require(
|
|
4
|
-
const path = require(
|
|
5
|
-
const { execSync } = require(
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { execSync } = require("child_process");
|
|
6
|
+
const readline = require("readline");
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
* API Development Tools Installer
|
|
9
|
+
* API Development Tools Installer v1.0.0
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* - Python hooks for programmatic enforcement
|
|
15
|
-
* - Settings configuration for hook registration
|
|
16
|
-
* - State file template for progress tracking
|
|
11
|
+
* Interactive CLI installer with:
|
|
12
|
+
* - ASCII art branding
|
|
13
|
+
* - Progress indicators
|
|
14
|
+
* - Comprehensive file installation
|
|
17
15
|
*
|
|
18
16
|
* Usage: npx @hustle-together/api-dev-tools --scope=project
|
|
19
17
|
*
|
|
@@ -21,916 +19,549 @@ const { execSync } = require('child_process');
|
|
|
21
19
|
* --with-storybook Auto-initialize Storybook for component development
|
|
22
20
|
* --with-playwright Auto-initialize Playwright for E2E testing
|
|
23
21
|
* --with-sandpack Auto-install Sandpack for live UI previews
|
|
22
|
+
* --silent Skip banner and reduce output
|
|
24
23
|
*/
|
|
25
24
|
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
green: '\x1b[32m',
|
|
38
|
-
blue: '\x1b[34m',
|
|
39
|
-
yellow: '\x1b[33m',
|
|
40
|
-
red: '\x1b[31m',
|
|
41
|
-
cyan: '\x1b[36m',
|
|
25
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
26
|
+
// ANSI Colors (red/black/white branding)
|
|
27
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
28
|
+
|
|
29
|
+
const c = {
|
|
30
|
+
reset: "\x1b[0m",
|
|
31
|
+
bold: "\x1b[1m",
|
|
32
|
+
dim: "\x1b[2m",
|
|
33
|
+
red: "\x1b[31m",
|
|
34
|
+
white: "\x1b[37m",
|
|
35
|
+
gray: "\x1b[90m",
|
|
42
36
|
};
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
39
|
+
// ASCII Art Banner
|
|
40
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
41
|
+
|
|
42
|
+
const BANNER = `
|
|
43
|
+
${c.red} ╔═══════════════════════════════════════════════════════════════╗
|
|
44
|
+
║ ║
|
|
45
|
+
║ ██╗ ██╗██╗ ██╗███████╗████████╗██╗ ███████╗ ║
|
|
46
|
+
║ ██║ ██║██║ ██║██╔════╝╚══██╔══╝██║ ██╔════╝ ║
|
|
47
|
+
║ ███████║██║ ██║███████╗ ██║ ██║ █████╗ ║
|
|
48
|
+
║ ██╔══██║██║ ██║╚════██║ ██║ ██║ ██╔══╝ ║
|
|
49
|
+
║ ██║ ██║╚██████╔╝███████║ ██║ ███████╗███████╗ ║
|
|
50
|
+
║ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚══════╝╚══════╝ ║
|
|
51
|
+
║ ║
|
|
52
|
+
╚═══════════════════════════════════════════════════════════════╝${c.reset}
|
|
53
|
+
|
|
54
|
+
${c.bold} API Development Tools for Claude Code${c.reset}
|
|
55
|
+
${c.dim} Interview-driven, research-first API development${c.reset}
|
|
56
|
+
${c.gray}v1.0.0${c.reset}
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
60
|
+
// Spinner Animation
|
|
61
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
62
|
+
|
|
63
|
+
const spinnerFrames = ["◐", "◓", "◑", "◒"];
|
|
64
|
+
let spinnerIndex = 0;
|
|
65
|
+
let spinnerInterval = null;
|
|
66
|
+
|
|
67
|
+
function startSpinner(message) {
|
|
68
|
+
process.stdout.write(`\r${c.gray}${spinnerFrames[0]}${c.reset} ${message}`);
|
|
69
|
+
spinnerInterval = setInterval(() => {
|
|
70
|
+
spinnerIndex = (spinnerIndex + 1) % spinnerFrames.length;
|
|
71
|
+
process.stdout.write(
|
|
72
|
+
`\r${c.gray}${spinnerFrames[spinnerIndex]}${c.reset} ${message}`,
|
|
73
|
+
);
|
|
74
|
+
}, 100);
|
|
46
75
|
}
|
|
47
76
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const pythonCommands = ['python3', 'python'];
|
|
53
|
-
|
|
54
|
-
for (const cmd of pythonCommands) {
|
|
55
|
-
try {
|
|
56
|
-
const version = execSync(`${cmd} --version 2>&1`, { encoding: 'utf8' }).trim();
|
|
57
|
-
if (version.includes('Python 3')) {
|
|
58
|
-
return { available: true, command: cmd, version };
|
|
59
|
-
}
|
|
60
|
-
} catch (e) {
|
|
61
|
-
// Command not found, try next
|
|
62
|
-
}
|
|
77
|
+
function stopSpinner(success, message) {
|
|
78
|
+
if (spinnerInterval) {
|
|
79
|
+
clearInterval(spinnerInterval);
|
|
80
|
+
spinnerInterval = null;
|
|
63
81
|
}
|
|
82
|
+
const icon = success ? `${c.white}●${c.reset}` : `${c.red}○${c.reset}`;
|
|
83
|
+
process.stdout.write(`\r${icon} ${message}\n`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
87
|
+
// Logging Utilities
|
|
88
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
64
89
|
|
|
65
|
-
|
|
90
|
+
function log(message) {
|
|
91
|
+
console.log(message);
|
|
66
92
|
}
|
|
67
93
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const checks = [
|
|
73
|
-
{ path: path.join(claudeDir, 'commands'), name: 'Commands directory' },
|
|
74
|
-
{ path: path.join(claudeDir, 'settings.json'), name: 'Settings file' },
|
|
75
|
-
{ path: path.join(claudeDir, 'api-dev-state.json'), name: 'State file' },
|
|
76
|
-
{ path: path.join(claudeDir, 'registry.json'), name: 'Registry file' },
|
|
77
|
-
{ path: path.join(claudeDir, 'performance-budgets.json'), name: 'Performance budgets' },
|
|
78
|
-
{ path: path.join(claudeDir, 'research'), name: 'Research cache directory' },
|
|
79
|
-
{ path: path.join(claudeDir, 'research', 'index.json'), name: 'Research index' },
|
|
80
|
-
];
|
|
94
|
+
function logStep(step, total, message) {
|
|
95
|
+
const progress = `${c.dim}[${step}/${total}]${c.reset}`;
|
|
96
|
+
console.log(`\n${progress} ${c.bold}${message}${c.reset}`);
|
|
97
|
+
}
|
|
81
98
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// Core utility hooks (5)
|
|
86
|
-
{ path: path.join(hooksDir, 'session-startup.py'), name: 'session-startup.py' },
|
|
87
|
-
{ path: path.join(hooksDir, 'enforce-external-research.py'), name: 'enforce-external-research.py' },
|
|
88
|
-
{ path: path.join(hooksDir, 'track-tool-use.py'), name: 'track-tool-use.py' },
|
|
89
|
-
{ path: path.join(hooksDir, 'periodic-reground.py'), name: 'periodic-reground.py' },
|
|
90
|
-
{ path: path.join(hooksDir, 'api-workflow-check.py'), name: 'api-workflow-check.py' },
|
|
91
|
-
// Phase enforcement hooks with user checkpoints (12 - one per phase)
|
|
92
|
-
{ path: path.join(hooksDir, 'enforce-disambiguation.py'), name: 'enforce-disambiguation.py' },
|
|
93
|
-
{ path: path.join(hooksDir, 'enforce-scope.py'), name: 'enforce-scope.py' },
|
|
94
|
-
{ path: path.join(hooksDir, 'enforce-research.py'), name: 'enforce-research.py' },
|
|
95
|
-
{ path: path.join(hooksDir, 'enforce-interview.py'), name: 'enforce-interview.py' },
|
|
96
|
-
{ path: path.join(hooksDir, 'enforce-deep-research.py'), name: 'enforce-deep-research.py' },
|
|
97
|
-
{ path: path.join(hooksDir, 'enforce-schema.py'), name: 'enforce-schema.py' },
|
|
98
|
-
{ path: path.join(hooksDir, 'enforce-environment.py'), name: 'enforce-environment.py' },
|
|
99
|
-
{ path: path.join(hooksDir, 'enforce-tdd-red.py'), name: 'enforce-tdd-red.py' },
|
|
100
|
-
{ path: path.join(hooksDir, 'verify-implementation.py'), name: 'verify-implementation.py' },
|
|
101
|
-
{ path: path.join(hooksDir, 'verify-after-green.py'), name: 'verify-after-green.py' },
|
|
102
|
-
{ path: path.join(hooksDir, 'enforce-verify.py'), name: 'enforce-verify.py' },
|
|
103
|
-
{ path: path.join(hooksDir, 'enforce-refactor.py'), name: 'enforce-refactor.py' },
|
|
104
|
-
{ path: path.join(hooksDir, 'enforce-documentation.py'), name: 'enforce-documentation.py' },
|
|
105
|
-
{ path: path.join(hooksDir, 'update-registry.py'), name: 'update-registry.py' }
|
|
106
|
-
);
|
|
107
|
-
}
|
|
99
|
+
function logSuccess(message) {
|
|
100
|
+
console.log(` ${c.white}●${c.reset} ${message}`);
|
|
101
|
+
}
|
|
108
102
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
103
|
+
function logInfo(message) {
|
|
104
|
+
console.log(` ${c.gray}○${c.reset} ${message}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function logWarn(message) {
|
|
108
|
+
console.log(` ${c.red}◆${c.reset} ${message}`);
|
|
109
|
+
}
|
|
115
110
|
|
|
116
|
-
|
|
111
|
+
function logError(message) {
|
|
112
|
+
console.log(` ${c.red}✗${c.reset} ${message}`);
|
|
117
113
|
}
|
|
118
114
|
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
116
|
+
// File Copy Utilities
|
|
117
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
121
118
|
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
log('⚠️ Warning: Python 3 not found', 'yellow');
|
|
126
|
-
log(' Enforcement hooks require Python 3 to run.', 'yellow');
|
|
127
|
-
log(' Hooks will be installed but may not work until Python 3 is available.', 'yellow');
|
|
128
|
-
log(' Install Python 3: https://www.python.org/downloads/\n', 'yellow');
|
|
129
|
-
} else {
|
|
130
|
-
log(`✅ Found ${python.version}`, 'green');
|
|
131
|
-
}
|
|
119
|
+
function copyDir(src, dest, options = {}) {
|
|
120
|
+
const { filter = () => true, overwrite = false } = options;
|
|
121
|
+
let copied = 0;
|
|
132
122
|
|
|
133
|
-
|
|
134
|
-
const targetDir = process.cwd();
|
|
135
|
-
const claudeDir = path.join(targetDir, '.claude');
|
|
136
|
-
const commandsDir = path.join(claudeDir, 'commands');
|
|
137
|
-
const hooksDir = path.join(claudeDir, 'hooks');
|
|
123
|
+
if (!fs.existsSync(src)) return copied;
|
|
138
124
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const sourceCommandsDir = path.join(packageDir, 'commands');
|
|
142
|
-
const sourceHooksDir = path.join(packageDir, 'hooks');
|
|
143
|
-
const sourceTemplatesDir = path.join(packageDir, 'templates');
|
|
144
|
-
|
|
145
|
-
// Verify source commands exist
|
|
146
|
-
if (!fs.existsSync(sourceCommandsDir)) {
|
|
147
|
-
log('❌ Error: Commands directory not found in package', 'red');
|
|
148
|
-
log(` Looking for: ${sourceCommandsDir}`, 'red');
|
|
149
|
-
process.exit(1);
|
|
125
|
+
if (!fs.existsSync(dest)) {
|
|
126
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
150
127
|
}
|
|
151
128
|
|
|
152
|
-
|
|
153
|
-
// 1. Install Commands
|
|
154
|
-
// ========================================
|
|
155
|
-
if (!fs.existsSync(commandsDir)) {
|
|
156
|
-
log(`📁 Creating directory: ${commandsDir}`, 'blue');
|
|
157
|
-
fs.mkdirSync(commandsDir, { recursive: true });
|
|
158
|
-
}
|
|
129
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
159
130
|
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
const srcPath = path.join(src, entry.name);
|
|
133
|
+
const destPath = path.join(dest, entry.name);
|
|
163
134
|
|
|
164
|
-
|
|
165
|
-
log('⚠️ Warning: No command files found to install', 'yellow');
|
|
166
|
-
} else {
|
|
167
|
-
log('📦 Installing commands:', 'blue');
|
|
168
|
-
commandFiles.forEach(file => {
|
|
169
|
-
const source = path.join(sourceCommandsDir, file);
|
|
170
|
-
const dest = path.join(commandsDir, file);
|
|
135
|
+
if (!filter(entry.name, srcPath)) continue;
|
|
171
136
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
137
|
+
if (entry.isDirectory()) {
|
|
138
|
+
copied += copyDir(srcPath, destPath, options);
|
|
139
|
+
} else {
|
|
140
|
+
if (!fs.existsSync(destPath) || overwrite) {
|
|
141
|
+
fs.copyFileSync(srcPath, destPath);
|
|
142
|
+
copied++;
|
|
177
143
|
}
|
|
178
|
-
}
|
|
144
|
+
}
|
|
179
145
|
}
|
|
180
146
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// ========================================
|
|
184
|
-
if (fs.existsSync(sourceHooksDir)) {
|
|
185
|
-
if (!fs.existsSync(hooksDir)) {
|
|
186
|
-
log(`\n📁 Creating directory: ${hooksDir}`, 'blue');
|
|
187
|
-
fs.mkdirSync(hooksDir, { recursive: true });
|
|
188
|
-
}
|
|
147
|
+
return copied;
|
|
148
|
+
}
|
|
189
149
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
);
|
|
150
|
+
function copyFile(src, dest, options = {}) {
|
|
151
|
+
const { overwrite = false, executable = false } = options;
|
|
193
152
|
|
|
194
|
-
|
|
195
|
-
log('\n🔒 Installing enforcement hooks:', 'cyan');
|
|
196
|
-
hookFiles.forEach(file => {
|
|
197
|
-
const source = path.join(sourceHooksDir, file);
|
|
198
|
-
const dest = path.join(hooksDir, file);
|
|
199
|
-
|
|
200
|
-
try {
|
|
201
|
-
fs.copyFileSync(source, dest);
|
|
202
|
-
// Make executable
|
|
203
|
-
fs.chmodSync(dest, '755');
|
|
204
|
-
log(` ✅ ${file}`, 'green');
|
|
205
|
-
} catch (error) {
|
|
206
|
-
log(` ❌ Failed to copy ${file}: ${error.message}`, 'red');
|
|
207
|
-
}
|
|
208
|
-
});
|
|
153
|
+
if (!fs.existsSync(src)) return false;
|
|
209
154
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
log(' • api-workflow-check.py - Prevents stopping until complete', 'blue');
|
|
214
|
-
}
|
|
155
|
+
const destDir = path.dirname(dest);
|
|
156
|
+
if (!fs.existsSync(destDir)) {
|
|
157
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
215
158
|
}
|
|
216
159
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const settingsDest = path.join(claudeDir, 'settings.json');
|
|
222
|
-
|
|
223
|
-
if (fs.existsSync(settingsSource)) {
|
|
224
|
-
log('\n⚙️ Configuring hook settings:', 'cyan');
|
|
225
|
-
|
|
226
|
-
try {
|
|
227
|
-
const newSettings = JSON.parse(fs.readFileSync(settingsSource, 'utf8'));
|
|
228
|
-
|
|
229
|
-
if (fs.existsSync(settingsDest)) {
|
|
230
|
-
// Merge with existing settings
|
|
231
|
-
const existingSettings = JSON.parse(fs.readFileSync(settingsDest, 'utf8'));
|
|
232
|
-
const mergedSettings = mergeSettings(existingSettings, newSettings);
|
|
233
|
-
fs.writeFileSync(settingsDest, JSON.stringify(mergedSettings, null, 2));
|
|
234
|
-
log(' ✅ Merged with existing settings.json', 'green');
|
|
235
|
-
} else {
|
|
236
|
-
// Create new settings file
|
|
237
|
-
fs.writeFileSync(settingsDest, JSON.stringify(newSettings, null, 2));
|
|
238
|
-
log(' ✅ Created settings.json with hook configuration', 'green');
|
|
239
|
-
}
|
|
240
|
-
} catch (error) {
|
|
241
|
-
log(` ❌ Failed to configure settings: ${error.message}`, 'red');
|
|
160
|
+
if (!fs.existsSync(dest) || overwrite) {
|
|
161
|
+
fs.copyFileSync(src, dest);
|
|
162
|
+
if (executable) {
|
|
163
|
+
fs.chmodSync(dest, "755");
|
|
242
164
|
}
|
|
165
|
+
return true;
|
|
243
166
|
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
244
169
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const stateSource = path.join(sourceTemplatesDir, 'api-dev-state.json');
|
|
249
|
-
const stateDest = path.join(claudeDir, 'api-dev-state.json');
|
|
250
|
-
|
|
251
|
-
if (fs.existsSync(stateSource)) {
|
|
252
|
-
log('\n📊 Setting up state tracking:', 'cyan');
|
|
170
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
171
|
+
// Check Prerequisites
|
|
172
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
253
173
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
174
|
+
function checkPython() {
|
|
175
|
+
const commands = ["python3", "python"];
|
|
176
|
+
for (const cmd of commands) {
|
|
177
|
+
try {
|
|
178
|
+
const version = execSync(`${cmd} --version 2>&1`, {
|
|
179
|
+
encoding: "utf8",
|
|
180
|
+
}).trim();
|
|
181
|
+
if (version.includes("Python 3")) {
|
|
182
|
+
return { ok: true, cmd, version };
|
|
260
183
|
}
|
|
261
|
-
}
|
|
262
|
-
|
|
184
|
+
} catch (e) {
|
|
185
|
+
// Continue to next command
|
|
263
186
|
}
|
|
264
187
|
}
|
|
188
|
+
return { ok: false };
|
|
189
|
+
}
|
|
265
190
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
191
|
+
function checkNode() {
|
|
192
|
+
try {
|
|
193
|
+
const version = process.version;
|
|
194
|
+
const major = parseInt(version.slice(1).split(".")[0], 10);
|
|
195
|
+
return { ok: major >= 18, version };
|
|
196
|
+
} catch (e) {
|
|
197
|
+
return { ok: false };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
274
200
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
201
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
202
|
+
// Main Installation
|
|
203
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
204
|
+
|
|
205
|
+
async function main() {
|
|
206
|
+
// Parse arguments
|
|
207
|
+
const args = process.argv.slice(2);
|
|
208
|
+
const withStorybook = args.includes("--with-storybook");
|
|
209
|
+
const withPlaywright = args.includes("--with-playwright");
|
|
210
|
+
const withSandpack = args.includes("--with-sandpack");
|
|
211
|
+
const silent = args.includes("--silent") || args.includes("-s");
|
|
212
|
+
|
|
213
|
+
// Show banner
|
|
214
|
+
if (!silent) {
|
|
215
|
+
console.clear();
|
|
216
|
+
log(BANNER);
|
|
285
217
|
}
|
|
286
218
|
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
const
|
|
219
|
+
// Directory setup
|
|
220
|
+
const targetDir = process.cwd();
|
|
221
|
+
const packageDir = path.dirname(__dirname);
|
|
222
|
+
const claudeDir = path.join(targetDir, ".claude");
|
|
223
|
+
const hooksDir = path.join(claudeDir, "hooks");
|
|
292
224
|
|
|
293
|
-
|
|
294
|
-
|
|
225
|
+
const totalSteps = 8;
|
|
226
|
+
let currentStep = 0;
|
|
295
227
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
log(' ✅ Created BRAND_GUIDE.md (customize for your project branding)', 'green');
|
|
300
|
-
} catch (error) {
|
|
301
|
-
log(` ❌ Failed to create brand guide: ${error.message}`, 'red');
|
|
302
|
-
}
|
|
303
|
-
} else {
|
|
304
|
-
log(' ℹ️ Brand guide already exists (preserved)', 'blue');
|
|
305
|
-
}
|
|
306
|
-
}
|
|
228
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
229
|
+
// Step 1: Check Prerequisites
|
|
230
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
307
231
|
|
|
308
|
-
|
|
309
|
-
// 4d. Install Performance Budgets (v3.9.0)
|
|
310
|
-
// ========================================
|
|
311
|
-
const perfBudgetsSource = path.join(sourceTemplatesDir, 'performance-budgets.json');
|
|
312
|
-
const perfBudgetsDest = path.join(claudeDir, 'performance-budgets.json');
|
|
232
|
+
logStep(++currentStep, totalSteps, "Checking prerequisites");
|
|
313
233
|
|
|
314
|
-
|
|
315
|
-
|
|
234
|
+
const node = checkNode();
|
|
235
|
+
if (node.ok) {
|
|
236
|
+
logSuccess(`Node.js ${node.version}`);
|
|
237
|
+
} else {
|
|
238
|
+
logError(`Node.js 18+ required (found ${node.version || "none"})`);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
316
241
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
} else {
|
|
325
|
-
log(' ℹ️ Performance budgets already exist (preserved)', 'blue');
|
|
326
|
-
}
|
|
242
|
+
const python = checkPython();
|
|
243
|
+
if (python.ok) {
|
|
244
|
+
logSuccess(`${python.version}`);
|
|
245
|
+
} else {
|
|
246
|
+
logWarn("Python 3 not found - hooks may not work");
|
|
247
|
+
logInfo("Install: https://www.python.org/downloads/");
|
|
327
248
|
}
|
|
328
249
|
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
const researchDir = path.join(claudeDir, 'research');
|
|
333
|
-
const researchIndexSource = path.join(sourceTemplatesDir, 'research-index.json');
|
|
334
|
-
const researchIndexDest = path.join(researchDir, 'index.json');
|
|
250
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
251
|
+
// Step 2: Install Commands
|
|
252
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
335
253
|
|
|
336
|
-
|
|
254
|
+
logStep(++currentStep, totalSteps, "Installing slash commands");
|
|
337
255
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
fs.mkdirSync(researchDir, { recursive: true });
|
|
341
|
-
log(' ✅ Created .claude/research/ directory', 'green');
|
|
342
|
-
} catch (error) {
|
|
343
|
-
log(` ❌ Failed to create research directory: ${error.message}`, 'red');
|
|
344
|
-
}
|
|
345
|
-
}
|
|
256
|
+
const commandsDir = path.join(claudeDir, "commands");
|
|
257
|
+
const sourceCommandsDir = path.join(packageDir, ".claude", "commands");
|
|
346
258
|
|
|
347
|
-
if (
|
|
348
|
-
|
|
349
|
-
fs.copyFileSync(researchIndexSource, researchIndexDest);
|
|
350
|
-
log(' ✅ Created research/index.json for freshness tracking', 'green');
|
|
351
|
-
} catch (error) {
|
|
352
|
-
log(` ❌ Failed to create research index: ${error.message}`, 'red');
|
|
353
|
-
}
|
|
354
|
-
} else if (fs.existsSync(researchIndexDest)) {
|
|
355
|
-
log(' ℹ️ Research index already exists (preserved)', 'blue');
|
|
259
|
+
if (!fs.existsSync(commandsDir)) {
|
|
260
|
+
fs.mkdirSync(commandsDir, { recursive: true });
|
|
356
261
|
}
|
|
357
262
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
log('\n🧪 Setting up Test UI:', 'cyan');
|
|
362
|
-
|
|
363
|
-
const testUiSourceDir = path.join(sourceTemplatesDir, 'api-test');
|
|
364
|
-
const hasNextJs = fs.existsSync(path.join(targetDir, 'next.config.js')) ||
|
|
365
|
-
fs.existsSync(path.join(targetDir, 'next.config.mjs')) ||
|
|
366
|
-
fs.existsSync(path.join(targetDir, 'next.config.ts'));
|
|
367
|
-
|
|
368
|
-
// Detect App Router structure (used by Test UI and Showcase Pages)
|
|
369
|
-
const appDir = fs.existsSync(path.join(targetDir, 'src', 'app'))
|
|
370
|
-
? path.join(targetDir, 'src', 'app')
|
|
371
|
-
: fs.existsSync(path.join(targetDir, 'app'))
|
|
372
|
-
? path.join(targetDir, 'app')
|
|
373
|
-
: null;
|
|
374
|
-
|
|
375
|
-
if (!hasNextJs) {
|
|
376
|
-
log(' ⚠️ Next.js not detected - skipping Test UI installation', 'yellow');
|
|
377
|
-
log(' 💡 Test UI requires Next.js App Router', 'yellow');
|
|
378
|
-
} else if (fs.existsSync(testUiSourceDir)) {
|
|
379
|
-
|
|
380
|
-
if (!appDir) {
|
|
381
|
-
log(' ⚠️ App Router not detected - skipping Test UI installation', 'yellow');
|
|
382
|
-
log(' 💡 Test UI requires Next.js App Router (app/ or src/app/)', 'yellow');
|
|
383
|
-
} else {
|
|
384
|
-
// Install test-structure API route
|
|
385
|
-
const apiTestStructureDir = path.join(appDir, 'api', 'test-structure');
|
|
386
|
-
const apiTestStructureSource = path.join(testUiSourceDir, 'test-structure', 'route.ts');
|
|
387
|
-
const apiTestStructureDest = path.join(apiTestStructureDir, 'route.ts');
|
|
263
|
+
const commandsCopied = copyDir(sourceCommandsDir, commandsDir, {
|
|
264
|
+
filter: (name) => name.endsWith(".md"),
|
|
265
|
+
});
|
|
388
266
|
|
|
389
|
-
|
|
390
|
-
fs.mkdirSync(apiTestStructureDir, { recursive: true });
|
|
391
|
-
}
|
|
267
|
+
logSuccess(`${commandsCopied} commands installed to .claude/commands/`);
|
|
392
268
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
log(' ✅ Created /api/test-structure route (parses Vitest files)', 'green');
|
|
397
|
-
} catch (error) {
|
|
398
|
-
log(` ❌ Failed to create test-structure API: ${error.message}`, 'red');
|
|
399
|
-
}
|
|
400
|
-
} else {
|
|
401
|
-
log(' ℹ️ /api/test-structure already exists (preserved)', 'blue');
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Install test UI page
|
|
405
|
-
const apiTestPageDir = path.join(appDir, 'api-test');
|
|
406
|
-
const apiTestPageSource = path.join(testUiSourceDir, 'page.tsx');
|
|
407
|
-
const apiTestPageDest = path.join(apiTestPageDir, 'page.tsx');
|
|
269
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
270
|
+
// Step 3: Install Hooks
|
|
271
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
408
272
|
|
|
409
|
-
|
|
410
|
-
fs.mkdirSync(apiTestPageDir, { recursive: true });
|
|
411
|
-
}
|
|
273
|
+
logStep(++currentStep, totalSteps, "Installing enforcement hooks");
|
|
412
274
|
|
|
413
|
-
|
|
414
|
-
try {
|
|
415
|
-
fs.copyFileSync(apiTestPageSource, apiTestPageDest);
|
|
416
|
-
log(' ✅ Created /api-test page (displays test structure)', 'green');
|
|
417
|
-
} catch (error) {
|
|
418
|
-
log(` ❌ Failed to create test UI page: ${error.message}`, 'red');
|
|
419
|
-
}
|
|
420
|
-
} else {
|
|
421
|
-
log(' ℹ️ /api-test page already exists (preserved)', 'blue');
|
|
422
|
-
}
|
|
275
|
+
const sourceHooksDir = path.join(packageDir, "hooks");
|
|
423
276
|
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
} else {
|
|
427
|
-
log(' ⚠️ Test UI templates not found in package', 'yellow');
|
|
277
|
+
if (!fs.existsSync(hooksDir)) {
|
|
278
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
428
279
|
}
|
|
429
280
|
|
|
430
|
-
//
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
log('\n🎨 Setting up showcase pages:', 'cyan');
|
|
434
|
-
|
|
435
|
-
if (hasNextJs && appDir) {
|
|
436
|
-
const showcaseTemplates = [
|
|
437
|
-
// Shared components first (required by other pages)
|
|
438
|
-
{
|
|
439
|
-
source: 'shared',
|
|
440
|
-
dest: path.join(appDir, 'shared'),
|
|
441
|
-
files: ['HeroHeader.tsx', 'index.ts'],
|
|
442
|
-
name: 'Shared components (HeroHeader)'
|
|
443
|
-
},
|
|
444
|
-
// API Showcase
|
|
445
|
-
{
|
|
446
|
-
source: 'api-showcase',
|
|
447
|
-
dest: path.join(appDir, 'api-showcase'),
|
|
448
|
-
files: ['page.tsx'],
|
|
449
|
-
componentsDir: '_components',
|
|
450
|
-
componentFiles: ['APIShowcase.tsx', 'APICard.tsx', 'APIModal.tsx', 'APITester.tsx'],
|
|
451
|
-
name: 'API Showcase'
|
|
452
|
-
},
|
|
453
|
-
// UI Showcase
|
|
454
|
-
{
|
|
455
|
-
source: 'ui-showcase',
|
|
456
|
-
dest: path.join(appDir, 'ui-showcase'),
|
|
457
|
-
files: ['page.tsx'],
|
|
458
|
-
componentsDir: '_components',
|
|
459
|
-
componentFiles: ['UIShowcase.tsx', 'PreviewCard.tsx', 'PreviewModal.tsx'],
|
|
460
|
-
name: 'UI Showcase'
|
|
461
|
-
},
|
|
462
|
-
// Dev Tools Landing
|
|
463
|
-
{
|
|
464
|
-
source: 'dev-tools',
|
|
465
|
-
dest: path.join(appDir, 'dev-tools'),
|
|
466
|
-
files: ['page.tsx'],
|
|
467
|
-
componentsDir: '_components',
|
|
468
|
-
componentFiles: ['DevToolsLanding.tsx'],
|
|
469
|
-
name: 'Dev Tools Landing'
|
|
470
|
-
}
|
|
471
|
-
];
|
|
472
|
-
|
|
473
|
-
for (const template of showcaseTemplates) {
|
|
474
|
-
const sourceDir = path.join(sourceTemplatesDir, template.source);
|
|
281
|
+
// Copy hooks lib directory first
|
|
282
|
+
const sourceHooksLibDir = path.join(sourceHooksDir, "lib");
|
|
283
|
+
const destHooksLibDir = path.join(hooksDir, "lib");
|
|
475
284
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
285
|
+
if (fs.existsSync(sourceHooksLibDir)) {
|
|
286
|
+
if (!fs.existsSync(destHooksLibDir)) {
|
|
287
|
+
fs.mkdirSync(destHooksLibDir, { recursive: true });
|
|
288
|
+
}
|
|
289
|
+
copyDir(sourceHooksLibDir, destHooksLibDir);
|
|
290
|
+
}
|
|
480
291
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
292
|
+
// Copy Python hook files
|
|
293
|
+
let hooksCopied = 0;
|
|
294
|
+
if (fs.existsSync(sourceHooksDir)) {
|
|
295
|
+
const hookFiles = fs
|
|
296
|
+
.readdirSync(sourceHooksDir)
|
|
297
|
+
.filter((f) => f.endsWith(".py"));
|
|
298
|
+
|
|
299
|
+
for (const file of hookFiles) {
|
|
300
|
+
const src = path.join(sourceHooksDir, file);
|
|
301
|
+
const dest = path.join(hooksDir, file);
|
|
302
|
+
|
|
303
|
+
if (!fs.existsSync(dest)) {
|
|
304
|
+
fs.copyFileSync(src, dest);
|
|
305
|
+
fs.chmodSync(dest, "755");
|
|
306
|
+
hooksCopied++;
|
|
484
307
|
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
485
310
|
|
|
486
|
-
|
|
311
|
+
logSuccess(`${hooksCopied} hooks installed to .claude/hooks/`);
|
|
312
|
+
logInfo("Includes: enforce-*, notify-*, track-token-usage.py");
|
|
487
313
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const destFile = path.join(template.dest, file);
|
|
314
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
315
|
+
// Step 4: Install Subagents
|
|
316
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
492
317
|
|
|
493
|
-
|
|
494
|
-
try {
|
|
495
|
-
fs.copyFileSync(srcFile, destFile);
|
|
496
|
-
installedFiles.push(file);
|
|
497
|
-
} catch (error) {
|
|
498
|
-
log(` ❌ Failed to copy ${file}: ${error.message}`, 'red');
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
318
|
+
logStep(++currentStep, totalSteps, "Installing subagents");
|
|
502
319
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
const srcComponentsDir = path.join(sourceDir, template.componentsDir);
|
|
506
|
-
const destComponentsDir = path.join(template.dest, template.componentsDir);
|
|
507
|
-
|
|
508
|
-
if (fs.existsSync(srcComponentsDir)) {
|
|
509
|
-
if (!fs.existsSync(destComponentsDir)) {
|
|
510
|
-
fs.mkdirSync(destComponentsDir, { recursive: true });
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
for (const file of template.componentFiles) {
|
|
514
|
-
const srcFile = path.join(srcComponentsDir, file);
|
|
515
|
-
const destFile = path.join(destComponentsDir, file);
|
|
516
|
-
|
|
517
|
-
if (fs.existsSync(srcFile) && !fs.existsSync(destFile)) {
|
|
518
|
-
try {
|
|
519
|
-
fs.copyFileSync(srcFile, destFile);
|
|
520
|
-
installedFiles.push(`_components/${file}`);
|
|
521
|
-
} catch (error) {
|
|
522
|
-
log(` ❌ Failed to copy ${file}: ${error.message}`, 'red');
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
320
|
+
const agentsDir = path.join(claudeDir, "agents");
|
|
321
|
+
const sourceAgentsDir = path.join(packageDir, ".claude", "agents");
|
|
528
322
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
} else {
|
|
532
|
-
log(` ℹ️ ${template.name} already exists (preserved)`, 'blue');
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
log('\n 💡 Showcase pages available at:', 'yellow');
|
|
537
|
-
log(' /dev-tools - Landing page with all dev tools', 'yellow');
|
|
538
|
-
log(' /api-showcase - Interactive API testing', 'yellow');
|
|
539
|
-
log(' /ui-showcase - Live component previews', 'yellow');
|
|
540
|
-
} else {
|
|
541
|
-
log(' ⚠️ Next.js App Router not detected - skipping showcase pages', 'yellow');
|
|
323
|
+
if (!fs.existsSync(agentsDir)) {
|
|
324
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
542
325
|
}
|
|
543
326
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
log('\n📦 Setting up component/page templates:', 'cyan');
|
|
327
|
+
const agentsCopied = copyDir(sourceAgentsDir, agentsDir, {
|
|
328
|
+
filter: (name) => name.endsWith(".md"),
|
|
329
|
+
});
|
|
548
330
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
},
|
|
555
|
-
{
|
|
556
|
-
source: 'page',
|
|
557
|
-
dest: path.join(claudeDir, 'templates', 'page'),
|
|
558
|
-
name: 'Page templates'
|
|
559
|
-
}
|
|
560
|
-
];
|
|
331
|
+
logSuccess(`${agentsCopied} subagents installed to .claude/agents/`);
|
|
332
|
+
logInfo("Haiku: parallel-researcher, research-validator, docs-generator");
|
|
333
|
+
logInfo(
|
|
334
|
+
"Sonnet: schema-generator, test-writer, implementation-reviewer, code-reviewer",
|
|
335
|
+
);
|
|
561
336
|
|
|
562
|
-
|
|
563
|
-
|
|
337
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
338
|
+
// Step 5: Install Configuration Files
|
|
339
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
564
340
|
|
|
565
|
-
|
|
566
|
-
log(` ⚠️ ${template.name} not found`, 'yellow');
|
|
567
|
-
continue;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
if (!fs.existsSync(template.dest)) {
|
|
571
|
-
fs.mkdirSync(template.dest, { recursive: true });
|
|
572
|
-
}
|
|
341
|
+
logStep(++currentStep, totalSteps, "Setting up configuration");
|
|
573
342
|
|
|
574
|
-
|
|
575
|
-
let copiedCount = 0;
|
|
343
|
+
const sourceTemplatesDir = path.join(packageDir, "templates");
|
|
576
344
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
345
|
+
// Settings
|
|
346
|
+
const settingsSource = path.join(sourceTemplatesDir, "settings.json");
|
|
347
|
+
const settingsDest = path.join(claudeDir, "settings.json");
|
|
580
348
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
349
|
+
if (fs.existsSync(settingsSource)) {
|
|
350
|
+
if (fs.existsSync(settingsDest)) {
|
|
351
|
+
// Merge settings
|
|
352
|
+
try {
|
|
353
|
+
const existing = JSON.parse(fs.readFileSync(settingsDest, "utf8"));
|
|
354
|
+
const newSettings = JSON.parse(fs.readFileSync(settingsSource, "utf8"));
|
|
355
|
+
const merged = mergeSettings(existing, newSettings);
|
|
356
|
+
fs.writeFileSync(settingsDest, JSON.stringify(merged, null, 2));
|
|
357
|
+
logSuccess("Merged settings.json");
|
|
358
|
+
} catch (e) {
|
|
359
|
+
logWarn("Could not merge settings.json");
|
|
588
360
|
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
if (copiedCount > 0) {
|
|
592
|
-
log(` ✅ ${template.name} (${copiedCount} files)`, 'green');
|
|
593
361
|
} else {
|
|
594
|
-
|
|
362
|
+
copyFile(settingsSource, settingsDest);
|
|
363
|
+
logSuccess("Created settings.json");
|
|
595
364
|
}
|
|
596
365
|
}
|
|
597
366
|
|
|
598
|
-
//
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
log('\n📊 Setting up manifest generation scripts:', 'cyan');
|
|
367
|
+
// State file
|
|
368
|
+
const stateSource = path.join(sourceTemplatesDir, "api-dev-state.json");
|
|
369
|
+
const stateDest = path.join(claudeDir, "api-dev-state.json");
|
|
602
370
|
|
|
603
|
-
|
|
604
|
-
|
|
371
|
+
if (!fs.existsSync(stateDest) && fs.existsSync(stateSource)) {
|
|
372
|
+
copyFile(stateSource, stateDest);
|
|
373
|
+
logSuccess("Created api-dev-state.json");
|
|
374
|
+
} else if (fs.existsSync(stateDest)) {
|
|
375
|
+
logInfo("State file preserved");
|
|
376
|
+
}
|
|
605
377
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
378
|
+
// Registry
|
|
379
|
+
const registrySource = path.join(sourceTemplatesDir, "registry.json");
|
|
380
|
+
const registryDest = path.join(claudeDir, "registry.json");
|
|
381
|
+
|
|
382
|
+
if (!fs.existsSync(registryDest) && fs.existsSync(registrySource)) {
|
|
383
|
+
copyFile(registrySource, registryDest);
|
|
384
|
+
logSuccess("Created registry.json");
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Research cache
|
|
388
|
+
const researchDir = path.join(claudeDir, "research");
|
|
389
|
+
if (!fs.existsSync(researchDir)) {
|
|
390
|
+
fs.mkdirSync(researchDir, { recursive: true });
|
|
391
|
+
const indexSource = path.join(sourceTemplatesDir, "research-index.json");
|
|
392
|
+
if (fs.existsSync(indexSource)) {
|
|
393
|
+
copyFile(indexSource, path.join(researchDir, "index.json"));
|
|
609
394
|
}
|
|
395
|
+
logSuccess("Created research cache");
|
|
396
|
+
}
|
|
610
397
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
398
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
399
|
+
// Step 6: Install Environment Template
|
|
400
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
614
401
|
|
|
615
|
-
|
|
616
|
-
scriptFiles.forEach(file => {
|
|
617
|
-
const source = path.join(sourceScriptsDir, file);
|
|
618
|
-
const dest = path.join(targetScriptsDir, file);
|
|
402
|
+
logStep(++currentStep, totalSteps, "Setting up environment");
|
|
619
403
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
log(` ❌ Failed to copy ${file}: ${error.message}`, 'red');
|
|
625
|
-
}
|
|
626
|
-
});
|
|
404
|
+
const templatesDestDir = path.join(targetDir, "templates");
|
|
405
|
+
if (!fs.existsSync(templatesDestDir)) {
|
|
406
|
+
fs.mkdirSync(templatesDestDir, { recursive: true });
|
|
407
|
+
}
|
|
627
408
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
} else {
|
|
636
|
-
log(' ⚠️ Scripts directory not found in package', 'yellow');
|
|
409
|
+
const envSource = path.join(sourceTemplatesDir, ".env.example");
|
|
410
|
+
const envDest = path.join(templatesDestDir, ".env.example");
|
|
411
|
+
|
|
412
|
+
if (fs.existsSync(envSource)) {
|
|
413
|
+
copyFile(envSource, envDest, { overwrite: true });
|
|
414
|
+
logSuccess("Created templates/.env.example");
|
|
415
|
+
logInfo("Copy to .env and configure your API keys");
|
|
637
416
|
}
|
|
638
417
|
|
|
639
|
-
//
|
|
640
|
-
//
|
|
641
|
-
//
|
|
642
|
-
// NOTE: We use `claude mcp add` directly because .mcp.json requires manual approval
|
|
643
|
-
// and doesn't auto-load. Using the CLI ensures servers are immediately available.
|
|
644
|
-
log('\n🔌 Configuring MCP servers:', 'cyan');
|
|
418
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
419
|
+
// Step 7: Configure MCP Servers
|
|
420
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
645
421
|
|
|
646
|
-
|
|
422
|
+
logStep(++currentStep, totalSteps, "Configuring MCP servers");
|
|
647
423
|
|
|
648
424
|
const mcpServers = [
|
|
649
|
-
{ name:
|
|
650
|
-
{ name:
|
|
425
|
+
{ name: "context7", cmd: "npx -y @upstash/context7-mcp" },
|
|
426
|
+
{ name: "github", cmd: "npx -y @modelcontextprotocol/server-github" },
|
|
427
|
+
{
|
|
428
|
+
name: "greptile",
|
|
429
|
+
cmd: "npx -y @anthropics/mcp-greptile",
|
|
430
|
+
optional: true,
|
|
431
|
+
},
|
|
651
432
|
];
|
|
652
433
|
|
|
653
434
|
for (const server of mcpServers) {
|
|
654
435
|
try {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
} catch (
|
|
661
|
-
// Server doesn't exist, add it
|
|
436
|
+
execSync(`claude mcp get ${server.name} 2>&1`, {
|
|
437
|
+
encoding: "utf8",
|
|
438
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
439
|
+
});
|
|
440
|
+
logInfo(`${server.name} already configured`);
|
|
441
|
+
} catch (e) {
|
|
662
442
|
try {
|
|
663
|
-
execSync(`claude mcp add ${server.name} -- ${server.
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
443
|
+
execSync(`claude mcp add ${server.name} -- ${server.cmd}`, {
|
|
444
|
+
encoding: "utf8",
|
|
445
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
446
|
+
});
|
|
447
|
+
logSuccess(`Added ${server.name}`);
|
|
448
|
+
} catch (addErr) {
|
|
449
|
+
if (server.optional) {
|
|
450
|
+
logInfo(
|
|
451
|
+
`${server.name} (optional) - configure with GREPTILE_API_KEY`,
|
|
452
|
+
);
|
|
453
|
+
} else {
|
|
454
|
+
logWarn(`Could not add ${server.name} - add manually`);
|
|
455
|
+
}
|
|
667
456
|
}
|
|
668
457
|
}
|
|
669
458
|
}
|
|
670
459
|
|
|
671
|
-
|
|
672
|
-
log(' 💡 Restart Claude Code for MCP tools to be available', 'yellow');
|
|
673
|
-
|
|
674
|
-
// ========================================
|
|
675
|
-
// 6. Update CLAUDE.md with workflow documentation
|
|
676
|
-
// ========================================
|
|
677
|
-
const claudeMdSection = path.join(sourceTemplatesDir, 'CLAUDE-SECTION.md');
|
|
678
|
-
const projectClaudeMd = path.join(targetDir, 'CLAUDE.md');
|
|
679
|
-
|
|
680
|
-
if (fs.existsSync(claudeMdSection)) {
|
|
681
|
-
log('\n📝 CLAUDE.md workflow documentation:', 'cyan');
|
|
682
|
-
|
|
683
|
-
const sectionContent = fs.readFileSync(claudeMdSection, 'utf8');
|
|
684
|
-
const sectionMarker = '## API Development Workflow (v3.0)';
|
|
685
|
-
|
|
686
|
-
if (fs.existsSync(projectClaudeMd)) {
|
|
687
|
-
const existingContent = fs.readFileSync(projectClaudeMd, 'utf8');
|
|
688
|
-
|
|
689
|
-
if (existingContent.includes(sectionMarker)) {
|
|
690
|
-
// Update existing section
|
|
691
|
-
const beforeSection = existingContent.split(sectionMarker)[0];
|
|
692
|
-
// Find the next ## heading or end of file
|
|
693
|
-
const afterMatch = existingContent.match(/## API Development Workflow[\s\S]*?((?=\n## )|$)/);
|
|
694
|
-
const afterSection = afterMatch ? existingContent.substring(existingContent.indexOf(afterMatch[0]) + afterMatch[0].length) : '';
|
|
695
|
-
|
|
696
|
-
fs.writeFileSync(projectClaudeMd, beforeSection + sectionContent + afterSection);
|
|
697
|
-
log(' ✅ Updated API Development Workflow section in CLAUDE.md', 'green');
|
|
698
|
-
} else {
|
|
699
|
-
// Append section
|
|
700
|
-
fs.appendFileSync(projectClaudeMd, '\n\n' + sectionContent);
|
|
701
|
-
log(' ✅ Added API Development Workflow section to CLAUDE.md', 'green');
|
|
702
|
-
}
|
|
703
|
-
} else {
|
|
704
|
-
// Create new CLAUDE.md with section
|
|
705
|
-
fs.writeFileSync(projectClaudeMd, '# Project Instructions\n\n' + sectionContent);
|
|
706
|
-
log(' ✅ Created CLAUDE.md with API Development Workflow section', 'green');
|
|
707
|
-
}
|
|
708
|
-
}
|
|
460
|
+
logInfo("Greptile requires GREPTILE_API_KEY + GITHUB_TOKEN for Phase 14");
|
|
709
461
|
|
|
710
|
-
//
|
|
711
|
-
//
|
|
712
|
-
//
|
|
713
|
-
|
|
714
|
-
|
|
462
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
463
|
+
// Step 8: Optional Tools
|
|
464
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
465
|
+
|
|
466
|
+
logStep(++currentStep, totalSteps, "Optional tools");
|
|
715
467
|
|
|
468
|
+
if (withStorybook || withPlaywright || withSandpack) {
|
|
716
469
|
if (withSandpack) {
|
|
470
|
+
startSpinner("Installing Sandpack...");
|
|
717
471
|
try {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
log(' pnpm add @codesandbox/sandpack-react', 'yellow');
|
|
729
|
-
}
|
|
472
|
+
execSync("npm install @codesandbox/sandpack-react 2>&1", {
|
|
473
|
+
cwd: targetDir,
|
|
474
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
475
|
+
});
|
|
476
|
+
stopSpinner(true, "Sandpack installed");
|
|
477
|
+
} catch (e) {
|
|
478
|
+
stopSpinner(
|
|
479
|
+
false,
|
|
480
|
+
"Sandpack install failed - run: npm install @codesandbox/sandpack-react",
|
|
481
|
+
);
|
|
730
482
|
}
|
|
731
483
|
}
|
|
732
484
|
|
|
733
485
|
if (withStorybook) {
|
|
486
|
+
startSpinner("Initializing Storybook (this takes a moment)...");
|
|
734
487
|
try {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
488
|
+
execSync("npx storybook@latest init --yes 2>&1", {
|
|
489
|
+
cwd: targetDir,
|
|
490
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
491
|
+
timeout: 300000,
|
|
492
|
+
});
|
|
493
|
+
stopSpinner(true, "Storybook initialized");
|
|
494
|
+
} catch (e) {
|
|
495
|
+
stopSpinner(
|
|
496
|
+
false,
|
|
497
|
+
"Storybook init failed - run: npx storybook@latest init",
|
|
498
|
+
);
|
|
742
499
|
}
|
|
743
500
|
}
|
|
744
501
|
|
|
745
502
|
if (withPlaywright) {
|
|
503
|
+
startSpinner("Initializing Playwright (this takes a moment)...");
|
|
746
504
|
try {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
505
|
+
execSync("npm init playwright@latest -- --yes 2>&1", {
|
|
506
|
+
cwd: targetDir,
|
|
507
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
508
|
+
timeout: 300000,
|
|
509
|
+
});
|
|
510
|
+
stopSpinner(true, "Playwright initialized");
|
|
511
|
+
} catch (e) {
|
|
512
|
+
stopSpinner(
|
|
513
|
+
false,
|
|
514
|
+
"Playwright init failed - run: npm init playwright@latest",
|
|
515
|
+
);
|
|
754
516
|
}
|
|
755
517
|
}
|
|
518
|
+
} else {
|
|
519
|
+
logInfo("None selected");
|
|
520
|
+
logInfo(
|
|
521
|
+
"Add later with: --with-storybook --with-playwright --with-sandpack",
|
|
522
|
+
);
|
|
756
523
|
}
|
|
757
524
|
|
|
758
|
-
//
|
|
759
|
-
//
|
|
760
|
-
//
|
|
761
|
-
|
|
762
|
-
log(
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
log('\n📦 v3.9.0 Features (included):', 'bright');
|
|
790
|
-
log(' • /hustle-ui-create command for components and pages', 'cyan');
|
|
791
|
-
log(' • UI Showcase page (grid + modal preview at /ui-showcase)', 'cyan');
|
|
792
|
-
log(' • API Showcase page (interactive testing at /api-showcase)', 'cyan');
|
|
793
|
-
log(' • Brand guide integration (.claude/BRAND_GUIDE.md)', 'cyan');
|
|
794
|
-
log(' • Performance budgets as TDD gates (memory, re-renders, timing)', 'cyan');
|
|
795
|
-
log(' • ShadCN component detection in src/components/ui/', 'cyan');
|
|
796
|
-
log(' • 4-step verification (responsive + brand + tests + memory)', 'cyan');
|
|
797
|
-
log(' • Storybook + Playwright testing templates with thresholds', 'cyan');
|
|
798
|
-
|
|
799
|
-
log('\n📦 v3.8.0 Features (included):', 'bright');
|
|
800
|
-
log(' • /hustle-combine command for API orchestration', 'cyan');
|
|
801
|
-
log(' • Central registry (registry.json) tracks all created elements', 'cyan');
|
|
802
|
-
|
|
803
|
-
log('\n🔒 User Checkpoint Enforcement:', 'bright');
|
|
804
|
-
log(' • Phase 1: "Which interpretation?" (disambiguation)', 'cyan');
|
|
805
|
-
log(' • Phase 2: "Scope correct?" (scope confirmation)', 'cyan');
|
|
806
|
-
log(' • Phase 3: "Proceed to interview?" (research summary)', 'cyan');
|
|
807
|
-
log(' • Phase 4: "Interview complete?" (all questions answered)', 'cyan');
|
|
808
|
-
log(' • Phase 5: "Approve searches?" (deep research proposal)', 'cyan');
|
|
809
|
-
log(' • Phase 6: "Schema matches interview?" (schema review)', 'cyan');
|
|
810
|
-
log(' • Phase 7: "Ready for testing?" (environment check)', 'cyan');
|
|
811
|
-
log(' • Phase 8: "Test plan looks good?" (test matrix)', 'cyan');
|
|
812
|
-
log(' • Phase 10: "Fix gaps?" (verification decision)', 'cyan');
|
|
813
|
-
log(' • Phase 12: "Documentation complete?" (final checklist)', 'cyan');
|
|
814
|
-
|
|
815
|
-
log('\n📚 Available Commands:', 'bright');
|
|
816
|
-
log(' API Development:', 'bright');
|
|
817
|
-
log(' /hustle-api-create [endpoint] - Complete 13-phase API workflow', 'blue');
|
|
818
|
-
log(' /hustle-combine [api|ui] - Combine existing APIs into orchestration', 'blue');
|
|
819
|
-
log(' /hustle-api-interview [endpoint] - Questions FROM research', 'blue');
|
|
820
|
-
log(' /hustle-api-research [library] - Adaptive propose-approve research', 'blue');
|
|
821
|
-
log(' /hustle-api-verify [endpoint] - Manual Phase 10 verification', 'blue');
|
|
822
|
-
log(' /hustle-api-env [endpoint] - Check API keys and environment', 'blue');
|
|
823
|
-
log(' /hustle-api-status [endpoint] - Track 13-phase progress', 'blue');
|
|
824
|
-
log('', 'blue');
|
|
825
|
-
log(' UI Development:', 'bright');
|
|
826
|
-
log(' /hustle-ui-create [name] - Create component or page (13-phase)', 'blue');
|
|
827
|
-
log(' • Brand guide integration', 'blue');
|
|
828
|
-
log(' • ShadCN component detection', 'blue');
|
|
829
|
-
log(' • 4-step verification', 'blue');
|
|
830
|
-
log(' • UI Showcase auto-update', 'blue');
|
|
831
|
-
|
|
832
|
-
log('\n🚀 Quick Start:', 'bright');
|
|
833
|
-
log(' API: /hustle-api-create my-endpoint', 'blue');
|
|
834
|
-
log(' UI: /hustle-ui-create Button', 'blue');
|
|
835
|
-
|
|
836
|
-
log('\n💡 Check progress anytime:', 'yellow');
|
|
837
|
-
log(' cat .claude/api-dev-state.json | jq \'.phases\'', 'yellow');
|
|
838
|
-
|
|
839
|
-
log('\n📖 Documentation:', 'bright');
|
|
840
|
-
log(` ${path.join(commandsDir, 'README.md')}\n`, 'blue');
|
|
841
|
-
|
|
842
|
-
log('\n🎨 Showcase Pages:', 'bright');
|
|
843
|
-
log(' /dev-tools - Landing page with all dev tools', 'blue');
|
|
844
|
-
log(' /api-showcase - Interactive API testing', 'blue');
|
|
845
|
-
log(' /ui-showcase - Live component previews', 'blue');
|
|
846
|
-
|
|
847
|
-
// Enhanced Optional Development Tools Section
|
|
848
|
-
log('\n' + '─'.repeat(60), 'yellow');
|
|
849
|
-
log('📦 OPTIONAL DEVELOPMENT TOOLS', 'yellow');
|
|
850
|
-
log('─'.repeat(60), 'yellow');
|
|
851
|
-
log(' The following tools are OPTIONAL but enhance UI development.', 'bright');
|
|
852
|
-
log(' Install now with flags, or later when prompted during /hustle-ui-create.\n', 'bright');
|
|
853
|
-
|
|
854
|
-
log(' ┌──────────────────────────────────────────────────────────┐', 'cyan');
|
|
855
|
-
log(' │ STORYBOOK (Component Development) │', 'cyan');
|
|
856
|
-
log(' ├──────────────────────────────────────────────────────────┤', 'cyan');
|
|
857
|
-
log(' │ Size: ~50MB │', 'cyan');
|
|
858
|
-
log(' │ Purpose: Visual component testing, interactive docs │', 'cyan');
|
|
859
|
-
log(' │ Required: /hustle-ui-create (component mode) │', 'cyan');
|
|
860
|
-
log(' │ Without: Cannot write .stories.tsx files │', 'cyan');
|
|
861
|
-
log(' │ Install: --with-storybook flag OR during UI workflow │', 'cyan');
|
|
862
|
-
log(' │ Command: npx storybook@latest init │', 'cyan');
|
|
863
|
-
log(' └──────────────────────────────────────────────────────────┘', 'cyan');
|
|
864
|
-
|
|
865
|
-
log(' ┌──────────────────────────────────────────────────────────┐', 'magenta');
|
|
866
|
-
log(' │ PLAYWRIGHT (E2E Testing) │', 'magenta');
|
|
867
|
-
log(' ├──────────────────────────────────────────────────────────┤', 'magenta');
|
|
868
|
-
log(' │ Size: ~200MB (includes browser binaries) │', 'magenta');
|
|
869
|
-
log(' │ Purpose: End-to-end page testing, cross-browser │', 'magenta');
|
|
870
|
-
log(' │ Required: /hustle-ui-create-page (page mode) │', 'magenta');
|
|
871
|
-
log(' │ Without: Cannot write .e2e.test.ts files │', 'magenta');
|
|
872
|
-
log(' │ Install: --with-playwright flag OR during UI workflow │', 'magenta');
|
|
873
|
-
log(' │ Command: npm init playwright@latest │', 'magenta');
|
|
874
|
-
log(' └──────────────────────────────────────────────────────────┘', 'magenta');
|
|
875
|
-
|
|
876
|
-
log(' ┌──────────────────────────────────────────────────────────┐', 'blue');
|
|
877
|
-
log(' │ SANDPACK (Live Editing) │', 'blue');
|
|
878
|
-
log(' ├──────────────────────────────────────────────────────────┤', 'blue');
|
|
879
|
-
log(' │ Size: ~5MB │', 'blue');
|
|
880
|
-
log(' │ Purpose: Live code editing in UI Showcase │', 'blue');
|
|
881
|
-
log(' │ Required: Interactive component playground │', 'blue');
|
|
882
|
-
log(' │ Without: Static previews only (still functional) │', 'blue');
|
|
883
|
-
log(' │ Install: --with-sandpack flag │', 'blue');
|
|
884
|
-
log(' │ Command: npm install @codesandbox/sandpack-react │', 'blue');
|
|
885
|
-
log(' └──────────────────────────────────────────────────────────┘', 'blue');
|
|
886
|
-
|
|
887
|
-
log('\n 💡 UI Workflow Behavior:', 'bright');
|
|
888
|
-
log(' When you run /hustle-ui-create without Storybook or Playwright:', 'bright');
|
|
889
|
-
log(' 1. The hook will BLOCK writing test/story files', 'bright');
|
|
890
|
-
log(' 2. You\'ll see the install command and be prompted to install', 'bright');
|
|
891
|
-
log(' 3. After installing, the workflow continues automatically', 'bright');
|
|
892
|
-
log(' 4. You can skip and install later, but Phase 8-10 will be blocked', 'bright');
|
|
893
|
-
|
|
894
|
-
log('\n Example with all optional tools:', 'yellow');
|
|
895
|
-
log(' npx @hustle-together/api-dev-tools --with-storybook --with-playwright --with-sandpack\n', 'yellow');
|
|
896
|
-
|
|
897
|
-
// ========================================
|
|
898
|
-
// 5. Verify Installation
|
|
899
|
-
// ========================================
|
|
900
|
-
const verification = verifyInstallation(claudeDir, hooksDir);
|
|
901
|
-
if (!verification.success) {
|
|
902
|
-
log('⚠️ Installation verification found issues:', 'yellow');
|
|
903
|
-
verification.failures.forEach(f => log(` • Missing: ${f}`, 'yellow'));
|
|
904
|
-
log(' Some features may not work correctly.\n', 'yellow');
|
|
905
|
-
}
|
|
525
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
526
|
+
// Summary
|
|
527
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
528
|
+
|
|
529
|
+
log(`
|
|
530
|
+
${c.red}═══════════════════════════════════════════════════════════════${c.reset}
|
|
531
|
+
${c.bold} Installation Complete${c.reset}
|
|
532
|
+
${c.red}═══════════════════════════════════════════════════════════════${c.reset}
|
|
533
|
+
|
|
534
|
+
${c.bold}Installed:${c.reset}
|
|
535
|
+
● Commands .claude/commands/ (slash commands)
|
|
536
|
+
● Hooks .claude/hooks/ (enforcement)
|
|
537
|
+
● Subagents .claude/agents/ (parallel processing)
|
|
538
|
+
● Config .claude/ (settings, state, registry)
|
|
539
|
+
● Templates templates/ (.env.example)
|
|
540
|
+
|
|
541
|
+
${c.bold}Quick Start:${c.reset}
|
|
542
|
+
${c.gray}$${c.reset} /api-create my-endpoint ${c.dim}# Build API endpoint${c.reset}
|
|
543
|
+
${c.gray}$${c.reset} /hustle-ui-create Button ${c.dim}# Build component${c.reset}
|
|
544
|
+
${c.gray}$${c.reset} /hustle-ui-create-page Home ${c.dim}# Build page${c.reset}
|
|
545
|
+
${c.gray}$${c.reset} /hustle-combine api ${c.dim}# Orchestrate APIs${c.reset}
|
|
546
|
+
|
|
547
|
+
${c.bold}Next Steps:${c.reset}
|
|
548
|
+
1. ${c.white}cp templates/.env.example .env${c.reset}
|
|
549
|
+
2. ${c.white}Configure your API keys in .env${c.reset}
|
|
550
|
+
3. ${c.white}Add GREPTILE_API_KEY + GITHUB_TOKEN${c.reset} for Phase 14 code review
|
|
551
|
+
4. ${c.white}/ntfy-setup${c.reset} for push notifications (optional)
|
|
552
|
+
5. ${c.white}Restart Claude Code${c.reset} for MCP tools
|
|
553
|
+
|
|
554
|
+
${c.dim}Documentation: https://github.com/hustle-together/api-dev-tools${c.reset}
|
|
555
|
+
`);
|
|
906
556
|
}
|
|
907
557
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
function mergeMcpServers(existing, newMcp) {
|
|
912
|
-
const merged = { ...existing };
|
|
913
|
-
merged.mcpServers = merged.mcpServers || {};
|
|
914
|
-
|
|
915
|
-
// Add new MCP servers that don't already exist
|
|
916
|
-
if (newMcp.mcpServers) {
|
|
917
|
-
for (const [name, config] of Object.entries(newMcp.mcpServers)) {
|
|
918
|
-
if (!merged.mcpServers[name]) {
|
|
919
|
-
merged.mcpServers[name] = config;
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
return merged;
|
|
925
|
-
}
|
|
558
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
559
|
+
// Settings Merge
|
|
560
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
926
561
|
|
|
927
|
-
/**
|
|
928
|
-
* Merge two settings objects, combining hooks arrays
|
|
929
|
-
*/
|
|
930
562
|
function mergeSettings(existing, newSettings) {
|
|
931
563
|
const merged = { ...existing };
|
|
932
564
|
|
|
933
|
-
// Merge hooks
|
|
934
565
|
if (newSettings.hooks) {
|
|
935
566
|
merged.hooks = merged.hooks || {};
|
|
936
567
|
|
|
@@ -939,11 +570,10 @@ function mergeSettings(existing, newSettings) {
|
|
|
939
570
|
merged.hooks[hookType] = [];
|
|
940
571
|
}
|
|
941
572
|
|
|
942
|
-
// Add new hooks that don't already exist (check by command path)
|
|
943
573
|
for (const newHook of newSettings.hooks[hookType]) {
|
|
944
|
-
const hookCommand = newHook.hooks?.[0]?.command ||
|
|
945
|
-
const exists = merged.hooks[hookType].some(existing => {
|
|
946
|
-
const existingCommand = existing.hooks?.[0]?.command ||
|
|
574
|
+
const hookCommand = newHook.hooks?.[0]?.command || "";
|
|
575
|
+
const exists = merged.hooks[hookType].some((existing) => {
|
|
576
|
+
const existingCommand = existing.hooks?.[0]?.command || "";
|
|
947
577
|
return existingCommand === hookCommand;
|
|
948
578
|
});
|
|
949
579
|
|
|
@@ -954,14 +584,27 @@ function mergeSettings(existing, newSettings) {
|
|
|
954
584
|
}
|
|
955
585
|
}
|
|
956
586
|
|
|
587
|
+
if (newSettings.permissions) {
|
|
588
|
+
merged.permissions = merged.permissions || {};
|
|
589
|
+
merged.permissions.allow = [
|
|
590
|
+
...new Set([
|
|
591
|
+
...(merged.permissions.allow || []),
|
|
592
|
+
...(newSettings.permissions.allow || []),
|
|
593
|
+
]),
|
|
594
|
+
];
|
|
595
|
+
merged.permissions.deny = [
|
|
596
|
+
...new Set([
|
|
597
|
+
...(merged.permissions.deny || []),
|
|
598
|
+
...(newSettings.permissions.deny || []),
|
|
599
|
+
]),
|
|
600
|
+
];
|
|
601
|
+
}
|
|
602
|
+
|
|
957
603
|
return merged;
|
|
958
604
|
}
|
|
959
605
|
|
|
960
|
-
// Run
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
} catch (error) {
|
|
964
|
-
log(`\n❌ Installation failed: ${error.message}`, 'red');
|
|
965
|
-
log(` ${error.stack}\n`, 'red');
|
|
606
|
+
// Run
|
|
607
|
+
main().catch((error) => {
|
|
608
|
+
console.error(`\n${c.red}Installation failed:${c.reset} ${error.message}`);
|
|
966
609
|
process.exit(1);
|
|
967
|
-
}
|
|
610
|
+
});
|