@codebakers/cli 1.0.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/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # @codebakers/cli
2
+
3
+ Official CLI for CodeBakers - AI prompt patterns for production-ready code.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npx @codebakers/cli setup
9
+ ```
10
+
11
+ Or install globally:
12
+
13
+ ```bash
14
+ npm install -g @codebakers/cli
15
+ codebakers setup
16
+ ```
17
+
18
+ ## Commands
19
+
20
+ ### Setup
21
+
22
+ Configure CodeBakers with your API key and set up your IDE:
23
+
24
+ ```bash
25
+ # Interactive setup
26
+ npx @codebakers/cli setup
27
+
28
+ # Setup for specific IDE
29
+ npx @codebakers/cli setup --ide cursor
30
+ npx @codebakers/cli setup --ide windsurf
31
+ npx @codebakers/cli setup --ide aider
32
+ npx @codebakers/cli setup --ide claude-code
33
+ ```
34
+
35
+ ### Claude Code MCP Server
36
+
37
+ Add CodeBakers as an MCP server in Claude Code:
38
+
39
+ ```bash
40
+ /mcp add codebakers npx -y @codebakers/cli serve
41
+ ```
42
+
43
+ ### Status
44
+
45
+ Check your configuration:
46
+
47
+ ```bash
48
+ npx @codebakers/cli status
49
+ ```
50
+
51
+ ### Logout
52
+
53
+ Remove stored API key:
54
+
55
+ ```bash
56
+ npx @codebakers/cli logout
57
+ ```
58
+
59
+ ## Supported IDEs
60
+
61
+ | IDE | Configuration File |
62
+ |-----|-------------------|
63
+ | Cursor | `.cursorrules` |
64
+ | Windsurf | `.windsurfrules` |
65
+ | Aider | `.aider.conf.yml` |
66
+ | Claude Code | `CLAUDE.md` + `.claude/` |
67
+
68
+ ## Getting Your API Key
69
+
70
+ 1. Sign up at [codebakers.ai](https://codebakers.ai)
71
+ 2. Go to Settings to find your API key
72
+ 3. Run `codebakers setup` and paste your key
73
+
74
+ ## What's Included
75
+
76
+ CodeBakers provides 34 production-ready pattern modules:
77
+
78
+ - **00-core** - Required standards, types, error handling
79
+ - **01-database** - Drizzle ORM, queries, migrations
80
+ - **02-auth** - Authentication, OAuth, 2FA, security
81
+ - **03-api** - API routes, validation, rate limiting
82
+ - **04-frontend** - React, forms, states, i18n
83
+ - **05-payments** - Stripe, subscriptions, billing
84
+ - **06-integrations** - Email, files, background jobs
85
+ - And 27 more modules...
86
+
87
+ ## License
88
+
89
+ MIT
@@ -0,0 +1,36 @@
1
+ // src/utils/config.ts
2
+ import Conf from "conf";
3
+ var config = new Conf({
4
+ projectName: "codebakers",
5
+ defaults: {
6
+ apiKey: null,
7
+ apiUrl: "https://codebakers.ai"
8
+ }
9
+ });
10
+ function getApiKey() {
11
+ return config.get("apiKey");
12
+ }
13
+ function setApiKey(key) {
14
+ config.set("apiKey", key);
15
+ }
16
+ function getApiUrl() {
17
+ return config.get("apiUrl");
18
+ }
19
+ function setApiUrl(url) {
20
+ config.set("apiUrl", url);
21
+ }
22
+ function clearConfig() {
23
+ config.clear();
24
+ }
25
+ function getConfigPath() {
26
+ return config.path;
27
+ }
28
+
29
+ export {
30
+ getApiKey,
31
+ setApiKey,
32
+ getApiUrl,
33
+ setApiUrl,
34
+ clearConfig,
35
+ getConfigPath
36
+ };
@@ -0,0 +1,16 @@
1
+ import {
2
+ clearConfig,
3
+ getApiKey,
4
+ getApiUrl,
5
+ getConfigPath,
6
+ setApiKey,
7
+ setApiUrl
8
+ } from "./chunk-7CKLRE2H.js";
9
+ export {
10
+ clearConfig,
11
+ getApiKey,
12
+ getApiUrl,
13
+ getConfigPath,
14
+ setApiKey,
15
+ setApiUrl
16
+ };
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,514 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getApiKey,
4
+ getApiUrl,
5
+ getConfigPath,
6
+ setApiKey
7
+ } from "./chunk-7CKLRE2H.js";
8
+
9
+ // src/index.ts
10
+ import { Command } from "commander";
11
+ import chalk2 from "chalk";
12
+
13
+ // src/commands/setup.ts
14
+ import inquirer from "inquirer";
15
+ import chalk from "chalk";
16
+ import ora from "ora";
17
+ import { writeFile, mkdir } from "fs/promises";
18
+ import { existsSync } from "fs";
19
+ import { join } from "path";
20
+
21
+ // src/utils/api.ts
22
+ var ApiError = class extends Error {
23
+ constructor(message, statusCode, code) {
24
+ super(message);
25
+ this.statusCode = statusCode;
26
+ this.code = code;
27
+ this.name = "ApiError";
28
+ }
29
+ };
30
+ async function makeRequest(endpoint, options = {}) {
31
+ const apiKey = getApiKey();
32
+ if (!apiKey) {
33
+ throw new ApiError("No API key configured. Run: codebakers setup", 401);
34
+ }
35
+ const baseUrl = getApiUrl();
36
+ const url = `${baseUrl}${endpoint}`;
37
+ const response = await fetch(url, {
38
+ ...options,
39
+ headers: {
40
+ "Content-Type": "application/json",
41
+ Authorization: `Bearer ${apiKey}`,
42
+ ...options.headers
43
+ }
44
+ });
45
+ const data = await response.json();
46
+ if (!response.ok) {
47
+ throw new ApiError(
48
+ data.error || "API request failed",
49
+ response.status,
50
+ data.code
51
+ );
52
+ }
53
+ return data;
54
+ }
55
+ async function validateApiKey(apiKey) {
56
+ const baseUrl = getApiUrl();
57
+ const response = await fetch(`${baseUrl}/api/patterns`, {
58
+ headers: {
59
+ Authorization: `Bearer ${apiKey}`
60
+ }
61
+ });
62
+ return response.ok;
63
+ }
64
+ async function listPatterns() {
65
+ return makeRequest("/api/patterns");
66
+ }
67
+ async function fetchPatterns(patterns) {
68
+ return makeRequest("/api/patterns", {
69
+ method: "POST",
70
+ body: JSON.stringify({ patterns })
71
+ });
72
+ }
73
+ async function fetchAllPatterns() {
74
+ const list = await listPatterns();
75
+ const allPatterns = {};
76
+ const patternNames = list.patterns.map((p) => p.name);
77
+ for (let i = 0; i < patternNames.length; i += 5) {
78
+ const batch = patternNames.slice(i, i + 5);
79
+ const result = await fetchPatterns(batch);
80
+ Object.assign(allPatterns, result.patterns);
81
+ }
82
+ const routerResult = await fetchPatterns(["router"]);
83
+ Object.assign(allPatterns, routerResult.patterns);
84
+ return allPatterns;
85
+ }
86
+
87
+ // src/commands/setup.ts
88
+ var IDE_CONFIGS = {
89
+ cursor: {
90
+ file: ".cursorrules",
91
+ description: "Cursor AI IDE"
92
+ },
93
+ windsurf: {
94
+ file: ".windsurfrules",
95
+ description: "Windsurf (Codeium) IDE"
96
+ },
97
+ aider: {
98
+ file: ".aider.conf.yml",
99
+ description: "Aider CLI"
100
+ },
101
+ "claude-code": {
102
+ file: "CLAUDE.md",
103
+ description: "Claude Code CLI"
104
+ }
105
+ };
106
+ function setupCommand(program2) {
107
+ program2.command("setup").description("Configure CodeBakers with your API key").option("--ide <type>", "IDE to configure (cursor, windsurf, aider, claude-code)").option("--key <apiKey>", "API key (or enter interactively)").option("--force", "Overwrite existing configuration").action(async (options) => {
108
+ console.log(chalk.bold("\n\u{1F36A} CodeBakers Setup\n"));
109
+ let apiKey = options.key;
110
+ const existingKey = getApiKey();
111
+ if (existingKey && !options.key) {
112
+ const { useExisting } = await inquirer.prompt([
113
+ {
114
+ type: "confirm",
115
+ name: "useExisting",
116
+ message: "API key already configured. Use existing key?",
117
+ default: true
118
+ }
119
+ ]);
120
+ if (useExisting) {
121
+ apiKey = existingKey;
122
+ }
123
+ }
124
+ if (!apiKey) {
125
+ const answers = await inquirer.prompt([
126
+ {
127
+ type: "password",
128
+ name: "apiKey",
129
+ message: "Enter your CodeBakers API key:",
130
+ mask: "*",
131
+ validate: (input) => {
132
+ if (!input.startsWith("cb_")) {
133
+ return "Invalid key format. API keys start with cb_";
134
+ }
135
+ return true;
136
+ }
137
+ }
138
+ ]);
139
+ apiKey = answers.apiKey;
140
+ }
141
+ const spinner = ora("Validating API key...").start();
142
+ try {
143
+ const isValid = await validateApiKey(apiKey);
144
+ if (!isValid) {
145
+ spinner.fail("Invalid API key");
146
+ console.log(chalk.red("\nAPI key validation failed."));
147
+ console.log(chalk.dim("Get your key at: https://codebakers.ai/settings"));
148
+ process.exit(1);
149
+ }
150
+ spinner.succeed("API key validated");
151
+ } catch (error) {
152
+ spinner.fail("Failed to validate API key");
153
+ console.log(chalk.red("\nCould not connect to CodeBakers API."));
154
+ console.log(chalk.dim("Check your internet connection and try again."));
155
+ process.exit(1);
156
+ }
157
+ setApiKey(apiKey);
158
+ console.log(chalk.green("\n\u2713 API key saved\n"));
159
+ let ide = options.ide;
160
+ if (!ide) {
161
+ const { selectedIde } = await inquirer.prompt([
162
+ {
163
+ type: "list",
164
+ name: "selectedIde",
165
+ message: "Which AI coding tool are you using?",
166
+ choices: [
167
+ { name: "Cursor", value: "cursor" },
168
+ { name: "Claude Code (CLI)", value: "claude-code" },
169
+ { name: "Windsurf (Codeium)", value: "windsurf" },
170
+ { name: "Aider", value: "aider" },
171
+ { name: "Skip for now", value: null }
172
+ ]
173
+ }
174
+ ]);
175
+ ide = selectedIde;
176
+ }
177
+ if (ide) {
178
+ await configureIDE(ide, options.force);
179
+ }
180
+ console.log(chalk.bold.green("\n\u{1F389} Setup complete!\n"));
181
+ if (ide === "claude-code") {
182
+ console.log(chalk.dim("Next step - add CodeBakers to Claude Code:"));
183
+ console.log(chalk.cyan(" /mcp add codebakers npx -y @codebakers/cli serve\n"));
184
+ } else if (ide) {
185
+ console.log(chalk.dim(`Pattern file created. Restart ${IDE_CONFIGS[ide].description} to load it.
186
+ `));
187
+ }
188
+ });
189
+ }
190
+ async function configureIDE(ide, force = false) {
191
+ const config = IDE_CONFIGS[ide];
192
+ const targetPath = join(process.cwd(), config.file);
193
+ const spinner = ora(`Fetching CodeBakers patterns...`).start();
194
+ try {
195
+ if (ide === "claude-code") {
196
+ const claudeDir = join(process.cwd(), ".claude");
197
+ if (!existsSync(claudeDir)) {
198
+ await mkdir(claudeDir, { recursive: true });
199
+ }
200
+ }
201
+ if (existsSync(targetPath) && !force) {
202
+ spinner.stop();
203
+ const { overwrite } = await inquirer.prompt([
204
+ {
205
+ type: "confirm",
206
+ name: "overwrite",
207
+ message: `${config.file} already exists. Overwrite?`,
208
+ default: false
209
+ }
210
+ ]);
211
+ if (!overwrite) {
212
+ console.log(chalk.yellow("\nSetup cancelled. Use --force to overwrite."));
213
+ return;
214
+ }
215
+ spinner.start();
216
+ }
217
+ const patterns = await fetchAllPatterns();
218
+ const patternList = await listPatterns();
219
+ spinner.text = "Generating configuration...";
220
+ let content;
221
+ if (ide === "claude-code") {
222
+ content = patterns["router"] || "";
223
+ for (const [name, patternContent] of Object.entries(patterns)) {
224
+ if (name !== "router") {
225
+ const patternPath = join(process.cwd(), ".claude", `${name}.md`);
226
+ await writeFile(patternPath, patternContent, "utf-8");
227
+ }
228
+ }
229
+ } else if (ide === "aider") {
230
+ content = generateAiderConfig(patterns);
231
+ } else {
232
+ content = generateRulesFile(patterns, patternList.version);
233
+ }
234
+ await writeFile(targetPath, content, "utf-8");
235
+ spinner.succeed(`Created ${config.file}`);
236
+ if (ide === "claude-code") {
237
+ console.log(chalk.dim(` + Created .claude/ folder with ${Object.keys(patterns).length - 1} pattern files`));
238
+ }
239
+ console.log(chalk.green(`
240
+ \u2713 ${IDE_CONFIGS[ide].description} configured`));
241
+ } catch (error) {
242
+ spinner.fail("Failed to configure IDE");
243
+ const message = error instanceof Error ? error.message : "Unknown error";
244
+ console.log(chalk.red(`
245
+ Error: ${message}`));
246
+ process.exit(1);
247
+ }
248
+ }
249
+ function generateRulesFile(patterns, version) {
250
+ const router = patterns["router"] || "";
251
+ let content = `# CodeBakers Patterns v${version}
252
+ # Generated by @codebakers/cli
253
+ # https://codebakers.ai
254
+
255
+ ${router}
256
+
257
+ ---
258
+
259
+ # PATTERN MODULES (Auto-loaded based on context)
260
+
261
+ `;
262
+ for (const [name, patternContent] of Object.entries(patterns)) {
263
+ if (name !== "router" && patternContent) {
264
+ content += `
265
+ ## Module: ${name}
266
+
267
+ ${patternContent}
268
+
269
+ ---
270
+ `;
271
+ }
272
+ }
273
+ return content;
274
+ }
275
+ function generateAiderConfig(patterns) {
276
+ const router = patterns["router"] || "";
277
+ return `# CodeBakers Aider Configuration
278
+ # Generated by @codebakers/cli
279
+ # https://codebakers.ai
280
+
281
+ read:
282
+ - .codebakers-patterns.md
283
+
284
+ # System message with CodeBakers patterns
285
+ message: |
286
+ ${router.split("\n").map((line) => " " + line).join("\n")}
287
+ `;
288
+ }
289
+
290
+ // src/commands/serve.ts
291
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
292
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
293
+ import {
294
+ CallToolRequestSchema,
295
+ ListToolsRequestSchema,
296
+ ListResourcesRequestSchema,
297
+ ReadResourceRequestSchema
298
+ } from "@modelcontextprotocol/sdk/types.js";
299
+ function serveCommand(program2) {
300
+ program2.command("serve").description("Run CodeBakers as an MCP server for Claude Code").action(async () => {
301
+ const apiKey = getApiKey();
302
+ if (!apiKey) {
303
+ console.error("No API key configured. Run: npx @codebakers/cli setup");
304
+ process.exit(1);
305
+ }
306
+ const server = new Server(
307
+ {
308
+ name: "codebakers",
309
+ version: "1.0.0"
310
+ },
311
+ {
312
+ capabilities: {
313
+ tools: {},
314
+ resources: {}
315
+ }
316
+ }
317
+ );
318
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
319
+ return {
320
+ tools: [
321
+ {
322
+ name: "get_pattern",
323
+ description: "Fetch a CodeBakers pattern module for production-ready code patterns. Available patterns: 00-core, 01-database, 02-auth, 03-api, 04-frontend, 05-payments, 06-integrations, 07-performance, 08-testing, 09-design, 10-generators, 11-realtime, 12-saas, 14-ai, and more.",
324
+ inputSchema: {
325
+ type: "object",
326
+ properties: {
327
+ patterns: {
328
+ type: "array",
329
+ items: { type: "string" },
330
+ description: 'Pattern names to fetch (e.g., ["00-core", "02-auth"]). Max 5 per request.'
331
+ }
332
+ },
333
+ required: ["patterns"]
334
+ }
335
+ },
336
+ {
337
+ name: "list_patterns",
338
+ description: "List all available CodeBakers pattern modules",
339
+ inputSchema: {
340
+ type: "object",
341
+ properties: {}
342
+ }
343
+ }
344
+ ]
345
+ };
346
+ });
347
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
348
+ try {
349
+ const result = await listPatterns();
350
+ return {
351
+ resources: result.patterns.map((p) => ({
352
+ uri: `codebakers://pattern/${p.name}`,
353
+ name: p.name,
354
+ description: `CodeBakers ${p.name} pattern module`,
355
+ mimeType: "text/markdown"
356
+ }))
357
+ };
358
+ } catch (error) {
359
+ return { resources: [] };
360
+ }
361
+ });
362
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
363
+ const uri = request.params.uri;
364
+ const match = uri.match(/^codebakers:\/\/pattern\/(.+)$/);
365
+ if (!match) {
366
+ throw new Error(`Invalid resource URI: ${uri}`);
367
+ }
368
+ const patternName = match[1];
369
+ try {
370
+ const result = await fetchPatterns([patternName]);
371
+ const content = result.patterns[patternName];
372
+ if (!content) {
373
+ throw new Error(`Pattern not found: ${patternName}`);
374
+ }
375
+ return {
376
+ contents: [
377
+ {
378
+ uri,
379
+ mimeType: "text/markdown",
380
+ text: content
381
+ }
382
+ ]
383
+ };
384
+ } catch (error) {
385
+ if (error instanceof ApiError) {
386
+ throw new Error(`API Error: ${error.message}`);
387
+ }
388
+ throw error;
389
+ }
390
+ });
391
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
392
+ const { name, arguments: args } = request.params;
393
+ if (name === "list_patterns") {
394
+ try {
395
+ const result = await listPatterns();
396
+ return {
397
+ content: [
398
+ {
399
+ type: "text",
400
+ text: `Available CodeBakers patterns (${result.total}):
401
+
402
+ ${result.patterns.map((p) => `- ${p.name}`).join("\n")}
403
+
404
+ Use get_pattern to fetch specific patterns.`
405
+ }
406
+ ]
407
+ };
408
+ } catch (error) {
409
+ const message = error instanceof Error ? error.message : "Unknown error";
410
+ return {
411
+ content: [{ type: "text", text: `Error: ${message}` }],
412
+ isError: true
413
+ };
414
+ }
415
+ }
416
+ if (name === "get_pattern") {
417
+ const patterns = args?.patterns || [];
418
+ if (patterns.length === 0) {
419
+ return {
420
+ content: [
421
+ { type: "text", text: "Error: No patterns specified" }
422
+ ],
423
+ isError: true
424
+ };
425
+ }
426
+ if (patterns.length > 5) {
427
+ return {
428
+ content: [
429
+ { type: "text", text: "Error: Maximum 5 patterns per request" }
430
+ ],
431
+ isError: true
432
+ };
433
+ }
434
+ try {
435
+ const result = await fetchPatterns(patterns);
436
+ const content = Object.entries(result.patterns).map(([name2, text]) => `# Pattern: ${name2}
437
+
438
+ ${text}`).join("\n\n---\n\n");
439
+ return {
440
+ content: [
441
+ {
442
+ type: "text",
443
+ text: content || `No patterns found for: ${patterns.join(", ")}`
444
+ }
445
+ ]
446
+ };
447
+ } catch (error) {
448
+ const message = error instanceof Error ? error.message : "Unknown error";
449
+ if (error instanceof ApiError) {
450
+ if (error.statusCode === 401) {
451
+ return {
452
+ content: [
453
+ {
454
+ type: "text",
455
+ text: "Error: Invalid API key. Run: npx @codebakers/cli setup"
456
+ }
457
+ ],
458
+ isError: true
459
+ };
460
+ }
461
+ if (error.statusCode === 402) {
462
+ return {
463
+ content: [
464
+ {
465
+ type: "text",
466
+ text: `Error: ${message}. Upgrade at: https://codebakers.ai/billing`
467
+ }
468
+ ],
469
+ isError: true
470
+ };
471
+ }
472
+ }
473
+ return {
474
+ content: [{ type: "text", text: `Error: ${message}` }],
475
+ isError: true
476
+ };
477
+ }
478
+ }
479
+ return {
480
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
481
+ isError: true
482
+ };
483
+ });
484
+ const transport = new StdioServerTransport();
485
+ await server.connect(transport);
486
+ });
487
+ }
488
+
489
+ // src/index.ts
490
+ var program = new Command();
491
+ program.name("codebakers").description("CodeBakers CLI - AI prompt patterns for production-ready code").version("1.0.0");
492
+ setupCommand(program);
493
+ serveCommand(program);
494
+ program.command("status").description("Check CodeBakers configuration status").action(() => {
495
+ console.log(chalk2.bold("\n\u{1F36A} CodeBakers Status\n"));
496
+ const apiKey = getApiKey();
497
+ if (apiKey) {
498
+ const maskedKey = apiKey.substring(0, 10) + "..." + apiKey.slice(-4);
499
+ console.log(chalk2.green("\u2713 API key configured"));
500
+ console.log(chalk2.dim(` Key: ${maskedKey}`));
501
+ } else {
502
+ console.log(chalk2.red("\u2717 No API key configured"));
503
+ console.log(chalk2.dim(" Run: codebakers setup"));
504
+ }
505
+ console.log(chalk2.dim(`
506
+ Config path: ${getConfigPath()}
507
+ `));
508
+ });
509
+ program.command("logout").description("Remove stored API key").action(async () => {
510
+ const { clearConfig } = await import("./config-R2H6JKGW.js");
511
+ clearConfig();
512
+ console.log(chalk2.green("\n\u2713 API key removed\n"));
513
+ });
514
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@codebakers/cli",
3
+ "version": "1.0.0",
4
+ "description": "CodeBakers CLI - AI prompt patterns for production-ready code",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "codebakers": "dist/index.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "build": "tsup src/index.ts --format esm --dts --clean",
12
+ "dev": "tsup src/index.ts --format esm --watch",
13
+ "prepublishOnly": "npm run build",
14
+ "test": "vitest run"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "keywords": [
20
+ "codebakers",
21
+ "ai",
22
+ "prompts",
23
+ "cursor",
24
+ "claude",
25
+ "aider",
26
+ "windsurf",
27
+ "mcp",
28
+ "cli"
29
+ ],
30
+ "author": "CodeBakers",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/codebakers/cli"
35
+ },
36
+ "homepage": "https://codebakers.ai",
37
+ "dependencies": {
38
+ "@modelcontextprotocol/sdk": "^1.0.0",
39
+ "chalk": "^5.3.0",
40
+ "commander": "^12.1.0",
41
+ "conf": "^12.0.0",
42
+ "inquirer": "^9.2.23",
43
+ "ora": "^8.0.1"
44
+ },
45
+ "devDependencies": {
46
+ "@types/inquirer": "^9.0.7",
47
+ "@types/node": "^20.11.0",
48
+ "tsup": "^8.0.2",
49
+ "typescript": "^5.3.3",
50
+ "vitest": "^1.2.0"
51
+ },
52
+ "engines": {
53
+ "node": ">=18"
54
+ }
55
+ }