@codebakers/cli 3.2.0 → 3.3.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.
@@ -89,6 +89,30 @@ class CodeBakersServer {
89
89
  }
90
90
  return {};
91
91
  }
92
+ /**
93
+ * Confirm download to server (non-blocking analytics)
94
+ */
95
+ async confirmDownload(version, moduleCount) {
96
+ try {
97
+ const headers = {
98
+ 'Content-Type': 'application/json',
99
+ ...this.getAuthHeaders(),
100
+ };
101
+ await fetch(`${this.apiUrl}/api/content/confirm`, {
102
+ method: 'POST',
103
+ headers,
104
+ body: JSON.stringify({
105
+ version,
106
+ moduleCount,
107
+ cliVersion: (0, api_js_1.getCliVersion)(),
108
+ command: 'auto-update',
109
+ }),
110
+ });
111
+ }
112
+ catch {
113
+ // Silently ignore - this is just for analytics
114
+ }
115
+ }
92
116
  /**
93
117
  * Automatically check for and apply pattern updates
94
118
  * Runs silently in background - no user intervention needed
@@ -178,6 +202,19 @@ class CodeBakersServer {
178
202
  cliVersion: (0, api_js_1.getCliVersion)(),
179
203
  };
180
204
  fs.writeFileSync(versionPath, JSON.stringify(versionInfo, null, 2));
205
+ // Write notification file for AI to read and show to user
206
+ const notificationPath = path.join(claudeDir, '.update-notification.json');
207
+ const notification = {
208
+ type: 'patterns_updated',
209
+ previousVersion: installed?.version || 'unknown',
210
+ newVersion: content.version,
211
+ moduleCount,
212
+ updatedAt: new Date().toISOString(),
213
+ message: `CodeBakers patterns have been automatically updated from v${installed?.version || 'unknown'} to v${content.version} (${moduleCount} modules). Your AI tools now have the latest production patterns.`,
214
+ };
215
+ fs.writeFileSync(notificationPath, JSON.stringify(notification, null, 2));
216
+ // Confirm to server (non-blocking, fire-and-forget)
217
+ this.confirmDownload(content.version, moduleCount).catch(() => { });
181
218
  this.autoUpdateChecked = true;
182
219
  this.autoUpdateInProgress = false;
183
220
  // Log success (visible in MCP logs)
@@ -902,6 +939,14 @@ class CodeBakersServer {
902
939
  required: ['request'],
903
940
  },
904
941
  },
942
+ {
943
+ name: 'check_update_notification',
944
+ description: 'ALWAYS CALL THIS AT THE START OF EACH SESSION. Checks if CodeBakers patterns were recently auto-updated and returns a notification message to show the user. If an update occurred, tell the user about it with the returned message. After showing, the notification is cleared.',
945
+ inputSchema: {
946
+ type: 'object',
947
+ properties: {},
948
+ },
949
+ },
905
950
  ],
906
951
  }));
907
952
  // Handle tool calls
@@ -987,6 +1032,8 @@ class CodeBakersServer {
987
1032
  return this.handleAddPage(args);
988
1033
  case 'add_api_route':
989
1034
  return this.handleAddApiRoute(args);
1035
+ case 'check_update_notification':
1036
+ return this.handleCheckUpdateNotification();
990
1037
  default:
991
1038
  throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
992
1039
  }
@@ -4266,6 +4313,40 @@ export default ${asyncKeyword}function ${pageName}Page() {${authCheck}
4266
4313
  }],
4267
4314
  };
4268
4315
  }
4316
+ /**
4317
+ * Check for update notifications and return message to show user
4318
+ */
4319
+ async handleCheckUpdateNotification() {
4320
+ const cwd = process.cwd();
4321
+ const notificationPath = path.join(cwd, '.claude', '.update-notification.json');
4322
+ try {
4323
+ if (!fs.existsSync(notificationPath)) {
4324
+ return {
4325
+ content: [{
4326
+ type: 'text',
4327
+ text: 'No update notification.',
4328
+ }],
4329
+ };
4330
+ }
4331
+ const notification = JSON.parse(fs.readFileSync(notificationPath, 'utf-8'));
4332
+ // Delete the notification file after reading (so it only shows once)
4333
+ fs.unlinkSync(notificationPath);
4334
+ return {
4335
+ content: [{
4336
+ type: 'text',
4337
+ text: `🍪 **CodeBakers Update**\n\n${notification.message}\n\n**Previous version:** ${notification.previousVersion}\n**New version:** ${notification.newVersion}\n**Modules:** ${notification.moduleCount}\n**Updated:** ${new Date(notification.updatedAt).toLocaleString()}`,
4338
+ }],
4339
+ };
4340
+ }
4341
+ catch {
4342
+ return {
4343
+ content: [{
4344
+ type: 'text',
4345
+ text: 'No update notification.',
4346
+ }],
4347
+ };
4348
+ }
4349
+ }
4269
4350
  parseApiRouteRequest(request) {
4270
4351
  const lower = request.toLowerCase();
4271
4352
  // Detect webhook
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "3.2.0",
3
+ "version": "3.3.1",
4
4
  "description": "CodeBakers CLI - Production patterns for AI-assisted development",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -15,7 +15,8 @@
15
15
  "mcp": "tsx src/mcp/server.ts",
16
16
  "test": "vitest run",
17
17
  "test:watch": "vitest",
18
- "test:coverage": "vitest run --coverage"
18
+ "test:coverage": "vitest run --coverage",
19
+ "postpublish": "node scripts/register-version.js"
19
20
  },
20
21
  "keywords": [
21
22
  "codebakers",
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Automatically registers a new CLI version with the CodeBakers server
5
+ * Called automatically after npm publish via postpublish hook
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const API_URL = process.env.CODEBAKERS_API_URL || 'https://codebakers.ai';
12
+ const ADMIN_API_KEY = process.env.CODEBAKERS_ADMIN_KEY;
13
+
14
+ async function registerVersion() {
15
+ // Read version from package.json
16
+ const packagePath = path.join(__dirname, '..', 'package.json');
17
+ const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
18
+ const version = pkg.version;
19
+
20
+ console.log(`\n📦 Registering CLI version ${version} with CodeBakers server...\n`);
21
+
22
+ if (!ADMIN_API_KEY) {
23
+ console.log('⚠️ CODEBAKERS_ADMIN_KEY not set - skipping auto-registration');
24
+ console.log(' You can manually add this version in Admin → CLI Versions\n');
25
+ return;
26
+ }
27
+
28
+ try {
29
+ // Read changelog if it exists
30
+ let changelog = '';
31
+ const changelogPath = path.join(__dirname, '..', 'CHANGELOG.md');
32
+ if (fs.existsSync(changelogPath)) {
33
+ const content = fs.readFileSync(changelogPath, 'utf-8');
34
+ // Extract the latest version's changes
35
+ const match = content.match(/## \[?\d+\.\d+\.\d+\]?[^\n]*\n([\s\S]*?)(?=## \[?\d+\.\d+\.\d+|$)/);
36
+ if (match) {
37
+ changelog = match[1].trim().slice(0, 2000); // Limit length
38
+ }
39
+ }
40
+
41
+ const response = await fetch(`${API_URL}/api/cli/register-version`, {
42
+ method: 'POST',
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ 'Authorization': `Bearer ${ADMIN_API_KEY}`,
46
+ },
47
+ body: JSON.stringify({
48
+ version,
49
+ npmTag: 'latest',
50
+ changelog,
51
+ minNodeVersion: '18',
52
+ }),
53
+ });
54
+
55
+ if (response.ok) {
56
+ const data = await response.json();
57
+ console.log(`✅ Version ${version} registered successfully!`);
58
+ console.log(` Status: ${data.data?.version?.status || 'draft'}`);
59
+ console.log(`\n Next steps:`);
60
+ console.log(` 1. Test the version`);
61
+ console.log(` 2. Go to Admin → CLI Versions`);
62
+ console.log(` 3. Promote to "testing" then "stable"`);
63
+ console.log(` 4. Enable auto-update when ready\n`);
64
+ } else {
65
+ const error = await response.json().catch(() => ({}));
66
+ if (error.error?.includes('already exists')) {
67
+ console.log(`ℹ️ Version ${version} already registered\n`);
68
+ } else {
69
+ console.log(`⚠️ Failed to register: ${error.error || response.statusText}`);
70
+ console.log(` You can manually add this version in Admin → CLI Versions\n`);
71
+ }
72
+ }
73
+ } catch (error) {
74
+ console.log(`⚠️ Could not reach server: ${error.message}`);
75
+ console.log(` You can manually add this version in Admin → CLI Versions\n`);
76
+ }
77
+ }
78
+
79
+ registerVersion();
@@ -39,6 +39,57 @@ interface GoOptions {
39
39
  verbose?: boolean;
40
40
  }
41
41
 
42
+ interface ConfirmData {
43
+ version: string;
44
+ moduleCount: number;
45
+ cliVersion?: string;
46
+ command: string;
47
+ projectName?: string;
48
+ }
49
+
50
+ interface AuthInfo {
51
+ apiKey?: string;
52
+ trialId?: string;
53
+ }
54
+
55
+ /**
56
+ * Get CLI version from package.json
57
+ */
58
+ function getCliVersion(): string {
59
+ try {
60
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
61
+ const pkg = require('../../package.json');
62
+ return pkg.version || '0.0.0';
63
+ } catch {
64
+ return '0.0.0';
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Confirm download to server (non-blocking, fire-and-forget)
70
+ */
71
+ async function confirmDownload(apiUrl: string, auth: AuthInfo, data: ConfirmData): Promise<void> {
72
+ try {
73
+ const headers: Record<string, string> = {
74
+ 'Content-Type': 'application/json',
75
+ };
76
+ if (auth.apiKey) {
77
+ headers['Authorization'] = `Bearer ${auth.apiKey}`;
78
+ }
79
+ if (auth.trialId) {
80
+ headers['X-Trial-ID'] = auth.trialId;
81
+ }
82
+
83
+ await fetch(`${apiUrl}/api/content/confirm`, {
84
+ method: 'POST',
85
+ headers,
86
+ body: JSON.stringify(data),
87
+ });
88
+ } catch {
89
+ // Silently ignore - this is just for analytics
90
+ }
91
+ }
92
+
42
93
  function log(message: string, options?: GoOptions): void {
43
94
  if (options?.verbose) {
44
95
  console.log(chalk.gray(` [verbose] ${message}`));
@@ -317,7 +368,7 @@ async function installPatternsWithApiKey(apiKey: string, options: GoOptions = {}
317
368
  log('Response OK, parsing JSON...', options);
318
369
  const content: ContentResponse = await response.json();
319
370
  log(`Received version: ${content.version}, modules: ${Object.keys(content.modules || {}).length}`, options);
320
- await writePatternFiles(cwd, content, spinner, options);
371
+ await writePatternFiles(cwd, content, spinner, options, { apiKey });
321
372
 
322
373
  } catch (error) {
323
374
  log(`Error: ${error instanceof Error ? error.message : String(error)}`, options);
@@ -363,13 +414,13 @@ async function installPatterns(trialId: string, options: GoOptions = {}): Promis
363
414
 
364
415
  const content: ContentResponse = await publicResponse.json();
365
416
  log(`Received version: ${content.version}, modules: ${Object.keys(content.modules || {}).length}`, options);
366
- await writePatternFiles(cwd, content, spinner, options);
417
+ await writePatternFiles(cwd, content, spinner, options, { trialId });
367
418
  return;
368
419
  }
369
420
 
370
421
  const content: ContentResponse = await response.json();
371
422
  log(`Received version: ${content.version}, modules: ${Object.keys(content.modules || {}).length}`, options);
372
- await writePatternFiles(cwd, content, spinner, options);
423
+ await writePatternFiles(cwd, content, spinner, options, { trialId });
373
424
 
374
425
  } catch (error) {
375
426
  log(`Error: ${error instanceof Error ? error.message : String(error)}`, options);
@@ -378,7 +429,13 @@ async function installPatterns(trialId: string, options: GoOptions = {}): Promis
378
429
  }
379
430
  }
380
431
 
381
- async function writePatternFiles(cwd: string, content: ContentResponse, spinner: ReturnType<typeof ora>, options: GoOptions = {}): Promise<void> {
432
+ async function writePatternFiles(
433
+ cwd: string,
434
+ content: ContentResponse,
435
+ spinner: ReturnType<typeof ora>,
436
+ options: GoOptions = {},
437
+ auth?: AuthInfo
438
+ ): Promise<void> {
382
439
  log(`Writing pattern files to ${cwd}...`, options);
383
440
  // Check if patterns already exist
384
441
  const claudeMdPath = join(cwd, 'CLAUDE.md');
@@ -393,7 +450,8 @@ async function writePatternFiles(cwd: string, content: ContentResponse, spinner:
393
450
  }
394
451
 
395
452
  // Write pattern modules to .claude/
396
- if (content.modules && Object.keys(content.modules).length > 0) {
453
+ const moduleCount = Object.keys(content.modules || {}).length;
454
+ if (content.modules && moduleCount > 0) {
397
455
  const modulesDir = join(cwd, '.claude');
398
456
  if (!existsSync(modulesDir)) {
399
457
  mkdirSync(modulesDir, { recursive: true });
@@ -415,5 +473,16 @@ async function writePatternFiles(cwd: string, content: ContentResponse, spinner:
415
473
  }
416
474
 
417
475
  spinner.succeed(`CodeBakers patterns installed (v${content.version})`);
418
- console.log(chalk.gray(` ${Object.keys(content.modules || {}).length} pattern modules ready\n`));
476
+ console.log(chalk.gray(` ${moduleCount} pattern modules ready\n`));
477
+
478
+ // Confirm download to server (non-blocking)
479
+ if (auth) {
480
+ const apiUrl = getApiUrl();
481
+ confirmDownload(apiUrl, auth, {
482
+ version: content.version,
483
+ moduleCount,
484
+ cliVersion: getCliVersion(),
485
+ command: 'go',
486
+ }).catch(() => {}); // Silently ignore
487
+ }
419
488
  }
@@ -11,6 +11,32 @@ interface ContentResponse {
11
11
  modules: Record<string, string>;
12
12
  }
13
13
 
14
+ interface ConfirmData {
15
+ version: string;
16
+ moduleCount: number;
17
+ cliVersion: string;
18
+ command: string;
19
+ projectName?: string;
20
+ }
21
+
22
+ /**
23
+ * Confirm download to server (non-blocking, fire-and-forget)
24
+ */
25
+ async function confirmDownload(apiUrl: string, apiKey: string, data: ConfirmData): Promise<void> {
26
+ try {
27
+ await fetch(`${apiUrl}/api/content/confirm`, {
28
+ method: 'POST',
29
+ headers: {
30
+ 'Content-Type': 'application/json',
31
+ 'Authorization': `Bearer ${apiKey}`,
32
+ },
33
+ body: JSON.stringify(data),
34
+ });
35
+ } catch {
36
+ // Silently ignore - this is just for analytics
37
+ }
38
+ }
39
+
14
40
  /**
15
41
  * Upgrade CodeBakers patterns to the latest version
16
42
  */
@@ -101,6 +127,14 @@ export async function upgrade(): Promise<void> {
101
127
  writeFileSync(join(claudeDir, '.version.json'), JSON.stringify(versionInfo, null, 2));
102
128
  console.log(chalk.green(' ✓ Version info saved'));
103
129
 
130
+ // Confirm download to server (non-blocking)
131
+ confirmDownload(apiUrl, apiKey, {
132
+ version: content.version,
133
+ moduleCount,
134
+ cliVersion: getCliVersion(),
135
+ command: 'upgrade',
136
+ }).catch(() => {}); // Silently ignore confirmation failures
137
+
104
138
  console.log(chalk.green(`\n ✅ Upgraded to patterns v${content.version}!\n`));
105
139
 
106
140
  // Show what's new if available
package/src/config.ts CHANGED
@@ -116,9 +116,12 @@ interface ConfigSchema {
116
116
  lastKeySync: string | null; // ISO date of last sync with server
117
117
  // Trial state (for zero-friction onboarding)
118
118
  trial: TrialState | null;
119
- // Update notification cache
119
+ // Update notification cache (CLI updates)
120
120
  lastUpdateCheck: string | null; // ISO date of last npm registry check
121
121
  latestKnownVersion: string | null; // Cached latest version from npm
122
+ // Pattern auto-update cache
123
+ lastPatternCheck: string | null; // ISO date of last pattern version check
124
+ latestPatternVersion: string | null; // Cached latest pattern version from server
122
125
  }
123
126
 
124
127
  // Create default service keys object with all keys set to null
@@ -128,7 +131,7 @@ const defaultServiceKeys: ServiceKeys = Object.fromEntries(
128
131
 
129
132
  const config = new Conf<ConfigSchema>({
130
133
  projectName: 'codebakers',
131
- projectVersion: '1.8.0',
134
+ projectVersion: '1.9.0',
132
135
  defaults: {
133
136
  apiKey: null,
134
137
  apiUrl: 'https://codebakers.ai',
@@ -138,6 +141,8 @@ const config = new Conf<ConfigSchema>({
138
141
  trial: null,
139
142
  lastUpdateCheck: null,
140
143
  latestKnownVersion: null,
144
+ lastPatternCheck: null,
145
+ latestPatternVersion: null,
141
146
  },
142
147
  // Migration to add new keys when upgrading from old version
143
148
  migrations: {
@@ -160,6 +165,15 @@ const config = new Conf<ConfigSchema>({
160
165
  store.set('trial', null);
161
166
  }
162
167
  },
168
+ '1.9.0': (store) => {
169
+ // Add pattern auto-update fields if not present
170
+ if (!store.has('lastPatternCheck')) {
171
+ store.set('lastPatternCheck', null);
172
+ }
173
+ if (!store.has('latestPatternVersion')) {
174
+ store.set('latestPatternVersion', null);
175
+ }
176
+ },
163
177
  },
164
178
  });
165
179
 
@@ -528,5 +542,42 @@ export function setCachedUpdateInfo(latestVersion: string): void {
528
542
  * Get the current CLI version from package.json
529
543
  */
530
544
  export function getCliVersion(): string {
531
- return '3.2.0'; // Keep in sync with package.json
545
+ return '3.3.0'; // Keep in sync with package.json
546
+ }
547
+
548
+ // ============================================
549
+ // Pattern Auto-Update Cache
550
+ // ============================================
551
+
552
+ const PATTERN_CHECK_INTERVAL_HOURS = 6; // Check more frequently than CLI
553
+
554
+ /**
555
+ * Get cached pattern version info if still valid (within 6 hours)
556
+ */
557
+ export function getCachedPatternInfo(): { latestVersion: string; checkedAt: string } | null {
558
+ const lastCheck = config.get('lastPatternCheck');
559
+ const latestVersion = config.get('latestPatternVersion');
560
+
561
+ if (!lastCheck || !latestVersion) return null;
562
+
563
+ const hoursSinceCheck = (Date.now() - new Date(lastCheck).getTime()) / (1000 * 60 * 60);
564
+ if (hoursSinceCheck > PATTERN_CHECK_INTERVAL_HOURS) return null;
565
+
566
+ return { latestVersion, checkedAt: lastCheck };
567
+ }
568
+
569
+ /**
570
+ * Cache the latest pattern version from server
571
+ */
572
+ export function setCachedPatternInfo(latestVersion: string): void {
573
+ config.set('lastPatternCheck', new Date().toISOString());
574
+ config.set('latestPatternVersion', latestVersion);
575
+ }
576
+
577
+ /**
578
+ * Clear pattern cache to force a fresh check
579
+ */
580
+ export function clearPatternCache(): void {
581
+ config.set('lastPatternCheck', null);
582
+ config.set('latestPatternVersion', null);
532
583
  }