@codebakers/cli 1.2.1 → 1.3.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/dist/commands/install-hook.js +42 -68
- package/dist/commands/scaffold.d.ts +4 -0
- package/dist/commands/scaffold.js +205 -0
- package/dist/index.js +6 -0
- package/dist/mcp/server.js +211 -0
- package/dist/templates/nextjs-supabase.d.ts +81 -0
- package/dist/templates/nextjs-supabase.js +356 -0
- package/package.json +1 -1
- package/src/commands/install-hook.ts +44 -68
- package/src/commands/scaffold.ts +196 -0
- package/src/index.ts +7 -0
- package/src/mcp/server.ts +244 -0
- package/src/templates/nextjs-supabase.ts +371 -0
|
@@ -11,70 +11,38 @@ const ora_1 = __importDefault(require("ora"));
|
|
|
11
11
|
const fs_1 = require("fs");
|
|
12
12
|
const path_1 = require("path");
|
|
13
13
|
const os_1 = require("os");
|
|
14
|
-
// Enhanced hook with
|
|
14
|
+
// Enhanced hook with visible feedback and concise instructions
|
|
15
15
|
const HOOK_TEMPLATE = {
|
|
16
16
|
hooks: {
|
|
17
17
|
UserPromptSubmit: [
|
|
18
18
|
{
|
|
19
19
|
type: "command",
|
|
20
|
-
command: `echo '
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- CLAUDE.md → Router & module instructions
|
|
29
|
-
- PRD.md → What we are building (requirements!)
|
|
30
|
-
- PROJECT-CONTEXT.md → Codebase knowledge
|
|
31
|
-
- PROJECT-STATE.md → What is in progress
|
|
32
|
-
- DECISIONS.md → Past architectural choices
|
|
33
|
-
|
|
34
|
-
▸ PHASE 2: PRE-FLIGHT CHECK (before writing code)
|
|
35
|
-
□ What existing code does this touch?
|
|
36
|
-
□ Is similar code in the codebase? (copy that pattern!)
|
|
37
|
-
□ Whats the data model?
|
|
38
|
-
□ What are the error cases?
|
|
39
|
-
□ Is someone else working on this? (check In Progress)
|
|
40
|
-
|
|
41
|
-
If PROJECT-CONTEXT.md is empty/stale, SCAN PROJECT FIRST:
|
|
42
|
-
- Read package.json
|
|
43
|
-
- Check file structure
|
|
44
|
-
- Find existing patterns
|
|
45
|
-
- Update PROJECT-CONTEXT.md
|
|
46
|
-
|
|
47
|
-
▸ PHASE 3: ACKNOWLEDGE & EXECUTE
|
|
48
|
-
Output: 📋 CodeBakers | [Type] | Modules: [list]
|
|
49
|
-
Then: Follow patterns from .claude/ folder EXACTLY
|
|
50
|
-
|
|
51
|
-
▸ PHASE 4: SELF-REVIEW (before saying done)
|
|
52
|
-
□ TypeScript compiles? (npx tsc --noEmit)
|
|
53
|
-
□ Imports resolve?
|
|
54
|
-
□ Error handling exists?
|
|
55
|
-
□ Matches existing patterns?
|
|
56
|
-
□ Tests written?
|
|
57
|
-
|
|
58
|
-
If ANY fails → FIX before responding
|
|
59
|
-
|
|
60
|
-
▸ PHASE 5: UPDATE STATE
|
|
61
|
-
- Update PROJECT-STATE.md (move to Completed)
|
|
62
|
-
- Add to DECISIONS.md if architectural choice made
|
|
63
|
-
|
|
64
|
-
════════════════════════════════════════════════════════════════
|
|
65
|
-
🔄 MULTI-AGENT MODE
|
|
66
|
-
════════════════════════════════════════════════════════════════
|
|
67
|
-
- Check PROJECT-STATE.md "In Progress" - dont duplicate work
|
|
68
|
-
- Add YOUR task to In Progress when starting
|
|
69
|
-
- If conflict → STOP and ask user
|
|
70
|
-
|
|
71
|
-
════════════════════════════════════════════════════════════════
|
|
72
|
-
💡 REMEMBER: Check existing code FIRST. Copy patterns. Validate.
|
|
73
|
-
════════════════════════════════════════════════════════════════'`
|
|
20
|
+
command: `echo '[CodeBakers] Loading project context...'`
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
PostToolUse: [
|
|
24
|
+
{
|
|
25
|
+
type: "command",
|
|
26
|
+
matcher: "Write|Edit",
|
|
27
|
+
command: `echo '[CodeBakers] Code written - remember to self-review before marking done'`
|
|
74
28
|
}
|
|
75
29
|
]
|
|
76
30
|
}
|
|
77
31
|
};
|
|
32
|
+
// Instructions that get injected into the system prompt
|
|
33
|
+
const CODEBAKERS_INSTRUCTIONS = `
|
|
34
|
+
<user-prompt-submit-hook>
|
|
35
|
+
[CodeBakers] Active - Follow these steps for EVERY request:
|
|
36
|
+
|
|
37
|
+
1. CONTEXT: Read CLAUDE.md, PROJECT-CONTEXT.md, PROJECT-STATE.md
|
|
38
|
+
2. PRE-FLIGHT: Check existing code patterns before writing new code
|
|
39
|
+
3. EXECUTE: Use patterns from .claude/ folder
|
|
40
|
+
4. SELF-REVIEW: Verify TypeScript compiles, imports resolve, error handling exists
|
|
41
|
+
5. UPDATE: Mark tasks complete in PROJECT-STATE.md
|
|
42
|
+
|
|
43
|
+
Output format: "[CodeBakers] Building [feature] using [patterns]"
|
|
44
|
+
</user-prompt-submit-hook>
|
|
45
|
+
`;
|
|
78
46
|
/**
|
|
79
47
|
* Install the CodeBakers hook into ~/.claude/settings.json
|
|
80
48
|
*/
|
|
@@ -117,20 +85,21 @@ async function installHook() {
|
|
|
117
85
|
console.log(chalk_1.default.yellow(' It will be replaced with the CodeBakers hook.\n'));
|
|
118
86
|
}
|
|
119
87
|
}
|
|
120
|
-
// Merge
|
|
88
|
+
// Merge hooks into settings
|
|
121
89
|
settings.hooks = settings.hooks || {};
|
|
122
90
|
settings.hooks.UserPromptSubmit = HOOK_TEMPLATE.hooks.UserPromptSubmit;
|
|
91
|
+
settings.hooks.PostToolUse = HOOK_TEMPLATE.hooks.PostToolUse;
|
|
123
92
|
// Write back
|
|
124
93
|
(0, fs_1.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2));
|
|
125
94
|
spinner.succeed('Hook installed successfully!');
|
|
126
|
-
console.log(chalk_1.default.white('\n
|
|
127
|
-
console.log(chalk_1.default.
|
|
128
|
-
console.log(chalk_1.default.
|
|
129
|
-
console.log(chalk_1.default.
|
|
130
|
-
console.log(chalk_1.default.gray(' ✓
|
|
131
|
-
console.log(chalk_1.default.gray(' ✓
|
|
132
|
-
console.log(chalk_1.default.gray(' ✓
|
|
133
|
-
console.log(chalk_1.default.gray(' ✓
|
|
95
|
+
console.log(chalk_1.default.white('\n You\'ll see [CodeBakers] feedback in terminal:\n'));
|
|
96
|
+
console.log(chalk_1.default.cyan(' [CodeBakers] Loading project context...'));
|
|
97
|
+
console.log(chalk_1.default.cyan(' [CodeBakers] Code written - remember to self-review\n'));
|
|
98
|
+
console.log(chalk_1.default.white(' What happens automatically:\n'));
|
|
99
|
+
console.log(chalk_1.default.gray(' ✓ Loads project context before every response'));
|
|
100
|
+
console.log(chalk_1.default.gray(' ✓ Pre-flight checks before writing code'));
|
|
101
|
+
console.log(chalk_1.default.gray(' ✓ Self-review reminders after code changes'));
|
|
102
|
+
console.log(chalk_1.default.gray(' ✓ Pattern-based development from .claude/ folder\n'));
|
|
134
103
|
console.log(chalk_1.default.yellow(' ⚠️ Restart Claude Code for changes to take effect.\n'));
|
|
135
104
|
}
|
|
136
105
|
catch (error) {
|
|
@@ -153,12 +122,17 @@ async function uninstallHook() {
|
|
|
153
122
|
return;
|
|
154
123
|
}
|
|
155
124
|
const settings = JSON.parse((0, fs_1.readFileSync)(settingsPath, 'utf-8'));
|
|
156
|
-
if (!settings.hooks?.UserPromptSubmit) {
|
|
157
|
-
spinner.info('No
|
|
125
|
+
if (!settings.hooks?.UserPromptSubmit && !settings.hooks?.PostToolUse) {
|
|
126
|
+
spinner.info('No CodeBakers hooks found. Nothing to remove.');
|
|
158
127
|
return;
|
|
159
128
|
}
|
|
160
|
-
// Remove
|
|
161
|
-
|
|
129
|
+
// Remove both hooks
|
|
130
|
+
if (settings.hooks?.UserPromptSubmit) {
|
|
131
|
+
delete settings.hooks.UserPromptSubmit;
|
|
132
|
+
}
|
|
133
|
+
if (settings.hooks?.PostToolUse) {
|
|
134
|
+
delete settings.hooks.PostToolUse;
|
|
135
|
+
}
|
|
162
136
|
// Clean up empty hooks object
|
|
163
137
|
if (Object.keys(settings.hooks).length === 0) {
|
|
164
138
|
delete settings.hooks;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.scaffold = scaffold;
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
+
const ora_1 = __importDefault(require("ora"));
|
|
42
|
+
const readline_1 = require("readline");
|
|
43
|
+
const fs_1 = require("fs");
|
|
44
|
+
const path_1 = require("path");
|
|
45
|
+
const child_process_1 = require("child_process");
|
|
46
|
+
const templates = __importStar(require("../templates/nextjs-supabase.js"));
|
|
47
|
+
async function prompt(question) {
|
|
48
|
+
const rl = (0, readline_1.createInterface)({
|
|
49
|
+
input: process.stdin,
|
|
50
|
+
output: process.stdout,
|
|
51
|
+
});
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
rl.question(question, (answer) => {
|
|
54
|
+
rl.close();
|
|
55
|
+
resolve(answer.trim());
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async function confirm(question) {
|
|
60
|
+
const answer = await prompt(`${question} (Y/n): `);
|
|
61
|
+
return answer.toLowerCase() !== 'n';
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Scaffold a new project with full structure
|
|
65
|
+
*/
|
|
66
|
+
async function scaffold() {
|
|
67
|
+
console.log(chalk_1.default.blue(`
|
|
68
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
69
|
+
║ ║
|
|
70
|
+
║ ${chalk_1.default.bold('CodeBakers Project Scaffolding')} ║
|
|
71
|
+
║ ║
|
|
72
|
+
║ Create a production-ready project in seconds ║
|
|
73
|
+
║ ║
|
|
74
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
75
|
+
`));
|
|
76
|
+
const cwd = process.cwd();
|
|
77
|
+
const files = (0, fs_1.readdirSync)(cwd);
|
|
78
|
+
const hasFiles = files.filter(f => !f.startsWith('.')).length > 0;
|
|
79
|
+
if (hasFiles) {
|
|
80
|
+
console.log(chalk_1.default.yellow(' ⚠️ This directory is not empty.\n'));
|
|
81
|
+
const proceed = await confirm(' Continue anyway? (Existing files may be overwritten)');
|
|
82
|
+
if (!proceed) {
|
|
83
|
+
console.log(chalk_1.default.gray('\n Run this command in an empty directory.\n'));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Select stack
|
|
88
|
+
console.log(chalk_1.default.white('\n Select your stack:\n'));
|
|
89
|
+
console.log(chalk_1.default.gray(' 1. ') + chalk_1.default.cyan('Next.js + Supabase + Drizzle') + chalk_1.default.gray(' (Recommended)'));
|
|
90
|
+
console.log(chalk_1.default.gray(' 2. ') + chalk_1.default.cyan('Next.js + Prisma') + chalk_1.default.gray(' (Coming soon)'));
|
|
91
|
+
console.log(chalk_1.default.gray(' 3. ') + chalk_1.default.cyan('Express API') + chalk_1.default.gray(' (Coming soon)\n'));
|
|
92
|
+
let stackChoice = '';
|
|
93
|
+
while (!['1', '2', '3'].includes(stackChoice)) {
|
|
94
|
+
stackChoice = await prompt(' Enter 1, 2, or 3: ');
|
|
95
|
+
}
|
|
96
|
+
if (stackChoice !== '1') {
|
|
97
|
+
console.log(chalk_1.default.yellow('\n That stack is coming soon! Using Next.js + Supabase + Drizzle.\n'));
|
|
98
|
+
stackChoice = '1';
|
|
99
|
+
}
|
|
100
|
+
// Get project name
|
|
101
|
+
const defaultName = cwd.split(/[\\/]/).pop() || 'my-project';
|
|
102
|
+
const projectName = await prompt(` Project name (${defaultName}): `) || defaultName;
|
|
103
|
+
console.log(chalk_1.default.green(`\n Creating ${projectName} with Next.js + Supabase + Drizzle...\n`));
|
|
104
|
+
// Create project structure
|
|
105
|
+
const spinner = (0, ora_1.default)(' Creating project structure...').start();
|
|
106
|
+
try {
|
|
107
|
+
// Create directories
|
|
108
|
+
const dirs = [
|
|
109
|
+
'src/app',
|
|
110
|
+
'src/components',
|
|
111
|
+
'src/lib/supabase',
|
|
112
|
+
'src/db',
|
|
113
|
+
'src/db/migrations',
|
|
114
|
+
'src/services',
|
|
115
|
+
'src/types',
|
|
116
|
+
'public',
|
|
117
|
+
];
|
|
118
|
+
for (const dir of dirs) {
|
|
119
|
+
const dirPath = (0, path_1.join)(cwd, dir);
|
|
120
|
+
if (!(0, fs_1.existsSync)(dirPath)) {
|
|
121
|
+
(0, fs_1.mkdirSync)(dirPath, { recursive: true });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
spinner.text = ' Writing configuration files...';
|
|
125
|
+
// Write package.json
|
|
126
|
+
const packageJson = { ...templates.PACKAGE_JSON, name: projectName };
|
|
127
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'package.json'), JSON.stringify(packageJson, null, 2));
|
|
128
|
+
// Write .env.example
|
|
129
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, '.env.example'), templates.ENV_EXAMPLE);
|
|
130
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, '.env.local'), templates.ENV_EXAMPLE);
|
|
131
|
+
// Write config files
|
|
132
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'drizzle.config.ts'), templates.DRIZZLE_CONFIG);
|
|
133
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'tailwind.config.ts'), templates.TAILWIND_CONFIG);
|
|
134
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'postcss.config.mjs'), templates.POSTCSS_CONFIG);
|
|
135
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'tsconfig.json'), JSON.stringify(templates.TSCONFIG, null, 2));
|
|
136
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'next.config.ts'), templates.NEXT_CONFIG);
|
|
137
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, '.gitignore'), templates.GITIGNORE);
|
|
138
|
+
spinner.text = ' Writing source files...';
|
|
139
|
+
// Write Supabase files
|
|
140
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'src/lib/supabase/server.ts'), templates.SUPABASE_SERVER);
|
|
141
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'src/lib/supabase/client.ts'), templates.SUPABASE_CLIENT);
|
|
142
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'src/lib/supabase/middleware.ts'), templates.SUPABASE_MIDDLEWARE);
|
|
143
|
+
// Write middleware
|
|
144
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'middleware.ts'), templates.MIDDLEWARE);
|
|
145
|
+
// Write database files
|
|
146
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'src/db/schema.ts'), templates.DB_SCHEMA);
|
|
147
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'src/db/index.ts'), templates.DB_INDEX);
|
|
148
|
+
// Write app files
|
|
149
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'src/app/globals.css'), templates.GLOBALS_CSS);
|
|
150
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'src/app/layout.tsx'), templates.LAYOUT_TSX);
|
|
151
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'src/app/page.tsx'), templates.PAGE_TSX);
|
|
152
|
+
// Write utils
|
|
153
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'src/lib/utils.ts'), templates.UTILS_CN);
|
|
154
|
+
spinner.succeed('Project structure created!');
|
|
155
|
+
// Ask about installing dependencies
|
|
156
|
+
console.log('');
|
|
157
|
+
const installDeps = await confirm(' Install dependencies with npm?');
|
|
158
|
+
if (installDeps) {
|
|
159
|
+
const installSpinner = (0, ora_1.default)(' Installing dependencies (this may take a minute)...').start();
|
|
160
|
+
try {
|
|
161
|
+
(0, child_process_1.execSync)('npm install', { cwd, stdio: 'pipe' });
|
|
162
|
+
installSpinner.succeed('Dependencies installed!');
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
installSpinner.warn('Could not install dependencies automatically');
|
|
166
|
+
console.log(chalk_1.default.gray(' Run `npm install` manually.\n'));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Success message
|
|
170
|
+
console.log(chalk_1.default.green(`
|
|
171
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
172
|
+
║ ║
|
|
173
|
+
║ ${chalk_1.default.bold('✓ Project scaffolded successfully!')} ║
|
|
174
|
+
║ ║
|
|
175
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
176
|
+
`));
|
|
177
|
+
console.log(chalk_1.default.white(' Project structure:\n'));
|
|
178
|
+
console.log(chalk_1.default.gray(' src/'));
|
|
179
|
+
console.log(chalk_1.default.gray(' ├── app/ ') + chalk_1.default.cyan('← Pages & layouts'));
|
|
180
|
+
console.log(chalk_1.default.gray(' ├── components/ ') + chalk_1.default.cyan('← React components'));
|
|
181
|
+
console.log(chalk_1.default.gray(' ├── lib/ ') + chalk_1.default.cyan('← Utilities & clients'));
|
|
182
|
+
console.log(chalk_1.default.gray(' │ └── supabase/ ') + chalk_1.default.cyan('← Supabase clients (ready!)'));
|
|
183
|
+
console.log(chalk_1.default.gray(' ├── db/ ') + chalk_1.default.cyan('← Database schema & queries'));
|
|
184
|
+
console.log(chalk_1.default.gray(' ├── services/ ') + chalk_1.default.cyan('← Business logic'));
|
|
185
|
+
console.log(chalk_1.default.gray(' └── types/ ') + chalk_1.default.cyan('← TypeScript types'));
|
|
186
|
+
console.log('');
|
|
187
|
+
console.log(chalk_1.default.white(' Next steps:\n'));
|
|
188
|
+
console.log(chalk_1.default.cyan(' 1. ') + chalk_1.default.gray('Update .env.local with your Supabase credentials'));
|
|
189
|
+
console.log(chalk_1.default.cyan(' 2. ') + chalk_1.default.gray('Run `npm run dev` to start development'));
|
|
190
|
+
console.log(chalk_1.default.cyan(' 3. ') + chalk_1.default.gray('Run `codebakers init` to add CodeBakers patterns'));
|
|
191
|
+
console.log(chalk_1.default.cyan(' 4. ') + chalk_1.default.gray('Start building with AI assistance!\n'));
|
|
192
|
+
console.log(chalk_1.default.white(' Supabase setup:\n'));
|
|
193
|
+
console.log(chalk_1.default.gray(' 1. Create a project at https://supabase.com'));
|
|
194
|
+
console.log(chalk_1.default.gray(' 2. Go to Settings → API'));
|
|
195
|
+
console.log(chalk_1.default.gray(' 3. Copy URL and anon key to .env.local'));
|
|
196
|
+
console.log(chalk_1.default.gray(' 4. Go to Settings → Database → Connection string'));
|
|
197
|
+
console.log(chalk_1.default.gray(' 5. Copy DATABASE_URL to .env.local\n'));
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
spinner.fail('Project scaffolding failed');
|
|
201
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
202
|
+
console.log(chalk_1.default.red(`\n Error: ${message}\n`));
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ const init_js_1 = require("./commands/init.js");
|
|
|
12
12
|
const serve_js_1 = require("./commands/serve.js");
|
|
13
13
|
const mcp_config_js_1 = require("./commands/mcp-config.js");
|
|
14
14
|
const setup_js_1 = require("./commands/setup.js");
|
|
15
|
+
const scaffold_js_1 = require("./commands/scaffold.js");
|
|
15
16
|
const program = new commander_1.Command();
|
|
16
17
|
program
|
|
17
18
|
.name('codebakers')
|
|
@@ -26,6 +27,11 @@ program
|
|
|
26
27
|
.command('init')
|
|
27
28
|
.description('Interactive project setup wizard')
|
|
28
29
|
.action(init_js_1.init);
|
|
30
|
+
program
|
|
31
|
+
.command('scaffold')
|
|
32
|
+
.alias('new')
|
|
33
|
+
.description('Create a new project with full stack scaffolding (Next.js + Supabase + Drizzle)')
|
|
34
|
+
.action(scaffold_js_1.scaffold);
|
|
29
35
|
program
|
|
30
36
|
.command('login')
|
|
31
37
|
.description('Login with your API key')
|
package/dist/mcp/server.js
CHANGED
|
@@ -325,6 +325,38 @@ class CodeBakersServer {
|
|
|
325
325
|
required: ['patterns'],
|
|
326
326
|
},
|
|
327
327
|
},
|
|
328
|
+
{
|
|
329
|
+
name: 'search_patterns',
|
|
330
|
+
description: 'Search CodeBakers patterns by keyword or topic. Returns relevant code snippets without reading entire files. Use this when you need specific guidance like "supabase auth setup", "optimistic updates", "soft delete", "form validation".',
|
|
331
|
+
inputSchema: {
|
|
332
|
+
type: 'object',
|
|
333
|
+
properties: {
|
|
334
|
+
query: {
|
|
335
|
+
type: 'string',
|
|
336
|
+
description: 'Search query (e.g., "supabase auth", "stripe checkout", "zod validation", "loading states")',
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
required: ['query'],
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: 'get_pattern_section',
|
|
344
|
+
description: 'Get a specific section from a pattern file instead of the whole file. Much faster than get_pattern for targeted lookups.',
|
|
345
|
+
inputSchema: {
|
|
346
|
+
type: 'object',
|
|
347
|
+
properties: {
|
|
348
|
+
pattern: {
|
|
349
|
+
type: 'string',
|
|
350
|
+
description: 'Pattern name (e.g., "02-auth", "03-api")',
|
|
351
|
+
},
|
|
352
|
+
section: {
|
|
353
|
+
type: 'string',
|
|
354
|
+
description: 'Section name or keyword to find within the pattern (e.g., "OAuth", "rate limiting", "error handling")',
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
required: ['pattern', 'section'],
|
|
358
|
+
},
|
|
359
|
+
},
|
|
328
360
|
],
|
|
329
361
|
}));
|
|
330
362
|
// Handle tool calls
|
|
@@ -342,6 +374,10 @@ class CodeBakersServer {
|
|
|
342
374
|
return this.handleListPatterns();
|
|
343
375
|
case 'get_patterns':
|
|
344
376
|
return this.handleGetPatterns(args);
|
|
377
|
+
case 'search_patterns':
|
|
378
|
+
return this.handleSearchPatterns(args);
|
|
379
|
+
case 'get_pattern_section':
|
|
380
|
+
return this.handleGetPatternSection(args);
|
|
345
381
|
default:
|
|
346
382
|
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
347
383
|
}
|
|
@@ -522,6 +558,181 @@ Show the user what their simple request was expanded into, then proceed with the
|
|
|
522
558
|
}
|
|
523
559
|
return response.json();
|
|
524
560
|
}
|
|
561
|
+
async handleSearchPatterns(args) {
|
|
562
|
+
const { query } = args;
|
|
563
|
+
// Call API endpoint for semantic search
|
|
564
|
+
const response = await fetch(`${this.apiUrl}/api/patterns/search`, {
|
|
565
|
+
method: 'POST',
|
|
566
|
+
headers: {
|
|
567
|
+
'Content-Type': 'application/json',
|
|
568
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
569
|
+
},
|
|
570
|
+
body: JSON.stringify({ query }),
|
|
571
|
+
});
|
|
572
|
+
if (!response.ok) {
|
|
573
|
+
// Fallback: If search endpoint doesn't exist, do client-side search
|
|
574
|
+
return this.fallbackSearch(query);
|
|
575
|
+
}
|
|
576
|
+
const data = await response.json();
|
|
577
|
+
const results = data.results
|
|
578
|
+
.map((r) => `### ${r.pattern} - ${r.section}\n\n\`\`\`typescript\n${r.content}\n\`\`\`\n\nRelevance: ${Math.round(r.relevance * 100)}%`)
|
|
579
|
+
.join('\n\n---\n\n');
|
|
580
|
+
return {
|
|
581
|
+
content: [
|
|
582
|
+
{
|
|
583
|
+
type: 'text',
|
|
584
|
+
text: `# Search Results for "${query}"\n\n${results || 'No results found. Try a different query.'}`,
|
|
585
|
+
},
|
|
586
|
+
],
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
async fallbackSearch(query) {
|
|
590
|
+
// Keyword-based fallback if API search not available
|
|
591
|
+
const keywordPatternMap = {
|
|
592
|
+
'auth': ['02-auth'],
|
|
593
|
+
'login': ['02-auth'],
|
|
594
|
+
'oauth': ['02-auth'],
|
|
595
|
+
'supabase': ['02-auth', '01-database'],
|
|
596
|
+
'database': ['01-database'],
|
|
597
|
+
'drizzle': ['01-database'],
|
|
598
|
+
'schema': ['01-database'],
|
|
599
|
+
'api': ['03-api'],
|
|
600
|
+
'route': ['03-api'],
|
|
601
|
+
'validation': ['03-api', '04-frontend'],
|
|
602
|
+
'zod': ['03-api', '04-frontend'],
|
|
603
|
+
'frontend': ['04-frontend'],
|
|
604
|
+
'form': ['04-frontend'],
|
|
605
|
+
'react': ['04-frontend'],
|
|
606
|
+
'component': ['04-frontend'],
|
|
607
|
+
'stripe': ['05-payments'],
|
|
608
|
+
'payment': ['05-payments'],
|
|
609
|
+
'checkout': ['05-payments'],
|
|
610
|
+
'subscription': ['05-payments'],
|
|
611
|
+
'email': ['06-integrations'],
|
|
612
|
+
'webhook': ['06-integrations'],
|
|
613
|
+
'cache': ['07-performance'],
|
|
614
|
+
'test': ['08-testing'],
|
|
615
|
+
'playwright': ['08-testing'],
|
|
616
|
+
'design': ['09-design'],
|
|
617
|
+
'ui': ['09-design'],
|
|
618
|
+
'accessibility': ['09-design'],
|
|
619
|
+
'websocket': ['11-realtime'],
|
|
620
|
+
'realtime': ['11-realtime'],
|
|
621
|
+
'notification': ['11-realtime'],
|
|
622
|
+
'saas': ['12-saas'],
|
|
623
|
+
'tenant': ['12-saas'],
|
|
624
|
+
'mobile': ['13-mobile'],
|
|
625
|
+
'expo': ['13-mobile'],
|
|
626
|
+
'ai': ['14-ai'],
|
|
627
|
+
'openai': ['14-ai'],
|
|
628
|
+
'embedding': ['14-ai'],
|
|
629
|
+
'analytics': ['26-analytics'],
|
|
630
|
+
'search': ['27-search'],
|
|
631
|
+
'animation': ['30-motion'],
|
|
632
|
+
'framer': ['30-motion'],
|
|
633
|
+
};
|
|
634
|
+
const lowerQuery = query.toLowerCase();
|
|
635
|
+
const matchedPatterns = new Set();
|
|
636
|
+
for (const [keyword, patterns] of Object.entries(keywordPatternMap)) {
|
|
637
|
+
if (lowerQuery.includes(keyword)) {
|
|
638
|
+
patterns.forEach(p => matchedPatterns.add(p));
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (matchedPatterns.size === 0) {
|
|
642
|
+
return {
|
|
643
|
+
content: [
|
|
644
|
+
{
|
|
645
|
+
type: 'text',
|
|
646
|
+
text: `# No patterns found for "${query}"\n\nTry:\n- "auth" for authentication patterns\n- "api" for API route patterns\n- "form" for frontend form patterns\n- "stripe" for payment patterns\n\nOr use \`list_patterns\` to see all available patterns.`,
|
|
647
|
+
},
|
|
648
|
+
],
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
const patterns = Array.from(matchedPatterns).slice(0, 3);
|
|
652
|
+
const result = await this.fetchPatterns(patterns);
|
|
653
|
+
const content = Object.entries(result.patterns || {})
|
|
654
|
+
.map(([name, text]) => `## ${name}\n\n${text}`)
|
|
655
|
+
.join('\n\n---\n\n');
|
|
656
|
+
return {
|
|
657
|
+
content: [
|
|
658
|
+
{
|
|
659
|
+
type: 'text',
|
|
660
|
+
text: `# Patterns matching "${query}"\n\nFound in: ${patterns.join(', ')}\n\n${content}`,
|
|
661
|
+
},
|
|
662
|
+
],
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
async handleGetPatternSection(args) {
|
|
666
|
+
const { pattern, section } = args;
|
|
667
|
+
// Fetch the full pattern first
|
|
668
|
+
const result = await this.fetchPatterns([pattern]);
|
|
669
|
+
if (!result.patterns || !result.patterns[pattern]) {
|
|
670
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, `Pattern "${pattern}" not found. Use list_patterns to see available patterns.`);
|
|
671
|
+
}
|
|
672
|
+
const fullContent = result.patterns[pattern];
|
|
673
|
+
// Find the section (case-insensitive search for headers or content)
|
|
674
|
+
const sectionLower = section.toLowerCase();
|
|
675
|
+
const lines = fullContent.split('\n');
|
|
676
|
+
const sections = [];
|
|
677
|
+
let currentSection = '';
|
|
678
|
+
let currentContent = [];
|
|
679
|
+
let capturing = false;
|
|
680
|
+
let relevanceScore = 0;
|
|
681
|
+
for (let i = 0; i < lines.length; i++) {
|
|
682
|
+
const line = lines[i];
|
|
683
|
+
// Check if this is a header
|
|
684
|
+
if (line.match(/^#{1,3}\s/)) {
|
|
685
|
+
// Save previous section if we were capturing
|
|
686
|
+
if (capturing && currentContent.length > 0) {
|
|
687
|
+
sections.push(`### ${currentSection}\n\n${currentContent.join('\n')}`);
|
|
688
|
+
}
|
|
689
|
+
currentSection = line.replace(/^#+\s*/, '');
|
|
690
|
+
currentContent = [];
|
|
691
|
+
// Check if this section matches our query
|
|
692
|
+
if (currentSection.toLowerCase().includes(sectionLower)) {
|
|
693
|
+
capturing = true;
|
|
694
|
+
relevanceScore++;
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
capturing = false;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
else if (capturing) {
|
|
701
|
+
currentContent.push(line);
|
|
702
|
+
}
|
|
703
|
+
// Also check content for keyword matches
|
|
704
|
+
if (!capturing && line.toLowerCase().includes(sectionLower)) {
|
|
705
|
+
// Found keyword in content, capture surrounding context
|
|
706
|
+
const start = Math.max(0, i - 5);
|
|
707
|
+
const end = Math.min(lines.length, i + 20);
|
|
708
|
+
const context = lines.slice(start, end).join('\n');
|
|
709
|
+
sections.push(`### Found at line ${i + 1}\n\n${context}`);
|
|
710
|
+
relevanceScore++;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
// Capture last section if we were still capturing
|
|
714
|
+
if (capturing && currentContent.length > 0) {
|
|
715
|
+
sections.push(`### ${currentSection}\n\n${currentContent.join('\n')}`);
|
|
716
|
+
}
|
|
717
|
+
if (sections.length === 0) {
|
|
718
|
+
return {
|
|
719
|
+
content: [
|
|
720
|
+
{
|
|
721
|
+
type: 'text',
|
|
722
|
+
text: `# Section "${section}" not found in ${pattern}\n\nThe pattern exists but doesn't contain a section matching "${section}".\n\nTry:\n- A broader search term\n- \`get_pattern ${pattern}\` to see the full content\n- \`search_patterns ${section}\` to search across all patterns`,
|
|
723
|
+
},
|
|
724
|
+
],
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
return {
|
|
728
|
+
content: [
|
|
729
|
+
{
|
|
730
|
+
type: 'text',
|
|
731
|
+
text: `# ${pattern} - "${section}"\n\n${sections.slice(0, 5).join('\n\n---\n\n')}`,
|
|
732
|
+
},
|
|
733
|
+
],
|
|
734
|
+
};
|
|
735
|
+
}
|
|
525
736
|
async run() {
|
|
526
737
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
527
738
|
await this.server.connect(transport);
|