@codebakers/cli 2.6.0 → 2.6.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.
@@ -9,6 +9,7 @@ const ora_1 = __importDefault(require("ora"));
9
9
  const fs_1 = require("fs");
10
10
  const path_1 = require("path");
11
11
  const config_js_1 = require("../config.js");
12
+ const api_js_1 = require("../lib/api.js");
12
13
  async function install() {
13
14
  console.log(chalk_1.default.blue('\n CodeBakers Install\n'));
14
15
  const apiKey = (0, config_js_1.getApiKey)();
@@ -41,14 +42,22 @@ async function install() {
41
42
  (0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'CLAUDE.md'), content.router);
42
43
  }
43
44
  // Write modules
45
+ const modulesDir = (0, path_1.join)(cwd, '.claude');
44
46
  if (content.modules && Object.keys(content.modules).length > 0) {
45
- const modulesDir = (0, path_1.join)(cwd, '.claude');
46
47
  if (!(0, fs_1.existsSync)(modulesDir)) {
47
48
  (0, fs_1.mkdirSync)(modulesDir, { recursive: true });
48
49
  }
49
50
  for (const [name, data] of Object.entries(content.modules)) {
50
51
  (0, fs_1.writeFileSync)((0, path_1.join)(modulesDir, name), data);
51
52
  }
53
+ // Write version file for tracking
54
+ const versionInfo = {
55
+ version: content.version,
56
+ moduleCount: Object.keys(content.modules).length,
57
+ installedAt: new Date().toISOString(),
58
+ cliVersion: (0, api_js_1.getCliVersion)(),
59
+ };
60
+ (0, fs_1.writeFileSync)((0, path_1.join)(modulesDir, '.version.json'), JSON.stringify(versionInfo, null, 2));
52
61
  }
53
62
  // Add to .gitignore if not present
54
63
  const gitignorePath = (0, path_1.join)(cwd, '.gitignore');
@@ -74,6 +74,15 @@ async function upgrade() {
74
74
  }
75
75
  console.log(chalk_1.default.green(` ✓ Updated ${moduleCount} modules in .claude/`));
76
76
  }
77
+ // Write version file for tracking
78
+ const versionInfo = {
79
+ version: content.version,
80
+ moduleCount,
81
+ updatedAt: new Date().toISOString(),
82
+ cliVersion: (0, api_js_1.getCliVersion)(),
83
+ };
84
+ (0, fs_1.writeFileSync)((0, path_1.join)(claudeDir, '.version.json'), JSON.stringify(versionInfo, null, 2));
85
+ console.log(chalk_1.default.green(' ✓ Version info saved'));
77
86
  console.log(chalk_1.default.green(`\n ✅ Upgraded to patterns v${content.version}!\n`));
78
87
  // Show what's new if available
79
88
  console.log(chalk_1.default.gray(' Changes take effect in your next AI session.\n'));
@@ -41,6 +41,7 @@ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
41
41
  const config_js_1 = require("../config.js");
42
42
  const audit_js_1 = require("../commands/audit.js");
43
43
  const heal_js_1 = require("../commands/heal.js");
44
+ const api_js_1 = require("../lib/api.js");
44
45
  const fs = __importStar(require("fs"));
45
46
  const path = __importStar(require("path"));
46
47
  const child_process_1 = require("child_process");
@@ -52,6 +53,8 @@ class CodeBakersServer {
52
53
  server;
53
54
  apiKey;
54
55
  apiUrl;
56
+ autoUpdateChecked = false;
57
+ autoUpdateInProgress = false;
55
58
  constructor() {
56
59
  this.apiKey = (0, config_js_1.getApiKey)();
57
60
  this.apiUrl = (0, config_js_1.getApiUrl)();
@@ -64,6 +67,109 @@ class CodeBakersServer {
64
67
  },
65
68
  });
66
69
  this.setupHandlers();
70
+ // Trigger auto-update check on startup (non-blocking)
71
+ this.checkAndAutoUpdate().catch(() => {
72
+ // Silently ignore errors - don't interrupt user
73
+ });
74
+ }
75
+ /**
76
+ * Automatically check for and apply pattern updates
77
+ * Runs silently in background - no user intervention needed
78
+ */
79
+ async checkAndAutoUpdate() {
80
+ if (this.autoUpdateChecked || this.autoUpdateInProgress || !this.apiKey) {
81
+ return;
82
+ }
83
+ this.autoUpdateInProgress = true;
84
+ try {
85
+ const cwd = process.cwd();
86
+ const versionPath = path.join(cwd, '.claude', '.version.json');
87
+ // Check if we should auto-update (once per 24 hours)
88
+ let lastCheck = null;
89
+ let installed = null;
90
+ if (fs.existsSync(versionPath)) {
91
+ try {
92
+ installed = JSON.parse(fs.readFileSync(versionPath, 'utf-8'));
93
+ const checkTime = installed?.updatedAt || installed?.installedAt;
94
+ if (checkTime) {
95
+ lastCheck = new Date(checkTime);
96
+ }
97
+ }
98
+ catch {
99
+ // Ignore parse errors
100
+ }
101
+ }
102
+ // Skip if checked within last 24 hours
103
+ if (lastCheck) {
104
+ const hoursSinceCheck = (Date.now() - lastCheck.getTime()) / (1000 * 60 * 60);
105
+ if (hoursSinceCheck < 24) {
106
+ this.autoUpdateChecked = true;
107
+ this.autoUpdateInProgress = false;
108
+ return;
109
+ }
110
+ }
111
+ // Fetch latest version
112
+ const response = await fetch(`${this.apiUrl}/api/content/version`, {
113
+ headers: { 'Authorization': `Bearer ${this.apiKey}` },
114
+ });
115
+ if (!response.ok) {
116
+ this.autoUpdateInProgress = false;
117
+ return;
118
+ }
119
+ const latest = await response.json();
120
+ // Check if update needed
121
+ if (installed && installed.version === latest.version) {
122
+ // Already up to date - update timestamp to avoid checking for 24h
123
+ installed.updatedAt = new Date().toISOString();
124
+ fs.writeFileSync(versionPath, JSON.stringify(installed, null, 2));
125
+ this.autoUpdateChecked = true;
126
+ this.autoUpdateInProgress = false;
127
+ return;
128
+ }
129
+ // Fetch full content and update
130
+ const contentResponse = await fetch(`${this.apiUrl}/api/content`, {
131
+ headers: { 'Authorization': `Bearer ${this.apiKey}` },
132
+ });
133
+ if (!contentResponse.ok) {
134
+ this.autoUpdateInProgress = false;
135
+ return;
136
+ }
137
+ const content = await contentResponse.json();
138
+ const claudeDir = path.join(cwd, '.claude');
139
+ // Ensure .claude directory exists
140
+ if (!fs.existsSync(claudeDir)) {
141
+ fs.mkdirSync(claudeDir, { recursive: true });
142
+ }
143
+ // Write updated modules
144
+ let moduleCount = 0;
145
+ if (content.modules) {
146
+ for (const [name, data] of Object.entries(content.modules)) {
147
+ fs.writeFileSync(path.join(claudeDir, name), data);
148
+ moduleCount++;
149
+ }
150
+ }
151
+ // Update CLAUDE.md if router content exists
152
+ if (content.router || content.claudeMd) {
153
+ const routerContent = content.claudeMd || content.router;
154
+ fs.writeFileSync(path.join(cwd, 'CLAUDE.md'), routerContent);
155
+ }
156
+ // Write version file
157
+ const versionInfo = {
158
+ version: content.version,
159
+ moduleCount,
160
+ updatedAt: new Date().toISOString(),
161
+ cliVersion: (0, api_js_1.getCliVersion)(),
162
+ };
163
+ fs.writeFileSync(versionPath, JSON.stringify(versionInfo, null, 2));
164
+ this.autoUpdateChecked = true;
165
+ this.autoUpdateInProgress = false;
166
+ // Log success (visible in MCP logs)
167
+ console.error(`[CodeBakers] Auto-updated patterns to v${content.version} (${moduleCount} modules)`);
168
+ }
169
+ catch {
170
+ // Silently fail - don't interrupt user's workflow
171
+ this.autoUpdateInProgress = false;
172
+ }
67
173
  }
68
174
  gatherProjectContext() {
69
175
  const cwd = process.cwd();
@@ -239,6 +345,46 @@ class CodeBakersServer {
239
345
  }
240
346
  return context;
241
347
  }
348
+ async checkPatternVersion() {
349
+ const cwd = process.cwd();
350
+ const versionPath = path.join(cwd, '.claude', '.version.json');
351
+ // Read local version
352
+ let installed = null;
353
+ if (fs.existsSync(versionPath)) {
354
+ try {
355
+ installed = JSON.parse(fs.readFileSync(versionPath, 'utf-8'));
356
+ }
357
+ catch {
358
+ // Ignore parse errors
359
+ }
360
+ }
361
+ // Fetch latest version from API
362
+ let latest = null;
363
+ try {
364
+ const response = await fetch(`${this.apiUrl}/api/content/version`, {
365
+ headers: this.apiKey ? { 'Authorization': `Bearer ${this.apiKey}` } : {},
366
+ });
367
+ if (response.ok) {
368
+ latest = await response.json();
369
+ }
370
+ }
371
+ catch {
372
+ // Ignore fetch errors
373
+ }
374
+ // Compare versions
375
+ let updateAvailable = false;
376
+ let message = null;
377
+ if (installed && latest) {
378
+ if (installed.version !== latest.version) {
379
+ updateAvailable = true;
380
+ message = `⚠️ Pattern update available: v${installed.version} → v${latest.version} (${latest.moduleCount - installed.moduleCount} new modules)\n Run \`codebakers upgrade\` to update`;
381
+ }
382
+ }
383
+ else if (!installed && latest) {
384
+ message = `ℹ️ No version tracking found. Run \`codebakers upgrade\` to sync patterns`;
385
+ }
386
+ return { installed, latest, updateAvailable, message };
387
+ }
242
388
  formatContextForPrompt(context) {
243
389
  const lines = [];
244
390
  lines.push(`Project: ${context.projectName}`);
@@ -602,7 +748,7 @@ class CodeBakersServer {
602
748
  case 'get_experience_level':
603
749
  return this.handleGetExperienceLevel();
604
750
  case 'get_status':
605
- return this.handleGetStatus();
751
+ return await this.handleGetStatus();
606
752
  case 'run_audit':
607
753
  return this.handleRunAudit();
608
754
  case 'heal':
@@ -1400,16 +1546,28 @@ phase: development
1400
1546
  };
1401
1547
  }
1402
1548
  }
1403
- handleGetStatus() {
1549
+ async handleGetStatus() {
1404
1550
  const level = (0, config_js_1.getExperienceLevel)();
1405
1551
  const context = this.gatherProjectContext();
1552
+ const versionCheck = await this.checkPatternVersion();
1553
+ const cliVersion = (0, api_js_1.getCliVersion)();
1554
+ // Build version status section
1555
+ let versionSection = `- **CLI Version:** ${cliVersion}`;
1556
+ if (versionCheck.installed) {
1557
+ versionSection += `\n- **Patterns Version:** ${versionCheck.installed.version} (${versionCheck.installed.moduleCount} modules)`;
1558
+ }
1559
+ // Build update alert if needed
1560
+ let updateAlert = '';
1561
+ if (versionCheck.message) {
1562
+ updateAlert = `\n\n## ${versionCheck.updateAvailable ? '⚠️ Update Available' : 'ℹ️ Version Info'}\n${versionCheck.message}\n`;
1563
+ }
1406
1564
  const statusText = `# ✅ CodeBakers is Active!
1407
1565
 
1408
1566
  ## Connection Status
1409
1567
  - **MCP Server:** Running
1410
1568
  - **API Connected:** Yes
1411
- - **Version:** 2.2.0
1412
-
1569
+ ${versionSection}
1570
+ ${updateAlert}
1413
1571
  ## Current Settings
1414
1572
  - **Experience Level:** ${level.charAt(0).toUpperCase() + level.slice(1)}
1415
1573
  - **Project:** ${context.projectName}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "2.6.0",
3
+ "version": "2.6.2",
4
4
  "description": "CodeBakers CLI - Production patterns for AI-assisted development",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -3,6 +3,7 @@ import ora from 'ora';
3
3
  import { writeFileSync, mkdirSync, existsSync } from 'fs';
4
4
  import { join } from 'path';
5
5
  import { getApiKey, getApiUrl } from '../config.js';
6
+ import { getCliVersion } from '../lib/api.js';
6
7
 
7
8
  interface ContentResponse {
8
9
  version: string;
@@ -51,8 +52,8 @@ export async function install(): Promise<void> {
51
52
  }
52
53
 
53
54
  // Write modules
55
+ const modulesDir = join(cwd, '.claude');
54
56
  if (content.modules && Object.keys(content.modules).length > 0) {
55
- const modulesDir = join(cwd, '.claude');
56
57
  if (!existsSync(modulesDir)) {
57
58
  mkdirSync(modulesDir, { recursive: true });
58
59
  }
@@ -60,6 +61,15 @@ export async function install(): Promise<void> {
60
61
  for (const [name, data] of Object.entries(content.modules)) {
61
62
  writeFileSync(join(modulesDir, name), data);
62
63
  }
64
+
65
+ // Write version file for tracking
66
+ const versionInfo = {
67
+ version: content.version,
68
+ moduleCount: Object.keys(content.modules).length,
69
+ installedAt: new Date().toISOString(),
70
+ cliVersion: getCliVersion(),
71
+ };
72
+ writeFileSync(join(modulesDir, '.version.json'), JSON.stringify(versionInfo, null, 2));
63
73
  }
64
74
 
65
75
  // Add to .gitignore if not present
@@ -91,6 +91,16 @@ export async function upgrade(): Promise<void> {
91
91
  console.log(chalk.green(` ✓ Updated ${moduleCount} modules in .claude/`));
92
92
  }
93
93
 
94
+ // Write version file for tracking
95
+ const versionInfo = {
96
+ version: content.version,
97
+ moduleCount,
98
+ updatedAt: new Date().toISOString(),
99
+ cliVersion: getCliVersion(),
100
+ };
101
+ writeFileSync(join(claudeDir, '.version.json'), JSON.stringify(versionInfo, null, 2));
102
+ console.log(chalk.green(' ✓ Version info saved'));
103
+
94
104
  console.log(chalk.green(`\n ✅ Upgraded to patterns v${content.version}!\n`));
95
105
 
96
106
  // Show what's new if available
package/src/mcp/server.ts CHANGED
@@ -11,11 +11,21 @@ import {
11
11
  import { getApiKey, getApiUrl, getExperienceLevel, setExperienceLevel, type ExperienceLevel } from '../config.js';
12
12
  import { audit as runAudit } from '../commands/audit.js';
13
13
  import { heal as runHeal } from '../commands/heal.js';
14
+ import { getCliVersion } from '../lib/api.js';
14
15
  import * as fs from 'fs';
15
16
  import * as path from 'path';
16
17
  import { execSync } from 'child_process';
17
18
  import * as templates from '../templates/nextjs-supabase.js';
18
19
 
20
+ // Version info type
21
+ interface VersionInfo {
22
+ version: string;
23
+ moduleCount: number;
24
+ installedAt?: string;
25
+ updatedAt?: string;
26
+ cliVersion: string;
27
+ }
28
+
19
29
  // Pattern cache to avoid repeated API calls
20
30
  const patternCache = new Map<string, { content: string; timestamp: number }>();
21
31
  const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
@@ -51,6 +61,8 @@ class CodeBakersServer {
51
61
  private server: Server;
52
62
  private apiKey: string | null;
53
63
  private apiUrl: string;
64
+ private autoUpdateChecked = false;
65
+ private autoUpdateInProgress = false;
54
66
 
55
67
  constructor() {
56
68
  this.apiKey = getApiKey();
@@ -69,6 +81,128 @@ class CodeBakersServer {
69
81
  );
70
82
 
71
83
  this.setupHandlers();
84
+
85
+ // Trigger auto-update check on startup (non-blocking)
86
+ this.checkAndAutoUpdate().catch(() => {
87
+ // Silently ignore errors - don't interrupt user
88
+ });
89
+ }
90
+
91
+ /**
92
+ * Automatically check for and apply pattern updates
93
+ * Runs silently in background - no user intervention needed
94
+ */
95
+ private async checkAndAutoUpdate(): Promise<void> {
96
+ if (this.autoUpdateChecked || this.autoUpdateInProgress || !this.apiKey) {
97
+ return;
98
+ }
99
+
100
+ this.autoUpdateInProgress = true;
101
+
102
+ try {
103
+ const cwd = process.cwd();
104
+ const versionPath = path.join(cwd, '.claude', '.version.json');
105
+
106
+ // Check if we should auto-update (once per 24 hours)
107
+ let lastCheck: Date | null = null;
108
+ let installed: VersionInfo | null = null;
109
+
110
+ if (fs.existsSync(versionPath)) {
111
+ try {
112
+ installed = JSON.parse(fs.readFileSync(versionPath, 'utf-8'));
113
+ const checkTime = installed?.updatedAt || installed?.installedAt;
114
+ if (checkTime) {
115
+ lastCheck = new Date(checkTime);
116
+ }
117
+ } catch {
118
+ // Ignore parse errors
119
+ }
120
+ }
121
+
122
+ // Skip if checked within last 24 hours
123
+ if (lastCheck) {
124
+ const hoursSinceCheck = (Date.now() - lastCheck.getTime()) / (1000 * 60 * 60);
125
+ if (hoursSinceCheck < 24) {
126
+ this.autoUpdateChecked = true;
127
+ this.autoUpdateInProgress = false;
128
+ return;
129
+ }
130
+ }
131
+
132
+ // Fetch latest version
133
+ const response = await fetch(`${this.apiUrl}/api/content/version`, {
134
+ headers: { 'Authorization': `Bearer ${this.apiKey}` },
135
+ });
136
+
137
+ if (!response.ok) {
138
+ this.autoUpdateInProgress = false;
139
+ return;
140
+ }
141
+
142
+ const latest = await response.json();
143
+
144
+ // Check if update needed
145
+ if (installed && installed.version === latest.version) {
146
+ // Already up to date - update timestamp to avoid checking for 24h
147
+ installed.updatedAt = new Date().toISOString();
148
+ fs.writeFileSync(versionPath, JSON.stringify(installed, null, 2));
149
+ this.autoUpdateChecked = true;
150
+ this.autoUpdateInProgress = false;
151
+ return;
152
+ }
153
+
154
+ // Fetch full content and update
155
+ const contentResponse = await fetch(`${this.apiUrl}/api/content`, {
156
+ headers: { 'Authorization': `Bearer ${this.apiKey}` },
157
+ });
158
+
159
+ if (!contentResponse.ok) {
160
+ this.autoUpdateInProgress = false;
161
+ return;
162
+ }
163
+
164
+ const content = await contentResponse.json();
165
+ const claudeDir = path.join(cwd, '.claude');
166
+
167
+ // Ensure .claude directory exists
168
+ if (!fs.existsSync(claudeDir)) {
169
+ fs.mkdirSync(claudeDir, { recursive: true });
170
+ }
171
+
172
+ // Write updated modules
173
+ let moduleCount = 0;
174
+ if (content.modules) {
175
+ for (const [name, data] of Object.entries(content.modules)) {
176
+ fs.writeFileSync(path.join(claudeDir, name), data as string);
177
+ moduleCount++;
178
+ }
179
+ }
180
+
181
+ // Update CLAUDE.md if router content exists
182
+ if (content.router || content.claudeMd) {
183
+ const routerContent = content.claudeMd || content.router;
184
+ fs.writeFileSync(path.join(cwd, 'CLAUDE.md'), routerContent);
185
+ }
186
+
187
+ // Write version file
188
+ const versionInfo: VersionInfo = {
189
+ version: content.version,
190
+ moduleCount,
191
+ updatedAt: new Date().toISOString(),
192
+ cliVersion: getCliVersion(),
193
+ };
194
+ fs.writeFileSync(versionPath, JSON.stringify(versionInfo, null, 2));
195
+
196
+ this.autoUpdateChecked = true;
197
+ this.autoUpdateInProgress = false;
198
+
199
+ // Log success (visible in MCP logs)
200
+ console.error(`[CodeBakers] Auto-updated patterns to v${content.version} (${moduleCount} modules)`);
201
+
202
+ } catch {
203
+ // Silently fail - don't interrupt user's workflow
204
+ this.autoUpdateInProgress = false;
205
+ }
72
206
  }
73
207
 
74
208
  private gatherProjectContext(): ProjectContext {
@@ -243,6 +377,54 @@ class CodeBakersServer {
243
377
  return context;
244
378
  }
245
379
 
380
+ private async checkPatternVersion(): Promise<{
381
+ installed: VersionInfo | null;
382
+ latest: { version: string; moduleCount: number } | null;
383
+ updateAvailable: boolean;
384
+ message: string | null;
385
+ }> {
386
+ const cwd = process.cwd();
387
+ const versionPath = path.join(cwd, '.claude', '.version.json');
388
+
389
+ // Read local version
390
+ let installed: VersionInfo | null = null;
391
+ if (fs.existsSync(versionPath)) {
392
+ try {
393
+ installed = JSON.parse(fs.readFileSync(versionPath, 'utf-8'));
394
+ } catch {
395
+ // Ignore parse errors
396
+ }
397
+ }
398
+
399
+ // Fetch latest version from API
400
+ let latest: { version: string; moduleCount: number } | null = null;
401
+ try {
402
+ const response = await fetch(`${this.apiUrl}/api/content/version`, {
403
+ headers: this.apiKey ? { 'Authorization': `Bearer ${this.apiKey}` } : {},
404
+ });
405
+ if (response.ok) {
406
+ latest = await response.json();
407
+ }
408
+ } catch {
409
+ // Ignore fetch errors
410
+ }
411
+
412
+ // Compare versions
413
+ let updateAvailable = false;
414
+ let message: string | null = null;
415
+
416
+ if (installed && latest) {
417
+ if (installed.version !== latest.version) {
418
+ updateAvailable = true;
419
+ message = `⚠️ Pattern update available: v${installed.version} → v${latest.version} (${latest.moduleCount - installed.moduleCount} new modules)\n Run \`codebakers upgrade\` to update`;
420
+ }
421
+ } else if (!installed && latest) {
422
+ message = `ℹ️ No version tracking found. Run \`codebakers upgrade\` to sync patterns`;
423
+ }
424
+
425
+ return { installed, latest, updateAvailable, message };
426
+ }
427
+
246
428
  private formatContextForPrompt(context: ProjectContext): string {
247
429
  const lines: string[] = [];
248
430
 
@@ -651,7 +833,7 @@ class CodeBakersServer {
651
833
  return this.handleGetExperienceLevel();
652
834
 
653
835
  case 'get_status':
654
- return this.handleGetStatus();
836
+ return await this.handleGetStatus();
655
837
 
656
838
  case 'run_audit':
657
839
  return this.handleRunAudit();
@@ -1579,17 +1761,31 @@ phase: development
1579
1761
  }
1580
1762
  }
1581
1763
 
1582
- private handleGetStatus() {
1764
+ private async handleGetStatus() {
1583
1765
  const level = getExperienceLevel();
1584
1766
  const context = this.gatherProjectContext();
1767
+ const versionCheck = await this.checkPatternVersion();
1768
+ const cliVersion = getCliVersion();
1769
+
1770
+ // Build version status section
1771
+ let versionSection = `- **CLI Version:** ${cliVersion}`;
1772
+ if (versionCheck.installed) {
1773
+ versionSection += `\n- **Patterns Version:** ${versionCheck.installed.version} (${versionCheck.installed.moduleCount} modules)`;
1774
+ }
1775
+
1776
+ // Build update alert if needed
1777
+ let updateAlert = '';
1778
+ if (versionCheck.message) {
1779
+ updateAlert = `\n\n## ${versionCheck.updateAvailable ? '⚠️ Update Available' : 'ℹ️ Version Info'}\n${versionCheck.message}\n`;
1780
+ }
1585
1781
 
1586
1782
  const statusText = `# ✅ CodeBakers is Active!
1587
1783
 
1588
1784
  ## Connection Status
1589
1785
  - **MCP Server:** Running
1590
1786
  - **API Connected:** Yes
1591
- - **Version:** 2.2.0
1592
-
1787
+ ${versionSection}
1788
+ ${updateAlert}
1593
1789
  ## Current Settings
1594
1790
  - **Experience Level:** ${level.charAt(0).toUpperCase() + level.slice(1)}
1595
1791
  - **Project:** ${context.projectName}