@codebakers/cli 3.3.0 → 3.3.2

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,27 @@ 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
+ },
950
+ {
951
+ name: 'update_patterns',
952
+ description: 'Download and update CodeBakers pattern files from the server. Use when user says "upgrade codebakers", "update patterns", "download latest patterns", "sync codebakers", or when patterns are missing or outdated. This tool fetches the latest CLAUDE.md router and all .claude/ module files from the server and writes them to disk.',
953
+ inputSchema: {
954
+ type: 'object',
955
+ properties: {
956
+ force: {
957
+ type: 'boolean',
958
+ description: 'Force update even if already at latest version (default: false)',
959
+ },
960
+ },
961
+ },
962
+ },
905
963
  ],
906
964
  }));
907
965
  // Handle tool calls
@@ -987,6 +1045,10 @@ class CodeBakersServer {
987
1045
  return this.handleAddPage(args);
988
1046
  case 'add_api_route':
989
1047
  return this.handleAddApiRoute(args);
1048
+ case 'check_update_notification':
1049
+ return this.handleCheckUpdateNotification();
1050
+ case 'update_patterns':
1051
+ return this.handleUpdatePatterns(args);
990
1052
  default:
991
1053
  throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
992
1054
  }
@@ -4266,6 +4328,40 @@ export default ${asyncKeyword}function ${pageName}Page() {${authCheck}
4266
4328
  }],
4267
4329
  };
4268
4330
  }
4331
+ /**
4332
+ * Check for update notifications and return message to show user
4333
+ */
4334
+ async handleCheckUpdateNotification() {
4335
+ const cwd = process.cwd();
4336
+ const notificationPath = path.join(cwd, '.claude', '.update-notification.json');
4337
+ try {
4338
+ if (!fs.existsSync(notificationPath)) {
4339
+ return {
4340
+ content: [{
4341
+ type: 'text',
4342
+ text: 'No update notification.',
4343
+ }],
4344
+ };
4345
+ }
4346
+ const notification = JSON.parse(fs.readFileSync(notificationPath, 'utf-8'));
4347
+ // Delete the notification file after reading (so it only shows once)
4348
+ fs.unlinkSync(notificationPath);
4349
+ return {
4350
+ content: [{
4351
+ type: 'text',
4352
+ 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()}`,
4353
+ }],
4354
+ };
4355
+ }
4356
+ catch {
4357
+ return {
4358
+ content: [{
4359
+ type: 'text',
4360
+ text: 'No update notification.',
4361
+ }],
4362
+ };
4363
+ }
4364
+ }
4269
4365
  parseApiRouteRequest(request) {
4270
4366
  const lower = request.toLowerCase();
4271
4367
  // Detect webhook
@@ -4451,6 +4547,137 @@ export async function POST(request: NextRequest) {
4451
4547
  ${handlers.join('\n')}
4452
4548
  `;
4453
4549
  }
4550
+ /**
4551
+ * Download and update CodeBakers patterns from server
4552
+ * This is the MCP equivalent of the `codebakers upgrade` CLI command
4553
+ */
4554
+ async handleUpdatePatterns(args) {
4555
+ const { force = false } = args;
4556
+ const cwd = process.cwd();
4557
+ const claudeMdPath = path.join(cwd, 'CLAUDE.md');
4558
+ const claudeDir = path.join(cwd, '.claude');
4559
+ const versionPath = path.join(claudeDir, '.version.json');
4560
+ let response = `# 🔄 CodeBakers Pattern Update\n\n`;
4561
+ try {
4562
+ // Check current version
4563
+ let currentVersion = null;
4564
+ let currentModuleCount = 0;
4565
+ if (fs.existsSync(versionPath)) {
4566
+ try {
4567
+ const versionInfo = JSON.parse(fs.readFileSync(versionPath, 'utf-8'));
4568
+ currentVersion = versionInfo.version;
4569
+ currentModuleCount = versionInfo.moduleCount || 0;
4570
+ }
4571
+ catch {
4572
+ // Ignore parse errors
4573
+ }
4574
+ }
4575
+ // Count current modules
4576
+ if (fs.existsSync(claudeDir)) {
4577
+ try {
4578
+ const files = fs.readdirSync(claudeDir).filter(f => f.endsWith('.md'));
4579
+ currentModuleCount = files.length;
4580
+ }
4581
+ catch {
4582
+ // Ignore read errors
4583
+ }
4584
+ }
4585
+ response += `## Current Status\n`;
4586
+ response += `- Version: ${currentVersion || 'Unknown'}\n`;
4587
+ response += `- Modules: ${currentModuleCount}\n\n`;
4588
+ // Fetch latest version info first
4589
+ const versionResponse = await fetch(`${this.apiUrl}/api/content/version`, {
4590
+ headers: this.getAuthHeaders(),
4591
+ });
4592
+ if (!versionResponse.ok) {
4593
+ throw new Error('Failed to check version from server');
4594
+ }
4595
+ const latestInfo = await versionResponse.json();
4596
+ const latestVersion = latestInfo.version;
4597
+ const latestModuleCount = latestInfo.moduleCount || 0;
4598
+ response += `## Server Status\n`;
4599
+ response += `- Latest Version: ${latestVersion}\n`;
4600
+ response += `- Available Modules: ${latestModuleCount}\n\n`;
4601
+ // Check if update needed
4602
+ const needsUpdate = force || !currentVersion || currentVersion !== latestVersion || currentModuleCount < latestModuleCount;
4603
+ if (!needsUpdate) {
4604
+ response += `✅ **Already up to date!**\n\n`;
4605
+ response += `Your patterns are current (v${latestVersion} with ${latestModuleCount} modules).\n`;
4606
+ response += `Use \`force: true\` to re-download anyway.\n`;
4607
+ return {
4608
+ content: [{
4609
+ type: 'text',
4610
+ text: response,
4611
+ }],
4612
+ };
4613
+ }
4614
+ response += `## Downloading Updates...\n\n`;
4615
+ // Fetch full content
4616
+ const contentResponse = await fetch(`${this.apiUrl}/api/content`, {
4617
+ headers: this.getAuthHeaders(),
4618
+ });
4619
+ if (!contentResponse.ok) {
4620
+ const error = await contentResponse.json().catch(() => ({}));
4621
+ throw new Error(error.error || error.message || 'Failed to fetch patterns');
4622
+ }
4623
+ const content = await contentResponse.json();
4624
+ const moduleCount = content.modules ? Object.keys(content.modules).length : 0;
4625
+ // Create .claude directory if needed
4626
+ if (!fs.existsSync(claudeDir)) {
4627
+ fs.mkdirSync(claudeDir, { recursive: true });
4628
+ response += `✓ Created .claude/ directory\n`;
4629
+ }
4630
+ // Update CLAUDE.md router
4631
+ if (content.router) {
4632
+ fs.writeFileSync(claudeMdPath, content.router);
4633
+ response += `✓ Updated CLAUDE.md (router)\n`;
4634
+ }
4635
+ // Update all modules
4636
+ if (content.modules && moduleCount > 0) {
4637
+ for (const [name, data] of Object.entries(content.modules)) {
4638
+ fs.writeFileSync(path.join(claudeDir, name), data);
4639
+ }
4640
+ response += `✓ Updated ${moduleCount} modules in .claude/\n`;
4641
+ }
4642
+ // Save version info
4643
+ const newVersionInfo = {
4644
+ version: content.version || latestVersion,
4645
+ moduleCount,
4646
+ installedAt: currentVersion ? undefined : new Date().toISOString(),
4647
+ updatedAt: new Date().toISOString(),
4648
+ cliVersion: (0, api_js_1.getCliVersion)(),
4649
+ };
4650
+ fs.writeFileSync(versionPath, JSON.stringify(newVersionInfo, null, 2));
4651
+ response += `✓ Saved version info\n`;
4652
+ // Confirm download to server (non-blocking analytics)
4653
+ this.confirmDownload(content.version || latestVersion, moduleCount).catch(() => { });
4654
+ response += `\n## ✅ Update Complete!\n\n`;
4655
+ response += `- **From:** v${currentVersion || 'none'} (${currentModuleCount} modules)\n`;
4656
+ response += `- **To:** v${content.version || latestVersion} (${moduleCount} modules)\n\n`;
4657
+ if (moduleCount > currentModuleCount) {
4658
+ response += `🆕 **${moduleCount - currentModuleCount} new modules added!**\n\n`;
4659
+ }
4660
+ response += `Your patterns are now up to date. The new patterns will be used in your next response.\n`;
4661
+ }
4662
+ catch (error) {
4663
+ const message = error instanceof Error ? error.message : 'Unknown error';
4664
+ response += `\n## ❌ Update Failed\n\n`;
4665
+ response += `Error: ${message}\n\n`;
4666
+ if (message.includes('401') || message.includes('Invalid') || message.includes('expired')) {
4667
+ response += `Your API key may be invalid or expired.\n`;
4668
+ response += `Run \`codebakers setup\` in terminal to reconfigure.\n`;
4669
+ }
4670
+ else {
4671
+ response += `Please try again or run \`codebakers upgrade\` in terminal.\n`;
4672
+ }
4673
+ }
4674
+ return {
4675
+ content: [{
4676
+ type: 'text',
4677
+ text: response,
4678
+ }],
4679
+ };
4680
+ }
4454
4681
  async run() {
4455
4682
  const transport = new stdio_js_1.StdioServerTransport();
4456
4683
  await this.server.connect(transport);
package/nul ADDED
@@ -0,0 +1 @@
1
+ vitest.config.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "3.3.0",
3
+ "version": "3.3.2",
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();
@@ -5,6 +5,7 @@ import { homedir } from 'os';
5
5
  import { isHookInstalled } from './install-hook.js';
6
6
  import { getApiKey } from '../config.js';
7
7
  import { checkApiKeyValidity, checkForUpdates, getCliVersion } from '../lib/api.js';
8
+ import { CODEBAKERS_STATS } from '../lib/stats.js';
8
9
 
9
10
  interface CheckResult {
10
11
  ok: boolean;
@@ -134,18 +135,18 @@ function checkProject(): CheckResult[] {
134
135
  const files = readdirSync(claudeDir).filter(f => f.endsWith('.md'));
135
136
  const moduleCount = files.length;
136
137
 
137
- if (moduleCount >= 40) {
138
+ if (moduleCount >= 50) {
138
139
  results.push({ ok: true, message: `${moduleCount} modules present (full set)` });
139
140
  } else if (moduleCount >= 10) {
140
141
  results.push({
141
142
  ok: true,
142
143
  message: `${moduleCount} modules present (partial set)`,
143
- details: 'Run: codebakers upgrade to get all 47 modules'
144
+ details: `Run: codebakers upgrade to get all ${CODEBAKERS_STATS.moduleCount} modules`
144
145
  });
145
146
  } else if (moduleCount > 0) {
146
147
  results.push({
147
148
  ok: false,
148
- message: `Only ${moduleCount} modules found (expected 47)`,
149
+ message: `Only ${moduleCount} modules found (expected ${CODEBAKERS_STATS.moduleCount})`,
149
150
  details: 'Run: codebakers upgrade to get all modules'
150
151
  });
151
152
  } else {
@@ -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
  }
@@ -4,6 +4,7 @@ import { writeFileSync, mkdirSync, existsSync } from 'fs';
4
4
  import { join } from 'path';
5
5
  import { getApiKey, getApiUrl } from '../config.js';
6
6
  import { getCliVersion } from '../lib/api.js';
7
+ import { CODEBAKERS_STATS } from '../lib/stats.js';
7
8
 
8
9
  interface ContentResponse {
9
10
  version: string;
@@ -94,7 +95,7 @@ export async function install(): Promise<void> {
94
95
  if (moduleCount > 0) {
95
96
  console.log(chalk.gray(` - .claude/ (${moduleCount} pattern modules)`));
96
97
  }
97
- console.log(chalk.blue('\n Start building! Your AI now knows 114 production patterns.\n'));
98
+ console.log(chalk.blue(`\n Start building! Your AI now knows ${CODEBAKERS_STATS.moduleCount} production modules.\n`));
98
99
  } catch (error) {
99
100
  spinner.fail('Installation failed');
100
101
  const message = error instanceof Error ? error.message : 'Unknown error';
@@ -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