@codebakers/cli 3.3.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.
- package/dist/commands/go.js +54 -6
- package/dist/commands/upgrade.js +25 -0
- package/dist/index.js +36 -18
- package/dist/lib/api.d.ts +10 -0
- package/dist/lib/api.js +73 -4
- package/dist/mcp/server.js +81 -0
- package/package.json +3 -2
- package/scripts/register-version.js +79 -0
- package/src/commands/go.ts +75 -6
- package/src/commands/upgrade.ts +34 -0
- package/src/index.ts +36 -19
- package/src/lib/api.ts +83 -4
- package/src/mcp/server.ts +90 -0
package/dist/commands/go.js
CHANGED
|
@@ -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
|
-
|
|
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(` ${
|
|
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
|
}
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -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.
|
|
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
|
-
//
|
|
46
|
+
// Use the API-based version check (with controlled rollout support)
|
|
46
47
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
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
|
-
│ ${
|
|
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
|
|
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 (!
|
|
177
|
+
if (!npmResponse.ok)
|
|
147
178
|
return null;
|
|
148
|
-
const
|
|
149
|
-
const latestVersion =
|
|
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
|
+
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -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.3.
|
|
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();
|
package/src/commands/go.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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(` ${
|
|
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
|
}
|
package/src/commands/upgrade.ts
CHANGED
|
@@ -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/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { go } from './commands/go.js';
|
|
|
23
23
|
import { extend } from './commands/extend.js';
|
|
24
24
|
import { billing } from './commands/billing.js';
|
|
25
25
|
import { getCachedUpdateInfo, setCachedUpdateInfo, getCliVersion, getCachedPatternInfo, setCachedPatternInfo, getApiKey, getApiUrl, getTrialState, hasValidAccess } from './config.js';
|
|
26
|
+
import { checkForUpdates } from './lib/api.js';
|
|
26
27
|
import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'fs';
|
|
27
28
|
import { join } from 'path';
|
|
28
29
|
|
|
@@ -30,37 +31,37 @@ import { join } from 'path';
|
|
|
30
31
|
// Automatic Update Notification
|
|
31
32
|
// ============================================
|
|
32
33
|
|
|
33
|
-
const CURRENT_VERSION = '3.3.
|
|
34
|
+
const CURRENT_VERSION = '3.3.1';
|
|
34
35
|
|
|
35
36
|
async function checkForUpdatesInBackground(): Promise<void> {
|
|
36
37
|
// Check if we have a valid cached result first (fast path)
|
|
37
38
|
const cached = getCachedUpdateInfo();
|
|
38
39
|
if (cached) {
|
|
39
40
|
if (cached.latestVersion !== CURRENT_VERSION) {
|
|
40
|
-
showUpdateBanner(CURRENT_VERSION, cached.latestVersion);
|
|
41
|
+
showUpdateBanner(CURRENT_VERSION, cached.latestVersion, false);
|
|
41
42
|
}
|
|
42
43
|
return;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
//
|
|
46
|
+
// Use the API-based version check (with controlled rollout support)
|
|
46
47
|
try {
|
|
47
|
-
const
|
|
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
|
-
});
|
|
48
|
+
const updateInfo = await checkForUpdates();
|
|
54
49
|
|
|
55
|
-
|
|
50
|
+
if (updateInfo) {
|
|
51
|
+
setCachedUpdateInfo(updateInfo.latestVersion);
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
// Show blocked version warning first (critical)
|
|
54
|
+
if (updateInfo.isBlocked) {
|
|
55
|
+
showBlockedVersionWarning(updateInfo.currentVersion, updateInfo.latestVersion);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
61
58
|
|
|
62
|
-
if
|
|
63
|
-
|
|
59
|
+
// Only show update banner if auto-update is enabled for this version
|
|
60
|
+
if (updateInfo.autoUpdateEnabled && updateInfo.autoUpdateVersion) {
|
|
61
|
+
showUpdateBanner(updateInfo.currentVersion, updateInfo.autoUpdateVersion, true);
|
|
62
|
+
} else if (updateInfo.updateAvailable) {
|
|
63
|
+
// Update available but not auto-update enabled - show regular banner
|
|
64
|
+
showUpdateBanner(updateInfo.currentVersion, updateInfo.latestVersion, false);
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
} catch {
|
|
@@ -68,11 +69,27 @@ async function checkForUpdatesInBackground(): Promise<void> {
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
function
|
|
72
|
+
function showBlockedVersionWarning(currentVersion: string, recommendedVersion: string): void {
|
|
73
|
+
console.log(chalk.red(`
|
|
74
|
+
╭─────────────────────────────────────────────────────────╮
|
|
75
|
+
│ │
|
|
76
|
+
│ ${chalk.bold('⚠️ VERSION BLOCKED')} │
|
|
77
|
+
│ │
|
|
78
|
+
│ Your CLI version ${chalk.gray(currentVersion)} has critical issues. │
|
|
79
|
+
│ Please update immediately to ${chalk.green(recommendedVersion)} │
|
|
80
|
+
│ │
|
|
81
|
+
│ Run ${chalk.cyan('npm i -g @codebakers/cli@latest')} to update │
|
|
82
|
+
│ │
|
|
83
|
+
╰─────────────────────────────────────────────────────────╯
|
|
84
|
+
`));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function showUpdateBanner(currentVersion: string, latestVersion: string, isRecommended: boolean): void {
|
|
88
|
+
const updateType = isRecommended ? chalk.green('Recommended update') : chalk.bold('Update available!');
|
|
72
89
|
console.log(chalk.yellow(`
|
|
73
90
|
╭─────────────────────────────────────────────────────────╮
|
|
74
91
|
│ │
|
|
75
|
-
│ ${
|
|
92
|
+
│ ${updateType} ${chalk.gray(currentVersion)} → ${chalk.green(latestVersion)} │
|
|
76
93
|
│ │
|
|
77
94
|
│ Run ${chalk.cyan('npm i -g @codebakers/cli@latest')} to update │
|
|
78
95
|
│ │
|
package/src/lib/api.ts
CHANGED
|
@@ -155,29 +155,108 @@ export function getCliVersion(): string {
|
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
157
|
* Check if there's a newer version of the CLI available
|
|
158
|
+
* Uses the CodeBakers API for controlled rollouts (only recommends stable, tested versions)
|
|
159
|
+
* Falls back to npm registry if API is unavailable
|
|
158
160
|
*/
|
|
159
161
|
export async function checkForUpdates(): Promise<{
|
|
160
162
|
currentVersion: string;
|
|
161
163
|
latestVersion: string;
|
|
162
164
|
updateAvailable: boolean;
|
|
165
|
+
autoUpdateEnabled: boolean;
|
|
166
|
+
autoUpdateVersion: string | null;
|
|
167
|
+
isBlocked: boolean;
|
|
163
168
|
} | null> {
|
|
164
169
|
try {
|
|
165
170
|
const currentVersion = getCliVersion();
|
|
166
|
-
const
|
|
171
|
+
const apiUrl = getApiUrl();
|
|
172
|
+
|
|
173
|
+
// First, try the CodeBakers API for controlled rollout info
|
|
174
|
+
try {
|
|
175
|
+
const response = await fetch(`${apiUrl}/api/cli/version`, {
|
|
176
|
+
headers: {
|
|
177
|
+
'Accept': 'application/json',
|
|
178
|
+
'X-CLI-Version': currentVersion,
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (response.ok) {
|
|
183
|
+
const data = await response.json();
|
|
184
|
+
const latestVersion = data.stableVersion || data.latestVersion;
|
|
185
|
+
const autoUpdateVersion = data.autoUpdateVersion;
|
|
186
|
+
const isBlocked = data.isBlocked === true;
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
currentVersion,
|
|
190
|
+
latestVersion,
|
|
191
|
+
updateAvailable: latestVersion !== currentVersion,
|
|
192
|
+
autoUpdateEnabled: data.autoUpdateEnabled === true,
|
|
193
|
+
autoUpdateVersion: autoUpdateVersion || null,
|
|
194
|
+
isBlocked,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
} catch {
|
|
198
|
+
// API unavailable, fall through to npm
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Fallback: check npm registry directly
|
|
202
|
+
const npmResponse = await fetch('https://registry.npmjs.org/@codebakers/cli/latest', {
|
|
167
203
|
headers: { 'Accept': 'application/json' },
|
|
168
204
|
});
|
|
169
205
|
|
|
170
|
-
if (!
|
|
206
|
+
if (!npmResponse.ok) return null;
|
|
171
207
|
|
|
172
|
-
const
|
|
173
|
-
const latestVersion =
|
|
208
|
+
const npmData = await npmResponse.json();
|
|
209
|
+
const latestVersion = npmData.version;
|
|
174
210
|
|
|
175
211
|
return {
|
|
176
212
|
currentVersion,
|
|
177
213
|
latestVersion,
|
|
178
214
|
updateAvailable: currentVersion !== latestVersion,
|
|
215
|
+
autoUpdateEnabled: false, // npm fallback doesn't have controlled rollout
|
|
216
|
+
autoUpdateVersion: null,
|
|
217
|
+
isBlocked: false,
|
|
179
218
|
};
|
|
180
219
|
} catch {
|
|
181
220
|
return null;
|
|
182
221
|
}
|
|
183
222
|
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Report a CLI error to the server for tracking
|
|
226
|
+
* This helps identify problematic versions for blocking
|
|
227
|
+
*/
|
|
228
|
+
export async function reportCliError(
|
|
229
|
+
errorType: string,
|
|
230
|
+
errorMessage: string,
|
|
231
|
+
context?: Record<string, unknown>
|
|
232
|
+
): Promise<void> {
|
|
233
|
+
try {
|
|
234
|
+
const apiUrl = getApiUrl();
|
|
235
|
+
const cliVersion = getCliVersion();
|
|
236
|
+
|
|
237
|
+
// Fire and forget - don't block on error reporting
|
|
238
|
+
fetch(`${apiUrl}/api/cli/error-report`, {
|
|
239
|
+
method: 'POST',
|
|
240
|
+
headers: {
|
|
241
|
+
'Content-Type': 'application/json',
|
|
242
|
+
'X-CLI-Version': cliVersion,
|
|
243
|
+
},
|
|
244
|
+
body: JSON.stringify({
|
|
245
|
+
cliVersion,
|
|
246
|
+
errorType,
|
|
247
|
+
errorMessage,
|
|
248
|
+
stackTrace: context?.stack,
|
|
249
|
+
context: JSON.stringify({
|
|
250
|
+
...context,
|
|
251
|
+
nodeVersion: process.version,
|
|
252
|
+
platform: process.platform,
|
|
253
|
+
arch: process.arch,
|
|
254
|
+
}),
|
|
255
|
+
}),
|
|
256
|
+
}).catch(() => {
|
|
257
|
+
// Ignore reporting failures
|
|
258
|
+
});
|
|
259
|
+
} catch {
|
|
260
|
+
// Never fail on error reporting
|
|
261
|
+
}
|
|
262
|
+
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -108,6 +108,31 @@ class CodeBakersServer {
|
|
|
108
108
|
return {};
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Confirm download to server (non-blocking analytics)
|
|
113
|
+
*/
|
|
114
|
+
private async confirmDownload(version: string, moduleCount: number): Promise<void> {
|
|
115
|
+
try {
|
|
116
|
+
const headers: Record<string, string> = {
|
|
117
|
+
'Content-Type': 'application/json',
|
|
118
|
+
...this.getAuthHeaders(),
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
await fetch(`${this.apiUrl}/api/content/confirm`, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers,
|
|
124
|
+
body: JSON.stringify({
|
|
125
|
+
version,
|
|
126
|
+
moduleCount,
|
|
127
|
+
cliVersion: getCliVersion(),
|
|
128
|
+
command: 'auto-update',
|
|
129
|
+
}),
|
|
130
|
+
});
|
|
131
|
+
} catch {
|
|
132
|
+
// Silently ignore - this is just for analytics
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
111
136
|
/**
|
|
112
137
|
* Automatically check for and apply pattern updates
|
|
113
138
|
* Runs silently in background - no user intervention needed
|
|
@@ -213,6 +238,21 @@ class CodeBakersServer {
|
|
|
213
238
|
};
|
|
214
239
|
fs.writeFileSync(versionPath, JSON.stringify(versionInfo, null, 2));
|
|
215
240
|
|
|
241
|
+
// Write notification file for AI to read and show to user
|
|
242
|
+
const notificationPath = path.join(claudeDir, '.update-notification.json');
|
|
243
|
+
const notification = {
|
|
244
|
+
type: 'patterns_updated',
|
|
245
|
+
previousVersion: installed?.version || 'unknown',
|
|
246
|
+
newVersion: content.version,
|
|
247
|
+
moduleCount,
|
|
248
|
+
updatedAt: new Date().toISOString(),
|
|
249
|
+
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.`,
|
|
250
|
+
};
|
|
251
|
+
fs.writeFileSync(notificationPath, JSON.stringify(notification, null, 2));
|
|
252
|
+
|
|
253
|
+
// Confirm to server (non-blocking, fire-and-forget)
|
|
254
|
+
this.confirmDownload(content.version, moduleCount).catch(() => {});
|
|
255
|
+
|
|
216
256
|
this.autoUpdateChecked = true;
|
|
217
257
|
this.autoUpdateInProgress = false;
|
|
218
258
|
|
|
@@ -984,6 +1024,15 @@ class CodeBakersServer {
|
|
|
984
1024
|
required: ['request'],
|
|
985
1025
|
},
|
|
986
1026
|
},
|
|
1027
|
+
{
|
|
1028
|
+
name: 'check_update_notification',
|
|
1029
|
+
description:
|
|
1030
|
+
'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.',
|
|
1031
|
+
inputSchema: {
|
|
1032
|
+
type: 'object' as const,
|
|
1033
|
+
properties: {},
|
|
1034
|
+
},
|
|
1035
|
+
},
|
|
987
1036
|
],
|
|
988
1037
|
}));
|
|
989
1038
|
|
|
@@ -1111,6 +1160,9 @@ class CodeBakersServer {
|
|
|
1111
1160
|
case 'add_api_route':
|
|
1112
1161
|
return this.handleAddApiRoute(args as { request: string });
|
|
1113
1162
|
|
|
1163
|
+
case 'check_update_notification':
|
|
1164
|
+
return this.handleCheckUpdateNotification();
|
|
1165
|
+
|
|
1114
1166
|
default:
|
|
1115
1167
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
1116
1168
|
}
|
|
@@ -4839,6 +4891,44 @@ export default ${asyncKeyword}function ${pageName}Page() {${authCheck}
|
|
|
4839
4891
|
};
|
|
4840
4892
|
}
|
|
4841
4893
|
|
|
4894
|
+
/**
|
|
4895
|
+
* Check for update notifications and return message to show user
|
|
4896
|
+
*/
|
|
4897
|
+
private async handleCheckUpdateNotification() {
|
|
4898
|
+
const cwd = process.cwd();
|
|
4899
|
+
const notificationPath = path.join(cwd, '.claude', '.update-notification.json');
|
|
4900
|
+
|
|
4901
|
+
try {
|
|
4902
|
+
if (!fs.existsSync(notificationPath)) {
|
|
4903
|
+
return {
|
|
4904
|
+
content: [{
|
|
4905
|
+
type: 'text' as const,
|
|
4906
|
+
text: 'No update notification.',
|
|
4907
|
+
}],
|
|
4908
|
+
};
|
|
4909
|
+
}
|
|
4910
|
+
|
|
4911
|
+
const notification = JSON.parse(fs.readFileSync(notificationPath, 'utf-8'));
|
|
4912
|
+
|
|
4913
|
+
// Delete the notification file after reading (so it only shows once)
|
|
4914
|
+
fs.unlinkSync(notificationPath);
|
|
4915
|
+
|
|
4916
|
+
return {
|
|
4917
|
+
content: [{
|
|
4918
|
+
type: 'text' as const,
|
|
4919
|
+
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()}`,
|
|
4920
|
+
}],
|
|
4921
|
+
};
|
|
4922
|
+
} catch {
|
|
4923
|
+
return {
|
|
4924
|
+
content: [{
|
|
4925
|
+
type: 'text' as const,
|
|
4926
|
+
text: 'No update notification.',
|
|
4927
|
+
}],
|
|
4928
|
+
};
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4842
4932
|
private parseApiRouteRequest(request: string): {
|
|
4843
4933
|
success: boolean;
|
|
4844
4934
|
routeName?: string;
|