@codebakers/cli 3.3.18 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "3.3.18",
3
+ "version": "3.4.1",
4
4
  "description": "CodeBakers CLI - Production patterns for AI-assisted development",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -9,10 +9,12 @@ import {
9
9
  setTrialState,
10
10
  getApiUrl,
11
11
  getApiKey,
12
+ setApiKey,
12
13
  isTrialExpired,
13
14
  getTrialDaysRemaining,
14
15
  type TrialState,
15
16
  } from '../config.js';
17
+ import { validateApiKey } from '../lib/api.js';
16
18
  import { getDeviceFingerprint } from '../lib/fingerprint.js';
17
19
 
18
20
  function prompt(question: string): Promise<string> {
@@ -98,6 +100,7 @@ function log(message: string, options?: GoOptions): void {
98
100
 
99
101
  /**
100
102
  * Zero-friction entry point - start using CodeBakers instantly
103
+ * Single command for both trial and paid users
101
104
  */
102
105
  export async function go(options: GoOptions = {}): Promise<void> {
103
106
  log('Starting go command...', options);
@@ -107,21 +110,22 @@ export async function go(options: GoOptions = {}): Promise<void> {
107
110
  console.log(chalk.blue(`
108
111
  ╔═══════════════════════════════════════════════════════════╗
109
112
  ║ ║
110
- ║ ${chalk.bold.white('CodeBakers - Zero Setup Required')}
113
+ ║ ${chalk.bold.white('CodeBakers - Get Started')}
111
114
  ║ ║
112
115
  ╚═══════════════════════════════════════════════════════════╝
113
116
  `));
114
117
 
115
118
  // Check if user already has an API key (paid user)
116
119
  log('Checking for existing API key...', options);
117
- const apiKey = getApiKey();
118
- if (apiKey) {
119
- log(`Found API key: ${apiKey.substring(0, 8)}...`, options);
120
- console.log(chalk.green(' ✓ You\'re already logged in with an API key!\n'));
120
+ const existingApiKey = getApiKey();
121
+ if (existingApiKey) {
122
+ log(`Found API key: ${existingApiKey.substring(0, 8)}...`, options);
123
+ console.log(chalk.green(' ✓ You\'re already logged in!\n'));
121
124
 
122
- // Still install patterns if not already installed
123
- await installPatternsWithApiKey(apiKey, options);
125
+ // Install patterns if not already installed
126
+ await installPatternsWithApiKey(existingApiKey, options);
124
127
  await configureMCP(options);
128
+ await showSuccessAndRestart();
125
129
  return;
126
130
  }
127
131
  log('No API key found, checking trial state...', options);
@@ -140,8 +144,8 @@ export async function go(options: GoOptions = {}): Promise<void> {
140
144
 
141
145
  // Install patterns if not already installed
142
146
  await installPatterns(existingTrial.trialId, options);
143
-
144
147
  await configureMCP(options);
148
+ await showSuccessAndRestart();
145
149
  return;
146
150
  }
147
151
 
@@ -149,15 +153,31 @@ export async function go(options: GoOptions = {}): Promise<void> {
149
153
  if (existingTrial && isTrialExpired()) {
150
154
  console.log(chalk.yellow(' ⚠️ Your trial has expired.\n'));
151
155
 
152
- if (existingTrial.stage === 'anonymous') {
153
- console.log(chalk.white(' Extend your trial for 7 more days with GitHub:\n'));
154
- console.log(chalk.cyan(' codebakers extend\n'));
155
- console.log(chalk.gray(' Or upgrade to Pro ($49/month):\n'));
156
- console.log(chalk.cyan(' codebakers upgrade\n'));
156
+ // Offer to login with API key or extend
157
+ console.log(chalk.white(' Options:\n'));
158
+ console.log(chalk.cyan(' [1] Login with API key') + chalk.gray(' (I have an account)'));
159
+ console.log(chalk.cyan(' [2] Extend trial') + chalk.gray(' (7 more days with GitHub)\n'));
160
+
161
+ const choice = await prompt(chalk.gray(' Enter 1 or 2: '));
162
+
163
+ if (choice === '1') {
164
+ await handleApiKeyLogin(options);
165
+ return;
157
166
  } else {
158
- console.log(chalk.white(' Ready to upgrade? $49/month for unlimited access:\n'));
159
- console.log(chalk.cyan(' codebakers upgrade\n'));
167
+ console.log(chalk.cyan('\n Run: codebakers extend\n'));
168
+ return;
160
169
  }
170
+ }
171
+
172
+ // New user - ask how they want to proceed
173
+ console.log(chalk.white(' How would you like to get started?\n'));
174
+ console.log(chalk.cyan(' [1] Start free 7-day trial') + chalk.gray(' (no signup required)'));
175
+ console.log(chalk.cyan(' [2] Login with API key') + chalk.gray(' (I have an account)\n'));
176
+
177
+ const choice = await prompt(chalk.gray(' Enter 1 or 2: '));
178
+
179
+ if (choice === '2') {
180
+ await handleApiKeyLogin(options);
161
181
  return;
162
182
  }
163
183
 
@@ -235,19 +255,8 @@ export async function go(options: GoOptions = {}): Promise<void> {
235
255
  // Configure MCP
236
256
  await configureMCP(options);
237
257
 
238
- // Show success message
239
- console.log(chalk.green(`
240
- ╔═══════════════════════════════════════════════════════════╗
241
- ║ ✅ CodeBakers is ready! ║
242
- ║ ║
243
- ║ ${chalk.white('Your 7-day free trial has started.')} ║
244
- ║ ║
245
- ║ ${chalk.gray('Try: "Build me a todo app with authentication"')} ║
246
- ╚═══════════════════════════════════════════════════════════╝
247
- `));
248
-
249
- // Attempt auto-restart Claude Code
250
- await attemptAutoRestart();
258
+ // Show success and restart
259
+ await showSuccessAndRestart();
251
260
 
252
261
  } catch (error) {
253
262
  spinner.fail('Failed to start trial');
@@ -289,10 +298,61 @@ async function configureMCP(options: GoOptions = {}): Promise<void> {
289
298
  }
290
299
  }
291
300
 
292
- async function attemptAutoRestart(): Promise<void> {
301
+ /**
302
+ * Handle API key login flow (for paid users)
303
+ */
304
+ async function handleApiKeyLogin(options: GoOptions = {}): Promise<void> {
305
+ console.log(chalk.white('\n Enter your API key\n'));
306
+ console.log(chalk.gray(' Find it at: https://codebakers.ai/dashboard\n'));
307
+
308
+ const apiKey = await prompt(chalk.cyan(' API Key: '));
309
+
310
+ if (!apiKey) {
311
+ console.log(chalk.red('\n API key is required.\n'));
312
+ return;
313
+ }
314
+
315
+ const spinner = ora('Validating API key...').start();
316
+
317
+ try {
318
+ await validateApiKey(apiKey);
319
+ spinner.succeed('API key validated');
320
+
321
+ // Save API key
322
+ setApiKey(apiKey);
323
+ console.log(chalk.green(' ✓ Logged in successfully!\n'));
324
+
325
+ // Install patterns
326
+ await installPatternsWithApiKey(apiKey, options);
327
+
328
+ // Configure MCP
329
+ await configureMCP(options);
330
+
331
+ // Show success
332
+ await showSuccessAndRestart();
333
+
334
+ } catch (error) {
335
+ spinner.fail('Invalid API key');
336
+ console.log(chalk.red('\n Could not validate API key.'));
337
+ console.log(chalk.gray(' Check your key at: https://codebakers.ai/dashboard\n'));
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Show success message and offer to restart
343
+ */
344
+ async function showSuccessAndRestart(): Promise<void> {
293
345
  const cwd = process.cwd();
294
346
 
295
- console.log(chalk.yellow('\n ⚠️ RESTART REQUIRED\n'));
347
+ console.log(chalk.green(`
348
+ ╔═══════════════════════════════════════════════════════════╗
349
+ ║ ✅ CodeBakers is ready! ║
350
+ ║ ║
351
+ ║ ${chalk.gray('Try: "Build me a todo app with authentication"')} ║
352
+ ╚═══════════════════════════════════════════════════════════╝
353
+ `));
354
+
355
+ console.log(chalk.yellow(' ⚠️ RESTART REQUIRED\n'));
296
356
  console.log(chalk.gray(' Claude Code needs to restart to load CodeBakers.\n'));
297
357
 
298
358
  const answer = await prompt(chalk.cyan(' Restart Claude Code now? (Y/n): '));
@@ -309,7 +369,6 @@ async function attemptAutoRestart(): Promise<void> {
309
369
  const isWindows = process.platform === 'win32';
310
370
 
311
371
  if (isWindows) {
312
- // On Windows, spawn a new Claude process detached and exit
313
372
  spawn('cmd', ['/c', 'start', 'claude'], {
314
373
  cwd,
315
374
  detached: true,
@@ -317,7 +376,6 @@ async function attemptAutoRestart(): Promise<void> {
317
376
  shell: true,
318
377
  }).unref();
319
378
  } else {
320
- // On Mac/Linux, spawn claude in new terminal
321
379
  spawn('claude', [], {
322
380
  cwd,
323
381
  detached: true,
@@ -329,13 +387,10 @@ async function attemptAutoRestart(): Promise<void> {
329
387
  console.log(chalk.green(' ✓ Claude Code is restarting...\n'));
330
388
  console.log(chalk.gray(' This terminal will close. Claude Code will open in a new window.\n'));
331
389
 
332
- // Give the spawn a moment to start
333
390
  await new Promise(resolve => setTimeout(resolve, 1000));
334
-
335
- // Exit this process
336
391
  process.exit(0);
337
392
 
338
- } catch (error) {
393
+ } catch {
339
394
  console.log(chalk.yellow(' Could not auto-restart. Please restart Claude Code manually.\n'));
340
395
  }
341
396
  }
@@ -0,0 +1,234 @@
1
+ import chalk from 'chalk';
2
+ import { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ const PRE_COMMIT_SCRIPT = `#!/bin/sh
6
+ # CodeBakers Pre-Commit Hook - Session Enforcement
7
+ # Blocks commits unless AI called discover_patterns and validate_complete
8
+
9
+ # Run the validation script
10
+ node "$(dirname "$0")/validate-session.js"
11
+ exit $?
12
+ `;
13
+
14
+ const VALIDATE_SESSION_SCRIPT = `#!/usr/bin/env node
15
+ /**
16
+ * CodeBakers Pre-Commit Validation
17
+ * Blocks commits unless a valid session exists
18
+ */
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+
23
+ const RED = '\\x1b[31m';
24
+ const GREEN = '\\x1b[32m';
25
+ const YELLOW = '\\x1b[33m';
26
+ const CYAN = '\\x1b[36m';
27
+ const RESET = '\\x1b[0m';
28
+
29
+ function log(color, message) {
30
+ console.log(color + message + RESET);
31
+ }
32
+
33
+ async function validateSession() {
34
+ const cwd = process.cwd();
35
+ const stateFile = path.join(cwd, '.codebakers.json');
36
+
37
+ // Check if this is a CodeBakers project
38
+ if (!fs.existsSync(stateFile)) {
39
+ return { valid: true, reason: 'not-codebakers-project' };
40
+ }
41
+
42
+ let state;
43
+ try {
44
+ state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
45
+ } catch (error) {
46
+ return { valid: false, reason: 'invalid-state-file' };
47
+ }
48
+
49
+ // Check if v6.0 server-enforced mode
50
+ if (!state.serverEnforced) {
51
+ return { valid: true, reason: 'legacy-project' };
52
+ }
53
+
54
+ // Check for session token (means discover_patterns was called)
55
+ const sessionToken = state.currentSessionToken;
56
+ if (!sessionToken) {
57
+ // Check if there's a recent passed validation
58
+ const lastValidation = state.lastValidation;
59
+ if (!lastValidation || !lastValidation.passed) {
60
+ return {
61
+ valid: false,
62
+ reason: 'no-session',
63
+ message: 'No active CodeBakers session.\\nAI must call discover_patterns before writing code.'
64
+ };
65
+ }
66
+ }
67
+
68
+ // Check session expiry
69
+ const sessionExpiry = state.sessionExpiresAt;
70
+ if (sessionExpiry && new Date(sessionExpiry) < new Date()) {
71
+ return {
72
+ valid: false,
73
+ reason: 'session-expired',
74
+ message: 'CodeBakers session has expired.\\nAI must call discover_patterns again.'
75
+ };
76
+ }
77
+
78
+ // Check if validation was completed
79
+ const lastValidation = state.lastValidation;
80
+ if (!lastValidation) {
81
+ return {
82
+ valid: false,
83
+ reason: 'no-validation',
84
+ message: 'No validation completed.\\nAI must call validate_complete before committing.'
85
+ };
86
+ }
87
+
88
+ // Check if validation passed
89
+ if (!lastValidation.passed) {
90
+ const issues = lastValidation.issues?.map(i => i.message || i).join(', ') || 'Unknown issues';
91
+ return {
92
+ valid: false,
93
+ reason: 'validation-failed',
94
+ message: 'Validation failed: ' + issues + '\\nAI must fix issues and call validate_complete again.'
95
+ };
96
+ }
97
+
98
+ // Check if validation is recent (within last 30 minutes)
99
+ const validationTime = new Date(lastValidation.timestamp);
100
+ const thirtyMinutesAgo = new Date(Date.now() - 30 * 60 * 1000);
101
+ if (validationTime < thirtyMinutesAgo) {
102
+ return {
103
+ valid: false,
104
+ reason: 'validation-stale',
105
+ message: 'Validation is stale (older than 30 minutes).\\nAI must call validate_complete again.'
106
+ };
107
+ }
108
+
109
+ return { valid: true, reason: 'session-valid' };
110
+ }
111
+
112
+ async function main() {
113
+ console.log('');
114
+ log(CYAN, ' 🍪 CodeBakers Pre-Commit Validation');
115
+ console.log('');
116
+
117
+ const result = await validateSession();
118
+
119
+ if (result.valid) {
120
+ if (result.reason === 'not-codebakers-project') {
121
+ log(GREEN, ' ✓ Not a CodeBakers project - commit allowed');
122
+ } else if (result.reason === 'legacy-project') {
123
+ log(GREEN, ' ✓ Legacy project (pre-6.0) - commit allowed');
124
+ } else {
125
+ log(GREEN, ' ✓ Valid CodeBakers session - commit allowed');
126
+ }
127
+ console.log('');
128
+ process.exit(0);
129
+ } else {
130
+ log(RED, ' ✗ Commit blocked: ' + result.reason);
131
+ console.log('');
132
+ if (result.message) {
133
+ log(YELLOW, ' ' + result.message.split('\\n').join('\\n '));
134
+ }
135
+ console.log('');
136
+ log(CYAN, ' How to fix:');
137
+ log(RESET, ' 1. AI must call discover_patterns before writing code');
138
+ log(RESET, ' 2. AI must call validate_complete before saying "done"');
139
+ log(RESET, ' 3. Both tools must pass for commits to be allowed');
140
+ console.log('');
141
+ log(YELLOW, ' To bypass (not recommended): git commit --no-verify');
142
+ console.log('');
143
+ process.exit(1);
144
+ }
145
+ }
146
+
147
+ main().catch(error => {
148
+ log(RED, ' Error: ' + error.message);
149
+ process.exit(1);
150
+ });
151
+ `;
152
+
153
+ export async function installPrecommit(): Promise<void> {
154
+ console.log(chalk.blue('\n CodeBakers Pre-Commit Hook Installation\n'));
155
+
156
+ const cwd = process.cwd();
157
+
158
+ // Check if this is a git repository
159
+ const gitDir = join(cwd, '.git');
160
+ if (!existsSync(gitDir)) {
161
+ console.log(chalk.red(' ✗ Not a git repository'));
162
+ console.log(chalk.gray(' Initialize git first: git init\n'));
163
+ process.exit(1);
164
+ }
165
+
166
+ // Check if this is a CodeBakers project
167
+ const stateFile = join(cwd, '.codebakers.json');
168
+ if (!existsSync(stateFile)) {
169
+ console.log(chalk.yellow(' ⚠️ No .codebakers.json found'));
170
+ console.log(chalk.gray(' Run codebakers upgrade first to enable server enforcement\n'));
171
+ }
172
+
173
+ // Create hooks directory if it doesn't exist
174
+ const hooksDir = join(gitDir, 'hooks');
175
+ if (!existsSync(hooksDir)) {
176
+ mkdirSync(hooksDir, { recursive: true });
177
+ }
178
+
179
+ // Write the pre-commit hook
180
+ const preCommitPath = join(hooksDir, 'pre-commit');
181
+ writeFileSync(preCommitPath, PRE_COMMIT_SCRIPT);
182
+
183
+ // Make it executable (Unix only, Windows ignores this)
184
+ try {
185
+ chmodSync(preCommitPath, '755');
186
+ } catch {
187
+ // Windows doesn't support chmod
188
+ }
189
+
190
+ console.log(chalk.green(' ✓ Created pre-commit hook'));
191
+
192
+ // Write the validation script
193
+ const validatePath = join(hooksDir, 'validate-session.js');
194
+ writeFileSync(validatePath, VALIDATE_SESSION_SCRIPT);
195
+
196
+ console.log(chalk.green(' ✓ Created validation script'));
197
+
198
+ // Check if husky is being used
199
+ const huskyDir = join(cwd, '.husky');
200
+ if (existsSync(huskyDir)) {
201
+ // Also install in husky
202
+ const huskyPreCommit = join(huskyDir, 'pre-commit');
203
+ let huskyContent = '';
204
+
205
+ if (existsSync(huskyPreCommit)) {
206
+ huskyContent = readFileSync(huskyPreCommit, 'utf-8');
207
+ if (!huskyContent.includes('validate-session')) {
208
+ huskyContent += '\n# CodeBakers session enforcement\nnode .git/hooks/validate-session.js\n';
209
+ writeFileSync(huskyPreCommit, huskyContent);
210
+ console.log(chalk.green(' ✓ Added to existing husky pre-commit'));
211
+ } else {
212
+ console.log(chalk.gray(' ✓ Husky hook already configured'));
213
+ }
214
+ } else {
215
+ huskyContent = '#!/usr/bin/env sh\n. "$(dirname -- "$0")/_/husky.sh"\n\n# CodeBakers session enforcement\nnode .git/hooks/validate-session.js\n';
216
+ writeFileSync(huskyPreCommit, huskyContent);
217
+ try {
218
+ chmodSync(huskyPreCommit, '755');
219
+ } catch {
220
+ // Windows
221
+ }
222
+ console.log(chalk.green(' ✓ Created husky pre-commit hook'));
223
+ }
224
+ }
225
+
226
+ console.log(chalk.green('\n ✅ Pre-commit hook installed!\n'));
227
+ console.log(chalk.cyan(' What this does:'));
228
+ console.log(chalk.gray(' - Blocks commits unless AI called discover_patterns'));
229
+ console.log(chalk.gray(' - Blocks commits unless AI called validate_complete'));
230
+ console.log(chalk.gray(' - Requires validation to pass before committing'));
231
+ console.log(chalk.gray(' - Validation expires after 30 minutes\n'));
232
+ console.log(chalk.yellow(' To bypass (not recommended):'));
233
+ console.log(chalk.gray(' git commit --no-verify\n'));
234
+ }