@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.
@@ -12,6 +12,7 @@ const os_1 = require("os");
12
12
  const install_hook_js_1 = require("./install-hook.js");
13
13
  const config_js_1 = require("../config.js");
14
14
  const api_js_1 = require("../lib/api.js");
15
+ const stats_js_1 = require("../lib/stats.js");
15
16
  /**
16
17
  * Run all health checks for CodeBakers setup
17
18
  */
@@ -118,20 +119,20 @@ function checkProject() {
118
119
  try {
119
120
  const files = (0, fs_1.readdirSync)(claudeDir).filter(f => f.endsWith('.md'));
120
121
  const moduleCount = files.length;
121
- if (moduleCount >= 40) {
122
+ if (moduleCount >= 50) {
122
123
  results.push({ ok: true, message: `${moduleCount} modules present (full set)` });
123
124
  }
124
125
  else if (moduleCount >= 10) {
125
126
  results.push({
126
127
  ok: true,
127
128
  message: `${moduleCount} modules present (partial set)`,
128
- details: 'Run: codebakers upgrade to get all 47 modules'
129
+ details: `Run: codebakers upgrade to get all ${stats_js_1.CODEBAKERS_STATS.moduleCount} modules`
129
130
  });
130
131
  }
131
132
  else if (moduleCount > 0) {
132
133
  results.push({
133
134
  ok: false,
134
- message: `Only ${moduleCount} modules found (expected 47)`,
135
+ message: `Only ${moduleCount} modules found (expected ${stats_js_1.CODEBAKERS_STATS.moduleCount})`,
135
136
  details: 'Run: codebakers upgrade to get all modules'
136
137
  });
137
138
  }
@@ -24,6 +24,43 @@ function prompt(question) {
24
24
  });
25
25
  });
26
26
  }
27
+ /**
28
+ * Get CLI version from package.json
29
+ */
30
+ function getCliVersion() {
31
+ try {
32
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
33
+ const pkg = require('../../package.json');
34
+ return pkg.version || '0.0.0';
35
+ }
36
+ catch {
37
+ return '0.0.0';
38
+ }
39
+ }
40
+ /**
41
+ * Confirm download to server (non-blocking, fire-and-forget)
42
+ */
43
+ async function confirmDownload(apiUrl, auth, data) {
44
+ try {
45
+ const headers = {
46
+ 'Content-Type': 'application/json',
47
+ };
48
+ if (auth.apiKey) {
49
+ headers['Authorization'] = `Bearer ${auth.apiKey}`;
50
+ }
51
+ if (auth.trialId) {
52
+ headers['X-Trial-ID'] = auth.trialId;
53
+ }
54
+ await fetch(`${apiUrl}/api/content/confirm`, {
55
+ method: 'POST',
56
+ headers,
57
+ body: JSON.stringify(data),
58
+ });
59
+ }
60
+ catch {
61
+ // Silently ignore - this is just for analytics
62
+ }
63
+ }
27
64
  function log(message, options) {
28
65
  if (options?.verbose) {
29
66
  console.log(chalk_1.default.gray(` [verbose] ${message}`));
@@ -264,7 +301,7 @@ async function installPatternsWithApiKey(apiKey, options = {}) {
264
301
  log('Response OK, parsing JSON...', options);
265
302
  const content = await response.json();
266
303
  log(`Received version: ${content.version}, modules: ${Object.keys(content.modules || {}).length}`, options);
267
- await writePatternFiles(cwd, content, spinner, options);
304
+ await writePatternFiles(cwd, content, spinner, options, { apiKey });
268
305
  }
269
306
  catch (error) {
270
307
  log(`Error: ${error instanceof Error ? error.message : String(error)}`, options);
@@ -305,12 +342,12 @@ async function installPatterns(trialId, options = {}) {
305
342
  }
306
343
  const content = await publicResponse.json();
307
344
  log(`Received version: ${content.version}, modules: ${Object.keys(content.modules || {}).length}`, options);
308
- await writePatternFiles(cwd, content, spinner, options);
345
+ await writePatternFiles(cwd, content, spinner, options, { trialId });
309
346
  return;
310
347
  }
311
348
  const content = await response.json();
312
349
  log(`Received version: ${content.version}, modules: ${Object.keys(content.modules || {}).length}`, options);
313
- await writePatternFiles(cwd, content, spinner, options);
350
+ await writePatternFiles(cwd, content, spinner, options, { trialId });
314
351
  }
315
352
  catch (error) {
316
353
  log(`Error: ${error instanceof Error ? error.message : String(error)}`, options);
@@ -318,7 +355,7 @@ async function installPatterns(trialId, options = {}) {
318
355
  console.log(chalk_1.default.gray(' Patterns will be available via MCP tools.\n'));
319
356
  }
320
357
  }
321
- async function writePatternFiles(cwd, content, spinner, options = {}) {
358
+ async function writePatternFiles(cwd, content, spinner, options = {}, auth) {
322
359
  log(`Writing pattern files to ${cwd}...`, options);
323
360
  // Check if patterns already exist
324
361
  const claudeMdPath = (0, path_1.join)(cwd, 'CLAUDE.md');
@@ -331,7 +368,8 @@ async function writePatternFiles(cwd, content, spinner, options = {}) {
331
368
  (0, fs_1.writeFileSync)(claudeMdPath, content.router);
332
369
  }
333
370
  // Write pattern modules to .claude/
334
- if (content.modules && Object.keys(content.modules).length > 0) {
371
+ const moduleCount = Object.keys(content.modules || {}).length;
372
+ if (content.modules && moduleCount > 0) {
335
373
  const modulesDir = (0, path_1.join)(cwd, '.claude');
336
374
  if (!(0, fs_1.existsSync)(modulesDir)) {
337
375
  (0, fs_1.mkdirSync)(modulesDir, { recursive: true });
@@ -350,5 +388,15 @@ async function writePatternFiles(cwd, content, spinner, options = {}) {
350
388
  }
351
389
  }
352
390
  spinner.succeed(`CodeBakers patterns installed (v${content.version})`);
353
- console.log(chalk_1.default.gray(` ${Object.keys(content.modules || {}).length} pattern modules ready\n`));
391
+ console.log(chalk_1.default.gray(` ${moduleCount} pattern modules ready\n`));
392
+ // Confirm download to server (non-blocking)
393
+ if (auth) {
394
+ const apiUrl = (0, config_js_1.getApiUrl)();
395
+ confirmDownload(apiUrl, auth, {
396
+ version: content.version,
397
+ moduleCount,
398
+ cliVersion: getCliVersion(),
399
+ command: 'go',
400
+ }).catch(() => { }); // Silently ignore
401
+ }
354
402
  }
@@ -10,6 +10,7 @@ const fs_1 = require("fs");
10
10
  const path_1 = require("path");
11
11
  const config_js_1 = require("../config.js");
12
12
  const api_js_1 = require("../lib/api.js");
13
+ const stats_js_1 = require("../lib/stats.js");
13
14
  async function install() {
14
15
  console.log(chalk_1.default.blue('\n CodeBakers Install\n'));
15
16
  const apiKey = (0, config_js_1.getApiKey)();
@@ -79,7 +80,7 @@ async function install() {
79
80
  if (moduleCount > 0) {
80
81
  console.log(chalk_1.default.gray(` - .claude/ (${moduleCount} pattern modules)`));
81
82
  }
82
- console.log(chalk_1.default.blue('\n Start building! Your AI now knows 114 production patterns.\n'));
83
+ console.log(chalk_1.default.blue(`\n Start building! Your AI now knows ${stats_js_1.CODEBAKERS_STATS.moduleCount} production modules.\n`));
83
84
  }
84
85
  catch (error) {
85
86
  spinner.fail('Installation failed');
@@ -10,6 +10,24 @@ const fs_1 = require("fs");
10
10
  const path_1 = require("path");
11
11
  const config_js_1 = require("../config.js");
12
12
  const api_js_1 = require("../lib/api.js");
13
+ /**
14
+ * Confirm download to server (non-blocking, fire-and-forget)
15
+ */
16
+ async function confirmDownload(apiUrl, apiKey, data) {
17
+ try {
18
+ await fetch(`${apiUrl}/api/content/confirm`, {
19
+ method: 'POST',
20
+ headers: {
21
+ 'Content-Type': 'application/json',
22
+ 'Authorization': `Bearer ${apiKey}`,
23
+ },
24
+ body: JSON.stringify(data),
25
+ });
26
+ }
27
+ catch {
28
+ // Silently ignore - this is just for analytics
29
+ }
30
+ }
13
31
  /**
14
32
  * Upgrade CodeBakers patterns to the latest version
15
33
  */
@@ -83,6 +101,13 @@ async function upgrade() {
83
101
  };
84
102
  (0, fs_1.writeFileSync)((0, path_1.join)(claudeDir, '.version.json'), JSON.stringify(versionInfo, null, 2));
85
103
  console.log(chalk_1.default.green(' ✓ Version info saved'));
104
+ // Confirm download to server (non-blocking)
105
+ confirmDownload(apiUrl, apiKey, {
106
+ version: content.version,
107
+ moduleCount,
108
+ cliVersion: (0, api_js_1.getCliVersion)(),
109
+ command: 'upgrade',
110
+ }).catch(() => { }); // Silently ignore confirmation failures
86
111
  console.log(chalk_1.default.green(`\n ✅ Upgraded to patterns v${content.version}!\n`));
87
112
  // Show what's new if available
88
113
  console.log(chalk_1.default.gray(' Changes take effect in your next AI session.\n'));
package/dist/index.js CHANGED
@@ -27,36 +27,39 @@ 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
29
  const config_js_2 = require("./config.js");
30
+ const api_js_1 = require("./lib/api.js");
30
31
  const fs_1 = require("fs");
31
32
  const path_1 = require("path");
32
33
  // ============================================
33
34
  // Automatic Update Notification
34
35
  // ============================================
35
- const CURRENT_VERSION = '3.3.0';
36
+ const CURRENT_VERSION = '3.3.1';
36
37
  async function checkForUpdatesInBackground() {
37
38
  // Check if we have a valid cached result first (fast path)
38
39
  const cached = (0, config_js_2.getCachedUpdateInfo)();
39
40
  if (cached) {
40
41
  if (cached.latestVersion !== CURRENT_VERSION) {
41
- showUpdateBanner(CURRENT_VERSION, cached.latestVersion);
42
+ showUpdateBanner(CURRENT_VERSION, cached.latestVersion, false);
42
43
  }
43
44
  return;
44
45
  }
45
- // Fetch from npm registry (with timeout to not block CLI)
46
+ // Use the API-based version check (with controlled rollout support)
46
47
  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);
48
+ const updateInfo = await (0, api_js_1.checkForUpdates)();
49
+ if (updateInfo) {
50
+ (0, config_js_2.setCachedUpdateInfo)(updateInfo.latestVersion);
51
+ // Show blocked version warning first (critical)
52
+ if (updateInfo.isBlocked) {
53
+ showBlockedVersionWarning(updateInfo.currentVersion, updateInfo.latestVersion);
54
+ return;
55
+ }
56
+ // Only show update banner if auto-update is enabled for this version
57
+ if (updateInfo.autoUpdateEnabled && updateInfo.autoUpdateVersion) {
58
+ showUpdateBanner(updateInfo.currentVersion, updateInfo.autoUpdateVersion, true);
59
+ }
60
+ else if (updateInfo.updateAvailable) {
61
+ // Update available but not auto-update enabled - show regular banner
62
+ showUpdateBanner(updateInfo.currentVersion, updateInfo.latestVersion, false);
60
63
  }
61
64
  }
62
65
  }
@@ -64,11 +67,26 @@ async function checkForUpdatesInBackground() {
64
67
  // Silently fail - don't block CLI for update check
65
68
  }
66
69
  }
67
- function showUpdateBanner(currentVersion, latestVersion) {
70
+ function showBlockedVersionWarning(currentVersion, recommendedVersion) {
71
+ console.log(chalk_1.default.red(`
72
+ ╭─────────────────────────────────────────────────────────╮
73
+ │ │
74
+ │ ${chalk_1.default.bold('⚠️ VERSION BLOCKED')} │
75
+ │ │
76
+ │ Your CLI version ${chalk_1.default.gray(currentVersion)} has critical issues. │
77
+ │ Please update immediately to ${chalk_1.default.green(recommendedVersion)} │
78
+ │ │
79
+ │ Run ${chalk_1.default.cyan('npm i -g @codebakers/cli@latest')} to update │
80
+ │ │
81
+ ╰─────────────────────────────────────────────────────────╯
82
+ `));
83
+ }
84
+ function showUpdateBanner(currentVersion, latestVersion, isRecommended) {
85
+ const updateType = isRecommended ? chalk_1.default.green('Recommended update') : chalk_1.default.bold('Update available!');
68
86
  console.log(chalk_1.default.yellow(`
69
87
  ╭─────────────────────────────────────────────────────────╮
70
88
  │ │
71
- │ ${chalk_1.default.bold('Update available!')} ${chalk_1.default.gray(currentVersion)} → ${chalk_1.default.green(latestVersion)} │
89
+ │ ${updateType} ${chalk_1.default.gray(currentVersion)} → ${chalk_1.default.green(latestVersion)} │
72
90
  │ │
73
91
  │ Run ${chalk_1.default.cyan('npm i -g @codebakers/cli@latest')} to update │
74
92
  │ │
package/dist/lib/api.d.ts CHANGED
@@ -37,9 +37,19 @@ export declare function checkApiKeyValidity(): Promise<{
37
37
  export declare function getCliVersion(): string;
38
38
  /**
39
39
  * Check if there's a newer version of the CLI available
40
+ * Uses the CodeBakers API for controlled rollouts (only recommends stable, tested versions)
41
+ * Falls back to npm registry if API is unavailable
40
42
  */
41
43
  export declare function checkForUpdates(): Promise<{
42
44
  currentVersion: string;
43
45
  latestVersion: string;
44
46
  updateAvailable: boolean;
47
+ autoUpdateEnabled: boolean;
48
+ autoUpdateVersion: string | null;
49
+ isBlocked: boolean;
45
50
  } | null>;
51
+ /**
52
+ * Report a CLI error to the server for tracking
53
+ * This helps identify problematic versions for blocking
54
+ */
55
+ export declare function reportCliError(errorType: string, errorMessage: string, context?: Record<string, unknown>): Promise<void>;
package/dist/lib/api.js CHANGED
@@ -7,6 +7,7 @@ exports.formatApiError = formatApiError;
7
7
  exports.checkApiKeyValidity = checkApiKeyValidity;
8
8
  exports.getCliVersion = getCliVersion;
9
9
  exports.checkForUpdates = checkForUpdates;
10
+ exports.reportCliError = reportCliError;
10
11
  const config_js_1 = require("../config.js");
11
12
  /**
12
13
  * Validate an API key format
@@ -136,24 +137,92 @@ function getCliVersion() {
136
137
  }
137
138
  /**
138
139
  * Check if there's a newer version of the CLI available
140
+ * Uses the CodeBakers API for controlled rollouts (only recommends stable, tested versions)
141
+ * Falls back to npm registry if API is unavailable
139
142
  */
140
143
  async function checkForUpdates() {
141
144
  try {
142
145
  const currentVersion = getCliVersion();
143
- const response = await fetch('https://registry.npmjs.org/@codebakers/cli/latest', {
146
+ const apiUrl = (0, config_js_1.getApiUrl)();
147
+ // First, try the CodeBakers API for controlled rollout info
148
+ try {
149
+ const response = await fetch(`${apiUrl}/api/cli/version`, {
150
+ headers: {
151
+ 'Accept': 'application/json',
152
+ 'X-CLI-Version': currentVersion,
153
+ },
154
+ });
155
+ if (response.ok) {
156
+ const data = await response.json();
157
+ const latestVersion = data.stableVersion || data.latestVersion;
158
+ const autoUpdateVersion = data.autoUpdateVersion;
159
+ const isBlocked = data.isBlocked === true;
160
+ return {
161
+ currentVersion,
162
+ latestVersion,
163
+ updateAvailable: latestVersion !== currentVersion,
164
+ autoUpdateEnabled: data.autoUpdateEnabled === true,
165
+ autoUpdateVersion: autoUpdateVersion || null,
166
+ isBlocked,
167
+ };
168
+ }
169
+ }
170
+ catch {
171
+ // API unavailable, fall through to npm
172
+ }
173
+ // Fallback: check npm registry directly
174
+ const npmResponse = await fetch('https://registry.npmjs.org/@codebakers/cli/latest', {
144
175
  headers: { 'Accept': 'application/json' },
145
176
  });
146
- if (!response.ok)
177
+ if (!npmResponse.ok)
147
178
  return null;
148
- const data = await response.json();
149
- const latestVersion = data.version;
179
+ const npmData = await npmResponse.json();
180
+ const latestVersion = npmData.version;
150
181
  return {
151
182
  currentVersion,
152
183
  latestVersion,
153
184
  updateAvailable: currentVersion !== latestVersion,
185
+ autoUpdateEnabled: false, // npm fallback doesn't have controlled rollout
186
+ autoUpdateVersion: null,
187
+ isBlocked: false,
154
188
  };
155
189
  }
156
190
  catch {
157
191
  return null;
158
192
  }
159
193
  }
194
+ /**
195
+ * Report a CLI error to the server for tracking
196
+ * This helps identify problematic versions for blocking
197
+ */
198
+ async function reportCliError(errorType, errorMessage, context) {
199
+ try {
200
+ const apiUrl = (0, config_js_1.getApiUrl)();
201
+ const cliVersion = getCliVersion();
202
+ // Fire and forget - don't block on error reporting
203
+ fetch(`${apiUrl}/api/cli/error-report`, {
204
+ method: 'POST',
205
+ headers: {
206
+ 'Content-Type': 'application/json',
207
+ 'X-CLI-Version': cliVersion,
208
+ },
209
+ body: JSON.stringify({
210
+ cliVersion,
211
+ errorType,
212
+ errorMessage,
213
+ stackTrace: context?.stack,
214
+ context: JSON.stringify({
215
+ ...context,
216
+ nodeVersion: process.version,
217
+ platform: process.platform,
218
+ arch: process.arch,
219
+ }),
220
+ }),
221
+ }).catch(() => {
222
+ // Ignore reporting failures
223
+ });
224
+ }
225
+ catch {
226
+ // Never fail on error reporting
227
+ }
228
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Central configuration for CodeBakers CLI stats.
3
+ * Keep in sync with server-side src/lib/stats.ts
4
+ */
5
+ export declare const CODEBAKERS_STATS: {
6
+ readonly moduleCount: 59;
7
+ readonly totalLinesDisplay: "50K+";
8
+ readonly patternCount: 150;
9
+ };
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ /**
3
+ * Central configuration for CodeBakers CLI stats.
4
+ * Keep in sync with server-side src/lib/stats.ts
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.CODEBAKERS_STATS = void 0;
8
+ exports.CODEBAKERS_STATS = {
9
+ moduleCount: 59,
10
+ totalLinesDisplay: '50K+',
11
+ patternCount: 150,
12
+ };