@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 +34 -0
- package/dist/config.js +81 -1
- package/dist/index.js +200 -2
- package/package.json +1 -1
- package/src/config.ts +93 -1
- package/src/index.ts +246 -2
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.
|
|
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.
|
|
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
|
-
|
|
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
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.
|
|
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.
|
|
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
|
-
|
|
471
|
+
// Still check for updates when showing welcome
|
|
472
|
+
checkForUpdatesInBackground().then(() => {
|
|
473
|
+
showWelcome();
|
|
474
|
+
});
|
|
231
475
|
} else {
|
|
232
476
|
program.parse();
|
|
233
477
|
}
|