@codebakers/cli 2.7.0 → 2.8.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,15 @@
1
+ interface PushOptions {
2
+ version: string;
3
+ changelog?: string;
4
+ autoPublish?: boolean;
5
+ sourcePath?: string;
6
+ }
7
+ /**
8
+ * Push patterns to the server via admin API
9
+ */
10
+ export declare function pushPatterns(options: PushOptions): Promise<void>;
11
+ /**
12
+ * Interactive push command
13
+ */
14
+ export declare function pushPatternsInteractive(): Promise<void>;
15
+ export {};
@@ -0,0 +1,186 @@
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.pushPatterns = pushPatterns;
7
+ exports.pushPatternsInteractive = pushPatternsInteractive;
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const ora_1 = __importDefault(require("ora"));
10
+ const readline_1 = require("readline");
11
+ const fs_1 = require("fs");
12
+ const path_1 = require("path");
13
+ const config_js_1 = require("../config.js");
14
+ function prompt(question) {
15
+ const rl = (0, readline_1.createInterface)({
16
+ input: process.stdin,
17
+ output: process.stdout,
18
+ });
19
+ return new Promise((resolve) => {
20
+ rl.question(question, (answer) => {
21
+ rl.close();
22
+ resolve(answer.trim());
23
+ });
24
+ });
25
+ }
26
+ /**
27
+ * Read all module files from a directory
28
+ */
29
+ function readModulesFromDir(dirPath) {
30
+ const modules = {};
31
+ if (!(0, fs_1.existsSync)(dirPath)) {
32
+ return modules;
33
+ }
34
+ const files = (0, fs_1.readdirSync)(dirPath).filter(f => f.endsWith('.md'));
35
+ for (const file of files) {
36
+ const filePath = (0, path_1.join)(dirPath, file);
37
+ const content = (0, fs_1.readFileSync)(filePath, 'utf-8');
38
+ const moduleName = (0, path_1.basename)(file, '.md');
39
+ modules[moduleName] = content;
40
+ }
41
+ return modules;
42
+ }
43
+ /**
44
+ * Push patterns to the server via admin API
45
+ */
46
+ async function pushPatterns(options) {
47
+ console.log(chalk_1.default.blue('\n CodeBakers Push Patterns\n'));
48
+ const sourcePath = options.sourcePath || process.cwd();
49
+ // Check for API key - first from config, then from environment
50
+ const apiKey = (0, config_js_1.getApiKey)() || process.env.CODEBAKERS_ADMIN_KEY;
51
+ if (!apiKey) {
52
+ console.log(chalk_1.default.red(' ✗ No API key found\n'));
53
+ console.log(chalk_1.default.gray(' Either:'));
54
+ console.log(chalk_1.default.cyan(' 1. Run `codebakers setup` to configure your API key'));
55
+ console.log(chalk_1.default.cyan(' 2. Set CODEBAKERS_ADMIN_KEY environment variable\n'));
56
+ process.exit(1);
57
+ }
58
+ // Read main files
59
+ const claudeMdPath = (0, path_1.join)(sourcePath, 'CLAUDE.md');
60
+ const cursorRulesPath = (0, path_1.join)(sourcePath, '.cursorrules');
61
+ let claudeMdContent = null;
62
+ let cursorRulesContent = null;
63
+ if ((0, fs_1.existsSync)(claudeMdPath)) {
64
+ claudeMdContent = (0, fs_1.readFileSync)(claudeMdPath, 'utf-8');
65
+ console.log(chalk_1.default.green(` ✓ Found CLAUDE.md (${(claudeMdContent.length / 1024).toFixed(1)} KB)`));
66
+ }
67
+ else {
68
+ console.log(chalk_1.default.yellow(` ⚠ CLAUDE.md not found at ${claudeMdPath}`));
69
+ }
70
+ if ((0, fs_1.existsSync)(cursorRulesPath)) {
71
+ cursorRulesContent = (0, fs_1.readFileSync)(cursorRulesPath, 'utf-8');
72
+ console.log(chalk_1.default.green(` ✓ Found .cursorrules (${(cursorRulesContent.length / 1024).toFixed(1)} KB)`));
73
+ }
74
+ else {
75
+ console.log(chalk_1.default.yellow(` ⚠ .cursorrules not found at ${cursorRulesPath}`));
76
+ }
77
+ if (!claudeMdContent && !cursorRulesContent) {
78
+ console.log(chalk_1.default.red('\n ✗ No pattern files found. Nothing to push.\n'));
79
+ process.exit(1);
80
+ }
81
+ // Read module directories
82
+ const claudeModulesPath = (0, path_1.join)(sourcePath, '.claude');
83
+ const cursorModulesPath = (0, path_1.join)(sourcePath, '.cursorrules-modules');
84
+ const modulesContent = readModulesFromDir(claudeModulesPath);
85
+ const cursorModulesContent = readModulesFromDir(cursorModulesPath);
86
+ const claudeModuleCount = Object.keys(modulesContent).length;
87
+ const cursorModuleCount = Object.keys(cursorModulesContent).length;
88
+ if (claudeModuleCount > 0) {
89
+ console.log(chalk_1.default.green(` ✓ Found ${claudeModuleCount} Claude modules in .claude/`));
90
+ }
91
+ if (cursorModuleCount > 0) {
92
+ console.log(chalk_1.default.green(` ✓ Found ${cursorModuleCount} Cursor modules in .cursorrules-modules/`));
93
+ }
94
+ console.log('');
95
+ // Show summary
96
+ console.log(chalk_1.default.white(' Push Summary:\n'));
97
+ console.log(chalk_1.default.gray(` Version: ${chalk_1.default.cyan(options.version)}`));
98
+ console.log(chalk_1.default.gray(` Auto-publish: ${options.autoPublish ? chalk_1.default.green('Yes') : chalk_1.default.yellow('No')}`));
99
+ if (options.changelog) {
100
+ console.log(chalk_1.default.gray(` Changelog: ${chalk_1.default.dim(options.changelog.slice(0, 60))}...`));
101
+ }
102
+ console.log('');
103
+ // Files to upload
104
+ console.log(chalk_1.default.white(' Files to upload:\n'));
105
+ if (claudeMdContent)
106
+ console.log(chalk_1.default.cyan(' • CLAUDE.md'));
107
+ if (cursorRulesContent)
108
+ console.log(chalk_1.default.cyan(' • .cursorrules'));
109
+ for (const mod of Object.keys(modulesContent)) {
110
+ console.log(chalk_1.default.dim(` • .claude/${mod}.md`));
111
+ }
112
+ for (const mod of Object.keys(cursorModulesContent)) {
113
+ console.log(chalk_1.default.dim(` • .cursorrules-modules/${mod}.md`));
114
+ }
115
+ console.log('');
116
+ // Confirm
117
+ const confirm = await prompt(chalk_1.default.gray(' Push these patterns? (y/N): '));
118
+ if (confirm.toLowerCase() !== 'y') {
119
+ console.log(chalk_1.default.gray('\n Cancelled.\n'));
120
+ process.exit(0);
121
+ }
122
+ // Push to server
123
+ const spinner = (0, ora_1.default)('Pushing patterns to server...').start();
124
+ try {
125
+ const apiUrl = (0, config_js_1.getApiUrl)();
126
+ const response = await fetch(`${apiUrl}/api/admin/content/push`, {
127
+ method: 'POST',
128
+ headers: {
129
+ 'Content-Type': 'application/json',
130
+ 'Authorization': `Bearer ${apiKey}`,
131
+ },
132
+ body: JSON.stringify({
133
+ version: options.version,
134
+ claudeMdContent,
135
+ cursorRulesContent,
136
+ modulesContent: claudeModuleCount > 0 ? modulesContent : undefined,
137
+ cursorModulesContent: cursorModuleCount > 0 ? cursorModulesContent : undefined,
138
+ changelog: options.changelog,
139
+ autoPublish: options.autoPublish,
140
+ }),
141
+ });
142
+ const data = await response.json();
143
+ if (!response.ok) {
144
+ spinner.fail(chalk_1.default.red(`Push failed: ${data.error || 'Unknown error'}`));
145
+ process.exit(1);
146
+ }
147
+ spinner.succeed(chalk_1.default.green('Patterns pushed successfully!'));
148
+ console.log(chalk_1.default.white('\n Result:\n'));
149
+ console.log(chalk_1.default.gray(` Version ID: ${chalk_1.default.cyan(data.version?.id || 'N/A')}`));
150
+ console.log(chalk_1.default.gray(` Version: ${chalk_1.default.cyan(data.version?.version || options.version)}`));
151
+ console.log(chalk_1.default.gray(` Published: ${data.published ? chalk_1.default.green('Yes') : chalk_1.default.yellow('No - run publish manually')}`));
152
+ console.log('');
153
+ console.log(chalk_1.default.green(` ${data.message}\n`));
154
+ }
155
+ catch (error) {
156
+ spinner.fail(chalk_1.default.red('Failed to connect to server'));
157
+ console.log(chalk_1.default.gray(`\n Error: ${error instanceof Error ? error.message : 'Unknown error'}\n`));
158
+ process.exit(1);
159
+ }
160
+ }
161
+ /**
162
+ * Interactive push command
163
+ */
164
+ async function pushPatternsInteractive() {
165
+ console.log(chalk_1.default.blue('\n CodeBakers Push Patterns\n'));
166
+ // Get version
167
+ const version = await prompt(chalk_1.default.cyan(' Version (e.g., 4.5): '));
168
+ if (!version) {
169
+ console.log(chalk_1.default.red('\n Version is required.\n'));
170
+ process.exit(1);
171
+ }
172
+ // Get changelog
173
+ const changelog = await prompt(chalk_1.default.cyan(' Changelog (optional): '));
174
+ // Ask about auto-publish
175
+ const autoPublishAnswer = await prompt(chalk_1.default.cyan(' Auto-publish? (y/N): '));
176
+ const autoPublish = autoPublishAnswer.toLowerCase() === 'y';
177
+ // Get source path
178
+ const sourcePathAnswer = await prompt(chalk_1.default.cyan(' Source path (Enter for current directory): '));
179
+ const sourcePath = sourcePathAnswer || process.cwd();
180
+ await pushPatterns({
181
+ version,
182
+ changelog: changelog || undefined,
183
+ autoPublish,
184
+ sourcePath,
185
+ });
186
+ }
@@ -10,6 +10,7 @@ const readline_1 = require("readline");
10
10
  const child_process_1 = require("child_process");
11
11
  const config_js_1 = require("../config.js");
12
12
  const api_js_1 = require("../lib/api.js");
13
+ const progress_js_1 = require("../lib/progress.js");
13
14
  function prompt(question) {
14
15
  const rl = (0, readline_1.createInterface)({
15
16
  input: process.stdin,
@@ -62,8 +63,18 @@ async function setup() {
62
63
  }
63
64
  catch (error) {
64
65
  spinner.fail('Invalid API key');
65
- if (error && typeof error === 'object' && 'recoverySteps' in error) {
66
- console.log(chalk_1.default.red(`\n ${(0, api_js_1.formatApiError)(error)}\n`));
66
+ // Use friendly error formatting
67
+ if (error && typeof error === 'object' && 'code' in error) {
68
+ const code = error.code;
69
+ if (code === 'NETWORK_ERROR') {
70
+ console.log((0, progress_js_1.formatFriendlyError)((0, progress_js_1.getNetworkError)()));
71
+ }
72
+ else if (code === 'UNAUTHORIZED' || code === 'INVALID_FORMAT') {
73
+ console.log((0, progress_js_1.formatFriendlyError)((0, progress_js_1.getAuthError)()));
74
+ }
75
+ else if ('recoverySteps' in error) {
76
+ console.log(chalk_1.default.red(`\n ${(0, api_js_1.formatApiError)(error)}\n`));
77
+ }
67
78
  }
68
79
  else {
69
80
  const message = error instanceof Error ? error.message : 'API key validation failed';
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ const upgrade_js_1 = require("./commands/upgrade.js");
22
22
  const config_js_1 = require("./commands/config.js");
23
23
  const audit_js_1 = require("./commands/audit.js");
24
24
  const heal_js_1 = require("./commands/heal.js");
25
+ const push_patterns_js_1 = require("./commands/push-patterns.js");
25
26
  // Show welcome message when no command is provided
26
27
  function showWelcome() {
27
28
  console.log(chalk_1.default.blue(`
@@ -63,7 +64,7 @@ const program = new commander_1.Command();
63
64
  program
64
65
  .name('codebakers')
65
66
  .description('CodeBakers CLI - Production patterns for AI-assisted development')
66
- .version('1.7.0');
67
+ .version('2.8.0');
67
68
  // Primary command - one-time setup
68
69
  program
69
70
  .command('setup')
@@ -142,6 +143,27 @@ program
142
143
  });
143
144
  }
144
145
  });
146
+ // Admin commands
147
+ program
148
+ .command('push-patterns')
149
+ .description('Push pattern files to the server (admin only)')
150
+ .option('-v, --version <version>', 'Version number (e.g., 4.5)')
151
+ .option('-c, --changelog <message>', 'Changelog message')
152
+ .option('-p, --publish', 'Auto-publish after push')
153
+ .option('-s, --source <path>', 'Source directory (default: current directory)')
154
+ .action(async (options) => {
155
+ if (options.version) {
156
+ await (0, push_patterns_js_1.pushPatterns)({
157
+ version: options.version,
158
+ changelog: options.changelog,
159
+ autoPublish: options.publish,
160
+ sourcePath: options.source,
161
+ });
162
+ }
163
+ else {
164
+ await (0, push_patterns_js_1.pushPatternsInteractive)();
165
+ }
166
+ });
145
167
  // MCP Server commands
146
168
  program
147
169
  .command('serve')
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Multi-step progress tracker for CLI operations
3
+ */
4
+ export interface Step {
5
+ name: string;
6
+ action: () => Promise<void>;
7
+ }
8
+ export declare class ProgressTracker {
9
+ private currentStep;
10
+ private totalSteps;
11
+ private steps;
12
+ private spinner;
13
+ constructor(steps: Step[]);
14
+ run(): Promise<void>;
15
+ private formatPrefix;
16
+ private formatStepText;
17
+ }
18
+ /**
19
+ * Show a success box after completing a command
20
+ */
21
+ export declare function showSuccessBox(title: string, lines: string[]): void;
22
+ /**
23
+ * Display a quick start guide with examples
24
+ */
25
+ export declare function showQuickStartGuide(): void;
26
+ /**
27
+ * Format user-friendly error with context and recovery steps
28
+ */
29
+ export interface FriendlyError {
30
+ title: string;
31
+ message: string;
32
+ cause?: string;
33
+ recovery: string[];
34
+ helpUrl?: string;
35
+ }
36
+ export declare function formatFriendlyError(error: FriendlyError): string;
37
+ /**
38
+ * Common error handlers with friendly messages
39
+ */
40
+ export declare function getNetworkError(): FriendlyError;
41
+ export declare function getAuthError(): FriendlyError;
42
+ export declare function getSubscriptionError(): FriendlyError;
@@ -0,0 +1,160 @@
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.ProgressTracker = void 0;
7
+ exports.showSuccessBox = showSuccessBox;
8
+ exports.showQuickStartGuide = showQuickStartGuide;
9
+ exports.formatFriendlyError = formatFriendlyError;
10
+ exports.getNetworkError = getNetworkError;
11
+ exports.getAuthError = getAuthError;
12
+ exports.getSubscriptionError = getSubscriptionError;
13
+ const chalk_1 = __importDefault(require("chalk"));
14
+ const ora_1 = __importDefault(require("ora"));
15
+ class ProgressTracker {
16
+ currentStep = 0;
17
+ totalSteps;
18
+ steps;
19
+ spinner = null;
20
+ constructor(steps) {
21
+ this.steps = steps;
22
+ this.totalSteps = steps.length;
23
+ }
24
+ async run() {
25
+ for (let i = 0; i < this.steps.length; i++) {
26
+ this.currentStep = i + 1;
27
+ const step = this.steps[i];
28
+ this.spinner = (0, ora_1.default)({
29
+ text: this.formatStepText(step.name),
30
+ prefixText: this.formatPrefix(),
31
+ }).start();
32
+ try {
33
+ await step.action();
34
+ this.spinner.succeed(this.formatStepText(step.name));
35
+ }
36
+ catch (error) {
37
+ this.spinner.fail(this.formatStepText(step.name));
38
+ throw error;
39
+ }
40
+ }
41
+ }
42
+ formatPrefix() {
43
+ return chalk_1.default.gray(` [${this.currentStep}/${this.totalSteps}]`);
44
+ }
45
+ formatStepText(text) {
46
+ return text;
47
+ }
48
+ }
49
+ exports.ProgressTracker = ProgressTracker;
50
+ /**
51
+ * Show a success box after completing a command
52
+ */
53
+ function showSuccessBox(title, lines) {
54
+ const maxLength = Math.max(title.length, ...lines.map(l => l.length)) + 4;
55
+ const border = '═'.repeat(maxLength);
56
+ console.log(chalk_1.default.green(`\n ╔${border}╗`));
57
+ console.log(chalk_1.default.green(` ║`) + chalk_1.default.bold.white(` ${title.padEnd(maxLength - 2)} `) + chalk_1.default.green(`║`));
58
+ console.log(chalk_1.default.green(` ╠${border}╣`));
59
+ for (const line of lines) {
60
+ console.log(chalk_1.default.green(` ║`) + chalk_1.default.white(` ${line.padEnd(maxLength - 2)} `) + chalk_1.default.green(`║`));
61
+ }
62
+ console.log(chalk_1.default.green(` ╚${border}╝\n`));
63
+ }
64
+ /**
65
+ * Display a quick start guide with examples
66
+ */
67
+ function showQuickStartGuide() {
68
+ console.log(chalk_1.default.blue('\n ═══════════════════════════════════════════════════════════'));
69
+ console.log(chalk_1.default.bold.white('\n 📚 Quick Start Guide\n'));
70
+ console.log(chalk_1.default.blue(' ═══════════════════════════════════════════════════════════\n'));
71
+ console.log(chalk_1.default.white(' Just ask the AI in natural language:\n'));
72
+ const examples = [
73
+ {
74
+ prompt: '"Build me a user dashboard with stats cards"',
75
+ desc: 'AI will use CodeBakers patterns for layout, components, and data fetching',
76
+ },
77
+ {
78
+ prompt: '"Add Stripe subscription to my app"',
79
+ desc: 'AI loads payment patterns: checkout, webhooks, customer portal',
80
+ },
81
+ {
82
+ prompt: '"Create an API endpoint for user profiles"',
83
+ desc: 'AI generates: route handler, validation, error handling, types',
84
+ },
85
+ {
86
+ prompt: '"Set up auth with Google and email login"',
87
+ desc: 'AI implements OAuth + email auth following security patterns',
88
+ },
89
+ ];
90
+ for (const example of examples) {
91
+ console.log(chalk_1.default.cyan(` → ${example.prompt}`));
92
+ console.log(chalk_1.default.gray(` ${example.desc}\n`));
93
+ }
94
+ console.log(chalk_1.default.blue(' ───────────────────────────────────────────────────────────\n'));
95
+ console.log(chalk_1.default.white(' Pro Tips:\n'));
96
+ console.log(chalk_1.default.gray(' • Ask the AI ') + chalk_1.default.cyan('"What patterns do you have for payments?"'));
97
+ console.log(chalk_1.default.gray(' • Say ') + chalk_1.default.cyan('"/build a SaaS app"') + chalk_1.default.gray(' to start a full project'));
98
+ console.log(chalk_1.default.gray(' • Try ') + chalk_1.default.cyan('"/audit"') + chalk_1.default.gray(' to check code quality'));
99
+ console.log(chalk_1.default.gray(' • Use ') + chalk_1.default.cyan('"codebakers doctor"') + chalk_1.default.gray(' if something seems wrong\n'));
100
+ console.log(chalk_1.default.blue(' ═══════════════════════════════════════════════════════════\n'));
101
+ }
102
+ function formatFriendlyError(error) {
103
+ let output = '';
104
+ output += chalk_1.default.red(`\n ❌ ${error.title}\n`);
105
+ output += chalk_1.default.white(`\n ${error.message}\n`);
106
+ if (error.cause) {
107
+ output += chalk_1.default.gray(`\n Why: ${error.cause}\n`);
108
+ }
109
+ if (error.recovery.length > 0) {
110
+ output += chalk_1.default.yellow(`\n How to fix:\n`);
111
+ for (let i = 0; i < error.recovery.length; i++) {
112
+ output += chalk_1.default.white(` ${i + 1}. ${error.recovery[i]}\n`);
113
+ }
114
+ }
115
+ if (error.helpUrl) {
116
+ output += chalk_1.default.gray(`\n More help: ${error.helpUrl}\n`);
117
+ }
118
+ return output;
119
+ }
120
+ /**
121
+ * Common error handlers with friendly messages
122
+ */
123
+ function getNetworkError() {
124
+ return {
125
+ title: 'Connection Failed',
126
+ message: 'Could not connect to CodeBakers servers.',
127
+ cause: 'This usually means a network issue or the server is temporarily unavailable.',
128
+ recovery: [
129
+ 'Check your internet connection',
130
+ 'Try again in a few seconds',
131
+ 'If using a VPN, try disabling it temporarily',
132
+ 'Check status.codebakers.ai for server status',
133
+ ],
134
+ helpUrl: 'https://codebakers.ai/docs/troubleshooting',
135
+ };
136
+ }
137
+ function getAuthError() {
138
+ return {
139
+ title: 'Authentication Failed',
140
+ message: 'Your API key is invalid or expired.',
141
+ recovery: [
142
+ 'Go to codebakers.ai/dashboard',
143
+ 'Copy your API key (starts with cb_)',
144
+ 'Run: codebakers setup',
145
+ ],
146
+ helpUrl: 'https://codebakers.ai/docs/getting-started',
147
+ };
148
+ }
149
+ function getSubscriptionError() {
150
+ return {
151
+ title: 'Subscription Required',
152
+ message: 'This feature requires an active subscription.',
153
+ recovery: [
154
+ 'Visit codebakers.ai/pricing to see plans',
155
+ 'Start a free trial with: codebakers setup',
156
+ 'Contact support if you believe this is an error',
157
+ ],
158
+ helpUrl: 'https://codebakers.ai/pricing',
159
+ };
160
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "description": "CodeBakers CLI - Production patterns for AI-assisted development",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -0,0 +1,222 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { createInterface } from 'readline';
4
+ import { existsSync, readFileSync, readdirSync } from 'fs';
5
+ import { join, basename } from 'path';
6
+ import { getApiUrl, getApiKey } from '../config.js';
7
+
8
+ function prompt(question: string): Promise<string> {
9
+ const rl = createInterface({
10
+ input: process.stdin,
11
+ output: process.stdout,
12
+ });
13
+
14
+ return new Promise((resolve) => {
15
+ rl.question(question, (answer) => {
16
+ rl.close();
17
+ resolve(answer.trim());
18
+ });
19
+ });
20
+ }
21
+
22
+ interface PushOptions {
23
+ version: string;
24
+ changelog?: string;
25
+ autoPublish?: boolean;
26
+ sourcePath?: string;
27
+ }
28
+
29
+ interface ModulesContent {
30
+ [key: string]: string;
31
+ }
32
+
33
+ /**
34
+ * Read all module files from a directory
35
+ */
36
+ function readModulesFromDir(dirPath: string): ModulesContent {
37
+ const modules: ModulesContent = {};
38
+
39
+ if (!existsSync(dirPath)) {
40
+ return modules;
41
+ }
42
+
43
+ const files = readdirSync(dirPath).filter(f => f.endsWith('.md'));
44
+
45
+ for (const file of files) {
46
+ const filePath = join(dirPath, file);
47
+ const content = readFileSync(filePath, 'utf-8');
48
+ const moduleName = basename(file, '.md');
49
+ modules[moduleName] = content;
50
+ }
51
+
52
+ return modules;
53
+ }
54
+
55
+ /**
56
+ * Push patterns to the server via admin API
57
+ */
58
+ export async function pushPatterns(options: PushOptions): Promise<void> {
59
+ console.log(chalk.blue('\n CodeBakers Push Patterns\n'));
60
+
61
+ const sourcePath = options.sourcePath || process.cwd();
62
+
63
+ // Check for API key - first from config, then from environment
64
+ const apiKey = getApiKey() || process.env.CODEBAKERS_ADMIN_KEY;
65
+ if (!apiKey) {
66
+ console.log(chalk.red(' ✗ No API key found\n'));
67
+ console.log(chalk.gray(' Either:'));
68
+ console.log(chalk.cyan(' 1. Run `codebakers setup` to configure your API key'));
69
+ console.log(chalk.cyan(' 2. Set CODEBAKERS_ADMIN_KEY environment variable\n'));
70
+ process.exit(1);
71
+ }
72
+
73
+ // Read main files
74
+ const claudeMdPath = join(sourcePath, 'CLAUDE.md');
75
+ const cursorRulesPath = join(sourcePath, '.cursorrules');
76
+
77
+ let claudeMdContent: string | null = null;
78
+ let cursorRulesContent: string | null = null;
79
+
80
+ if (existsSync(claudeMdPath)) {
81
+ claudeMdContent = readFileSync(claudeMdPath, 'utf-8');
82
+ console.log(chalk.green(` ✓ Found CLAUDE.md (${(claudeMdContent.length / 1024).toFixed(1)} KB)`));
83
+ } else {
84
+ console.log(chalk.yellow(` ⚠ CLAUDE.md not found at ${claudeMdPath}`));
85
+ }
86
+
87
+ if (existsSync(cursorRulesPath)) {
88
+ cursorRulesContent = readFileSync(cursorRulesPath, 'utf-8');
89
+ console.log(chalk.green(` ✓ Found .cursorrules (${(cursorRulesContent.length / 1024).toFixed(1)} KB)`));
90
+ } else {
91
+ console.log(chalk.yellow(` ⚠ .cursorrules not found at ${cursorRulesPath}`));
92
+ }
93
+
94
+ if (!claudeMdContent && !cursorRulesContent) {
95
+ console.log(chalk.red('\n ✗ No pattern files found. Nothing to push.\n'));
96
+ process.exit(1);
97
+ }
98
+
99
+ // Read module directories
100
+ const claudeModulesPath = join(sourcePath, '.claude');
101
+ const cursorModulesPath = join(sourcePath, '.cursorrules-modules');
102
+
103
+ const modulesContent = readModulesFromDir(claudeModulesPath);
104
+ const cursorModulesContent = readModulesFromDir(cursorModulesPath);
105
+
106
+ const claudeModuleCount = Object.keys(modulesContent).length;
107
+ const cursorModuleCount = Object.keys(cursorModulesContent).length;
108
+
109
+ if (claudeModuleCount > 0) {
110
+ console.log(chalk.green(` ✓ Found ${claudeModuleCount} Claude modules in .claude/`));
111
+ }
112
+
113
+ if (cursorModuleCount > 0) {
114
+ console.log(chalk.green(` ✓ Found ${cursorModuleCount} Cursor modules in .cursorrules-modules/`));
115
+ }
116
+
117
+ console.log('');
118
+
119
+ // Show summary
120
+ console.log(chalk.white(' Push Summary:\n'));
121
+ console.log(chalk.gray(` Version: ${chalk.cyan(options.version)}`));
122
+ console.log(chalk.gray(` Auto-publish: ${options.autoPublish ? chalk.green('Yes') : chalk.yellow('No')}`));
123
+ if (options.changelog) {
124
+ console.log(chalk.gray(` Changelog: ${chalk.dim(options.changelog.slice(0, 60))}...`));
125
+ }
126
+ console.log('');
127
+
128
+ // Files to upload
129
+ console.log(chalk.white(' Files to upload:\n'));
130
+ if (claudeMdContent) console.log(chalk.cyan(' • CLAUDE.md'));
131
+ if (cursorRulesContent) console.log(chalk.cyan(' • .cursorrules'));
132
+ for (const mod of Object.keys(modulesContent)) {
133
+ console.log(chalk.dim(` • .claude/${mod}.md`));
134
+ }
135
+ for (const mod of Object.keys(cursorModulesContent)) {
136
+ console.log(chalk.dim(` • .cursorrules-modules/${mod}.md`));
137
+ }
138
+ console.log('');
139
+
140
+ // Confirm
141
+ const confirm = await prompt(chalk.gray(' Push these patterns? (y/N): '));
142
+ if (confirm.toLowerCase() !== 'y') {
143
+ console.log(chalk.gray('\n Cancelled.\n'));
144
+ process.exit(0);
145
+ }
146
+
147
+ // Push to server
148
+ const spinner = ora('Pushing patterns to server...').start();
149
+
150
+ try {
151
+ const apiUrl = getApiUrl();
152
+ const response = await fetch(`${apiUrl}/api/admin/content/push`, {
153
+ method: 'POST',
154
+ headers: {
155
+ 'Content-Type': 'application/json',
156
+ 'Authorization': `Bearer ${apiKey}`,
157
+ },
158
+ body: JSON.stringify({
159
+ version: options.version,
160
+ claudeMdContent,
161
+ cursorRulesContent,
162
+ modulesContent: claudeModuleCount > 0 ? modulesContent : undefined,
163
+ cursorModulesContent: cursorModuleCount > 0 ? cursorModulesContent : undefined,
164
+ changelog: options.changelog,
165
+ autoPublish: options.autoPublish,
166
+ }),
167
+ });
168
+
169
+ const data = await response.json();
170
+
171
+ if (!response.ok) {
172
+ spinner.fail(chalk.red(`Push failed: ${data.error || 'Unknown error'}`));
173
+ process.exit(1);
174
+ }
175
+
176
+ spinner.succeed(chalk.green('Patterns pushed successfully!'));
177
+
178
+ console.log(chalk.white('\n Result:\n'));
179
+ console.log(chalk.gray(` Version ID: ${chalk.cyan(data.version?.id || 'N/A')}`));
180
+ console.log(chalk.gray(` Version: ${chalk.cyan(data.version?.version || options.version)}`));
181
+ console.log(chalk.gray(` Published: ${data.published ? chalk.green('Yes') : chalk.yellow('No - run publish manually')}`));
182
+ console.log('');
183
+ console.log(chalk.green(` ${data.message}\n`));
184
+
185
+ } catch (error) {
186
+ spinner.fail(chalk.red('Failed to connect to server'));
187
+ console.log(chalk.gray(`\n Error: ${error instanceof Error ? error.message : 'Unknown error'}\n`));
188
+ process.exit(1);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Interactive push command
194
+ */
195
+ export async function pushPatternsInteractive(): Promise<void> {
196
+ console.log(chalk.blue('\n CodeBakers Push Patterns\n'));
197
+
198
+ // Get version
199
+ const version = await prompt(chalk.cyan(' Version (e.g., 4.5): '));
200
+ if (!version) {
201
+ console.log(chalk.red('\n Version is required.\n'));
202
+ process.exit(1);
203
+ }
204
+
205
+ // Get changelog
206
+ const changelog = await prompt(chalk.cyan(' Changelog (optional): '));
207
+
208
+ // Ask about auto-publish
209
+ const autoPublishAnswer = await prompt(chalk.cyan(' Auto-publish? (y/N): '));
210
+ const autoPublish = autoPublishAnswer.toLowerCase() === 'y';
211
+
212
+ // Get source path
213
+ const sourcePathAnswer = await prompt(chalk.cyan(' Source path (Enter for current directory): '));
214
+ const sourcePath = sourcePathAnswer || process.cwd();
215
+
216
+ await pushPatterns({
217
+ version,
218
+ changelog: changelog || undefined,
219
+ autoPublish,
220
+ sourcePath,
221
+ });
222
+ }
@@ -4,6 +4,7 @@ import { createInterface } from 'readline';
4
4
  import { execSync } from 'child_process';
5
5
  import { setApiKey, getApiKey, getApiUrl, syncServiceKeys, SERVICE_KEY_LABELS, type ServiceName } from '../config.js';
6
6
  import { validateApiKey, formatApiError, checkForUpdates, getCliVersion, type ApiError } from '../lib/api.js';
7
+ import { showQuickStartGuide, formatFriendlyError, getNetworkError, getAuthError } from '../lib/progress.js';
7
8
 
8
9
  function prompt(question: string): Promise<string> {
9
10
  const rl = createInterface({
@@ -67,8 +68,16 @@ export async function setup(): Promise<void> {
67
68
  } catch (error) {
68
69
  spinner.fail('Invalid API key');
69
70
 
70
- if (error && typeof error === 'object' && 'recoverySteps' in error) {
71
- console.log(chalk.red(`\n ${formatApiError(error as ApiError)}\n`));
71
+ // Use friendly error formatting
72
+ if (error && typeof error === 'object' && 'code' in error) {
73
+ const code = (error as { code: string }).code;
74
+ if (code === 'NETWORK_ERROR') {
75
+ console.log(formatFriendlyError(getNetworkError()));
76
+ } else if (code === 'UNAUTHORIZED' || code === 'INVALID_FORMAT') {
77
+ console.log(formatFriendlyError(getAuthError()));
78
+ } else if ('recoverySteps' in error) {
79
+ console.log(chalk.red(`\n ${formatApiError(error as ApiError)}\n`));
80
+ }
72
81
  } else {
73
82
  const message = error instanceof Error ? error.message : 'API key validation failed';
74
83
  console.log(chalk.red(`\n ${message}\n`));
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ import { upgrade } from './commands/upgrade.js';
18
18
  import { config } from './commands/config.js';
19
19
  import { audit } from './commands/audit.js';
20
20
  import { heal, healWatch } from './commands/heal.js';
21
+ import { pushPatterns, pushPatternsInteractive } from './commands/push-patterns.js';
21
22
 
22
23
  // Show welcome message when no command is provided
23
24
  function showWelcome(): void {
@@ -68,7 +69,7 @@ const program = new Command();
68
69
  program
69
70
  .name('codebakers')
70
71
  .description('CodeBakers CLI - Production patterns for AI-assisted development')
71
- .version('1.7.0');
72
+ .version('2.8.0');
72
73
 
73
74
  // Primary command - one-time setup
74
75
  program
@@ -162,6 +163,27 @@ program
162
163
  }
163
164
  });
164
165
 
166
+ // Admin commands
167
+ program
168
+ .command('push-patterns')
169
+ .description('Push pattern files to the server (admin only)')
170
+ .option('-v, --version <version>', 'Version number (e.g., 4.5)')
171
+ .option('-c, --changelog <message>', 'Changelog message')
172
+ .option('-p, --publish', 'Auto-publish after push')
173
+ .option('-s, --source <path>', 'Source directory (default: current directory)')
174
+ .action(async (options) => {
175
+ if (options.version) {
176
+ await pushPatterns({
177
+ version: options.version,
178
+ changelog: options.changelog,
179
+ autoPublish: options.publish,
180
+ sourcePath: options.source,
181
+ });
182
+ } else {
183
+ await pushPatternsInteractive();
184
+ }
185
+ });
186
+
165
187
  // MCP Server commands
166
188
  program
167
189
  .command('serve')
@@ -0,0 +1,192 @@
1
+ import chalk from 'chalk';
2
+ import ora, { Ora } from 'ora';
3
+
4
+ /**
5
+ * Multi-step progress tracker for CLI operations
6
+ */
7
+ export interface Step {
8
+ name: string;
9
+ action: () => Promise<void>;
10
+ }
11
+
12
+ export class ProgressTracker {
13
+ private currentStep = 0;
14
+ private totalSteps: number;
15
+ private steps: Step[];
16
+ private spinner: Ora | null = null;
17
+
18
+ constructor(steps: Step[]) {
19
+ this.steps = steps;
20
+ this.totalSteps = steps.length;
21
+ }
22
+
23
+ async run(): Promise<void> {
24
+ for (let i = 0; i < this.steps.length; i++) {
25
+ this.currentStep = i + 1;
26
+ const step = this.steps[i];
27
+
28
+ this.spinner = ora({
29
+ text: this.formatStepText(step.name),
30
+ prefixText: this.formatPrefix(),
31
+ }).start();
32
+
33
+ try {
34
+ await step.action();
35
+ this.spinner.succeed(this.formatStepText(step.name));
36
+ } catch (error) {
37
+ this.spinner.fail(this.formatStepText(step.name));
38
+ throw error;
39
+ }
40
+ }
41
+ }
42
+
43
+ private formatPrefix(): string {
44
+ return chalk.gray(` [${this.currentStep}/${this.totalSteps}]`);
45
+ }
46
+
47
+ private formatStepText(text: string): string {
48
+ return text;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Show a success box after completing a command
54
+ */
55
+ export function showSuccessBox(title: string, lines: string[]): void {
56
+ const maxLength = Math.max(title.length, ...lines.map(l => l.length)) + 4;
57
+ const border = '═'.repeat(maxLength);
58
+
59
+ console.log(chalk.green(`\n ╔${border}╗`));
60
+ console.log(chalk.green(` ║`) + chalk.bold.white(` ${title.padEnd(maxLength - 2)} `) + chalk.green(`║`));
61
+ console.log(chalk.green(` ╠${border}╣`));
62
+
63
+ for (const line of lines) {
64
+ console.log(chalk.green(` ║`) + chalk.white(` ${line.padEnd(maxLength - 2)} `) + chalk.green(`║`));
65
+ }
66
+
67
+ console.log(chalk.green(` ╚${border}╝\n`));
68
+ }
69
+
70
+ /**
71
+ * Display a quick start guide with examples
72
+ */
73
+ export function showQuickStartGuide(): void {
74
+ console.log(chalk.blue('\n ═══════════════════════════════════════════════════════════'));
75
+ console.log(chalk.bold.white('\n 📚 Quick Start Guide\n'));
76
+ console.log(chalk.blue(' ═══════════════════════════════════════════════════════════\n'));
77
+
78
+ console.log(chalk.white(' Just ask the AI in natural language:\n'));
79
+
80
+ const examples = [
81
+ {
82
+ prompt: '"Build me a user dashboard with stats cards"',
83
+ desc: 'AI will use CodeBakers patterns for layout, components, and data fetching',
84
+ },
85
+ {
86
+ prompt: '"Add Stripe subscription to my app"',
87
+ desc: 'AI loads payment patterns: checkout, webhooks, customer portal',
88
+ },
89
+ {
90
+ prompt: '"Create an API endpoint for user profiles"',
91
+ desc: 'AI generates: route handler, validation, error handling, types',
92
+ },
93
+ {
94
+ prompt: '"Set up auth with Google and email login"',
95
+ desc: 'AI implements OAuth + email auth following security patterns',
96
+ },
97
+ ];
98
+
99
+ for (const example of examples) {
100
+ console.log(chalk.cyan(` → ${example.prompt}`));
101
+ console.log(chalk.gray(` ${example.desc}\n`));
102
+ }
103
+
104
+ console.log(chalk.blue(' ───────────────────────────────────────────────────────────\n'));
105
+
106
+ console.log(chalk.white(' Pro Tips:\n'));
107
+ console.log(chalk.gray(' • Ask the AI ') + chalk.cyan('"What patterns do you have for payments?"'));
108
+ console.log(chalk.gray(' • Say ') + chalk.cyan('"/build a SaaS app"') + chalk.gray(' to start a full project'));
109
+ console.log(chalk.gray(' • Try ') + chalk.cyan('"/audit"') + chalk.gray(' to check code quality'));
110
+ console.log(chalk.gray(' • Use ') + chalk.cyan('"codebakers doctor"') + chalk.gray(' if something seems wrong\n'));
111
+
112
+ console.log(chalk.blue(' ═══════════════════════════════════════════════════════════\n'));
113
+ }
114
+
115
+ /**
116
+ * Format user-friendly error with context and recovery steps
117
+ */
118
+ export interface FriendlyError {
119
+ title: string;
120
+ message: string;
121
+ cause?: string;
122
+ recovery: string[];
123
+ helpUrl?: string;
124
+ }
125
+
126
+ export function formatFriendlyError(error: FriendlyError): string {
127
+ let output = '';
128
+
129
+ output += chalk.red(`\n ❌ ${error.title}\n`);
130
+ output += chalk.white(`\n ${error.message}\n`);
131
+
132
+ if (error.cause) {
133
+ output += chalk.gray(`\n Why: ${error.cause}\n`);
134
+ }
135
+
136
+ if (error.recovery.length > 0) {
137
+ output += chalk.yellow(`\n How to fix:\n`);
138
+ for (let i = 0; i < error.recovery.length; i++) {
139
+ output += chalk.white(` ${i + 1}. ${error.recovery[i]}\n`);
140
+ }
141
+ }
142
+
143
+ if (error.helpUrl) {
144
+ output += chalk.gray(`\n More help: ${error.helpUrl}\n`);
145
+ }
146
+
147
+ return output;
148
+ }
149
+
150
+ /**
151
+ * Common error handlers with friendly messages
152
+ */
153
+ export function getNetworkError(): FriendlyError {
154
+ return {
155
+ title: 'Connection Failed',
156
+ message: 'Could not connect to CodeBakers servers.',
157
+ cause: 'This usually means a network issue or the server is temporarily unavailable.',
158
+ recovery: [
159
+ 'Check your internet connection',
160
+ 'Try again in a few seconds',
161
+ 'If using a VPN, try disabling it temporarily',
162
+ 'Check status.codebakers.ai for server status',
163
+ ],
164
+ helpUrl: 'https://codebakers.ai/docs/troubleshooting',
165
+ };
166
+ }
167
+
168
+ export function getAuthError(): FriendlyError {
169
+ return {
170
+ title: 'Authentication Failed',
171
+ message: 'Your API key is invalid or expired.',
172
+ recovery: [
173
+ 'Go to codebakers.ai/dashboard',
174
+ 'Copy your API key (starts with cb_)',
175
+ 'Run: codebakers setup',
176
+ ],
177
+ helpUrl: 'https://codebakers.ai/docs/getting-started',
178
+ };
179
+ }
180
+
181
+ export function getSubscriptionError(): FriendlyError {
182
+ return {
183
+ title: 'Subscription Required',
184
+ message: 'This feature requires an active subscription.',
185
+ recovery: [
186
+ 'Visit codebakers.ai/pricing to see plans',
187
+ 'Start a free trial with: codebakers setup',
188
+ 'Contact support if you believe this is an error',
189
+ ],
190
+ helpUrl: 'https://codebakers.ai/pricing',
191
+ };
192
+ }