vibetachyon 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -0
- package/dist/index.js +36 -0
- package/dist/installer.js +139 -0
- package/dist/mcp-server.js +774 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# š VibeTachyon MCP v1.0
|
|
2
|
+
### The "God-Tier" Frontend AI Copilot Engine
|
|
3
|
+
|
|
4
|
+
VibeTachyon is not just another MCP server. It is a high-performance **Autonomous Frontend Engineering System** designed to transform any AI (Claude, GPT, Gemini) into a God-Tier developer. Built on top of the VibeCodes ecosystem, it bridges the gap between design (Figma), **data architecture (Prisma, Drizzle, SQL)**, and pixel-perfect execution.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## š Elite Features
|
|
9
|
+
|
|
10
|
+
### š§ The Self-Healing Matrix
|
|
11
|
+
* **AST Auto-Correction:** Uses `self_heal_typescript_errors` to run local `tsc` checks. If the AI generates a bug, Tachyon catches the AST error and forces the AI to fix it before you even see it.
|
|
12
|
+
* **Vision QA Loop:** Use `initiate_visual_qa` to perform pixel-perfect audits. Paste a screenshot, and Tachyon will autonomously patch Tailwind margins, padding, and alignment.
|
|
13
|
+
|
|
14
|
+
### šØ Design-to-Code Bridge
|
|
15
|
+
* **Figma Transcription:** `fetch_figma_design` pulls raw JSON nodes directly from Figma API and maps them to clean Tailwind/React code.
|
|
16
|
+
* **Theme Cloning:** `get_vibe_colors` and `learn_user_preferences` scan your local `globals.css` and `.prettierrc` to clone your exact coding style and color palette.
|
|
17
|
+
|
|
18
|
+
### š”ļø "Accept-All" Guardrails (Zero friction)
|
|
19
|
+
* **Framework Agnostic:** Dynamically detects if you are using **Next.js** or **Vite**. It automatically translates `<Image>` to `<img>` and `Link` to `<a>` to prevent framework mismatches.
|
|
20
|
+
* **Dependency Resolver:** `analyze_and_install_deps` identifies missing packages (framer-motion, lucide, etc.) and generates the exact install command.
|
|
21
|
+
|
|
22
|
+
### š Enterprise Standards
|
|
23
|
+
* **VibeSec Penetration Tester:** `run_security_penetration` automatically scans newly generated code for XSS vectors, SQL Injection risks, and hardcoded Secrets. If a vulnerability is found, it blocks delivery and forces the AI to rewrite the logic safely.
|
|
24
|
+
* **VibePilot CLI:** `update_terminal_status` gives the AI a literal "face" in your terminal. It writes chalk-colored progress bars and statuses (e.g. `š§ Compiling AST Fixes...`) to `stderr` so you can visually watch the bot think.
|
|
25
|
+
* **VibeSpeed Local (Lighthouse):** `initiate_vibespeed_audit` runs an automated headless Lighthouse test on localhost. If the Performance score is <90, the Copilot forces itself to refactor the page (lazy loading, optimized images) until it passes.
|
|
26
|
+
* **Data Intelligence:** `analyze_business_logic` scans for **Prisma, Drizzle, or raw TS models** to ground UI generation in your real data structure. No hard dependencies.
|
|
27
|
+
* **Telemetry Engine:** `enforce_telemetry_tracking` forces the injection of `data-vibe-tracking` attributes for instant PostHog/GA integration.
|
|
28
|
+
* **i18n Globalization:** `enforce_i18n_standards` forbids hardcoded strings, forcing text extraction for easy translation.
|
|
29
|
+
* **SEO Heuristics:** `enforce_seo_metadata` injects framework-native SEO/OpenGraph tags for maximum ranking.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## š ļø Toolset Overview
|
|
34
|
+
|
|
35
|
+
| Category | Tools | Description |
|
|
36
|
+
| :--- | :--- | :--- |
|
|
37
|
+
| **Search** | `search_vibecodes_snippets`, `compose_landing_page` | Semantic RAG search for UI and full-page composition. |
|
|
38
|
+
| **Analysis** | `scan_local_architecture`, `analyze_business_logic` | Scans `package.json`, Prisma schemas, and API routes. |
|
|
39
|
+
| **Styling** | `get_vibe_colors`, `patch_tailwind_config` | Real-time theme extraction and Tailwind config patching. |
|
|
40
|
+
| **QA** | `self_heal_typescript_errors`, `initiate_visual_qa` | Autonomous AST error fixing and AI Vision validation. |
|
|
41
|
+
| **Guardrails** | `enforce_atomic_design`, `audit_a11y_wcag`, `enforce_seo_metadata` | Enforces 150-line limits, WCAG compliance, and SEO tags. |
|
|
42
|
+
| **Integration** | `fetch_figma_design`, `generate_local_assets` | Connects Figma designs and localizes remote assets. |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## š Getting Started (Automatic Installation)
|
|
47
|
+
|
|
48
|
+
The easiest way to get VibeTachyon running in your IDE is via our interactive installer.
|
|
49
|
+
|
|
50
|
+
### 1. Run the Installer
|
|
51
|
+
In your project terminal, run:
|
|
52
|
+
```bash
|
|
53
|
+
npx vibetachyon@latest
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Follow the Prompts
|
|
57
|
+
1. **Select your IDE:** Cursor, Windsurf, Claude Desktop, VS Code (Cline/Roo), etc.
|
|
58
|
+
2. **Authenticate:** Paste your **VibeCodes MAX Token**.
|
|
59
|
+
3. **Done:** The installer will autonomously inject the MCP configuration into your IDE's global settings.
|
|
60
|
+
|
|
61
|
+
### 3. Manual Configuration (Optional)
|
|
62
|
+
If you prefer manual setup, add this to your MCP config file:
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"vibetachyon": {
|
|
67
|
+
"command": "npx",
|
|
68
|
+
"args": ["-y", "vibetachyon@latest", "server"],
|
|
69
|
+
"env": {
|
|
70
|
+
"VIBECODES_TOKEN": "YOUR_TOKEN_HERE"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## š Coding Philosophy
|
|
80
|
+
VibeTachyon enforces **Atomic Design** and **Framework Awareness**. It treats the code as a living organism that must be performant (minified snippets), accessible (WCAG), and measurable (Telemetry).
|
|
81
|
+
|
|
82
|
+
**Made with š by Davi Ravieri.**
|
|
83
|
+
"Make it work, make it beautiful, make it God-Tier."
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const prompts_1 = require("@clack/prompts");
|
|
8
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
9
|
+
const mcp_server_js_1 = require("./mcp-server.js");
|
|
10
|
+
const installer_js_1 = require("./installer.js");
|
|
11
|
+
async function main() {
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
// Run as MCP Server if "server" is passed as argument
|
|
14
|
+
if (args[0] === 'server') {
|
|
15
|
+
await (0, mcp_server_js_1.startMcpServer)();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
// CLI Interactive Mode
|
|
19
|
+
console.log();
|
|
20
|
+
(0, prompts_1.intro)(picocolors_1.default.bgBlue(picocolors_1.default.white(' ā” VibeTachyon MCP CLI ')));
|
|
21
|
+
const action = await (0, prompts_1.select)({
|
|
22
|
+
message: 'What would you like to do with VibeTachyon?',
|
|
23
|
+
options: [
|
|
24
|
+
{ value: 'install', label: 'Install/Sync VibeTachyon on my Code Agents (Cursor, Windsurf, Claude)' },
|
|
25
|
+
{ value: 'exit', label: 'Exit' }
|
|
26
|
+
]
|
|
27
|
+
});
|
|
28
|
+
if ((0, prompts_1.isCancel)(action) || action === 'exit') {
|
|
29
|
+
(0, prompts_1.outro)('Aborted.');
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
if (action === 'install') {
|
|
33
|
+
await (0, installer_js_1.runInstallFlow)();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runInstallFlow = runInstallFlow;
|
|
7
|
+
const prompts_1 = require("@clack/prompts");
|
|
8
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
async function runInstallFlow() {
|
|
13
|
+
const agent = await (0, prompts_1.select)({
|
|
14
|
+
message: 'Select your preferred Code Agent to inject VibeTachyon:',
|
|
15
|
+
options: [
|
|
16
|
+
{ value: 'cursor', label: 'Cursor' },
|
|
17
|
+
{ value: 'windsurf', label: 'Windsurf' },
|
|
18
|
+
{ value: 'claude', label: 'Claude Desktop / Claude Code' },
|
|
19
|
+
{ value: 'gemini', label: 'Gemini CLI / Antigravity' },
|
|
20
|
+
{ value: 'cline', label: 'Cline / Roo Code (VS Code)' },
|
|
21
|
+
{ value: 'copilot', label: 'GitHub Copilot (VS Code)' },
|
|
22
|
+
{ value: 'codex', label: 'Codex CLI' },
|
|
23
|
+
{ value: 'augment', label: 'Augment Code' }
|
|
24
|
+
]
|
|
25
|
+
});
|
|
26
|
+
if ((0, prompts_1.isCancel)(agent)) {
|
|
27
|
+
(0, prompts_1.outro)('Aborted.');
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
const apiKey = await (0, prompts_1.text)({
|
|
31
|
+
message: 'Paste your VibeCodes MAX Access Token:',
|
|
32
|
+
placeholder: 'vcf_...',
|
|
33
|
+
validate(value) {
|
|
34
|
+
if (value.length === 0)
|
|
35
|
+
return 'Token is required!';
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
if ((0, prompts_1.isCancel)(apiKey)) {
|
|
39
|
+
(0, prompts_1.outro)('Aborted.');
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
const s = (0, prompts_1.spinner)();
|
|
43
|
+
s.start(`Connecting to VibeCodes and verifying token...`);
|
|
44
|
+
try {
|
|
45
|
+
// NOTE: In a real production environment, this URL would be 'https://vibecodes.com.br/api/vibecodes/mcp/verify'
|
|
46
|
+
// For development/local testing, you can change this to http://localhost:3000
|
|
47
|
+
// Use environment variable for the API URL, default to a placeholder
|
|
48
|
+
// TODO: Replace with your actual production domain once purchased
|
|
49
|
+
const API_URL = process.env.VIBETACHYON_API_URL || 'http://127.0.0.1:3000/api/vibecodes/mcp/verify';
|
|
50
|
+
const response = await fetch(API_URL, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { 'Content-Type': 'application/json' },
|
|
53
|
+
body: JSON.stringify({ token: apiKey })
|
|
54
|
+
});
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
if (!response.ok || !data.success) {
|
|
57
|
+
s.stop(picocolors_1.default.red(`Authentication Failed. ${data.error || 'Invalid Token.'}`));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
s.message(picocolors_1.default.green(`ā
Authenticated as ${data.name || 'VibeCodes MAX User'}. Injecting MCP into ${agent}...`));
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
s.stop(picocolors_1.default.red(`Network Error: Please check your connection or the VibeCodes API.`));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
// Path resolution logic
|
|
67
|
+
const home = os_1.default.homedir();
|
|
68
|
+
let mcpConfigPath = '';
|
|
69
|
+
let configObject = {};
|
|
70
|
+
// Standard MCP Server definition
|
|
71
|
+
const serverDefinition = {
|
|
72
|
+
command: "npx",
|
|
73
|
+
args: ["-y", "vibetachyon@latest", "server"],
|
|
74
|
+
env: {
|
|
75
|
+
VIBECODES_TOKEN: apiKey
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
switch (agent) {
|
|
79
|
+
case 'cursor':
|
|
80
|
+
mcpConfigPath = path_1.default.join(home, '.cursor', 'mcp.json');
|
|
81
|
+
configObject = { mcpServers: { vibetachyon: serverDefinition } };
|
|
82
|
+
break;
|
|
83
|
+
case 'windsurf':
|
|
84
|
+
mcpConfigPath = path_1.default.join(home, '.codeium', 'windsurf', 'mcp_config.json');
|
|
85
|
+
configObject = { mcpServers: { vibetachyon: serverDefinition } };
|
|
86
|
+
break;
|
|
87
|
+
case 'claude':
|
|
88
|
+
mcpConfigPath = path_1.default.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
89
|
+
configObject = { mcpServers: { vibetachyon: serverDefinition } };
|
|
90
|
+
break;
|
|
91
|
+
case 'gemini':
|
|
92
|
+
mcpConfigPath = path_1.default.join(home, '.gemini', 'antigravity', 'mcp.json');
|
|
93
|
+
configObject = { mcpServers: { vibetachyon: serverDefinition } };
|
|
94
|
+
break;
|
|
95
|
+
case 'cline':
|
|
96
|
+
mcpConfigPath = path_1.default.join(process.cwd(), '.vscode', 'roo_mcp.json');
|
|
97
|
+
configObject = { mcpServers: { vibetachyon: serverDefinition } };
|
|
98
|
+
break;
|
|
99
|
+
case 'copilot':
|
|
100
|
+
mcpConfigPath = path_1.default.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'github.copilot', 'mcp.json');
|
|
101
|
+
configObject = { mcpServers: { vibetachyon: serverDefinition } };
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
mcpConfigPath = path_1.default.join(home, '.vibetachyon', 'mcp_config.json');
|
|
105
|
+
configObject = { mcpServers: { vibetachyon: serverDefinition } };
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
// Ensure directory exists
|
|
109
|
+
const dir = path_1.default.dirname(mcpConfigPath);
|
|
110
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
111
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
// Merge if file exists, or create new
|
|
114
|
+
let finalConfig = configObject;
|
|
115
|
+
if (fs_1.default.existsSync(mcpConfigPath)) {
|
|
116
|
+
try {
|
|
117
|
+
const existing = JSON.parse(fs_1.default.readFileSync(mcpConfigPath, 'utf8'));
|
|
118
|
+
if (existing.mcpServers) {
|
|
119
|
+
existing.mcpServers.vibetachyon = serverDefinition;
|
|
120
|
+
finalConfig = existing;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
// If corrupt, overwrite with our new config
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
fs_1.default.writeFileSync(mcpConfigPath, JSON.stringify(finalConfig, null, 2));
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
s.stop(picocolors_1.default.red(`Error writing configuration: ${err.message}`));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
s.stop(picocolors_1.default.green('Injection completed successfully.'));
|
|
134
|
+
console.log(picocolors_1.default.gray('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
135
|
+
console.log(picocolors_1.default.gray('ā') + picocolors_1.default.bold(picocolors_1.default.cyan(' Server configuration created at: ')));
|
|
136
|
+
console.log(picocolors_1.default.gray('ā') + ' ' + picocolors_1.default.dim(mcpConfigPath));
|
|
137
|
+
console.log(picocolors_1.default.gray('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
138
|
+
(0, prompts_1.outro)(picocolors_1.default.green('š VibeTachyon is ready! Your IDE is now connected to VibeCodes.'));
|
|
139
|
+
}
|
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startMcpServer = startMcpServer;
|
|
7
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
8
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
9
|
+
const zod_1 = require("zod");
|
|
10
|
+
const supabase_js_1 = require("@supabase/supabase-js");
|
|
11
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
const util_1 = __importDefault(require("util"));
|
|
15
|
+
const execPromise = util_1.default.promisify(child_process_1.exec);
|
|
16
|
+
// Initialize Supabase. Note: These ENV variables will be injected or provided by the installer/user config in the future.
|
|
17
|
+
// For now, we use placeholders expecting them in process.env
|
|
18
|
+
const supabaseUrl = process.env.VIBECODES_SUPABASE_URL || 'https://yhrcxrtzaxamvxtjrdif.supabase.co';
|
|
19
|
+
const supabaseKey = process.env.VIBECODES_SUPABASE_ANON_KEY || 'your-default-anon-key';
|
|
20
|
+
const supabase = (0, supabase_js_1.createClient)(supabaseUrl, supabaseKey);
|
|
21
|
+
/**
|
|
22
|
+
* Minifies a code snippet by removing comments and extra whitespace to save tokens.
|
|
23
|
+
*/
|
|
24
|
+
function minifySnippet(code) {
|
|
25
|
+
// Remove multi-line comments
|
|
26
|
+
let minified = code.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
27
|
+
// Remove single-line comments (but NOT inside URLs/strings)
|
|
28
|
+
minified = minified.split('\n').map(line => {
|
|
29
|
+
// Simple heuristic: if // appears after http: or https:, don't treat as comment
|
|
30
|
+
const commentIndex = line.indexOf('//');
|
|
31
|
+
if (commentIndex !== -1 && !line.includes('http://') && !line.includes('https://')) {
|
|
32
|
+
return line.substring(0, commentIndex).trimEnd();
|
|
33
|
+
}
|
|
34
|
+
return line;
|
|
35
|
+
}).join('\n');
|
|
36
|
+
// Collapse excess newlines but keep some structure for the LLM
|
|
37
|
+
return minified.replace(/\n\s*\n/g, '\n').trim();
|
|
38
|
+
}
|
|
39
|
+
async function startMcpServer() {
|
|
40
|
+
const server = new mcp_js_1.McpServer({
|
|
41
|
+
name: "VibeTachyon MCP",
|
|
42
|
+
version: "1.0.0"
|
|
43
|
+
});
|
|
44
|
+
// Tool: Searching Snippets
|
|
45
|
+
// Tool: Searching Snippets
|
|
46
|
+
server.tool("search_vibecodes_snippets", "Search for UI components in the VibeCodes repository (both public and MCP-exclusive). Returns TSX/HTML/CSS code.", {
|
|
47
|
+
query: zod_1.z.string().describe("Search query for the UI component (e.g. 'pricing table', 'neon button')"),
|
|
48
|
+
limit: zod_1.z.number().optional().describe("Max number of results to return (default: 5)"),
|
|
49
|
+
projectDir: zod_1.z.string().optional().describe("Absolute path to the user's project root. Crucial for auto-formatting code correctly.")
|
|
50
|
+
}, async ({ query, limit = 5, projectDir }) => {
|
|
51
|
+
console.error(`[VibeTachyon] Searching Semantic DB for: ${query}`);
|
|
52
|
+
try {
|
|
53
|
+
// Use the Advanced RAG Postgres RPC for semantic natural language TSVector search
|
|
54
|
+
const { data: allResults, error } = await supabase
|
|
55
|
+
.rpc('search_vibecodes_mcp', {
|
|
56
|
+
search_query: query,
|
|
57
|
+
max_limit: limit
|
|
58
|
+
});
|
|
59
|
+
if (error) {
|
|
60
|
+
console.error("[VibeTachyon] Supabase RPC Error:", error);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
if (!allResults || allResults.length === 0) {
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text", text: `No VibeCodes components found for "${query}". Try different semantics.` }]
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const formattedResults = allResults.map((item) => {
|
|
69
|
+
const srcInfo = item.source_table === 'mcp_exclusive' ? '[ā PREMIUM MCP EXCLUSIVE]' : '[š VIBECODES HUB]';
|
|
70
|
+
const minifiedCode = minifySnippet(item.code);
|
|
71
|
+
return `### ${item.title} ${srcInfo}\n${item.description || ''}\n\n\`\`\`tsx\n${minifiedCode}\n\`\`\``;
|
|
72
|
+
}).join('\n\n---\n\n');
|
|
73
|
+
// --- ZERO FRICTION "ACCEPT-ALL" GUARDRAILS ---
|
|
74
|
+
const root = projectDir || process.cwd();
|
|
75
|
+
let frameworkContext = "";
|
|
76
|
+
try {
|
|
77
|
+
const pkgPath = path_1.default.join(root, 'package.json');
|
|
78
|
+
if (await fs_extra_1.default.pathExists(pkgPath)) {
|
|
79
|
+
const pkg = await fs_extra_1.default.readJson(pkgPath);
|
|
80
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
81
|
+
const isVite = !!deps['vite'];
|
|
82
|
+
const isNext = !!deps['next'];
|
|
83
|
+
frameworkContext += `\n\n=======================================================\n`;
|
|
84
|
+
frameworkContext += `[CRITICAL COPILOT DIRECTIVE - ZERO FRICTION GUARANTEE]\n`;
|
|
85
|
+
frameworkContext += `You are generating code for a user who will likely click "Accept All". You MUST NOT output incompatible code that breaks their app. `;
|
|
86
|
+
frameworkContext += `USER ENVIRONMENT: ${isNext ? 'Next.js' : isVite ? 'Vite/React SPA' : 'React/Other'}.\n\n`;
|
|
87
|
+
if (isVite) {
|
|
88
|
+
frameworkContext += `šØ FATAL VITE ERROR PREVENTION:\n- DO NOT use Next.js '<Image>' or 'next/link'.\n- DO NOT use '"use client"'.\n- You MUST implicitly translate the components above into standard '<img>' and '<a>' tags BEFORE writing the final code.\n`;
|
|
89
|
+
frameworkContext += `- Tell the user to run this command if animations are used: \`npm install framer-motion lucide-react clsx tailwind-merge\`\n`;
|
|
90
|
+
}
|
|
91
|
+
else if (isNext) {
|
|
92
|
+
frameworkContext += `šØ NEXT.JS OPTIMIZATION:\n- Use Server Components by default.\n- If the component uses hooks (useState) or onClick, you MUST prepend '"use client";' at the very top of the generated file.\n`;
|
|
93
|
+
frameworkContext += `- Ensure image domains are whitelisted (tell them to update next.config.ts if an external src is used).\n`;
|
|
94
|
+
}
|
|
95
|
+
frameworkContext += `=======================================================\n`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
// silently fail the context extraction if no pkg json found
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
content: [{ type: "text", text: `š§ VibeTachyon found ${allResults.length} highly relevant components:\n\n${formattedResults}${frameworkContext}` }]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error("[VibeTachyon] Supabase Search Error:", error);
|
|
107
|
+
return {
|
|
108
|
+
content: [{ type: "text", text: `Error searching VibeCodes repository: ${error}` }]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// Example Tool: Access VibeCodes Rules
|
|
113
|
+
server.tool("get_vibecodes_architecture", "Get instructions on how to design and build components adhering to the VibeCodes Neon Blue Design System", {}, async () => {
|
|
114
|
+
return {
|
|
115
|
+
content: [
|
|
116
|
+
{
|
|
117
|
+
type: "text",
|
|
118
|
+
text: `VibeCodes Design System Rules:\n1. Dark mode natively (backgrounds using hsl).\n2. Glassmorphism: Borders should be white/10 or primary/20.\n3. Shadows: Use aggressive radial shadows like shadow-[0_0_20px_rgba(30,136,229,0.15)].\n4. Use Lucide React icons.\n5. Use Shadcn UI as base but apply custom Vibe themes.`
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
// Tool: Scan Local Architecture
|
|
124
|
+
server.tool("scan_local_architecture", "Scan the user's local project setup (package.json, tsconfig.json, components.json) to understand how to correctly import and structure generated UI components.", {
|
|
125
|
+
projectDir: zod_1.z.string().describe("Absolute path to the user's project root (e.g. /Users/admin/my-project). Ask the user or find it via terminal if unknown.")
|
|
126
|
+
}, async ({ projectDir }) => {
|
|
127
|
+
const root = projectDir || process.cwd();
|
|
128
|
+
let context = `[VibeTachyon Architecture Scanner] Project Root: ${root}\n\n`;
|
|
129
|
+
try {
|
|
130
|
+
// Check components.json (Shadcn UI)
|
|
131
|
+
const componentsPath = path_1.default.join(root, 'components.json');
|
|
132
|
+
if (await fs_extra_1.default.pathExists(componentsPath)) {
|
|
133
|
+
context += `- Detected Shadcn UI (components.json).\n`;
|
|
134
|
+
const compJson = await fs_extra_1.default.readJson(componentsPath);
|
|
135
|
+
context += ` - Components Path: ${compJson?.aliases?.components || '@/components'}\n`;
|
|
136
|
+
context += ` - Utils Path: ${compJson?.aliases?.utils || '@/lib/utils'}\n`;
|
|
137
|
+
}
|
|
138
|
+
// Check tsconfig.json (Paths)
|
|
139
|
+
let tsConfigPath = path_1.default.join(root, 'tsconfig.json');
|
|
140
|
+
if (!(await fs_extra_1.default.pathExists(tsConfigPath))) {
|
|
141
|
+
// Try one dir up in case they are inside src
|
|
142
|
+
tsConfigPath = path_1.default.join(root, '..', 'tsconfig.json');
|
|
143
|
+
}
|
|
144
|
+
if (await fs_extra_1.default.pathExists(tsConfigPath)) {
|
|
145
|
+
context += `- Detected tsconfig.json.\n`;
|
|
146
|
+
// Note: fs.readJson can throw on comments in tsconfig, so we use readFileSync and strip basic comments or ignore parsing errors gracefully
|
|
147
|
+
try {
|
|
148
|
+
const tsRaw = await fs_extra_1.default.readFile(tsConfigPath, 'utf8');
|
|
149
|
+
const tsClean = tsRaw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
150
|
+
const tsJson = JSON.parse(tsClean);
|
|
151
|
+
if (tsJson?.compilerOptions?.paths) {
|
|
152
|
+
context += ` - Aliases available: ${Object.keys(tsJson.compilerOptions.paths).join(', ')}\n`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
context += ` - (Could not parse paths automatically due to complex tsconfig format)\n`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Check dependencies
|
|
160
|
+
const pkgPath = path_1.default.join(root, 'package.json');
|
|
161
|
+
if (await fs_extra_1.default.pathExists(pkgPath)) {
|
|
162
|
+
const pkg = await fs_extra_1.default.readJson(pkgPath);
|
|
163
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
164
|
+
const keyDeps = ['framer-motion', 'lucide-react', 'clsx', 'tailwind-merge', 'next', 'react'];
|
|
165
|
+
const installed = keyDeps.filter(d => deps[d]);
|
|
166
|
+
context += `- Key UI Dependencies Installed: ${installed.join(', ')}\n`;
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
content: [{ type: "text", text: `${context}\n\nINSTRUCTION FOR AI: Always respect these paths and aliases when generating TSX code for the user. Do not use generic './components' if an alias like '@/' is available.` }]
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
return {
|
|
174
|
+
content: [{ type: "text", text: `Error scanning architecture: ${err}` }]
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
// Tool: Get Vibe Colors
|
|
179
|
+
server.tool("get_vibe_colors", "Extract local CSS variables from globals.css to ensure generated components match the user's specific dark/light theme native colors.", {
|
|
180
|
+
cssFilePath: zod_1.z.string().describe("Absolute path to the user's main CSS file (e.g., /Users/admin/my-project/src/app/globals.css). Search for it before utilizing.")
|
|
181
|
+
}, async ({ cssFilePath }) => {
|
|
182
|
+
try {
|
|
183
|
+
if (!(await fs_extra_1.default.pathExists(cssFilePath))) {
|
|
184
|
+
return {
|
|
185
|
+
content: [{ type: "text", text: `Error: Could not find CSS file at ${cssFilePath}. Please provide the correct path (e.g., src/app/globals.css or src/index.css).` }]
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const cssContent = await fs_extra_1.default.readFile(cssFilePath, 'utf-8');
|
|
189
|
+
// Extract --variable names
|
|
190
|
+
const matches = cssContent.match(/--[a-zA-Z0-9-]+(?=\s*:)/g);
|
|
191
|
+
if (!matches || matches.length === 0) {
|
|
192
|
+
return {
|
|
193
|
+
content: [{ type: "text", text: `Scanned ${cssFilePath} but couldn't find explicit CSS variables (like --primary, --background). Instruct AI to stick to standard Tailwind neutral colors.` }]
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const uniqueVars = Array.from(new Set(matches.map(m => m.trim())));
|
|
197
|
+
return {
|
|
198
|
+
content: [{
|
|
199
|
+
type: "text",
|
|
200
|
+
text: `[VibeTachyon Theming Scanner] Found the following CSS variables in the project:\n${uniqueVars.join(', ')}\n\nINSTRUCTION FOR AI: When generating the component, DO NOT hardcode standard colors (like 'bg-blue-500'). Instead, dynamically inject the user's variables using Tailwind semantic semantic classes (like 'bg-background', 'text-primary', 'border-border') to flawlessly match their existing theme layout.`
|
|
201
|
+
}]
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
return {
|
|
206
|
+
content: [{ type: "text", text: `Error parsing CSS colors: ${err}` }]
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
// Tool: Analyze and Install Deps
|
|
211
|
+
server.tool("analyze_and_install_deps", "Analyze a list of required npm packages for a UI component and return the exact terminal command the user (or AI Copilot) must run to install the missing dependencies.", {
|
|
212
|
+
projectDir: zod_1.z.string().describe("Absolute path to the user's project root (where package.json lives)."),
|
|
213
|
+
requiredDependencies: zod_1.z.array(zod_1.z.string()).describe("List of npm package names requested by the UI component (e.g., ['framer-motion', 'clsx'])")
|
|
214
|
+
}, async ({ projectDir, requiredDependencies }) => {
|
|
215
|
+
const root = projectDir || process.cwd();
|
|
216
|
+
try {
|
|
217
|
+
const pkgPath = path_1.default.join(root, 'package.json');
|
|
218
|
+
if (!(await fs_extra_1.default.pathExists(pkgPath))) {
|
|
219
|
+
return {
|
|
220
|
+
content: [{ type: "text", text: `Error: package.json not found in ${root}. Please ensure project directory is correct.` }]
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const pkg = await fs_extra_1.default.readJson(pkgPath);
|
|
224
|
+
const localDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
225
|
+
const missingDeps = requiredDependencies.filter(dep => !localDeps[dep]);
|
|
226
|
+
if (missingDeps.length === 0) {
|
|
227
|
+
return {
|
|
228
|
+
content: [{ type: "text", text: `All required dependencies (${requiredDependencies.join(', ')}) are already installed! Proceed with generating the component.` }]
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
// Determine package manager heuristically
|
|
232
|
+
let installer = 'npm install';
|
|
233
|
+
if (await fs_extra_1.default.pathExists(path_1.default.join(root, 'bun.lockb')))
|
|
234
|
+
installer = 'bun add';
|
|
235
|
+
else if (await fs_extra_1.default.pathExists(path_1.default.join(root, 'yarn.lock')))
|
|
236
|
+
installer = 'yarn add';
|
|
237
|
+
else if (await fs_extra_1.default.pathExists(path_1.default.join(root, 'pnpm-lock.yaml')))
|
|
238
|
+
installer = 'pnpm add';
|
|
239
|
+
const cmd = `${installer} ${missingDeps.join(' ')}`;
|
|
240
|
+
return {
|
|
241
|
+
content: [{ type: "text", text: `[CRITICAL ACTION REQUIRED]\nThe requested component needs packages that are NOT currently installed in this project.\n\nMissing dependencies: ${missingDeps.join(', ')}\n\nINSTRUCTION FOR AI: You MUST execute the following command in the user's terminal to install them, OR ask the user specifically to run it before writing the component code:\n\n\`\`\`bash\n${cmd}\n\`\`\`` }]
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
return {
|
|
246
|
+
content: [{ type: "text", text: `Error analyzing dependencies: ${err}` }]
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
// Tool: Resolve Component Tree (Dependency Matrix)
|
|
251
|
+
server.tool("resolve_component_tree", "Fetch multiple UI components at once (e.g. ['button', 'badge', 'card']). Crucial for resolving dependency trees when a complex component requires smaller sub-components.", {
|
|
252
|
+
componentNames: zod_1.z.array(zod_1.z.string()).describe("List of component names to fetch (e.g., ['button', 'avatar'])")
|
|
253
|
+
}, async ({ componentNames }) => {
|
|
254
|
+
try {
|
|
255
|
+
let allResults = [];
|
|
256
|
+
for (const query of componentNames) {
|
|
257
|
+
const { data, error } = await supabase
|
|
258
|
+
.rpc('search_vibecodes_mcp', { search_query: query, max_limit: 1 });
|
|
259
|
+
if (!error && data && data.length > 0) {
|
|
260
|
+
allResults.push(data[0]);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (allResults.length === 0) {
|
|
264
|
+
return { content: [{ type: "text", text: `Could not find any of the requested components in the VibeCodes registry.` }] };
|
|
265
|
+
}
|
|
266
|
+
const formattedResults = allResults.map((item) => {
|
|
267
|
+
return `### Sub-Component: ${item.title}\n\`\`\`tsx\n${item.code}\n\`\`\``;
|
|
268
|
+
}).join('\n\n---\n\n');
|
|
269
|
+
return {
|
|
270
|
+
content: [{ type: "text", text: `[DEPENDENCY MATRIX RESOLVED]\n\nINSTRUCTION FOR AI: The following sub-components are required. Create their files in the appropriate UI folder before integrating the main component.\n\n${formattedResults}` }]
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
return { content: [{ type: "text", text: `Error resolving tree: ${err}` }] };
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
// Tool: Patch Tailwind Config
|
|
278
|
+
server.tool("patch_tailwind_config", "Read the user's local tailwind config file to allow the AI to safely inject new keyframes, animations, or colors required by VibeCodes components.", {
|
|
279
|
+
projectDir: zod_1.z.string().describe("Absolute path to the user's project root.")
|
|
280
|
+
}, async ({ projectDir }) => {
|
|
281
|
+
const root = projectDir || process.cwd();
|
|
282
|
+
try {
|
|
283
|
+
let tailwindPath = path_1.default.join(root, 'tailwind.config.ts');
|
|
284
|
+
if (!(await fs_extra_1.default.pathExists(tailwindPath))) {
|
|
285
|
+
tailwindPath = path_1.default.join(root, 'tailwind.config.js');
|
|
286
|
+
}
|
|
287
|
+
if (!(await fs_extra_1.default.pathExists(tailwindPath))) {
|
|
288
|
+
return { content: [{ type: "text", text: `Error: Could not find tailwind.config.ts or .js in ${root}.` }] };
|
|
289
|
+
}
|
|
290
|
+
const tailwindContent = await fs_extra_1.default.readFile(tailwindPath, 'utf8');
|
|
291
|
+
return {
|
|
292
|
+
content: [{ type: "text", text: `[TAILWIND AUTO-PATCHER]\n\nHere is the user's current Tailwind configuration:\n\n\`\`\`typescript\n${tailwindContent}\n\`\`\`\n\nINSTRUCTION FOR AI: You must merge the required animations/colors into this configuration. Write the updated complete configuration file back to \`${tailwindPath}\`. DO NOT DELETE existing user configurations.` }]
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
return { content: [{ type: "text", text: `Error reading tailwind config: ${err}` }] };
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
// Tool: Compose Landing Page
|
|
300
|
+
server.tool("compose_landing_page", "Macro-composer that autonomously queries the VibeCodes RAG database for a full Landing Page structure (Navbar, Hero, Features, Pricing, Footer).", {
|
|
301
|
+
themeOrIndustry: zod_1.z.string().describe("The theme, industry, or style of the page (e.g., 'SaaS Finance Dark Mode', 'Dental Clinic Clean')")
|
|
302
|
+
}, async ({ themeOrIndustry }) => {
|
|
303
|
+
try {
|
|
304
|
+
const sections = ['navbar', 'hero section', 'features grid', 'pricing table', 'footer'];
|
|
305
|
+
let combinedPage = `[VIBEPAGE COMPOSER: ${themeOrIndustry}]\n\nINSTRUCTION FOR AI: Assemble a complete page using the following 5 elite components.\n\n`;
|
|
306
|
+
for (const section of sections) {
|
|
307
|
+
const query = `${themeOrIndustry} ${section}`;
|
|
308
|
+
const { data, error } = await supabase
|
|
309
|
+
.rpc('search_vibecodes_mcp', { search_query: query, max_limit: 1 });
|
|
310
|
+
if (!error && data && data.length > 0) {
|
|
311
|
+
combinedPage += `### === ${section.toUpperCase()} ===\n**Title:** ${data[0].title}\n\`\`\`tsx\n${data[0].code}\n\`\`\`\n\n`;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return { content: [{ type: "text", text: combinedPage }] };
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
return { content: [{ type: "text", text: `Error composing landing page: ${err}` }] };
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
// Tool: Generate Local Assets (Download)
|
|
321
|
+
server.tool("generate_local_assets", "Downloads a remote image (asset/placeholder) directly into the user's local project public directory.", {
|
|
322
|
+
projectDir: zod_1.z.string().describe("Absolute path to the user's project root."),
|
|
323
|
+
imageUrl: zod_1.z.string().describe("URL of the image to download."),
|
|
324
|
+
filename: zod_1.z.string().describe("Desired filename (e.g., 'hero-bg.webp').")
|
|
325
|
+
}, async ({ projectDir, imageUrl, filename }) => {
|
|
326
|
+
const root = projectDir || process.cwd();
|
|
327
|
+
try {
|
|
328
|
+
const publicDir = path_1.default.join(root, 'public', 'images', 'vibecodes');
|
|
329
|
+
await fs_extra_1.default.ensureDir(publicDir);
|
|
330
|
+
const filePath = path_1.default.join(publicDir, filename);
|
|
331
|
+
const res = await fetch(imageUrl);
|
|
332
|
+
if (!res.ok)
|
|
333
|
+
throw new Error(`Failed to fetch image: ${res.statusText}`);
|
|
334
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
335
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
336
|
+
await fs_extra_1.default.writeFile(filePath, buffer);
|
|
337
|
+
const localPath = `/images/vibecodes/${filename}`;
|
|
338
|
+
return {
|
|
339
|
+
content: [{ type: "text", text: `[ASSET GENERATOR]\nSuccessfully downloaded image to local filesystem.\n\nINSTRUCTION FOR AI: You can now use this local path in your React component: \`${localPath}\`` }]
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
catch (err) {
|
|
343
|
+
return { content: [{ type: "text", text: `Error downloading asset: ${err}` }] };
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
// Tool: Analyze Business Logic (Backend Wiring)
|
|
347
|
+
server.tool("analyze_business_logic", "Scans for database schemas (Prisma, Drizzle, SQL, or TS Models) and API route structures to ground UI generation in your real data structure. Crucial for dynamically wiring UI forms and tables directly to the backend logic.", {
|
|
348
|
+
projectDir: zod_1.z.string().describe("Absolute path to the user's project root.")
|
|
349
|
+
}, async ({ projectDir }) => {
|
|
350
|
+
const root = projectDir || process.cwd();
|
|
351
|
+
try {
|
|
352
|
+
const prismaPath = path_1.default.join(root, 'prisma', 'schema.prisma');
|
|
353
|
+
const drizzlePath = path_1.default.join(root, 'db', 'schema.ts');
|
|
354
|
+
const drizzlePathAlt = path_1.default.join(root, 'src', 'db', 'schema.ts');
|
|
355
|
+
const drizzlePathSimple = path_1.default.join(root, 'drizzle', 'schema.ts');
|
|
356
|
+
const modelsPath = path_1.default.join(root, 'src', 'models');
|
|
357
|
+
let context = `[DATA-BINDING MATRIX INITIATED]\n\n`;
|
|
358
|
+
// 1. Check Prisma
|
|
359
|
+
if (await fs_extra_1.default.pathExists(prismaPath)) {
|
|
360
|
+
const schemaContent = await fs_extra_1.default.readFile(prismaPath, 'utf8');
|
|
361
|
+
context += `==== [DATA SCHEMA] Prisma Schema Detected ====\n\`\`\`prisma\n${schemaContent}\n\`\`\`\n\n`;
|
|
362
|
+
}
|
|
363
|
+
// 2. Check Drizzle
|
|
364
|
+
// Note: find doesn't support async well, using manual loop
|
|
365
|
+
for (const p of [drizzlePath, drizzlePathAlt, drizzlePathSimple]) {
|
|
366
|
+
if (await fs_extra_1.default.pathExists(p)) {
|
|
367
|
+
const schemaContent = await fs_extra_1.default.readFile(p, 'utf8');
|
|
368
|
+
context += `==== [DATA SCHEMA] Drizzle Schema Detected in ${path_1.default.basename(p)} ====\n\`\`\`typescript\n${schemaContent}\n\`\`\`\n\n`;
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// 3. Check TS Models
|
|
373
|
+
if (await fs_extra_1.default.pathExists(modelsPath)) {
|
|
374
|
+
const files = await fs_extra_1.default.readdir(modelsPath);
|
|
375
|
+
const tsFiles = files.filter(f => f.endsWith('.ts'));
|
|
376
|
+
if (tsFiles.length > 0) {
|
|
377
|
+
context += `==== [DATA SCHEMA] TypeScript Models Found in /src/models ====\n`;
|
|
378
|
+
for (const f of tsFiles.slice(0, 5)) { // Max 5 files to avoid token bloat
|
|
379
|
+
const content = await fs_extra_1.default.readFile(path_1.default.join(modelsPath, f), 'utf8');
|
|
380
|
+
context += `/* ${f} */\n${content}\n\n`;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (context === `[DATA-BINDING MATRIX INITIATED]\n\n`) {
|
|
385
|
+
context += `No explicit Prisma, Drizzle, or TS Model schema found. Search for other backend files or ask the user.\n\n`;
|
|
386
|
+
}
|
|
387
|
+
const apiDir = path_1.default.join(root, 'src', 'app', 'api');
|
|
388
|
+
if (await fs_extra_1.default.pathExists(apiDir)) {
|
|
389
|
+
// Shallow read to hint at existing endpoints
|
|
390
|
+
const dirs = await fs_extra_1.default.readdir(apiDir);
|
|
391
|
+
context += `==== API Routes Detected ====\nThe following API endpoint namespaces exist in /src/app/api:\n${dirs.join(', ')}\n\n`;
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
content: [{
|
|
395
|
+
type: "text",
|
|
396
|
+
text: `${context}INSTRUCTION FOR AI: You must use this specific backend schema to generate TypeScript interfaces (or Zod schemas) for the UI components. Do not invent generic fields; map the UI inputs precisely to the database columns provided above.`
|
|
397
|
+
}]
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
return { content: [{ type: "text", text: `Error analyzing business logic: ${err}` }] };
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
// Tool: Learn User Preferences (Shadow Profile)
|
|
405
|
+
server.tool("learn_user_preferences", "Scan the user's configuration files (.prettierrc, .eslintrc.json) and components to clone their exact coding style (e.g. arrow vs normal functions, semicolons, quotes).", {
|
|
406
|
+
projectDir: zod_1.z.string().describe("Absolute path to the user's project root.")
|
|
407
|
+
}, async ({ projectDir }) => {
|
|
408
|
+
const root = projectDir || process.cwd();
|
|
409
|
+
try {
|
|
410
|
+
let prefs = `[VIBETACHYON STYLE CLONING]\n\n`;
|
|
411
|
+
const prettierPath = path_1.default.join(root, '.prettierrc');
|
|
412
|
+
if (await fs_extra_1.default.pathExists(prettierPath)) {
|
|
413
|
+
const prettier = await fs_extra_1.default.readFile(prettierPath, 'utf8');
|
|
414
|
+
prefs += `User Prettier Config:\n\`\`\`json\n${prettier}\n\`\`\`\n\n`;
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
prefs += `No .prettierrc found. Assume standard Prettier defaults.\n\n`;
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
content: [{
|
|
421
|
+
type: "text",
|
|
422
|
+
text: `${prefs}INSTRUCTION FOR AI: You MUST forcefully adapt any generated code to respect the styling rules detected above (e.g. single vs double quotes, trailing commas, semicolons). If generating React components, adapt the syntax (Arrow Functions vs Function Declarations) to perfectly match the user's implicit repository style.`
|
|
423
|
+
}]
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
catch (err) {
|
|
427
|
+
return { content: [{ type: "text", text: `Error learning user preferences: ${err}` }] };
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
// Tool: Enforce Atomic Design (AST Refactoring Constraint)
|
|
431
|
+
server.tool("enforce_atomic_design", "Force the AI Copilot to split large requested features into modular, scalable, atomic React components (e.g., breaking a Dashboard into Sidebar, Header, StatCards) instead of one giant file.", {}, async () => {
|
|
432
|
+
return {
|
|
433
|
+
content: [{
|
|
434
|
+
type: "text",
|
|
435
|
+
text: `[ATOMIC DESIGN CONSTRAINT ENFORCED]\n\nINSTRUCTION FOR AI: You are FORBIDDEN from generating components larger than 150 lines of code. If the user requests a complex page or large feature, you MUST:\n1. Break it down into smaller atomic pieces.\n2. Create separate files for each piece inside 'src/components/features/' or 'src/components/ui/'.\n3. Export each atomic component cleanly.\n4. Compose the final requested page using these atomic imports. DO NOT print a single massive file.`
|
|
436
|
+
}]
|
|
437
|
+
};
|
|
438
|
+
});
|
|
439
|
+
// Tool: Audit Accessibility (WCAG / A11Y)
|
|
440
|
+
server.tool("audit_a11y_wcag", "Injects a strict Web Content Accessibility Guidelines (WCAG) heuristic rulebook to ensure universally accessible UI generation.", {}, async () => {
|
|
441
|
+
return {
|
|
442
|
+
content: [{
|
|
443
|
+
type: "text",
|
|
444
|
+
text: `[WCAG A11Y AUDIT MATRIX APPLIED]\n\nINSTRUCTION FOR AI: Every component you generate MUST strictly adhere to these accessibility requirements:\n- Buttons must have clear 'aria-label' if text is visually hidden or iconography-only.\n- Form inputs must have an associated 'id' and '<label htmlFor=\"id\">'.\n- Dialogs/Modals must trap focus and include 'aria-modal=\"true\"' and 'role=\"dialog\"'.\n- Interactive elements must visibly show focus states ('focus:ring-2 focus:ring-primary focus:outline-none').\n- Do not use '<div>' for clickable zones; always use '<button>' or '<a>' with proper 'href'.`
|
|
445
|
+
}]
|
|
446
|
+
};
|
|
447
|
+
});
|
|
448
|
+
// Tool: Resolve Breaking Changes (Chaos Engineering)
|
|
449
|
+
server.tool("resolve_breaking_changes", "Helper to resolve framework version mismatches between older copied components (e.g., Next.js 13 Code) and the user's current environment (e.g., Vite, Next.js 15, React 19).", {
|
|
450
|
+
projectDir: zod_1.z.string().describe("Absolute path to the user's project root.")
|
|
451
|
+
}, async ({ projectDir }) => {
|
|
452
|
+
const root = projectDir || process.cwd();
|
|
453
|
+
try {
|
|
454
|
+
const pkgPath = path_1.default.join(root, 'package.json');
|
|
455
|
+
if (!(await fs_extra_1.default.pathExists(pkgPath))) {
|
|
456
|
+
return { content: [{ type: "text", text: `Error: package.json not found in ${root}.` }] };
|
|
457
|
+
}
|
|
458
|
+
const pkg = await fs_extra_1.default.readJson(pkgPath);
|
|
459
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
460
|
+
let isVite = !!deps['vite'];
|
|
461
|
+
let isNext = !!deps['next'];
|
|
462
|
+
let constraints = `[CHAOS RESOLVER APPLIED]\n\n`;
|
|
463
|
+
constraints += `User Environment Detected:\n`;
|
|
464
|
+
constraints += `React: ${deps['react'] || 'Unknown'}\n`;
|
|
465
|
+
constraints += `Framework: ${isNext ? 'Next.js' : isVite ? 'Vite/React SPA' : 'Custom/Other'}\n\n`;
|
|
466
|
+
constraints += `INSTRUCTION FOR AI: Review your generated code to match the framework:\n`;
|
|
467
|
+
if (isVite) {
|
|
468
|
+
constraints += `- The user is on Vite (React SPA). DO NOT use Next.js specific components like <Image> or <Link>. Use standard <img> and <a> (or react-router <Link> if installed).\n`;
|
|
469
|
+
constraints += `- DO NOT use 'use client' directives. DO NOT use Server Components.\n`;
|
|
470
|
+
}
|
|
471
|
+
else if (isNext) {
|
|
472
|
+
constraints += `- The user is on Next.js. If Next.js 14/15 or React 19, you MUST prefer Server Components by default. Add '\"use client\";' ONLY if hooks (useState, useEffect) or browser APIs are strictly needed.\n`;
|
|
473
|
+
constraints += `- Replace legacy Next.js Image imports or Link tags with their modern equivalents.\n`;
|
|
474
|
+
}
|
|
475
|
+
return { content: [{ type: "text", text: constraints }] };
|
|
476
|
+
}
|
|
477
|
+
catch (err) {
|
|
478
|
+
return { content: [{ type: "text", text: `Error resolving breaking changes: ${err}` }] };
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
// Tool: Guarantee Image Domains (UX Protection)
|
|
482
|
+
server.tool("guarantee_image_domains", "Scans for external image URLs in a component and forces the AI to handle them perfectly depending on whether the user is on Next.js or Vite.", {
|
|
483
|
+
projectDir: zod_1.z.string().describe("Absolute path to the user's project root.")
|
|
484
|
+
}, async ({ projectDir }) => {
|
|
485
|
+
const root = projectDir || process.cwd();
|
|
486
|
+
try {
|
|
487
|
+
const pkgPath = path_1.default.join(root, 'package.json');
|
|
488
|
+
if (!(await fs_extra_1.default.pathExists(pkgPath)))
|
|
489
|
+
return { content: [{ type: "text", text: "No package.json found." }] };
|
|
490
|
+
const pkg = await fs_extra_1.default.readJson(pkgPath);
|
|
491
|
+
const isNext = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
|
|
492
|
+
if (isNext) {
|
|
493
|
+
let configPath = path_1.default.join(root, 'next.config.ts');
|
|
494
|
+
if (!(await fs_extra_1.default.pathExists(configPath)))
|
|
495
|
+
configPath = path_1.default.join(root, 'next.config.mjs');
|
|
496
|
+
if (!(await fs_extra_1.default.pathExists(configPath)))
|
|
497
|
+
configPath = path_1.default.join(root, 'next.config.js');
|
|
498
|
+
let configContent = "No next.config found.";
|
|
499
|
+
if (await fs_extra_1.default.pathExists(configPath)) {
|
|
500
|
+
configContent = await fs_extra_1.default.readFile(configPath, 'utf8');
|
|
501
|
+
}
|
|
502
|
+
return {
|
|
503
|
+
content: [{
|
|
504
|
+
type: "text",
|
|
505
|
+
text: `[IMAGE DOMAIN WHITELIST PROTOCOL]\nUser Framework: Next.js\nUser's Current Next Config:\n\`\`\`typescript\n${configContent}\n\`\`\`\n\nINSTRUCTION FOR AI: If the component uses '<Image src=\"https://external.com/\" />', it WILL BREAK unless the domain is whitelisted. You MUST update 'images.remotePatterns' in '${path_1.default.basename(configPath)}'.`
|
|
506
|
+
}]
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
return {
|
|
511
|
+
content: [{
|
|
512
|
+
type: "text",
|
|
513
|
+
text: `[IMAGE PROTOCOL]\nUser Framework: Vite/React SPA\n\nINSTRUCTION FOR AI: The user is NOT using Next.js. Do NOT use 'next/image'. Use standard HTML '<img src=\"...\" />'. You do NOT need to whitelist any domains in Vite.`
|
|
514
|
+
}]
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
catch (err) {
|
|
519
|
+
return { content: [{ type: "text", text: `Error guaranteeing image domains: ${err}` }] };
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
// Tool: Guarantee Layout Providers (UX Protection)
|
|
523
|
+
server.tool("guarantee_layout_providers", "Checks the user's root layout or App entry point to ensure Toaster (notifications) and ThemeProvider (dark mode) are present so components render correctly.", {
|
|
524
|
+
projectDir: zod_1.z.string().describe("Absolute path to the user's project root.")
|
|
525
|
+
}, async ({ projectDir }) => {
|
|
526
|
+
const root = projectDir || process.cwd();
|
|
527
|
+
try {
|
|
528
|
+
const pkgPath = path_1.default.join(root, 'package.json');
|
|
529
|
+
if (!(await fs_extra_1.default.pathExists(pkgPath)))
|
|
530
|
+
return { content: [{ type: "text", text: "No package.json found." }] };
|
|
531
|
+
const pkg = await fs_extra_1.default.readJson(pkgPath);
|
|
532
|
+
const isNext = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
|
|
533
|
+
let rootPath = '';
|
|
534
|
+
if (isNext) {
|
|
535
|
+
const l1 = path_1.default.join(root, 'src', 'app', 'layout.tsx');
|
|
536
|
+
const l2 = path_1.default.join(root, 'app', 'layout.tsx');
|
|
537
|
+
if (await fs_extra_1.default.pathExists(l1))
|
|
538
|
+
rootPath = l1;
|
|
539
|
+
else if (await fs_extra_1.default.pathExists(l2))
|
|
540
|
+
rootPath = l2;
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
const app1 = path_1.default.join(root, 'src', 'App.tsx');
|
|
544
|
+
const app2 = path_1.default.join(root, 'src', 'main.tsx');
|
|
545
|
+
if (await fs_extra_1.default.pathExists(app1))
|
|
546
|
+
rootPath = app1;
|
|
547
|
+
else if (await fs_extra_1.default.pathExists(app2))
|
|
548
|
+
rootPath = app2;
|
|
549
|
+
}
|
|
550
|
+
if (!rootPath) {
|
|
551
|
+
return { content: [{ type: "text", text: `Could not find root layout or App.tsx in ${root}. Ensure paths are correct.` }] };
|
|
552
|
+
}
|
|
553
|
+
const layoutContent = await fs_extra_1.default.readFile(rootPath, 'utf8');
|
|
554
|
+
return {
|
|
555
|
+
content: [{
|
|
556
|
+
type: "text",
|
|
557
|
+
text: `[ROOT LAYOUT PROVIDER PROTOCOL]\nUser Framework: ${isNext ? 'Next.js' : 'Vite/React SPA'}\nRoot File Detected: ${rootPath}\n\nUser's Current Root Layout/App:\n\`\`\`tsx\n${layoutContent}\n\`\`\`\n\nINSTRUCTION FOR AI: Review the user's root file. If they requested a component that uses Toast notifications (sonner) or Dark Mode (next-themes or context), you MUST ensure the corresponding <Toaster /> or <ThemeProvider> wrappers are present. If missing, boldly instruct them to add it, or patch the root file directly.`
|
|
558
|
+
}]
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
catch (err) {
|
|
562
|
+
return { content: [{ type: "text", text: `Error guaranteeing layout providers: ${err}` }] };
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
// Tool: Fetch Figma Design (Figma Bridge)
|
|
566
|
+
server.tool("fetch_figma_design", "Connects to the Figma API to extract layout, colors, typography, and structure. Requires FIGMA_ACCESS_TOKEN in env.", {
|
|
567
|
+
fileKey: zod_1.z.string().describe("The Figma file key from the URL (e.g. figma.com/file/xxxx/...)."),
|
|
568
|
+
nodeId: zod_1.z.string().describe("The specific node ID of the component (e.g., '1:23').")
|
|
569
|
+
}, async ({ fileKey, nodeId }) => {
|
|
570
|
+
try {
|
|
571
|
+
const figmaToken = process.env.FIGMA_ACCESS_TOKEN;
|
|
572
|
+
if (!figmaToken) {
|
|
573
|
+
return { content: [{ type: "text", text: `Error: FIGMA_ACCESS_TOKEN not found in environment. Please instruct the user to set it up to bridge Figma designs.` }] };
|
|
574
|
+
}
|
|
575
|
+
// Node fetch requires fetch API available in Node 18+
|
|
576
|
+
const response = await fetch(`https://api.figma.com/v1/files/${fileKey}/nodes?ids=${nodeId}`, {
|
|
577
|
+
headers: { 'X-FIGMA-TOKEN': figmaToken }
|
|
578
|
+
});
|
|
579
|
+
if (!response.ok) {
|
|
580
|
+
throw new Error(`Figma API responded with ${response.status}: ${await response.text()}`);
|
|
581
|
+
}
|
|
582
|
+
const data = await response.json();
|
|
583
|
+
const nodeData = data.nodes[nodeId]?.document;
|
|
584
|
+
if (!nodeData) {
|
|
585
|
+
return { content: [{ type: "text", text: `Node ${nodeId} not found in Figma file ${fileKey}.` }] };
|
|
586
|
+
}
|
|
587
|
+
// Simplistic extraction for context
|
|
588
|
+
const extractedText = JSON.stringify(nodeData, null, 2);
|
|
589
|
+
let visualLayoutConstraints = `[FIGMA-TO-CODE BRIDGE APPLIED]\n\n`;
|
|
590
|
+
visualLayoutConstraints += `Raw Figma JSON Structure Snippet:\n\`\`\`json\n${extractedText.substring(0, 1500)}...\n\`\`\`\n\n`;
|
|
591
|
+
visualLayoutConstraints += `INSTRUCTION FOR AI: The user wants you to convert this raw Figma JSON node into a physical React component. Analyze the absolute positions, Auto Layout flex constraints, fills (colors), and typography. Map the Figma hex colors to Tailwind utility classes, and map Auto Layout to Tailwind Flexbox. Use 'search_vibecodes_snippets' if you need a specific base (like a Shadcn UI Button) to assemble the final design.`;
|
|
592
|
+
return { content: [{ type: "text", text: visualLayoutConstraints }] };
|
|
593
|
+
}
|
|
594
|
+
catch (err) {
|
|
595
|
+
return { content: [{ type: "text", text: `Error fetching from Figma: ${err}` }] };
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
// Tool: Enforce SEO Metadata
|
|
599
|
+
server.tool("enforce_seo_metadata", "Forces the AI to inject optimal SEO Metadata, OpenGraph tags, and Title tagging into the generation of any Page component.", {}, async () => {
|
|
600
|
+
return {
|
|
601
|
+
content: [{
|
|
602
|
+
type: "text",
|
|
603
|
+
text: `[SEO RANKING HEURISTIC APPLIED]\n\nINSTRUCTION FOR AI: The user is generating a full web page. You MUST inject perfect SEO metadata native to the framework.\n- If Next.js App Router: Export a 'metadata' constant with 'title', 'description', and 'openGraph' fields.\n- If Vite/React SPA: Use react-helmet-async (or similar) to inject '<title>' and '<meta name=\"description\">' within the component render.\nDO NOT generate a page without SEO tags. It MUST rank well.`
|
|
604
|
+
}]
|
|
605
|
+
};
|
|
606
|
+
});
|
|
607
|
+
// Tool: Self-Heal TypeScript Errors (AST Check)
|
|
608
|
+
server.tool("self_heal_typescript_errors", "Runs local TypeScript transpilation (tsc --noEmit) on a specific file to catch hidden bugs. Use this immediately after generating or modifying a complex component to self-correct any AST errors before the user loads the page.", {
|
|
609
|
+
filePath: zod_1.z.string().describe("Absolute path to the TypeScript/TSX file you want to typecheck.")
|
|
610
|
+
}, async ({ filePath }) => {
|
|
611
|
+
try {
|
|
612
|
+
if (!(await fs_extra_1.default.pathExists(filePath))) {
|
|
613
|
+
return { content: [{ type: "text", text: `Error: File ${filePath} not found.` }] };
|
|
614
|
+
}
|
|
615
|
+
const dir = path_1.default.dirname(filePath);
|
|
616
|
+
try {
|
|
617
|
+
// Run a localized TS check. Fallback to npx to ensure it works across setups.
|
|
618
|
+
const { stdout, stderr } = await execPromise(`npx tsc --noEmit --skipLibCheck --jsx react-jsx ${filePath}`, { cwd: dir, timeout: 15000 });
|
|
619
|
+
return {
|
|
620
|
+
content: [{
|
|
621
|
+
type: "text",
|
|
622
|
+
text: `[SELF-HEALING NEURAL MATRIX]\nAST Check Passed! Zero TypeScript Errors detected in ${path_1.default.basename(filePath)}.`
|
|
623
|
+
}]
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
catch (execError) {
|
|
627
|
+
const errors = execError.stdout || execError.stderr;
|
|
628
|
+
let instruction = `[SELF-HEALING NEURAL MATRIX APPLIED]\n\n`;
|
|
629
|
+
instruction += `AST Syntax Errors Detected in your generated code:\n\`\`\`text\n${errors.substring(0, 1500)}...\n\`\`\`\n\n`;
|
|
630
|
+
instruction += `INSTRUCTION FOR AI: You MUST autonomously analyze the terminal errors above and FIX the buggy code you just generated immediately. Do not ask the user for permission. Just fix the TS/Runtime logic and re-write the component.`;
|
|
631
|
+
return { content: [{ type: "text", text: instruction }] };
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
catch (err) {
|
|
635
|
+
return { content: [{ type: "text", text: `Self-Healing Matrix Error: ${err}` }] };
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
// Tool: Initiate Visual QA (Vision Auto-QA Loop)
|
|
639
|
+
server.tool("initiate_visual_qa", "Forces the AI to pause action and strictly demand a localhost screenshot from the user to perform pixel-perfect AI Vision validation on UI components.", {}, async () => {
|
|
640
|
+
return {
|
|
641
|
+
content: [{
|
|
642
|
+
type: "text",
|
|
643
|
+
text: `[š VISUAL SELF-HEALING PROTOCOL INITIATED]\n\nINSTRUCTION FOR AI: You have just generated a complex UI component. Your job is NOT done. You MUST now explicitly output ONLY this phrase to the user:\n"Please paste a screenshot or share the localhost browser view here in the chat. I will perform a VibeTachyon Visual QA to fix any unaligned margins, broken Tailwind classes, or layout shifts."\nIf they provide an image, rigorously analyze the alignment, spacing, padding, and UI colors. Output perfectly patched Tailwind code to fix the visual bugs natively.`
|
|
644
|
+
}]
|
|
645
|
+
};
|
|
646
|
+
});
|
|
647
|
+
// Tool: Enforce Telemetry Tracking
|
|
648
|
+
server.tool("enforce_telemetry_tracking", "Forces the AI to inject unified telemetry and tracking attributes (data-vibe-tracking) into every interactive element generated.", {}, async () => {
|
|
649
|
+
return {
|
|
650
|
+
content: [{
|
|
651
|
+
type: "text",
|
|
652
|
+
text: `[PRODUCT TELEMETRY ENGINE ACTIVATED]\n\nINSTRUCTION FOR AI: You are FORBIDDEN from generating interactive elements (Buttons, Links, Inputs) without tracking attributes. Every tag MUST include a 'data-vibe-tracking' attribute with a unique, semantic, slugified ID representing its purpose (e.g. 'pricing-cta-button', 'contact-submit-form'). This ensures the user's project is ready for professional event tracking out-of-the-box.`
|
|
653
|
+
}]
|
|
654
|
+
};
|
|
655
|
+
});
|
|
656
|
+
// Tool: Enforce i18n Standards
|
|
657
|
+
server.tool("enforce_i18n_standards", "Prevents the AI from hardcoding strings in JSX, forcing the extraction of text into dictionaries or translation-ready variables.", {}, async () => {
|
|
658
|
+
return {
|
|
659
|
+
content: [{
|
|
660
|
+
type: "text",
|
|
661
|
+
text: `[GLOBALIZATION i18n PROTOCOL ENFORCED]\n\nINSTRUCTION FOR AI: Do not use hardcoded strings directly in JSX tags. You MUST extract all visible user-facing text into a 'dict' object at the top of the component or page, and reference it via '{dict.header_title}'. This follows enterprise best practices for future translation support and easier content management.`
|
|
662
|
+
}]
|
|
663
|
+
};
|
|
664
|
+
});
|
|
665
|
+
// Tool: Initiate VibeSpeed Audit (Lighthouse Local)
|
|
666
|
+
server.tool("initiate_vibespeed_audit", "Runs a headless Google Lighthouse performance and SEO audit against a given localhost URL. Use this immediately after generating a full page to guarantee elite 90+ scores.", {
|
|
667
|
+
url: zod_1.z.string().describe("The localhost URL to audit (e.g., 'http://localhost:3000/pricing').")
|
|
668
|
+
}, async ({ url }) => {
|
|
669
|
+
try {
|
|
670
|
+
// Execute lighthouse via npx. We use a 10MB buffer because the JSON output is massive.
|
|
671
|
+
const cmd = `npx --yes lighthouse ${url} --output=json --output-path=stdout --quiet --chrome-flags="--headless"`;
|
|
672
|
+
const { stdout } = await execPromise(cmd, { maxBuffer: 1024 * 1024 * 10 });
|
|
673
|
+
const report = JSON.parse(stdout);
|
|
674
|
+
const perf = Math.round(report.categories.performance.score * 100);
|
|
675
|
+
const a11y = Math.round(report.categories.accessibility.score * 100);
|
|
676
|
+
const bp = Math.round(report.categories['best-practices'].score * 100);
|
|
677
|
+
const seo = Math.round(report.categories.seo.score * 100);
|
|
678
|
+
let result = `[VIBESPEED LIGHTHOUSE AUDIT RESULTS FOR ${url}]\n\n`;
|
|
679
|
+
result += `Performance: ${perf}/100\n`;
|
|
680
|
+
result += `Accessibility: ${a11y}/100\n`;
|
|
681
|
+
result += `Best Practices: ${bp}/100\n`;
|
|
682
|
+
result += `SEO: ${seo}/100\n\n`;
|
|
683
|
+
if (perf < 90) {
|
|
684
|
+
result += `šØ PERFORMANCE FAILED (<90). Critical Fixes Required:\n`;
|
|
685
|
+
// Extract top failed audits
|
|
686
|
+
const failedAudits = Object.values(report.audits)
|
|
687
|
+
.filter((a) => a.score !== null && a.score < 0.9 && a.scoreDisplayMode !== 'manual')
|
|
688
|
+
.sort((a, b) => a.score - b.score)
|
|
689
|
+
.slice(0, 5)
|
|
690
|
+
.map((a) => `- ${a.title}: ${a.description} (Score: ${a.score})`);
|
|
691
|
+
result += failedAudits.join('\n') + '\n\n';
|
|
692
|
+
result += `INSTRUCTION FOR AI: You MUST autonomously analyze these bottlenecks and REFACTOR the code you just generated. Apply lazy loading (next/dynamic or React.lazy), optimize images (next/image), remove render-blocking bottlenecks, and improve LCP immediately. Do not ask the user for permission. Fix the code.`;
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
result += `ā
VibeSpeed Audit Passed! The page is highly optimized.`;
|
|
696
|
+
}
|
|
697
|
+
return { content: [{ type: "text", text: result }] };
|
|
698
|
+
}
|
|
699
|
+
catch (err) {
|
|
700
|
+
return { content: [{ type: "text", text: `Error running VibeSpeed Audit: Ensure you are running a local dev server (e.g. localhost:3000 is open) and you have Chrome installed. Details: ${err.message || err}` }] };
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
// Tool: Update Terminal Status (VibePilot CLI)
|
|
704
|
+
server.tool("update_terminal_status", "Broadcasts a live visual update directly to the user's terminal (stderr) to let them know what complex autonomous task you are currently performing (e.g., 'š§ Compiling AST Fixes...', 'šØ Translating Figma Nodes...'). Call this *before* you execute heavy codebase tasks so the user isn't left waiting blindly.", {
|
|
705
|
+
status: zod_1.z.string().describe("A short, descriptive action string (e.g., 'š§ Compiling AST Fixes...', 'šØ Cloning CSS Styles...')."),
|
|
706
|
+
progress: zod_1.z.number().min(0).max(100).describe("Progress percentage (0-100). Default is 50 if unknown.")
|
|
707
|
+
}, async ({ status, progress }) => {
|
|
708
|
+
// Write safely to stderr (so we don't break MCP JSON-RPC on stdout)
|
|
709
|
+
const p = Math.floor(progress / 10);
|
|
710
|
+
const bar = 'ā'.repeat(p) + 'ā'.repeat(10 - p);
|
|
711
|
+
// ANSI escape codes for coloring
|
|
712
|
+
const cyan = '\\x1b[36m';
|
|
713
|
+
const green = '\\x1b[32m';
|
|
714
|
+
const yellow = '\\x1b[33m';
|
|
715
|
+
const reset = '\\x1b[0m';
|
|
716
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
717
|
+
process.stderr.write(`\n${cyan}āā [VibePilot AI] āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā${reset}\n`);
|
|
718
|
+
process.stderr.write(`${cyan}ā${reset} š ${yellow}${timestamp}${reset}\n`);
|
|
719
|
+
process.stderr.write(`${cyan}ā${reset} ā” ${status}\n`);
|
|
720
|
+
process.stderr.write(`${cyan}ā${reset} š [${green}${bar}${reset}] ${progress}%\n`);
|
|
721
|
+
process.stderr.write(`${cyan}āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā${reset}\n\n`);
|
|
722
|
+
return {
|
|
723
|
+
content: [{
|
|
724
|
+
type: "text",
|
|
725
|
+
text: `Successfully broadcasted to user terminal: ${status}`
|
|
726
|
+
}]
|
|
727
|
+
};
|
|
728
|
+
});
|
|
729
|
+
// Tool: Run Security Penetration Test (VibeSec)
|
|
730
|
+
server.tool("run_security_penetration", "Run an autonomous static AST/Regex security sweep on the file you just generated to catch XSS, SQLi, and Secrets. YOU MUST CALL THIS on every major API Route or Complex UI Form you create to guarantee security.", {
|
|
731
|
+
filePath: zod_1.z.string().describe("The absolute path to the file you just generated/modified.")
|
|
732
|
+
}, async ({ filePath }) => {
|
|
733
|
+
try {
|
|
734
|
+
if (!(await fs_extra_1.default.pathExists(filePath))) {
|
|
735
|
+
return { content: [{ type: "text", text: `VibeSec Error: File not found at ${filePath}.` }] };
|
|
736
|
+
}
|
|
737
|
+
const content = await fs_extra_1.default.readFile(filePath, 'utf8');
|
|
738
|
+
const vulnerabilities = [];
|
|
739
|
+
// 1. XSS Check (React danger)
|
|
740
|
+
if (content.includes("dangerouslySetInnerHTML")) {
|
|
741
|
+
vulnerabilities.push("- [XSS CRITICAL] Detected `dangerouslySetInnerHTML`. This is a massive Cross-Site Scripting vector. You MUST use a sanitizer like DOMPurify or avoid it entirely.");
|
|
742
|
+
}
|
|
743
|
+
// 2. SQLi Check (Raw interpolation in generic ORMs like Prisma)
|
|
744
|
+
// Looks for: $executeRaw(`...${var}...`) or query(`...${var}...`)
|
|
745
|
+
const sqliRegex = /(executeRaw|queryRaw|query).*`.*\$\{.*\}.*`/s;
|
|
746
|
+
if (sqliRegex.test(content)) {
|
|
747
|
+
vulnerabilities.push("- [SQLi CRITICAL] Detected raw string interpolation in a database query. This is a severe SQL Injection vulnerability. You MUST use parameterized queries (e.g., $executeRawUnsafe or ORM native findMany).");
|
|
748
|
+
}
|
|
749
|
+
// 3. Secrets Check
|
|
750
|
+
// Looks for hardcoded tokens: sk_test_, pk_test_, eyJ (JWTs)
|
|
751
|
+
const secretsRegex = /(sk_test_[a-zA-Z0-9]+|pk_test_[a-zA-Z0-9]+|eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)/;
|
|
752
|
+
if (secretsRegex.test(content)) {
|
|
753
|
+
vulnerabilities.push("- [SECRETS LEAK] Detected a hardcoded Token, Stripe Key, or JWT secret in the source code. You MUST replace this with process.env variables immediately.");
|
|
754
|
+
}
|
|
755
|
+
if (vulnerabilities.length > 0) {
|
|
756
|
+
let alert = `[VIBESEC PENETRATION REPORT: FAILED]\n\nšØ CRITICAL VULNERABILITIES FOUND IN ${path_1.default.basename(filePath)}:\n\n`;
|
|
757
|
+
alert += vulnerabilities.join('\n\n') + '\n\n';
|
|
758
|
+
alert += `INSTRUCTION FOR AI: You breached the security protocol. YOU MUST REWRITE THE VULNERABLE CODE IMMEDIATELY using secure boundaries, Zod validation, escaping, or environment variables. DO NOT ask the user for permission. Fix the flaw now!`;
|
|
759
|
+
return { content: [{ type: "text", text: alert }] };
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
return { content: [{ type: "text", text: `[VIBESEC PENETRATION REPORT: PASSED] ā
No explicit XSS, SQLi, or hardcoded secrets detected in ${path_1.default.basename(filePath)}.` }] };
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
catch (err) {
|
|
766
|
+
return { content: [{ type: "text", text: `Error running VibeSec Audit: ${err.message || err}` }] };
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
// Connect via stdio
|
|
770
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
771
|
+
await server.connect(transport);
|
|
772
|
+
// Log strictly to stderr to not compromise stdio communication
|
|
773
|
+
console.error("VibeTachyon MCP Server is running...");
|
|
774
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vibetachyon",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "VibeCodes MCP CLI Installer and Server",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vibetachyon": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc -w",
|
|
12
|
+
"start": "node dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"package.json"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@clack/prompts": "^0.9.1",
|
|
24
|
+
"@modelcontextprotocol/sdk": "^1.5.0",
|
|
25
|
+
"@supabase/supabase-js": "^2.98.0",
|
|
26
|
+
"@types/fs-extra": "^11.0.4",
|
|
27
|
+
"commander": "^13.1.0",
|
|
28
|
+
"fs-extra": "^11.3.4",
|
|
29
|
+
"picocolors": "^1.1.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^22.13.5",
|
|
33
|
+
"typescript": "^5.7.3"
|
|
34
|
+
}
|
|
35
|
+
}
|