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 CHANGED
@@ -1,100 +1,184 @@
1
- # 🎸 Start Vibing Stacks
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
- Or install globally:
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
- ## Features
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
- - **🔍 Auto-detection** — Scans your project files to suggest the right stack
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
- ## 📋 Supported Stacks
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
- | Stack | Status | Frameworks |
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
- ## 🏗️ What It Creates
107
+ Before modifying anything, the CLI scans your project for existing patterns:
43
108
 
44
109
  ```
45
- .claude/
46
- ├── agents/ # 6 universal agents (stack-aware)
47
- ├── skills/ # Stack-specific + shared skills
48
- ├── hooks/ # stop-validator + prompt-inject
49
- ├── config/ # Project config, quality gates, security rules
50
- ├── commands/ # /feature, /fix, /validate
51
- └── settings.json # Claude Code permissions
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
- ## 🔧 Options
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 installation
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
- ## 🐘 PHP Requirements
135
+ Or install globally:
67
136
 
68
- When selecting the PHP stack, the tool validates and auto-installs:
137
+ ```bash
138
+ npm install -g start-vibing-stacks
139
+ svs # shortcut alias
140
+ ```
69
141
 
70
- - **PHP >= 8.3** (via `brew install php@8.3` on macOS)
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
- ### Laravel + Octane (RoadRunner)
144
+ Once configured, Claude Code follows this workflow on every task:
75
145
 
76
- Select "Laravel + Octane (RoadRunner)" in the framework menu for:
77
- - RoadRunner server configuration
78
- - Octane-specific skills and patterns
79
- - High-performance deployment guides
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
- ## 📝 Cursor IDE Support
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
- If `.cursorrules` is detected in your project, the rules are automatically imported into the Claude agent configuration. Both AI tools work with the same context.
164
+ ## Cursor IDE Support
84
165
 
85
- ## 🔄 How It Works
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
- 1. **Detect** — Scans project for `composer.json`, `package.json`, etc.
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
- ## 📄 License
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
- MIT
175
+ Missing dependencies are auto-installed via Homebrew on macOS.
96
176
 
97
- ## 🏠 Credits
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('1.0.0');
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: detectedFramework || undefined,
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 envContent = readFileIfExists(join(projectDir, '.env.example')) ||
428
- readFileIfExists(join(projectDir, '.env'));
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 frontendDir = join(PACKAGE_ROOT, 'stacks', 'frontend', config.frontend);
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 VERSION = '2.2.0';
6
- const gradient = (text) => {
7
- const colors = [chalk.hex('#FF6B6B'), chalk.hex('#FF8E53'), chalk.hex('#FFBD2E'), chalk.hex('#48BB78'), chalk.hex('#4299E1'), chalk.hex('#9F7AEA')];
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-vibing-stacks",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "description": "AI-powered multi-stack dev workflow for Claude Code. Supports PHP, Node.js, Python and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 (Laravel + Inertia)
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
- /* resources/css/app.css */
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: Vite Config
34
+ ### Step 3: Init Preline on Route Changes (MANDATORY)
35
35
 
36
- ```js
37
- // vite.config.js
38
- import { defineConfig } from 'vite';
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
- export default defineConfig({
43
- plugins: [
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
- ### Step 4: Init Preline in Inertia (MANDATORY)
43
+ export function PrelineInit() {
44
+ const pathname = usePathname();
51
45
 
52
- ```tsx
53
- // resources/js/app.tsx
54
- import { router } from '@inertiajs/react';
55
-
56
- // Re-init Preline components after every SPA navigation
57
- router.on('navigate', () => {
58
- setTimeout(() => {
59
- import('preline/preline').then(({ HSStaticMethods }) => {
60
- HSStaticMethods.autoInit();
61
- });
62
- }, 100);
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 Inertia navigation.
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 + Inertia:
84
- - Replace `<a href>` with `<Link href>` (Inertia)
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
- - Use `usePage().props` for dynamic data
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 (Inertia)
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 ./resources/css/themes/brand.css
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 Inertia navigation |
358
+ | Skip `HSStaticMethods.autoInit()` | Always re-init after client-side navigation |