@codebakers/cli 3.1.0 → 3.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/config.d.ts CHANGED
@@ -43,6 +43,10 @@ interface ConfigSchema {
43
43
  serviceKeys: ServiceKeys;
44
44
  lastKeySync: string | null;
45
45
  trial: TrialState | null;
46
+ lastUpdateCheck: string | null;
47
+ latestKnownVersion: string | null;
48
+ lastPatternCheck: string | null;
49
+ latestPatternVersion: string | null;
46
50
  }
47
51
  export declare function getApiKey(): string | null;
48
52
  export declare function setApiKey(key: string): void;
@@ -105,4 +109,34 @@ export declare function hasValidAccess(): boolean;
105
109
  * Get authentication mode: 'apiKey', 'trial', or 'none'
106
110
  */
107
111
  export declare function getAuthMode(): 'apiKey' | 'trial' | 'none';
112
+ /**
113
+ * Get cached update info if still valid (within 24 hours)
114
+ */
115
+ export declare function getCachedUpdateInfo(): {
116
+ latestVersion: string;
117
+ checkedAt: string;
118
+ } | null;
119
+ /**
120
+ * Cache the latest version from npm registry
121
+ */
122
+ export declare function setCachedUpdateInfo(latestVersion: string): void;
123
+ /**
124
+ * Get the current CLI version from package.json
125
+ */
126
+ export declare function getCliVersion(): string;
127
+ /**
128
+ * Get cached pattern version info if still valid (within 6 hours)
129
+ */
130
+ export declare function getCachedPatternInfo(): {
131
+ latestVersion: string;
132
+ checkedAt: string;
133
+ } | null;
134
+ /**
135
+ * Cache the latest pattern version from server
136
+ */
137
+ export declare function setCachedPatternInfo(latestVersion: string): void;
138
+ /**
139
+ * Clear pattern cache to force a fresh check
140
+ */
141
+ export declare function clearPatternCache(): void;
108
142
  export {};
package/dist/config.js CHANGED
@@ -32,6 +32,12 @@ exports.isTrialExpired = isTrialExpired;
32
32
  exports.getTrialDaysRemaining = getTrialDaysRemaining;
33
33
  exports.hasValidAccess = hasValidAccess;
34
34
  exports.getAuthMode = getAuthMode;
35
+ exports.getCachedUpdateInfo = getCachedUpdateInfo;
36
+ exports.setCachedUpdateInfo = setCachedUpdateInfo;
37
+ exports.getCliVersion = getCliVersion;
38
+ exports.getCachedPatternInfo = getCachedPatternInfo;
39
+ exports.setCachedPatternInfo = setCachedPatternInfo;
40
+ exports.clearPatternCache = clearPatternCache;
35
41
  const conf_1 = __importDefault(require("conf"));
36
42
  const fs_1 = require("fs");
37
43
  const path_1 = require("path");
@@ -118,7 +124,7 @@ exports.PROVISIONABLE_KEYS = ['github', 'supabase', 'vercel'];
118
124
  const defaultServiceKeys = Object.fromEntries(exports.SERVICE_KEYS.map(key => [key, null]));
119
125
  const config = new conf_1.default({
120
126
  projectName: 'codebakers',
121
- projectVersion: '1.8.0',
127
+ projectVersion: '1.9.0',
122
128
  defaults: {
123
129
  apiKey: null,
124
130
  apiUrl: 'https://codebakers.ai',
@@ -126,6 +132,10 @@ const config = new conf_1.default({
126
132
  serviceKeys: defaultServiceKeys,
127
133
  lastKeySync: null,
128
134
  trial: null,
135
+ lastUpdateCheck: null,
136
+ latestKnownVersion: null,
137
+ lastPatternCheck: null,
138
+ latestPatternVersion: null,
129
139
  },
130
140
  // Migration to add new keys when upgrading from old version
131
141
  migrations: {
@@ -146,6 +156,15 @@ const config = new conf_1.default({
146
156
  store.set('trial', null);
147
157
  }
148
158
  },
159
+ '1.9.0': (store) => {
160
+ // Add pattern auto-update fields if not present
161
+ if (!store.has('lastPatternCheck')) {
162
+ store.set('lastPatternCheck', null);
163
+ }
164
+ if (!store.has('latestPatternVersion')) {
165
+ store.set('latestPatternVersion', null);
166
+ }
167
+ },
149
168
  },
150
169
  });
151
170
  // ============================================================
@@ -411,3 +430,64 @@ function getAuthMode() {
411
430
  return 'trial';
412
431
  return 'none';
413
432
  }
433
+ // ============================================
434
+ // Update Notification Cache
435
+ // ============================================
436
+ const UPDATE_CHECK_INTERVAL_HOURS = 24;
437
+ /**
438
+ * Get cached update info if still valid (within 24 hours)
439
+ */
440
+ function getCachedUpdateInfo() {
441
+ const lastCheck = config.get('lastUpdateCheck');
442
+ const latestVersion = config.get('latestKnownVersion');
443
+ if (!lastCheck || !latestVersion)
444
+ return null;
445
+ const hoursSinceCheck = (Date.now() - new Date(lastCheck).getTime()) / (1000 * 60 * 60);
446
+ if (hoursSinceCheck > UPDATE_CHECK_INTERVAL_HOURS)
447
+ return null;
448
+ return { latestVersion, checkedAt: lastCheck };
449
+ }
450
+ /**
451
+ * Cache the latest version from npm registry
452
+ */
453
+ function setCachedUpdateInfo(latestVersion) {
454
+ config.set('lastUpdateCheck', new Date().toISOString());
455
+ config.set('latestKnownVersion', latestVersion);
456
+ }
457
+ /**
458
+ * Get the current CLI version from package.json
459
+ */
460
+ function getCliVersion() {
461
+ return '3.3.0'; // Keep in sync with package.json
462
+ }
463
+ // ============================================
464
+ // Pattern Auto-Update Cache
465
+ // ============================================
466
+ const PATTERN_CHECK_INTERVAL_HOURS = 6; // Check more frequently than CLI
467
+ /**
468
+ * Get cached pattern version info if still valid (within 6 hours)
469
+ */
470
+ function getCachedPatternInfo() {
471
+ const lastCheck = config.get('lastPatternCheck');
472
+ const latestVersion = config.get('latestPatternVersion');
473
+ if (!lastCheck || !latestVersion)
474
+ return null;
475
+ const hoursSinceCheck = (Date.now() - new Date(lastCheck).getTime()) / (1000 * 60 * 60);
476
+ if (hoursSinceCheck > PATTERN_CHECK_INTERVAL_HOURS)
477
+ return null;
478
+ return { latestVersion, checkedAt: lastCheck };
479
+ }
480
+ /**
481
+ * Cache the latest pattern version from server
482
+ */
483
+ function setCachedPatternInfo(latestVersion) {
484
+ config.set('lastPatternCheck', new Date().toISOString());
485
+ config.set('latestPatternVersion', latestVersion);
486
+ }
487
+ /**
488
+ * Clear pattern cache to force a fresh check
489
+ */
490
+ function clearPatternCache() {
491
+ config.set('lastPatternCheck', null);
492
+ config.set('latestPatternVersion', null);
493
+ }
package/dist/index.js CHANGED
@@ -26,6 +26,193 @@ const push_patterns_js_1 = require("./commands/push-patterns.js");
26
26
  const go_js_1 = require("./commands/go.js");
27
27
  const extend_js_1 = require("./commands/extend.js");
28
28
  const billing_js_1 = require("./commands/billing.js");
29
+ const config_js_2 = require("./config.js");
30
+ const fs_1 = require("fs");
31
+ const path_1 = require("path");
32
+ // ============================================
33
+ // Automatic Update Notification
34
+ // ============================================
35
+ const CURRENT_VERSION = '3.3.0';
36
+ async function checkForUpdatesInBackground() {
37
+ // Check if we have a valid cached result first (fast path)
38
+ const cached = (0, config_js_2.getCachedUpdateInfo)();
39
+ if (cached) {
40
+ if (cached.latestVersion !== CURRENT_VERSION) {
41
+ showUpdateBanner(CURRENT_VERSION, cached.latestVersion);
42
+ }
43
+ return;
44
+ }
45
+ // Fetch from npm registry (with timeout to not block CLI)
46
+ try {
47
+ const controller = new AbortController();
48
+ const timeout = setTimeout(() => controller.abort(), 3000);
49
+ const response = await fetch('https://registry.npmjs.org/@codebakers/cli/latest', {
50
+ headers: { 'Accept': 'application/json' },
51
+ signal: controller.signal,
52
+ });
53
+ clearTimeout(timeout);
54
+ if (response.ok) {
55
+ const data = await response.json();
56
+ const latestVersion = data.version;
57
+ (0, config_js_2.setCachedUpdateInfo)(latestVersion);
58
+ if (latestVersion !== CURRENT_VERSION) {
59
+ showUpdateBanner(CURRENT_VERSION, latestVersion);
60
+ }
61
+ }
62
+ }
63
+ catch {
64
+ // Silently fail - don't block CLI for update check
65
+ }
66
+ }
67
+ function showUpdateBanner(currentVersion, latestVersion) {
68
+ console.log(chalk_1.default.yellow(`
69
+ ╭─────────────────────────────────────────────────────────╮
70
+ │ │
71
+ │ ${chalk_1.default.bold('Update available!')} ${chalk_1.default.gray(currentVersion)} → ${chalk_1.default.green(latestVersion)} │
72
+ │ │
73
+ │ Run ${chalk_1.default.cyan('npm i -g @codebakers/cli@latest')} to update │
74
+ │ │
75
+ ╰─────────────────────────────────────────────────────────╯
76
+ `));
77
+ }
78
+ function getLocalPatternVersion() {
79
+ const cwd = process.cwd();
80
+ const versionFile = (0, path_1.join)(cwd, '.claude', '.version.json');
81
+ if (!(0, fs_1.existsSync)(versionFile))
82
+ return null;
83
+ try {
84
+ const content = (0, fs_1.readFileSync)(versionFile, 'utf-8');
85
+ const info = JSON.parse(content);
86
+ return info.version;
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ }
92
+ function isCodeBakersProject() {
93
+ const cwd = process.cwd();
94
+ return (0, fs_1.existsSync)((0, path_1.join)(cwd, 'CLAUDE.md')) || (0, fs_1.existsSync)((0, path_1.join)(cwd, '.claude'));
95
+ }
96
+ async function autoUpdatePatterns() {
97
+ // Only auto-update if this is a CodeBakers project
98
+ if (!isCodeBakersProject())
99
+ return;
100
+ // Only auto-update if user has valid access
101
+ if (!(0, config_js_2.hasValidAccess)())
102
+ return;
103
+ const localVersion = getLocalPatternVersion();
104
+ // Check if we have a valid cached result first (fast path)
105
+ const cached = (0, config_js_2.getCachedPatternInfo)();
106
+ if (cached) {
107
+ // If local matches latest, nothing to do
108
+ if (localVersion === cached.latestVersion)
109
+ return;
110
+ // If we know there's an update but haven't updated yet, do it now
111
+ if (localVersion !== cached.latestVersion) {
112
+ await performPatternUpdate(cached.latestVersion);
113
+ }
114
+ return;
115
+ }
116
+ // Fetch from server to check for updates (with timeout)
117
+ try {
118
+ const controller = new AbortController();
119
+ const timeout = setTimeout(() => controller.abort(), 5000);
120
+ const apiUrl = (0, config_js_2.getApiUrl)();
121
+ const apiKey = (0, config_js_2.getApiKey)();
122
+ const trial = (0, config_js_2.getTrialState)();
123
+ // Build authorization header
124
+ let authHeader = '';
125
+ if (apiKey) {
126
+ authHeader = `Bearer ${apiKey}`;
127
+ }
128
+ else if (trial?.trialId) {
129
+ authHeader = `Trial ${trial.trialId}`;
130
+ }
131
+ if (!authHeader)
132
+ return;
133
+ // First, check the version endpoint (lightweight)
134
+ const versionResponse = await fetch(`${apiUrl}/api/content/version`, {
135
+ method: 'GET',
136
+ headers: {
137
+ 'Authorization': authHeader,
138
+ },
139
+ signal: controller.signal,
140
+ });
141
+ clearTimeout(timeout);
142
+ if (versionResponse.ok) {
143
+ const versionData = await versionResponse.json();
144
+ const serverVersion = versionData.version;
145
+ // Cache the version info
146
+ (0, config_js_2.setCachedPatternInfo)(serverVersion);
147
+ // If local version is different, update
148
+ if (localVersion !== serverVersion) {
149
+ await performPatternUpdate(serverVersion);
150
+ }
151
+ }
152
+ }
153
+ catch {
154
+ // Silently fail - don't block CLI for pattern check
155
+ }
156
+ }
157
+ async function performPatternUpdate(targetVersion) {
158
+ const cwd = process.cwd();
159
+ const claudeMdPath = (0, path_1.join)(cwd, 'CLAUDE.md');
160
+ const claudeDir = (0, path_1.join)(cwd, '.claude');
161
+ try {
162
+ const apiUrl = (0, config_js_2.getApiUrl)();
163
+ const apiKey = (0, config_js_2.getApiKey)();
164
+ const trial = (0, config_js_2.getTrialState)();
165
+ let authHeader = '';
166
+ if (apiKey) {
167
+ authHeader = `Bearer ${apiKey}`;
168
+ }
169
+ else if (trial?.trialId) {
170
+ authHeader = `Trial ${trial.trialId}`;
171
+ }
172
+ if (!authHeader)
173
+ return;
174
+ const controller = new AbortController();
175
+ const timeout = setTimeout(() => controller.abort(), 10000);
176
+ const response = await fetch(`${apiUrl}/api/content`, {
177
+ method: 'GET',
178
+ headers: {
179
+ 'Authorization': authHeader,
180
+ },
181
+ signal: controller.signal,
182
+ });
183
+ clearTimeout(timeout);
184
+ if (!response.ok)
185
+ return;
186
+ const content = await response.json();
187
+ // Update CLAUDE.md
188
+ if (content.router) {
189
+ (0, fs_1.writeFileSync)(claudeMdPath, content.router);
190
+ }
191
+ // Update pattern modules
192
+ if (content.modules && Object.keys(content.modules).length > 0) {
193
+ if (!(0, fs_1.existsSync)(claudeDir)) {
194
+ (0, fs_1.mkdirSync)(claudeDir, { recursive: true });
195
+ }
196
+ for (const [name, data] of Object.entries(content.modules)) {
197
+ (0, fs_1.writeFileSync)((0, path_1.join)(claudeDir, name), data);
198
+ }
199
+ }
200
+ // Write version file
201
+ const moduleCount = Object.keys(content.modules || {}).length;
202
+ const versionInfo = {
203
+ version: content.version,
204
+ moduleCount,
205
+ updatedAt: new Date().toISOString(),
206
+ cliVersion: (0, config_js_2.getCliVersion)(),
207
+ };
208
+ (0, fs_1.writeFileSync)((0, path_1.join)(claudeDir, '.version.json'), JSON.stringify(versionInfo, null, 2));
209
+ // Show subtle notification
210
+ console.log(chalk_1.default.green(` ✓ Patterns auto-updated to v${content.version} (${moduleCount} modules)\n`));
211
+ }
212
+ catch {
213
+ // Silently fail - don't block the user
214
+ }
215
+ }
29
216
  // Show welcome message when no command is provided
30
217
  function showWelcome() {
31
218
  console.log(chalk_1.default.blue(`
@@ -67,7 +254,7 @@ const program = new commander_1.Command();
67
254
  program
68
255
  .name('codebakers')
69
256
  .description('CodeBakers CLI - Production patterns for AI-assisted development')
70
- .version('3.1.0');
257
+ .version('3.3.0');
71
258
  // Zero-friction trial entry (no signup required)
72
259
  program
73
260
  .command('go')
@@ -199,9 +386,20 @@ program
199
386
  .command('mcp-uninstall')
200
387
  .description('Remove MCP configuration from Claude Code')
201
388
  .action(mcp_config_js_1.mcpUninstall);
389
+ // Add update check hook (runs before every command)
390
+ program.hook('preAction', async () => {
391
+ // Run CLI update check and pattern auto-update in parallel
392
+ await Promise.all([
393
+ checkForUpdatesInBackground(),
394
+ autoUpdatePatterns(),
395
+ ]);
396
+ });
202
397
  // Show welcome if no command provided
203
398
  if (process.argv.length <= 2) {
204
- showWelcome();
399
+ // Still check for updates when showing welcome
400
+ checkForUpdatesInBackground().then(() => {
401
+ showWelcome();
402
+ });
205
403
  }
206
404
  else {
207
405
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "CodeBakers CLI - Production patterns for AI-assisted development",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/config.ts CHANGED
@@ -116,6 +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 (CLI updates)
120
+ lastUpdateCheck: string | null; // ISO date of last npm registry check
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
119
125
  }
120
126
 
121
127
  // Create default service keys object with all keys set to null
@@ -125,7 +131,7 @@ const defaultServiceKeys: ServiceKeys = Object.fromEntries(
125
131
 
126
132
  const config = new Conf<ConfigSchema>({
127
133
  projectName: 'codebakers',
128
- projectVersion: '1.8.0',
134
+ projectVersion: '1.9.0',
129
135
  defaults: {
130
136
  apiKey: null,
131
137
  apiUrl: 'https://codebakers.ai',
@@ -133,6 +139,10 @@ const config = new Conf<ConfigSchema>({
133
139
  serviceKeys: defaultServiceKeys,
134
140
  lastKeySync: null,
135
141
  trial: null,
142
+ lastUpdateCheck: null,
143
+ latestKnownVersion: null,
144
+ lastPatternCheck: null,
145
+ latestPatternVersion: null,
136
146
  },
137
147
  // Migration to add new keys when upgrading from old version
138
148
  migrations: {
@@ -155,6 +165,15 @@ const config = new Conf<ConfigSchema>({
155
165
  store.set('trial', null);
156
166
  }
157
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
+ },
158
177
  },
159
178
  });
160
179
 
@@ -489,3 +508,76 @@ export function getAuthMode(): 'apiKey' | 'trial' | 'none' {
489
508
 
490
509
  return 'none';
491
510
  }
511
+
512
+ // ============================================
513
+ // Update Notification Cache
514
+ // ============================================
515
+
516
+ const UPDATE_CHECK_INTERVAL_HOURS = 24;
517
+
518
+ /**
519
+ * Get cached update info if still valid (within 24 hours)
520
+ */
521
+ export function getCachedUpdateInfo(): { latestVersion: string; checkedAt: string } | null {
522
+ const lastCheck = config.get('lastUpdateCheck');
523
+ const latestVersion = config.get('latestKnownVersion');
524
+
525
+ if (!lastCheck || !latestVersion) return null;
526
+
527
+ const hoursSinceCheck = (Date.now() - new Date(lastCheck).getTime()) / (1000 * 60 * 60);
528
+ if (hoursSinceCheck > UPDATE_CHECK_INTERVAL_HOURS) return null;
529
+
530
+ return { latestVersion, checkedAt: lastCheck };
531
+ }
532
+
533
+ /**
534
+ * Cache the latest version from npm registry
535
+ */
536
+ export function setCachedUpdateInfo(latestVersion: string): void {
537
+ config.set('lastUpdateCheck', new Date().toISOString());
538
+ config.set('latestKnownVersion', latestVersion);
539
+ }
540
+
541
+ /**
542
+ * Get the current CLI version from package.json
543
+ */
544
+ export function getCliVersion(): string {
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);
583
+ }
package/src/index.ts CHANGED
@@ -22,6 +22,238 @@ import { pushPatterns, pushPatternsInteractive } from './commands/push-patterns.
22
22
  import { go } from './commands/go.js';
23
23
  import { extend } from './commands/extend.js';
24
24
  import { billing } from './commands/billing.js';
25
+ import { getCachedUpdateInfo, setCachedUpdateInfo, getCliVersion, getCachedPatternInfo, setCachedPatternInfo, getApiKey, getApiUrl, getTrialState, hasValidAccess } from './config.js';
26
+ import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'fs';
27
+ import { join } from 'path';
28
+
29
+ // ============================================
30
+ // Automatic Update Notification
31
+ // ============================================
32
+
33
+ const CURRENT_VERSION = '3.3.0';
34
+
35
+ async function checkForUpdatesInBackground(): Promise<void> {
36
+ // Check if we have a valid cached result first (fast path)
37
+ const cached = getCachedUpdateInfo();
38
+ if (cached) {
39
+ if (cached.latestVersion !== CURRENT_VERSION) {
40
+ showUpdateBanner(CURRENT_VERSION, cached.latestVersion);
41
+ }
42
+ return;
43
+ }
44
+
45
+ // Fetch from npm registry (with timeout to not block CLI)
46
+ try {
47
+ const controller = new AbortController();
48
+ const timeout = setTimeout(() => controller.abort(), 3000);
49
+
50
+ const response = await fetch('https://registry.npmjs.org/@codebakers/cli/latest', {
51
+ headers: { 'Accept': 'application/json' },
52
+ signal: controller.signal,
53
+ });
54
+
55
+ clearTimeout(timeout);
56
+
57
+ if (response.ok) {
58
+ const data = await response.json();
59
+ const latestVersion = data.version;
60
+ setCachedUpdateInfo(latestVersion);
61
+
62
+ if (latestVersion !== CURRENT_VERSION) {
63
+ showUpdateBanner(CURRENT_VERSION, latestVersion);
64
+ }
65
+ }
66
+ } catch {
67
+ // Silently fail - don't block CLI for update check
68
+ }
69
+ }
70
+
71
+ function showUpdateBanner(currentVersion: string, latestVersion: string): void {
72
+ console.log(chalk.yellow(`
73
+ ╭─────────────────────────────────────────────────────────╮
74
+ │ │
75
+ │ ${chalk.bold('Update available!')} ${chalk.gray(currentVersion)} → ${chalk.green(latestVersion)} │
76
+ │ │
77
+ │ Run ${chalk.cyan('npm i -g @codebakers/cli@latest')} to update │
78
+ │ │
79
+ ╰─────────────────────────────────────────────────────────╯
80
+ `));
81
+ }
82
+
83
+ // ============================================
84
+ // Automatic Pattern Updates
85
+ // ============================================
86
+
87
+ interface PatternVersionInfo {
88
+ version: string;
89
+ moduleCount: number;
90
+ updatedAt: string;
91
+ cliVersion: string;
92
+ }
93
+
94
+ interface ContentResponse {
95
+ version: string;
96
+ router: string;
97
+ modules: Record<string, string>;
98
+ }
99
+
100
+ function getLocalPatternVersion(): string | null {
101
+ const cwd = process.cwd();
102
+ const versionFile = join(cwd, '.claude', '.version.json');
103
+
104
+ if (!existsSync(versionFile)) return null;
105
+
106
+ try {
107
+ const content = readFileSync(versionFile, 'utf-8');
108
+ const info: PatternVersionInfo = JSON.parse(content);
109
+ return info.version;
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ function isCodeBakersProject(): boolean {
116
+ const cwd = process.cwd();
117
+ return existsSync(join(cwd, 'CLAUDE.md')) || existsSync(join(cwd, '.claude'));
118
+ }
119
+
120
+ async function autoUpdatePatterns(): Promise<void> {
121
+ // Only auto-update if this is a CodeBakers project
122
+ if (!isCodeBakersProject()) return;
123
+
124
+ // Only auto-update if user has valid access
125
+ if (!hasValidAccess()) return;
126
+
127
+ const localVersion = getLocalPatternVersion();
128
+
129
+ // Check if we have a valid cached result first (fast path)
130
+ const cached = getCachedPatternInfo();
131
+ if (cached) {
132
+ // If local matches latest, nothing to do
133
+ if (localVersion === cached.latestVersion) return;
134
+ // If we know there's an update but haven't updated yet, do it now
135
+ if (localVersion !== cached.latestVersion) {
136
+ await performPatternUpdate(cached.latestVersion);
137
+ }
138
+ return;
139
+ }
140
+
141
+ // Fetch from server to check for updates (with timeout)
142
+ try {
143
+ const controller = new AbortController();
144
+ const timeout = setTimeout(() => controller.abort(), 5000);
145
+
146
+ const apiUrl = getApiUrl();
147
+ const apiKey = getApiKey();
148
+ const trial = getTrialState();
149
+
150
+ // Build authorization header
151
+ let authHeader = '';
152
+ if (apiKey) {
153
+ authHeader = `Bearer ${apiKey}`;
154
+ } else if (trial?.trialId) {
155
+ authHeader = `Trial ${trial.trialId}`;
156
+ }
157
+
158
+ if (!authHeader) return;
159
+
160
+ // First, check the version endpoint (lightweight)
161
+ const versionResponse = await fetch(`${apiUrl}/api/content/version`, {
162
+ method: 'GET',
163
+ headers: {
164
+ 'Authorization': authHeader,
165
+ },
166
+ signal: controller.signal,
167
+ });
168
+
169
+ clearTimeout(timeout);
170
+
171
+ if (versionResponse.ok) {
172
+ const versionData = await versionResponse.json();
173
+ const serverVersion = versionData.version;
174
+
175
+ // Cache the version info
176
+ setCachedPatternInfo(serverVersion);
177
+
178
+ // If local version is different, update
179
+ if (localVersion !== serverVersion) {
180
+ await performPatternUpdate(serverVersion);
181
+ }
182
+ }
183
+ } catch {
184
+ // Silently fail - don't block CLI for pattern check
185
+ }
186
+ }
187
+
188
+ async function performPatternUpdate(targetVersion: string): Promise<void> {
189
+ const cwd = process.cwd();
190
+ const claudeMdPath = join(cwd, 'CLAUDE.md');
191
+ const claudeDir = join(cwd, '.claude');
192
+
193
+ try {
194
+ const apiUrl = getApiUrl();
195
+ const apiKey = getApiKey();
196
+ const trial = getTrialState();
197
+
198
+ let authHeader = '';
199
+ if (apiKey) {
200
+ authHeader = `Bearer ${apiKey}`;
201
+ } else if (trial?.trialId) {
202
+ authHeader = `Trial ${trial.trialId}`;
203
+ }
204
+
205
+ if (!authHeader) return;
206
+
207
+ const controller = new AbortController();
208
+ const timeout = setTimeout(() => controller.abort(), 10000);
209
+
210
+ const response = await fetch(`${apiUrl}/api/content`, {
211
+ method: 'GET',
212
+ headers: {
213
+ 'Authorization': authHeader,
214
+ },
215
+ signal: controller.signal,
216
+ });
217
+
218
+ clearTimeout(timeout);
219
+
220
+ if (!response.ok) return;
221
+
222
+ const content: ContentResponse = await response.json();
223
+
224
+ // Update CLAUDE.md
225
+ if (content.router) {
226
+ writeFileSync(claudeMdPath, content.router);
227
+ }
228
+
229
+ // Update pattern modules
230
+ if (content.modules && Object.keys(content.modules).length > 0) {
231
+ if (!existsSync(claudeDir)) {
232
+ mkdirSync(claudeDir, { recursive: true });
233
+ }
234
+
235
+ for (const [name, data] of Object.entries(content.modules)) {
236
+ writeFileSync(join(claudeDir, name), data);
237
+ }
238
+ }
239
+
240
+ // Write version file
241
+ const moduleCount = Object.keys(content.modules || {}).length;
242
+ const versionInfo: PatternVersionInfo = {
243
+ version: content.version,
244
+ moduleCount,
245
+ updatedAt: new Date().toISOString(),
246
+ cliVersion: getCliVersion(),
247
+ };
248
+ writeFileSync(join(claudeDir, '.version.json'), JSON.stringify(versionInfo, null, 2));
249
+
250
+ // Show subtle notification
251
+ console.log(chalk.green(` ✓ Patterns auto-updated to v${content.version} (${moduleCount} modules)\n`));
252
+
253
+ } catch {
254
+ // Silently fail - don't block the user
255
+ }
256
+ }
25
257
 
26
258
  // Show welcome message when no command is provided
27
259
  function showWelcome(): void {
@@ -72,7 +304,7 @@ const program = new Command();
72
304
  program
73
305
  .name('codebakers')
74
306
  .description('CodeBakers CLI - Production patterns for AI-assisted development')
75
- .version('3.1.0');
307
+ .version('3.3.0');
76
308
 
77
309
  // Zero-friction trial entry (no signup required)
78
310
  program
@@ -225,9 +457,21 @@ program
225
457
  .description('Remove MCP configuration from Claude Code')
226
458
  .action(mcpUninstall);
227
459
 
460
+ // Add update check hook (runs before every command)
461
+ program.hook('preAction', async () => {
462
+ // Run CLI update check and pattern auto-update in parallel
463
+ await Promise.all([
464
+ checkForUpdatesInBackground(),
465
+ autoUpdatePatterns(),
466
+ ]);
467
+ });
468
+
228
469
  // Show welcome if no command provided
229
470
  if (process.argv.length <= 2) {
230
- showWelcome();
471
+ // Still check for updates when showing welcome
472
+ checkForUpdatesInBackground().then(() => {
473
+ showWelcome();
474
+ });
231
475
  } else {
232
476
  program.parse();
233
477
  }