start-vibing-stacks 2.3.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -58
- package/dist/detector.js +19 -5
- package/dist/index.js +15 -3
- package/dist/scanner.js +23 -2
- package/dist/setup.js +17 -1
- package/dist/types.d.ts +4 -0
- package/dist/ui.js +6 -5
- package/package.json +1 -1
- package/stacks/_shared/config/security-rules.json +27 -5
- package/stacks/frontend/react/skills/preline-ui/SKILL.md +31 -35
- package/stacks/frontend/react/skills/react-standards/SKILL.md +20 -20
- package/stacks/frontend/react/skills/react-ui-patterns/SKILL.md +78 -42
- package/stacks/frontend/react/skills/tailwind-patterns/SKILL.md +1 -1
- package/stacks/frontend/react/skills/zod-validation/SKILL.md +84 -18
- package/stacks/nodejs/skills/nextjs-app-router/SKILL.md +101 -0
- package/stacks/nodejs/stack.json +43 -121
- package/templates/CLAUDE-nodejs.md +323 -0
- package/templates/CLAUDE-php.md +131 -10
package/README.md
CHANGED
|
@@ -1,100 +1,184 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Start Vibing Stacks
|
|
2
2
|
|
|
3
|
-
**Multi-stack AI-powered development workflow for Claude Code.**
|
|
3
|
+
**Multi-stack AI-powered development workflow for Claude Code & Cursor.**
|
|
4
4
|
|
|
5
|
-
One command to set up agents, skills, hooks, and quality gates — tailored to your stack.
|
|
6
|
-
|
|
7
|
-
## 🚀 Quick Start
|
|
5
|
+
One command to set up agents, skills, hooks, security rules, and quality gates — tailored to your stack.
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
8
|
npx start-vibing-stacks
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
## What It Does
|
|
12
|
+
|
|
13
|
+
Start Vibing Stacks transforms Claude Code into a stack-aware AI partner. Instead of a generic assistant, you get an AI that understands your framework, enforces your coding standards, and blocks insecure patterns — all before a single line of code is written.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
You run the CLI
|
|
17
|
+
↓
|
|
18
|
+
Detects your stack (PHP/Node.js) from project files
|
|
19
|
+
↓
|
|
20
|
+
Scans existing standards (.cursorrules, composer.json, tsconfig, eslint, .env)
|
|
21
|
+
↓
|
|
22
|
+
Asks: adapt to YOUR standards or use defaults?
|
|
23
|
+
↓
|
|
24
|
+
Copies 6 agents + 25-40 skills + hooks + security rules
|
|
25
|
+
↓
|
|
26
|
+
Generates CLAUDE.md with architecture, rules, FORBIDDEN patterns
|
|
27
|
+
↓
|
|
28
|
+
Launches Claude Code — fully configured
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Supported Stacks
|
|
32
|
+
|
|
33
|
+
### PHP 8.3+
|
|
34
|
+
|
|
35
|
+
| Option | Choices |
|
|
36
|
+
|--------|---------|
|
|
37
|
+
| **Frameworks** | Laravel 12 + Octane (RoadRunner) + Inertia.js, Laravel 12 (standard) |
|
|
38
|
+
| **Databases** | MySQL / MariaDB, PostgreSQL, SQLite |
|
|
39
|
+
| **Frontend** | React 19 + Inertia.js + TailwindCSS 4, Blade + TailwindCSS, Livewire + Alpine.js, API only |
|
|
40
|
+
| **Skills** | 13 PHP-specific (Octane, PHPStan, PHPUnit, Eloquent, API Security, Inertia i18n, ...) |
|
|
41
|
+
|
|
42
|
+
### Node.js / TypeScript
|
|
43
|
+
|
|
44
|
+
| Option | Choices |
|
|
45
|
+
|--------|---------|
|
|
46
|
+
| **Frameworks** | Next.js (App Router), Nuxt, Astro, Express, Fastify, Vanilla Node.js |
|
|
47
|
+
| **Databases** | MongoDB, PostgreSQL, MySQL, SQLite/Turso, Redis (Upstash), None |
|
|
48
|
+
| **Frontend** | React 19 + TailwindCSS 4, Vue.js / Nuxt, Svelte / SvelteKit, API only |
|
|
49
|
+
| **Skills** | 5 Node-specific (TypeScript strict, Next.js App Router, tRPC, Bun, Mongoose) + 9 frontend skills |
|
|
50
|
+
|
|
51
|
+
### Python (Coming Soon)
|
|
52
|
+
|
|
53
|
+
Django, FastAPI, Flask support is planned.
|
|
54
|
+
|
|
55
|
+
## What Gets Installed
|
|
14
56
|
|
|
15
|
-
```bash
|
|
16
|
-
npm install -g start-vibing-stacks
|
|
17
|
-
svs # shortcut
|
|
18
57
|
```
|
|
58
|
+
your-project/
|
|
59
|
+
├── CLAUDE.md # AI memory — architecture, rules, FORBIDDEN patterns
|
|
60
|
+
└── .claude/
|
|
61
|
+
├── agents/ # 6 universal agents
|
|
62
|
+
│ ├── research-web.md # Researches best practices before new features
|
|
63
|
+
│ ├── documenter.md # Maps files to domains, tracks changes
|
|
64
|
+
│ ├── domain-updater.md # Records problems, solutions, learnings
|
|
65
|
+
│ ├── commit-manager.md # Conventional commits, merge workflow
|
|
66
|
+
│ ├── tester.md # Creates tests (Vitest/PHPUnit/Playwright)
|
|
67
|
+
│ └── claude-md-compactor.md # Compacts CLAUDE.md when > 40k chars
|
|
68
|
+
├── skills/ # 25-40 skills (stack + shared + frontend)
|
|
69
|
+
│ ├── quality-gate/ # Typecheck, lint, test validation
|
|
70
|
+
│ ├── security-scan/ # OWASP checks per language
|
|
71
|
+
│ ├── git-workflow/ # Branch management, conventional commits
|
|
72
|
+
│ ├── codebase-knowledge/ # Domain documentation system
|
|
73
|
+
│ └── ... # Stack-specific skills
|
|
74
|
+
├── hooks/
|
|
75
|
+
│ ├── stop-validator.ts # Blocks incomplete tasks (branch, git, docs)
|
|
76
|
+
│ └── user-prompt-submit.ts # Injects workflow + standards context
|
|
77
|
+
├── commands/ # /feature, /fix, /research, /validate
|
|
78
|
+
├── config/
|
|
79
|
+
│ ├── active-project.json # Stack, framework, database, skills
|
|
80
|
+
│ ├── security-rules.json # OWASP checks + env exposure rules
|
|
81
|
+
│ ├── standards-review.json # Imported project standards
|
|
82
|
+
│ └── ... # Quality gates, testing, domain mapping
|
|
83
|
+
└── settings.json # Claude Code permissions & model config
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Security Features
|
|
87
|
+
|
|
88
|
+
### Environment Variable Protection (Node.js)
|
|
89
|
+
|
|
90
|
+
The tool enforces strict separation of server and client environment variables:
|
|
19
91
|
|
|
20
|
-
|
|
92
|
+
- **Scanner**: Detects `NEXT_PUBLIC_` with sensitive words (SECRET, TOKEN, API_KEY) in `.env*` files
|
|
93
|
+
- **CLAUDE.md**: FORBIDDEN rules prevent AI from exposing secrets in browser bundles
|
|
94
|
+
- **Skills**: Teach API proxy patterns — external API calls must go through Route Handlers
|
|
95
|
+
- **security-rules.json**: Automated detection patterns for security audits
|
|
21
96
|
|
|
22
|
-
|
|
23
|
-
- **🐘 PHP 8.3+** — PHPStan, PHPUnit, Composer, Laravel/Octane
|
|
24
|
-
- **📦 Node.js/TS** — Vitest, TypeScript, Bun, Next.js, Express
|
|
25
|
-
- **🎯 Multi-framework** — Choose your framework, database, frontend, deploy target
|
|
26
|
-
- **🤖 6 Universal Agents** — research, documenter, domain-updater, commit-manager, tester, compactor
|
|
27
|
-
- **🛡️ Quality Gates** — Stack-specific validation before every commit
|
|
28
|
-
- **🔒 Security Skills** — OWASP checks adapted per language
|
|
29
|
-
- **📝 .cursorrules support** — Imports existing Cursor IDE rules
|
|
30
|
-
- **⚡ Auto-install** — Validates and installs missing deps (Homebrew on macOS)
|
|
97
|
+
### PHP Security
|
|
31
98
|
|
|
32
|
-
|
|
99
|
+
- OWASP Top 10 adapted for Laravel + Octane
|
|
100
|
+
- Octane-safe patterns (no static state, no globals)
|
|
101
|
+
- `env()` restriction (config files only)
|
|
102
|
+
- Frontend secret isolation (Inertia props)
|
|
103
|
+
- Rate limiting, CORS, CSP, encryption at rest
|
|
33
104
|
|
|
34
|
-
|
|
35
|
-
|-------|--------|------------|
|
|
36
|
-
| 🐘 PHP 8.3+ | ✅ Ready | Laravel, Laravel+Octane |
|
|
37
|
-
| 📦 Node.js/TS | ✅ Ready | Next.js, Nuxt, Astro, Express, Fastify |
|
|
38
|
-
| 🐍 Python | 🔜 Soon | Django, FastAPI, Flask |
|
|
39
|
-
| 🦀 Rust | 🔜 Soon | Actix, Axum |
|
|
40
|
-
| 🐹 Go | 🔜 Soon | Gin, Echo |
|
|
105
|
+
## Standards Review
|
|
41
106
|
|
|
42
|
-
|
|
107
|
+
Before modifying anything, the CLI scans your project for existing patterns:
|
|
43
108
|
|
|
44
109
|
```
|
|
45
|
-
.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
110
|
+
Scans: .cursorrules, composer.json, package.json, tsconfig.json,
|
|
111
|
+
eslint config, phpstan.neon, .env files, framework configs,
|
|
112
|
+
lockfiles, deploy configs, quality tool configs
|
|
113
|
+
|
|
114
|
+
Detects: 50+ npm packages, 17+ Composer packages, TypeScript strict mode,
|
|
115
|
+
path aliases, ESLint config, PHPStan level, package manager,
|
|
116
|
+
deploy targets, exposed secrets in NEXT_PUBLIC_*
|
|
117
|
+
|
|
118
|
+
Result: "Adapt to your standards" or "Use plugin defaults"
|
|
119
|
+
→ Saved in standards-review.json
|
|
120
|
+
→ Injected into every Claude prompt via hook
|
|
52
121
|
```
|
|
53
122
|
|
|
54
|
-
##
|
|
123
|
+
## CLI Options
|
|
55
124
|
|
|
56
125
|
```bash
|
|
57
126
|
npx start-vibing-stacks [options]
|
|
58
127
|
|
|
59
128
|
--force Overwrite existing configuration
|
|
60
|
-
--no-claude Skip Claude Code
|
|
129
|
+
--no-claude Skip Claude Code launch
|
|
61
130
|
--no-install Skip dependency installation
|
|
62
131
|
--help, -h Show help
|
|
63
132
|
--version, -v Show version
|
|
64
133
|
```
|
|
65
134
|
|
|
66
|
-
|
|
135
|
+
Or install globally:
|
|
67
136
|
|
|
68
|
-
|
|
137
|
+
```bash
|
|
138
|
+
npm install -g start-vibing-stacks
|
|
139
|
+
svs # shortcut alias
|
|
140
|
+
```
|
|
69
141
|
|
|
70
|
-
|
|
71
|
-
- **Composer >= 2.0** (auto-installed using PHP if missing)
|
|
72
|
-
- **Node.js >= 18** (for frontend tooling)
|
|
142
|
+
## How the Workflow Works
|
|
73
143
|
|
|
74
|
-
|
|
144
|
+
Once configured, Claude Code follows this workflow on every task:
|
|
75
145
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
146
|
+
```
|
|
147
|
+
0. TODO LIST → Creates detailed task breakdown
|
|
148
|
+
1. BRANCH → Creates feature/ | fix/ | refactor/ | test/
|
|
149
|
+
2. RESEARCH → Runs research-web agent for new features
|
|
150
|
+
3. IMPLEMENT → Follows stack rules + strict types + security
|
|
151
|
+
4. TEST → Runs tester agent (PHPUnit / Vitest / Playwright)
|
|
152
|
+
5. DOCUMENT → Runs documenter agent for modified files
|
|
153
|
+
6. UPDATE → Updates CLAUDE.md with changes
|
|
154
|
+
7. QUALITY → Runs quality gates (typecheck, lint, test, build)
|
|
155
|
+
8. COMMIT → Conventional commits, merge to main
|
|
156
|
+
```
|
|
80
157
|
|
|
81
|
-
|
|
158
|
+
The **stop-validator hook** blocks task completion if:
|
|
159
|
+
- Not on `main` branch (work must be merged)
|
|
160
|
+
- Uncommitted changes exist
|
|
161
|
+
- CLAUDE.md wasn't updated
|
|
162
|
+
- Source files lack documentation
|
|
82
163
|
|
|
83
|
-
|
|
164
|
+
## Cursor IDE Support
|
|
84
165
|
|
|
85
|
-
|
|
166
|
+
If `.cursorrules` is detected, the rules are automatically imported into the Claude configuration. Both AI tools work with the same context.
|
|
86
167
|
|
|
87
|
-
|
|
88
|
-
2. **Select** — You choose stack, framework, database, frontend, deploy
|
|
89
|
-
3. **Validate** — Checks system requirements, installs missing tools
|
|
90
|
-
4. **Configure** — Copies agents, skills, hooks tailored to your choices
|
|
91
|
-
5. **Launch** — Starts Claude Code with everything pre-configured
|
|
168
|
+
## Requirements
|
|
92
169
|
|
|
93
|
-
|
|
170
|
+
| Stack | Requirements |
|
|
171
|
+
|-------|-------------|
|
|
172
|
+
| **PHP** | PHP >= 8.3, Composer >= 2.0, Node.js >= 18 |
|
|
173
|
+
| **Node.js** | Node.js >= 18 (Bun optional) |
|
|
94
174
|
|
|
95
|
-
|
|
175
|
+
Missing dependencies are auto-installed via Homebrew on macOS.
|
|
96
176
|
|
|
97
|
-
##
|
|
177
|
+
## Credits
|
|
98
178
|
|
|
99
179
|
Inspired by [start-vibing](https://www.npmjs.com/package/start-vibing).
|
|
100
180
|
Built by [FantasyLake](https://github.com/f1sc4ll-ai).
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
MIT
|
package/dist/detector.js
CHANGED
|
@@ -25,10 +25,6 @@ const STACK_SIGNATURES = [
|
|
|
25
25
|
{ id: 'python', files: ['pyproject.toml'], weight: 90, reason: 'pyproject.toml detected' },
|
|
26
26
|
{ id: 'python', files: ['Pipfile'], weight: 85, reason: 'Pipfile detected' },
|
|
27
27
|
{ id: 'python', files: ['setup.py'], weight: 75, reason: 'setup.py detected' },
|
|
28
|
-
// Rust
|
|
29
|
-
{ id: 'rust', files: ['Cargo.toml'], weight: 95, reason: 'Cargo.toml detected' },
|
|
30
|
-
// Go
|
|
31
|
-
{ id: 'go', files: ['go.mod'], weight: 95, reason: 'go.mod detected' },
|
|
32
28
|
];
|
|
33
29
|
export function detectProject(projectDir) {
|
|
34
30
|
const result = {
|
|
@@ -95,8 +91,26 @@ export function detectNodeFramework(projectDir) {
|
|
|
95
91
|
return 'nextjs';
|
|
96
92
|
if (existsSync(join(projectDir, 'nuxt.config.ts')))
|
|
97
93
|
return 'nuxt';
|
|
98
|
-
if (existsSync(join(projectDir, 'astro.config.mjs')))
|
|
94
|
+
if (existsSync(join(projectDir, 'astro.config.mjs')) || existsSync(join(projectDir, 'astro.config.ts')))
|
|
99
95
|
return 'astro';
|
|
96
|
+
if (existsSync(join(projectDir, 'svelte.config.js')) || existsSync(join(projectDir, 'svelte.config.ts')))
|
|
97
|
+
return 'svelte';
|
|
98
|
+
const pkgPath = join(projectDir, 'package.json');
|
|
99
|
+
if (existsSync(pkgPath)) {
|
|
100
|
+
try {
|
|
101
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
102
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
103
|
+
if (deps['express'])
|
|
104
|
+
return 'express';
|
|
105
|
+
if (deps['fastify'])
|
|
106
|
+
return 'fastify';
|
|
107
|
+
if (deps['hono'])
|
|
108
|
+
return 'hono';
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// ignore parse errors
|
|
112
|
+
}
|
|
113
|
+
}
|
|
100
114
|
return null;
|
|
101
115
|
}
|
|
102
116
|
export function detectPythonFramework(projectDir) {
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* Detects project stack, validates requirements, and configures agents.
|
|
7
7
|
*/
|
|
8
8
|
import { existsSync, readFileSync } from 'fs';
|
|
9
|
-
import { join, basename } from 'path';
|
|
9
|
+
import { join, basename, dirname, resolve } from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
10
11
|
import inquirer from 'inquirer';
|
|
11
12
|
import chalk from 'chalk';
|
|
12
13
|
import * as ui from './ui.js';
|
|
@@ -15,6 +16,10 @@ import { autoInstall, installComposer, installClaudeCode } from './installer.js'
|
|
|
15
16
|
import { loadStackConfig, setupProject } from './setup.js';
|
|
16
17
|
import { selectMcpServers, installMcpServers } from './mcp.js';
|
|
17
18
|
import { scanProjectStandards } from './scanner.js';
|
|
19
|
+
const __cli_filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __cli_dirname = dirname(__cli_filename);
|
|
21
|
+
const CLI_ROOT = resolve(__cli_dirname, '..');
|
|
22
|
+
const PKG_VERSION = JSON.parse(readFileSync(join(CLI_ROOT, 'package.json'), 'utf8')).version;
|
|
18
23
|
// =============================================================================
|
|
19
24
|
// CLI Arguments
|
|
20
25
|
// =============================================================================
|
|
@@ -28,7 +33,7 @@ const FLAGS = {
|
|
|
28
33
|
version: args.includes('--version') || args.includes('-v'),
|
|
29
34
|
};
|
|
30
35
|
if (FLAGS.version) {
|
|
31
|
-
console.log(
|
|
36
|
+
console.log(PKG_VERSION);
|
|
32
37
|
process.exit(0);
|
|
33
38
|
}
|
|
34
39
|
if (FLAGS.help) {
|
|
@@ -158,12 +163,15 @@ async function main() {
|
|
|
158
163
|
: stackId === 'python'
|
|
159
164
|
? detectPythonFramework(projectDir)
|
|
160
165
|
: null;
|
|
166
|
+
const defaultFramework = detectedFramework ||
|
|
167
|
+
stackConfig.frameworks.find((f) => f.default)?.id ||
|
|
168
|
+
undefined;
|
|
161
169
|
const { framework } = await inquirer.prompt([
|
|
162
170
|
{
|
|
163
171
|
type: 'list',
|
|
164
172
|
name: 'framework',
|
|
165
173
|
message: 'Select framework:',
|
|
166
|
-
default:
|
|
174
|
+
default: defaultFramework,
|
|
167
175
|
choices: stackConfig.frameworks.map((f) => ({
|
|
168
176
|
name: `${f.icon} ${f.name}`,
|
|
169
177
|
value: f.id,
|
|
@@ -185,11 +193,13 @@ async function main() {
|
|
|
185
193
|
]);
|
|
186
194
|
// ─── Step 5: Frontend ──────────────────────────────────────────────────
|
|
187
195
|
const compatibleFrontends = stackConfig.frontendOptions.filter((f) => !f.frameworks || f.frameworks.includes(framework));
|
|
196
|
+
const defaultFrontend = compatibleFrontends.find((f) => f.default)?.id || undefined;
|
|
188
197
|
const { frontend } = await inquirer.prompt([
|
|
189
198
|
{
|
|
190
199
|
type: 'list',
|
|
191
200
|
name: 'frontend',
|
|
192
201
|
message: 'Frontend included?',
|
|
202
|
+
default: defaultFrontend,
|
|
193
203
|
choices: compatibleFrontends.map((f) => ({
|
|
194
204
|
name: `${f.icon} ${f.name}`,
|
|
195
205
|
value: f.id,
|
|
@@ -249,12 +259,14 @@ async function main() {
|
|
|
249
259
|
}
|
|
250
260
|
}
|
|
251
261
|
// ─── Step 7: Show Summary & Confirm ────────────────────────────────────
|
|
262
|
+
const selectedFrontend = compatibleFrontends.find((f) => f.id === frontend);
|
|
252
263
|
const config = {
|
|
253
264
|
name: projectName,
|
|
254
265
|
stack: stackId,
|
|
255
266
|
framework,
|
|
256
267
|
database,
|
|
257
268
|
frontend,
|
|
269
|
+
frontendSkillsDir: selectedFrontend?.skillsDir,
|
|
258
270
|
deploy,
|
|
259
271
|
path: projectDir,
|
|
260
272
|
createdAt: new Date().toISOString(),
|
package/dist/scanner.js
CHANGED
|
@@ -102,6 +102,8 @@ const NPM_PACKAGES = {
|
|
|
102
102
|
// Media
|
|
103
103
|
'uploadthing': { category: 'media', name: 'UploadThing file uploads' },
|
|
104
104
|
'sharp': { category: 'media', name: 'Sharp image processing' },
|
|
105
|
+
// Design System
|
|
106
|
+
'preline': { category: 'ui', name: 'Preline UI design system' },
|
|
105
107
|
};
|
|
106
108
|
function readFileIfExists(path) {
|
|
107
109
|
if (!existsSync(path))
|
|
@@ -424,8 +426,14 @@ function scanProjectFiles(projectDir) {
|
|
|
424
426
|
patterns.push({ category: 'quality', name: 'Prettier config present', confidence: 100 });
|
|
425
427
|
}
|
|
426
428
|
// Environment files (works for both PHP and Node.js projects)
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
+
const envFiles = ['.env.example', '.env', '.env.local', '.env.development', '.env.production'];
|
|
430
|
+
let envContent = null;
|
|
431
|
+
for (const envFile of envFiles) {
|
|
432
|
+
const content = readFileIfExists(join(projectDir, envFile));
|
|
433
|
+
if (content) {
|
|
434
|
+
envContent = (envContent || '') + '\n' + content;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
429
437
|
if (envContent) {
|
|
430
438
|
if (/REDIS_HOST|REDIS_URL/i.test(envContent)) {
|
|
431
439
|
patterns.push({ category: 'cache', name: 'Redis configured', confidence: 85 });
|
|
@@ -442,6 +450,19 @@ function scanProjectFiles(projectDir) {
|
|
|
442
450
|
if (/STRIPE_SECRET|STRIPE_KEY/i.test(envContent)) {
|
|
443
451
|
patterns.push({ category: 'billing', name: 'Stripe payments configured', confidence: 85 });
|
|
444
452
|
}
|
|
453
|
+
// Security: detect NEXT_PUBLIC_ with sensitive values
|
|
454
|
+
const sensitivePublicEnv = envContent.match(/^NEXT_PUBLIC_\w*(SECRET|TOKEN|PRIVATE|PASSWORD|CREDENTIAL|OPENAI|API_KEY|DATABASE)\w*\s*=/gmi);
|
|
455
|
+
if (sensitivePublicEnv) {
|
|
456
|
+
for (const match of sensitivePublicEnv) {
|
|
457
|
+
const varName = match.split('=')[0].trim();
|
|
458
|
+
patterns.push({
|
|
459
|
+
category: 'security',
|
|
460
|
+
name: `EXPOSED SECRET: ${varName} is public (visible in browser)`,
|
|
461
|
+
confidence: 100,
|
|
462
|
+
detail: `${varName} uses NEXT_PUBLIC_ prefix which embeds the value in the client JS bundle. Remove NEXT_PUBLIC_ and access via Route Handler or Server Action.`,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
445
466
|
}
|
|
446
467
|
if (patterns.length === 0)
|
|
447
468
|
return null;
|
package/dist/setup.js
CHANGED
|
@@ -7,6 +7,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSy
|
|
|
7
7
|
import { join, dirname, resolve } from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import ora from 'ora';
|
|
10
|
+
import * as ui from './ui.js';
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = dirname(__filename);
|
|
12
13
|
const PACKAGE_ROOT = resolve(__dirname, '..');
|
|
@@ -89,7 +90,8 @@ export async function setupProject(projectDir, config, options = {}) {
|
|
|
89
90
|
copyDirRecursive(stackConfigDir, join(claudeDir, 'config'), options.force);
|
|
90
91
|
// 8. Copy frontend-specific skills if applicable
|
|
91
92
|
if (config.frontend && config.frontend !== 'none') {
|
|
92
|
-
const
|
|
93
|
+
const frontendId = config.frontendSkillsDir || config.frontend;
|
|
94
|
+
const frontendDir = join(PACKAGE_ROOT, 'stacks', 'frontend', frontendId);
|
|
93
95
|
if (existsSync(frontendDir)) {
|
|
94
96
|
const feSkillCount = copyDirRecursive(join(frontendDir, 'skills'), join(claudeDir, 'skills'), options.force);
|
|
95
97
|
spinner.text = `Loaded ${feSkillCount} frontend skills`;
|
|
@@ -286,6 +288,20 @@ export async function setupProject(projectDir, config, options = {}) {
|
|
|
286
288
|
settings.quality_gates = gates;
|
|
287
289
|
}
|
|
288
290
|
writeFileSync(join(claudeDir, 'settings.json'), JSON.stringify(settings, null, '\t'));
|
|
291
|
+
// 14. Detect Preline and suggest official agent skills
|
|
292
|
+
const projectPkgPath = join(projectDir, 'package.json');
|
|
293
|
+
if (existsSync(projectPkgPath)) {
|
|
294
|
+
try {
|
|
295
|
+
const projectPkg = JSON.parse(readFileSync(projectPkgPath, 'utf8'));
|
|
296
|
+
const allDeps = { ...projectPkg.dependencies, ...projectPkg.devDependencies };
|
|
297
|
+
if (allDeps['preline'] && !existsSync(join(claudeDir, 'skills', 'preline-theme-generator'))) {
|
|
298
|
+
ui.info('Preline UI detected — run: npx skills add htmlstreamofficial/preline');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// package.json parse error, skip
|
|
303
|
+
}
|
|
304
|
+
}
|
|
289
305
|
spinner.succeed(`Setup complete: ${agentCount} agents, ${sharedSkillCount + stackSkillCount} skills, ${hookCount} hooks`);
|
|
290
306
|
return true;
|
|
291
307
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export interface FrameworkOption {
|
|
|
32
32
|
icon: string;
|
|
33
33
|
detectFiles?: string[];
|
|
34
34
|
skills?: string[];
|
|
35
|
+
default?: boolean;
|
|
35
36
|
extra?: Record<string, unknown>;
|
|
36
37
|
}
|
|
37
38
|
export interface DatabaseOption {
|
|
@@ -44,6 +45,8 @@ export interface FrontendOption {
|
|
|
44
45
|
name: string;
|
|
45
46
|
icon: string;
|
|
46
47
|
frameworks?: string[];
|
|
48
|
+
skillsDir?: string;
|
|
49
|
+
default?: boolean;
|
|
47
50
|
}
|
|
48
51
|
export interface DeployTarget {
|
|
49
52
|
id: string;
|
|
@@ -67,6 +70,7 @@ export interface ProjectConfig {
|
|
|
67
70
|
framework: string;
|
|
68
71
|
database: string;
|
|
69
72
|
frontend: string;
|
|
73
|
+
frontendSkillsDir?: string;
|
|
70
74
|
deploy: string;
|
|
71
75
|
path: string;
|
|
72
76
|
createdAt: string;
|
package/dist/ui.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Start Vibing Stacks — Terminal UI
|
|
3
3
|
*/
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import { join, dirname, resolve } from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
4
7
|
import chalk from 'chalk';
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
return text.split('').map((c, i) => colors[i % colors.length](c)).join('');
|
|
9
|
-
};
|
|
8
|
+
const __ui_filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __ui_dirname = dirname(__ui_filename);
|
|
10
|
+
const VERSION = JSON.parse(readFileSync(join(resolve(__ui_dirname, '..'), 'package.json'), 'utf8')).version;
|
|
10
11
|
export const LOGO = `
|
|
11
12
|
${chalk.hex('#9F7AEA')(' ╔═══════════════════════════════════════════════╗')}
|
|
12
13
|
${chalk.hex('#9F7AEA')(' ║')} ${chalk.hex('#9F7AEA')('║')}
|
package/package.json
CHANGED
|
@@ -5,17 +5,39 @@
|
|
|
5
5
|
},
|
|
6
6
|
"sensitivePatterns": {
|
|
7
7
|
"forbidden": [
|
|
8
|
-
"eval(",
|
|
9
|
-
"exec(",
|
|
10
|
-
"system(",
|
|
11
|
-
"passthru(",
|
|
12
|
-
"shell_exec(",
|
|
13
8
|
"password:",
|
|
14
9
|
"passwordHash",
|
|
15
10
|
"apiKey:",
|
|
16
11
|
"secret:"
|
|
17
12
|
]
|
|
18
13
|
},
|
|
14
|
+
"envExposure": {
|
|
15
|
+
"$comment": "NEXT_PUBLIC_ vars are embedded in browser JS bundle. These patterns indicate secrets being exposed to the client.",
|
|
16
|
+
"forbiddenPublicEnvPatterns": [
|
|
17
|
+
"NEXT_PUBLIC_.*SECRET",
|
|
18
|
+
"NEXT_PUBLIC_.*TOKEN",
|
|
19
|
+
"NEXT_PUBLIC_.*PRIVATE",
|
|
20
|
+
"NEXT_PUBLIC_.*PASSWORD",
|
|
21
|
+
"NEXT_PUBLIC_.*CREDENTIAL"
|
|
22
|
+
],
|
|
23
|
+
"forbiddenPublicEnvExact": [
|
|
24
|
+
"NEXT_PUBLIC_OPENAI_KEY",
|
|
25
|
+
"NEXT_PUBLIC_OPENAI_API_KEY",
|
|
26
|
+
"NEXT_PUBLIC_STRIPE_SECRET",
|
|
27
|
+
"NEXT_PUBLIC_STRIPE_SECRET_KEY",
|
|
28
|
+
"NEXT_PUBLIC_DATABASE_URL",
|
|
29
|
+
"NEXT_PUBLIC_SUPABASE_SERVICE_KEY",
|
|
30
|
+
"NEXT_PUBLIC_FIREBASE_ADMIN_KEY"
|
|
31
|
+
],
|
|
32
|
+
"safePublicEnvPatterns": [
|
|
33
|
+
"NEXT_PUBLIC_.*URL",
|
|
34
|
+
"NEXT_PUBLIC_.*ID",
|
|
35
|
+
"NEXT_PUBLIC_STRIPE_KEY",
|
|
36
|
+
"NEXT_PUBLIC_GA_",
|
|
37
|
+
"NEXT_PUBLIC_SENTRY_DSN"
|
|
38
|
+
],
|
|
39
|
+
"rule": "API keys, secrets, and tokens MUST stay server-side. Use Route Handlers or Server Actions as proxy."
|
|
40
|
+
},
|
|
19
41
|
"cookies": {
|
|
20
42
|
"httpOnly": true,
|
|
21
43
|
"secure": true,
|
|
@@ -10,7 +10,7 @@ Preline is a **semantic token-based design system** built on TailwindCSS. It pro
|
|
|
10
10
|
- Theme generator for custom color schemes
|
|
11
11
|
- Light + dark mode via `data-theme` + `.dark`
|
|
12
12
|
|
|
13
|
-
## Installation
|
|
13
|
+
## Installation
|
|
14
14
|
|
|
15
15
|
### Step 1: Install
|
|
16
16
|
|
|
@@ -21,7 +21,7 @@ npm install preline @tailwindcss/forms
|
|
|
21
21
|
### Step 2: CSS Config
|
|
22
22
|
|
|
23
23
|
```css
|
|
24
|
-
/*
|
|
24
|
+
/* src/app/globals.css (Next.js) or styles/globals.css */
|
|
25
25
|
@import "tailwindcss";
|
|
26
26
|
|
|
27
27
|
/* Preline — MUST be in this order */
|
|
@@ -31,39 +31,35 @@ npm install preline @tailwindcss/forms
|
|
|
31
31
|
@import "./node_modules/preline/themes/theme.css"; /* Base theme */
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
### Step 3:
|
|
34
|
+
### Step 3: Init Preline on Route Changes (MANDATORY)
|
|
35
35
|
|
|
36
|
-
```
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
import laravel from 'laravel-vite-plugin';
|
|
40
|
-
import react from '@vitejs/plugin-react';
|
|
36
|
+
```tsx
|
|
37
|
+
// src/components/PrelineInit.tsx
|
|
38
|
+
'use client';
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
laravel({ input: ['resources/css/app.css', 'resources/js/app.tsx'], refresh: true }),
|
|
45
|
-
react(),
|
|
46
|
-
],
|
|
47
|
-
});
|
|
48
|
-
```
|
|
40
|
+
import { usePathname } from 'next/navigation';
|
|
41
|
+
import { useEffect } from 'react';
|
|
49
42
|
|
|
50
|
-
|
|
43
|
+
export function PrelineInit() {
|
|
44
|
+
const pathname = usePathname();
|
|
51
45
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
import
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const timer = setTimeout(() => {
|
|
48
|
+
import('preline/preline').then(({ HSStaticMethods }) => {
|
|
49
|
+
HSStaticMethods.autoInit();
|
|
50
|
+
});
|
|
51
|
+
}, 100);
|
|
52
|
+
return () => clearTimeout(timer);
|
|
53
|
+
}, [pathname]);
|
|
54
|
+
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Add to root layout:
|
|
59
|
+
// <PrelineInit />
|
|
64
60
|
```
|
|
65
61
|
|
|
66
|
-
**Rule:** Without `HSStaticMethods.autoInit()`, dropdowns, modals, and accordions will NOT work after
|
|
62
|
+
**Rule:** Without `HSStaticMethods.autoInit()`, dropdowns, modals, and accordions will NOT work after client-side navigation.
|
|
67
63
|
|
|
68
64
|
## Templates & Components (840+ free)
|
|
69
65
|
|
|
@@ -80,11 +76,11 @@ router.on('navigate', () => {
|
|
|
80
76
|
|
|
81
77
|
1. Browse https://preline.co/examples.html
|
|
82
78
|
2. Click a block → copy the HTML/JSX
|
|
83
|
-
3. Adapt to React
|
|
84
|
-
- Replace `<a href>` with `<Link href>` (
|
|
79
|
+
3. Adapt to React:
|
|
80
|
+
- Replace `<a href>` with Next.js `<Link href>` (from `next/link`)
|
|
85
81
|
- Replace `class=` with `className=`
|
|
86
82
|
- Add Preline `data-*` attributes for interactive components
|
|
87
|
-
-
|
|
83
|
+
- Pass data via props or fetch with TanStack Query / Server Components
|
|
88
84
|
|
|
89
85
|
### Example: Copy a Hero Block
|
|
90
86
|
|
|
@@ -245,7 +241,7 @@ function ThemeToggle() {
|
|
|
245
241
|
}
|
|
246
242
|
```
|
|
247
243
|
|
|
248
|
-
## Using Components with React
|
|
244
|
+
## Using Components with React
|
|
249
245
|
|
|
250
246
|
### Navbar
|
|
251
247
|
|
|
@@ -327,7 +323,7 @@ function ThemeToggle() {
|
|
|
327
323
|
|
|
328
324
|
```bash
|
|
329
325
|
# Generate theme from config
|
|
330
|
-
npx preline-theme-generator /tmp/config.json ./
|
|
326
|
+
npx preline-theme-generator /tmp/config.json ./src/styles/themes/brand.css
|
|
331
327
|
|
|
332
328
|
# Config format:
|
|
333
329
|
{
|
|
@@ -359,4 +355,4 @@ npx preline-theme-generator /tmp/config.json ./resources/css/themes/brand.css
|
|
|
359
355
|
| Force HTML class changes | Theme activation via `data-theme` only |
|
|
360
356
|
| Invent token names | Follow Preline's naming system |
|
|
361
357
|
| `@apply` for component styles | React components with token classes |
|
|
362
|
-
| Skip `HSStaticMethods.autoInit()` | Always re-init after
|
|
358
|
+
| Skip `HSStaticMethods.autoInit()` | Always re-init after client-side navigation |
|