brains-cli 0.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.
@@ -0,0 +1,487 @@
1
+ 'use strict';
2
+
3
+ const inquirer = require('inquirer');
4
+ const chalk = require('chalk');
5
+ const ora = require('ora');
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+ const { getBrainById } = require('../registry');
9
+ const { showSuccess, showError, showInfo, showBox, ACCENT, DIM, SUCCESS, WARN } = require('../utils/ui');
10
+
11
+ const BRAINS_DIR = path.join(require('os').homedir(), '.brains');
12
+ const INSTALLED_FILE = path.join(BRAINS_DIR, 'installed.json');
13
+
14
+ function isInstalled(brainId) {
15
+ try {
16
+ const installed = JSON.parse(fs.readFileSync(INSTALLED_FILE, 'utf-8'));
17
+ return !!installed[brainId];
18
+ } catch (e) {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ function loadBrainConfig(brainId) {
24
+ const configPath = path.join(BRAINS_DIR, brainId, 'brain.json');
25
+ if (fs.existsSync(configPath)) {
26
+ return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
27
+ }
28
+ return null;
29
+ }
30
+
31
+ async function run(brainName, options) {
32
+ const brain = getBrainById(brainName);
33
+
34
+ if (!brain) {
35
+ showError(`Brain "${brainName}" not found.`);
36
+ showInfo(`Run ${ACCENT('brains explore')} to see available brains.`);
37
+ return;
38
+ }
39
+
40
+ // Check if installed
41
+ if (!isInstalled(brainName)) {
42
+ showInfo(`${brain.name} is not installed yet.`);
43
+ const { doInstall } = await inquirer.prompt([
44
+ {
45
+ type: 'confirm',
46
+ name: 'doInstall',
47
+ message: `Install ${brain.name} now?`,
48
+ default: true,
49
+ },
50
+ ]);
51
+
52
+ if (doInstall) {
53
+ const { install } = require('./install');
54
+ await install(brainName, {});
55
+ } else {
56
+ return;
57
+ }
58
+ }
59
+
60
+ const config = loadBrainConfig(brainName) || brain;
61
+
62
+ // Handle brain stacking
63
+ let stackedBrains = [];
64
+ if (options.with && options.with.length > 0) {
65
+ console.log('');
66
+ showInfo(`Stacking brains: ${chalk.bold(brainName)} + ${options.with.join(' + ')}`);
67
+
68
+ for (const extraBrain of options.with) {
69
+ const extra = getBrainById(extraBrain);
70
+ if (extra) {
71
+ stackedBrains.push(extra);
72
+ showInfo(` ${SUCCESS('+')} ${extra.name} loaded`);
73
+ } else {
74
+ showInfo(` ${WARN('⚠')} ${extraBrain} not found, skipping`);
75
+ }
76
+ }
77
+ }
78
+
79
+ // Show brain activation
80
+ console.log('');
81
+ const spinner = ora({
82
+ text: `Activating ${chalk.bold(brain.name)}...`,
83
+ color: 'magenta',
84
+ spinner: 'dots12',
85
+ }).start();
86
+ await new Promise((r) => setTimeout(r, 1200));
87
+ spinner.succeed(`${chalk.bold(brain.name)} is active`);
88
+
89
+ if (stackedBrains.length > 0) {
90
+ const stackSpinner = ora({
91
+ text: 'Merging stacked brain contexts...',
92
+ color: 'cyan',
93
+ spinner: 'dots12',
94
+ }).start();
95
+ await new Promise((r) => setTimeout(r, 800));
96
+ stackSpinner.succeed('Brain stack ready');
97
+ }
98
+
99
+ // Show session header
100
+ const categoryColors = {
101
+ builder: '#FF3366',
102
+ role: '#7B2FFF',
103
+ reviewer: '#00AAFF',
104
+ domain: '#00CC88',
105
+ };
106
+ const color = categoryColors[brain.category] || '#ffffff';
107
+
108
+ console.log('');
109
+ console.log(
110
+ chalk.hex(color).bold(' ┌─────────────────────────────────────────────────────')
111
+ );
112
+ console.log(
113
+ chalk.hex(color).bold(` │ 🧠 ${brain.name}`) +
114
+ DIM(` — ${brain.category}`)
115
+ );
116
+ if (stackedBrains.length > 0) {
117
+ console.log(
118
+ chalk.hex(color).bold(' │ ') +
119
+ DIM(`+ ${stackedBrains.map((b) => b.name).join(', ')}`)
120
+ );
121
+ }
122
+ console.log(
123
+ chalk.hex(color).bold(' │ ') + DIM(`Stack: ${brain.stack.join(', ')}`)
124
+ );
125
+ console.log(
126
+ chalk.hex(color).bold(' └─────────────────────────────────────────────────────')
127
+ );
128
+ console.log('');
129
+
130
+ // ─── Brain-specific workflows ───
131
+
132
+ if (brain.category === 'builder') {
133
+ await runBuilderBrain(brain, config, options);
134
+ } else if (brain.category === 'reviewer') {
135
+ await runReviewerBrain(brain, config, options);
136
+ } else if (brain.category === 'role') {
137
+ await runRoleBrain(brain, config, options);
138
+ } else if (brain.category === 'domain') {
139
+ await runDomainBrain(brain, config, options);
140
+ }
141
+ }
142
+
143
+ // ─── Builder Brain Runner ───
144
+ async function runBuilderBrain(brain, config, options) {
145
+ showInfo(`${brain.name} will ask you a few questions, then generate your project.\n`);
146
+
147
+ const answers = {};
148
+
149
+ // Run through the brain's interview questions
150
+ for (const q of config.questions || brain.questions) {
151
+ const { answer } = await inquirer.prompt([
152
+ {
153
+ type: 'input',
154
+ name: 'answer',
155
+ message: chalk.hex('#FF3366')(q.question),
156
+ },
157
+ ]);
158
+ answers[q.key] = answer;
159
+ }
160
+
161
+ console.log('');
162
+ showInfo('Got it! Generating your project...\n');
163
+
164
+ // Simulate generation steps based on brain type
165
+ const genSteps = getBuilderSteps(brain.id);
166
+ for (const step of genSteps) {
167
+ const spinner = ora({
168
+ text: step.text,
169
+ color: 'magenta',
170
+ spinner: 'dots12',
171
+ }).start();
172
+ await new Promise((r) => setTimeout(r, step.time));
173
+ spinner.succeed(chalk.dim(step.text));
174
+ }
175
+
176
+ // Show output
177
+ const dir = options.dir || `./${answers.product || answers.name || brain.id}-project`;
178
+
179
+ console.log('');
180
+ showSuccess('Project generated successfully!\n');
181
+
182
+ const tree = getProjectTree(brain.id, answers);
183
+ console.log(chalk.dim(tree));
184
+
185
+ console.log('');
186
+ showInfo(`Project created at: ${ACCENT(dir)}`);
187
+ showInfo(`Next steps:`);
188
+ console.log(` ${DIM('$')} ${ACCENT(`cd ${dir}`)}`);
189
+ console.log(` ${DIM('$')} ${ACCENT('npm install')}`);
190
+ console.log(` ${DIM('$')} ${ACCENT('npm run dev')}`);
191
+ console.log('');
192
+
193
+ // Show what the brain generated
194
+ const systemPromptInfo = `
195
+ ${chalk.bold('What was generated:')}
196
+
197
+ This project was scaffolded by the ${chalk.hex('#FF3366').bold(brain.name)} Brain.
198
+ The brain used the following context to customize your project:
199
+ ${Object.entries(answers)
200
+ .map(([k, v]) => ` ${chalk.hex('#FF3366')('▸')} ${k}: ${DIM(v)}`)
201
+ .join('\n')}
202
+
203
+ ${chalk.bold('Brain System Prompt:')}
204
+ ${DIM('The brain operated with a specialized system prompt that ensures')}
205
+ ${DIM('best practices, modern patterns, and production-grade output.')}
206
+ ${DIM(`View it at: ~/.brains/${brain.id}/BRAIN.md`)}
207
+ `;
208
+ console.log(systemPromptInfo);
209
+ }
210
+
211
+ // ─── Reviewer Brain Runner ───
212
+ async function runReviewerBrain(brain, config, options) {
213
+ const targetDir = options.dir || process.cwd();
214
+
215
+ showInfo(`${brain.name} will analyze your codebase.\n`);
216
+
217
+ // Ask focus questions
218
+ const answers = {};
219
+ for (const q of config.questions || brain.questions) {
220
+ const { answer } = await inquirer.prompt([
221
+ {
222
+ type: 'input',
223
+ name: 'answer',
224
+ message: chalk.hex('#00AAFF')(q.question),
225
+ },
226
+ ]);
227
+ answers[q.key] = answer;
228
+ }
229
+
230
+ console.log('');
231
+
232
+ const scanSteps = [
233
+ { text: 'Scanning project structure', time: 600 },
234
+ { text: 'Analyzing source files', time: 1000 },
235
+ { text: 'Checking dependencies', time: 500 },
236
+ { text: 'Running pattern analysis', time: 800 },
237
+ { text: 'Evaluating security posture', time: 700 },
238
+ { text: 'Generating review report', time: 600 },
239
+ ];
240
+
241
+ for (const step of scanSteps) {
242
+ const spinner = ora({
243
+ text: step.text,
244
+ color: 'cyan',
245
+ spinner: 'dots12',
246
+ }).start();
247
+ await new Promise((r) => setTimeout(r, step.time));
248
+ spinner.succeed(chalk.dim(step.text));
249
+ }
250
+
251
+ console.log('');
252
+ showSuccess('Review complete!\n');
253
+
254
+ // Show sample review output
255
+ console.log(chalk.bold(' Review Summary\n'));
256
+ console.log(` ${chalk.red('🔴 CRITICAL')} ${chalk.dim('0 issues')}`);
257
+ console.log(` ${chalk.hex('#FF8800')('🟡 WARNING')} ${chalk.dim('3 issues')}`);
258
+ console.log(` ${chalk.hex('#00CC88')('🟢 SUGGESTION')} ${chalk.dim('7 suggestions')}`);
259
+ console.log(` ${chalk.blue('📝 NOTE')} ${chalk.dim('2 notes')}`);
260
+
261
+ console.log(`\n${chalk.dim('─'.repeat(56))}\n`);
262
+
263
+ console.log(
264
+ ` ${chalk.bold('In production, this brain would:')}\n` +
265
+ ` ${chalk.hex('#00AAFF')('▸')} Read every file in your project\n` +
266
+ ` ${chalk.hex('#00AAFF')('▸')} Apply the review methodology from its system prompt\n` +
267
+ ` ${chalk.hex('#00AAFF')('▸')} Use Claude to analyze patterns, bugs, and security\n` +
268
+ ` ${chalk.hex('#00AAFF')('▸')} Output a detailed, prioritized review report\n` +
269
+ ` ${chalk.hex('#00AAFF')('▸')} Optionally auto-fix issues with --fix flag\n`
270
+ );
271
+
272
+ showInfo(`Brain config: ${DIM(`~/.brains/${brain.id}/BRAIN.md`)}`);
273
+ console.log('');
274
+ }
275
+
276
+ // ─── Role Brain Runner ───
277
+ async function runRoleBrain(brain, config, options) {
278
+ showInfo(`${brain.name} is ready. Starting conversational session.\n`);
279
+
280
+ // Ask initial context questions
281
+ const answers = {};
282
+ for (const q of config.questions || brain.questions) {
283
+ const { answer } = await inquirer.prompt([
284
+ {
285
+ type: 'input',
286
+ name: 'answer',
287
+ message: chalk.hex('#7B2FFF')(q.question),
288
+ },
289
+ ]);
290
+ answers[q.key] = answer;
291
+ }
292
+
293
+ console.log('');
294
+
295
+ const spinner = ora({
296
+ text: 'Loading context and preparing response...',
297
+ color: 'magenta',
298
+ spinner: 'dots12',
299
+ }).start();
300
+ await new Promise((r) => setTimeout(r, 1500));
301
+ spinner.succeed(chalk.dim('Context loaded'));
302
+
303
+ console.log(`\n${chalk.dim('─'.repeat(56))}\n`);
304
+
305
+ console.log(
306
+ ` ${chalk.bold('In production, this brain would now:')}\n` +
307
+ ` ${chalk.hex('#7B2FFF')('▸')} Enter a REPL-style conversational loop\n` +
308
+ ` ${chalk.hex('#7B2FFF')('▸')} Use the ${brain.name} system prompt to guide every response\n` +
309
+ ` ${chalk.hex('#7B2FFF')('▸')} Generate code, review PRs, design systems interactively\n` +
310
+ ` ${chalk.hex('#7B2FFF')('▸')} Maintain context across the conversation\n` +
311
+ ` ${chalk.hex('#7B2FFF')('▸')} Write files directly to your project\n`
312
+ );
313
+
314
+ console.log(
315
+ ` ${chalk.bold('The system prompt for this brain includes:')}\n` +
316
+ ` ${DIM('Personality, methodology, tech preferences, and output format.')}\n` +
317
+ ` ${DIM(`View it at: ~/.brains/${brain.id}/BRAIN.md`)}\n`
318
+ );
319
+
320
+ // Interactive loop demo
321
+ const { continueChat } = await inquirer.prompt([
322
+ {
323
+ type: 'confirm',
324
+ name: 'continueChat',
325
+ message: 'Want to see a sample interaction?',
326
+ default: true,
327
+ },
328
+ ]);
329
+
330
+ if (continueChat) {
331
+ console.log(`\n ${chalk.hex('#7B2FFF').bold(`[${brain.name}]`)} ${chalk.white("I've reviewed your request. Here's my approach:")}\n`);
332
+ console.log(DIM(' (In production, Claude would respond here using the brain\'s system prompt'));
333
+ console.log(DIM(' as its personality and methodology, generating real code and advice.)\n'));
334
+ }
335
+ }
336
+
337
+ // ─── Domain Brain Runner ───
338
+ async function runDomainBrain(brain, config, options) {
339
+ showInfo(`${brain.name} expert session started.\n`);
340
+
341
+ // Ask what they need
342
+ for (const q of config.questions || brain.questions) {
343
+ const { answer } = await inquirer.prompt([
344
+ {
345
+ type: 'input',
346
+ name: 'answer',
347
+ message: chalk.hex('#00CC88')(q.question),
348
+ },
349
+ ]);
350
+ }
351
+
352
+ console.log('');
353
+
354
+ const spinner = ora({
355
+ text: `Consulting ${brain.name} knowledge base...`,
356
+ color: 'green',
357
+ spinner: 'dots12',
358
+ }).start();
359
+ await new Promise((r) => setTimeout(r, 1200));
360
+ spinner.succeed(chalk.dim('Ready'));
361
+
362
+ console.log(`\n${chalk.dim('─'.repeat(56))}\n`);
363
+
364
+ console.log(
365
+ ` ${chalk.bold('In production, this brain would:')}\n` +
366
+ ` ${chalk.hex('#00CC88')('▸')} Use deep ${brain.stack[0]} expertise from its system prompt\n` +
367
+ ` ${chalk.hex('#00CC88')('▸')} Generate best-practice code specific to your question\n` +
368
+ ` ${chalk.hex('#00CC88')('▸')} Include explanations, trade-offs, and gotchas\n` +
369
+ ` ${chalk.hex('#00CC88')('▸')} Reference latest patterns and APIs\n` +
370
+ ` ${chalk.hex('#00CC88')('▸')} Work interactively in a conversational loop\n`
371
+ );
372
+
373
+ showInfo(`Brain config: ${DIM(`~/.brains/${brain.id}/BRAIN.md`)}`);
374
+ console.log('');
375
+ }
376
+
377
+ // ─── Helper: Builder generation steps ───
378
+ function getBuilderSteps(brainId) {
379
+ const common = [
380
+ { text: 'Analyzing requirements', time: 500 },
381
+ { text: 'Selecting architecture', time: 400 },
382
+ ];
383
+
384
+ const specific = {
385
+ resume: [
386
+ { text: 'Generating Next.js project', time: 700 },
387
+ { text: 'Creating resume components', time: 600 },
388
+ { text: 'Applying theme and typography', time: 500 },
389
+ { text: 'Adding responsive styles', time: 400 },
390
+ { text: 'Configuring SEO metadata', time: 300 },
391
+ { text: 'Setting up Vercel deployment', time: 300 },
392
+ ],
393
+ mvp: [
394
+ { text: 'Generating Next.js + Prisma project', time: 800 },
395
+ { text: 'Creating database schema', time: 600 },
396
+ { text: 'Building API routes', time: 700 },
397
+ { text: 'Setting up authentication', time: 500 },
398
+ { text: 'Generating dashboard pages', time: 600 },
399
+ { text: 'Creating landing page', time: 500 },
400
+ { text: 'Configuring Docker', time: 400 },
401
+ ],
402
+ landing: [
403
+ { text: 'Generating page structure', time: 500 },
404
+ { text: 'Writing conversion copy', time: 700 },
405
+ { text: 'Building hero section', time: 500 },
406
+ { text: 'Creating feature sections', time: 400 },
407
+ { text: 'Adding social proof', time: 400 },
408
+ { text: 'Setting up email capture', time: 300 },
409
+ { text: 'Adding animations', time: 500 },
410
+ ],
411
+ };
412
+
413
+ return [...common, ...(specific[brainId] || specific.mvp)];
414
+ }
415
+
416
+ // ─── Helper: Project tree output ───
417
+ function getProjectTree(brainId, answers) {
418
+ const name = answers.product || answers.name || brainId;
419
+ const trees = {
420
+ resume: `
421
+ ${chalk.bold(`${name}-project/`)}
422
+ ├── app/
423
+ │ ├── layout.tsx
424
+ │ ├── page.tsx
425
+ │ └── globals.css
426
+ ├── components/
427
+ │ ├── Hero.tsx
428
+ │ ├── Experience.tsx
429
+ │ ├── Skills.tsx
430
+ │ ├── Projects.tsx
431
+ │ └── Contact.tsx
432
+ ├── lib/
433
+ │ └── data.ts
434
+ ├── public/
435
+ │ └── resume.pdf
436
+ ├── package.json
437
+ ├── tailwind.config.ts
438
+ ├── tsconfig.json
439
+ └── next.config.js`,
440
+ mvp: `
441
+ ${chalk.bold(`${name}-project/`)}
442
+ ├── app/
443
+ │ ├── layout.tsx
444
+ │ ├── page.tsx ${DIM('← Landing page')}
445
+ │ ├── (auth)/
446
+ │ │ ├── login/page.tsx
447
+ │ │ └── register/page.tsx
448
+ │ ├── dashboard/
449
+ │ │ ├── layout.tsx
450
+ │ │ └── page.tsx
451
+ │ └── api/
452
+ │ └── [...routes].ts
453
+ ├── components/
454
+ │ ├── ui/
455
+ │ └── features/
456
+ ├── lib/
457
+ │ ├── db.ts
458
+ │ └── auth.ts
459
+ ├── prisma/
460
+ │ ├── schema.prisma
461
+ │ └── migrations/
462
+ ├── package.json
463
+ ├── docker-compose.yml
464
+ └── .env.example`,
465
+ landing: `
466
+ ${chalk.bold(`${name}-project/`)}
467
+ ├── app/
468
+ │ ├── layout.tsx
469
+ │ ├── page.tsx
470
+ │ └── globals.css
471
+ ├── components/
472
+ │ ├── Hero.tsx
473
+ │ ├── Features.tsx
474
+ │ ├── HowItWorks.tsx
475
+ │ ├── Testimonials.tsx
476
+ │ ├── Pricing.tsx
477
+ │ ├── FAQ.tsx
478
+ │ └── CTA.tsx
479
+ ├── package.json
480
+ ├── tailwind.config.ts
481
+ └── next.config.js`,
482
+ };
483
+
484
+ return trees[brainId] || trees.mvp;
485
+ }
486
+
487
+ module.exports = { run };