create-better-t-stack 3.46.1 → 3.48.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 CHANGED
@@ -94,9 +94,9 @@ Options:
94
94
 
95
95
  ```bash
96
96
  # Raw JSON payload input (agent-friendly)
97
- create-better-t-stack create-json --input '{"projectName":"my-app","yes":true,"dryRun":true}'
98
- create-better-t-stack add-json --input '{"projectDir":"./my-app","addons":["wxt"],"addonOptions":{"wxt":{"template":"react"}}}'
99
- create-better-t-stack create-json --input '{"projectName":"db-app","database":"postgres","orm":"drizzle","dbSetup":"neon","dbSetupOptions":{"mode":"manual"}}'
97
+ create-better-t-stack create-json --json '{"projectName":"my-app","yes":true,"dryRun":true}'
98
+ create-better-t-stack add-json --json '{"projectDir":"./my-app","addons":["wxt"],"addonOptions":{"wxt":{"template":"react"}}}'
99
+ create-better-t-stack create-json --json '{"projectName":"db-app","database":"postgres","orm":"drizzle","dbSetup":"neon","dbSetupOptions":{"mode":"manual"}}'
100
100
 
101
101
  # Runtime schema/introspection output
102
102
  create-better-t-stack schema --name all
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { D as setProcessMode, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as types_exports, y as getLatestCLIVersion } from "./src-_HMF_A8q.mjs";
2
+ import { D as setProcessMode, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as types_exports, y as getLatestCLIVersion } from "./src-DswDqMzo.mjs";
3
3
  import { lineByLineConsoleLogger } from "trpc-cli";
4
4
  import z from "zod";
5
5
  import { McpServer } from "@modelcontextprotocol/server";
package/dist/index.d.mts CHANGED
@@ -592,7 +592,7 @@ export declare const router: {
592
592
  "expo/skills": "expo/skills";
593
593
  "prisma/skills": "prisma/skills";
594
594
  "elysiajs/skills": "elysiajs/skills";
595
- "waynesutton/convexskills": "waynesutton/convexskills";
595
+ "get-convex/agent-skills": "get-convex/agent-skills";
596
596
  "msmps/opentui-skill": "msmps/opentui-skill";
597
597
  "haydenbleasel/ultracite": "haydenbleasel/ultracite";
598
598
  "https://www.evlog.dev": "https://www.evlog.dev";
@@ -1119,7 +1119,7 @@ export declare const router: {
1119
1119
  "expo/skills": "expo/skills";
1120
1120
  "prisma/skills": "prisma/skills";
1121
1121
  "elysiajs/skills": "elysiajs/skills";
1122
- "waynesutton/convexskills": "waynesutton/convexskills";
1122
+ "get-convex/agent-skills": "get-convex/agent-skills";
1123
1123
  "msmps/opentui-skill": "msmps/opentui-skill";
1124
1124
  "haydenbleasel/ultracite": "haydenbleasel/ultracite";
1125
1125
  "https://www.evlog.dev": "https://www.evlog.dev";
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { C as DirectoryConflictError, E as ValidationError, S as DatabaseSetupError, T as UserCancelledError, _ as ProjectLauncherSchema, a as TEMPLATE_COUNT, b as CLIError, c as builder, d as createVirtual, f as docs, g as sponsors, h as router, i as SchemaNameSchema, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, w as ProjectCreationError, x as CompatibilityError } from "./src-_HMF_A8q.mjs";
2
+ import { C as DirectoryConflictError, E as ValidationError, S as DatabaseSetupError, T as UserCancelledError, _ as ProjectLauncherSchema, a as TEMPLATE_COUNT, b as CLIError, c as builder, d as createVirtual, f as docs, g as sponsors, h as router, i as SchemaNameSchema, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, w as ProjectCreationError, x as CompatibilityError } from "./src-DswDqMzo.mjs";
3
3
  export { CLIError, CompatibilityError, DatabaseSetupError, DirectoryConflictError, EMBEDDED_TEMPLATES, GeneratorError, ProjectCreationError, ProjectLauncherSchema, Result, SchemaNameSchema, TEMPLATE_COUNT, UserCancelledError, ValidationError, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generate, getSchemaResult, router, sponsors };
@@ -228,7 +228,8 @@ function defaultContext() {
228
228
  silent: false,
229
229
  verbose: false,
230
230
  promptShown: false,
231
- analyticsDisabled: false
231
+ analyticsDisabled: false,
232
+ stepFailures: []
232
233
  };
233
234
  }
234
235
  function getContext() {
@@ -290,7 +291,8 @@ function createContext(options) {
290
291
  analyticsDisabled: options.analyticsDisabled ?? false,
291
292
  projectDir: options.projectDir,
292
293
  projectName: options.projectName,
293
- packageManager: options.packageManager
294
+ packageManager: options.packageManager,
295
+ stepFailures: []
294
296
  };
295
297
  }
296
298
  async function runWithContextAsync(options, fn) {
@@ -337,6 +339,8 @@ function beginInterruptibleScope() {
337
339
  endInterruptibleScope();
338
340
  stepInterrupted = false;
339
341
  anyStepInterrupted = false;
342
+ const ctx = tryGetContext();
343
+ if (ctx) ctx.stepFailures = [];
340
344
  listener = () => {
341
345
  stepInterrupted = true;
342
346
  anyStepInterrupted = true;
@@ -360,6 +364,13 @@ function wasInterrupted() {
360
364
  function wasAnyStepInterrupted() {
361
365
  return anyStepInterrupted;
362
366
  }
367
+ /** Records an optional step that failed, so silent/JSON callers can report it. */
368
+ function recordStepFailure(message) {
369
+ tryGetContext()?.stepFailures.push(message);
370
+ }
371
+ function getStepFailures() {
372
+ return [...tryGetContext()?.stepFailures ?? []];
373
+ }
363
374
  function getInterruptSignal() {
364
375
  return controller?.signal;
365
376
  }
@@ -1697,10 +1708,6 @@ function parseBtsConfig(content) {
1697
1708
  const errors = [];
1698
1709
  const value = parse(content, errors, { allowTrailingComma: true });
1699
1710
  if (errors.length > 0) throw new Error("Invalid JSONC in bts.jsonc");
1700
- const selections = value?.addonOptions?.skills?.selections;
1701
- if (Array.isArray(selections)) {
1702
- for (const selection of selections) if (selection?.source === "yusukebe/hono-skill") selection.source = "honojs/skills";
1703
- }
1704
1711
  return BetterTStackConfigSchema.parse(value);
1705
1712
  }
1706
1713
  async function readBtsConfig(projectDir) {
@@ -1888,7 +1895,7 @@ function getNodeToolingRequirements(config) {
1888
1895
  addNodeRequirement(requirements, ">=22.12.0", "Astro 7");
1889
1896
  break;
1890
1897
  case "nuxt":
1891
- addNodeRequirement(requirements, "^22.19.0 || ^24.11.0 || >=26.0.0", "Nuxt 4");
1898
+ addNodeRequirement(requirements, "^22.22.3 || ^24.15.0 || >=26.0.0", "Nuxt 4.6");
1892
1899
  break;
1893
1900
  case "solid":
1894
1901
  addNodeRequirement(requirements, ">=24.0.0", "Solid");
@@ -1897,6 +1904,8 @@ function getNodeToolingRequirements(config) {
1897
1904
  addNodeRequirement(requirements, ">=22.22.0", "React Router 8");
1898
1905
  break;
1899
1906
  case "svelte":
1907
+ addNodeRequirement(requirements, ">=22.17.0", "SvelteKit 3");
1908
+ break;
1900
1909
  case "tanstack-router":
1901
1910
  addNodeRequirement(requirements, "^20.19.0 || >=22.12.0", "Vite 8");
1902
1911
  break;
@@ -1951,11 +1960,8 @@ function getBaselineRequirements(packageManager, hostRuntime) {
1951
1960
  }
1952
1961
  function getLocalVersionRequirements(config, hostRuntime) {
1953
1962
  const requirements = getBaselineRequirements(config.packageManager, hostRuntime);
1954
- if (config.packageManager !== "bun") requirements.push(...getNodeToolingRequirements(config));
1955
- else {
1956
- requirements.push(...getAddonNodeRequirements(config));
1957
- if (config.runtime === "node") addNodeRequirement(requirements, ">=22.0.0", "the selected Node.js server runtime");
1958
- }
1963
+ requirements.push(...getNodeToolingRequirements(config));
1964
+ if (config.runtime === "node") addNodeRequirement(requirements, ">=22.0.0", "the selected Node.js server runtime");
1959
1965
  return requirements;
1960
1966
  }
1961
1967
  function formatRequirementError(requirement, rawVersion) {
@@ -2082,6 +2088,7 @@ async function runOptionalStep(step, cancelledMessage) {
2082
2088
  cliLog.warn(pc.yellow(cancelledMessage));
2083
2089
  return;
2084
2090
  }
2091
+ recordStepFailure(result.error.message);
2085
2092
  cliConsola.error(pc.red(result.error.message));
2086
2093
  }
2087
2094
  //#endregion
@@ -2278,9 +2285,24 @@ function addNuxtEvlogSetup(content, serviceName) {
2278
2285
  return nextContent;
2279
2286
  }
2280
2287
  function addSvelteViteEvlogSetup(content, serviceName) {
2281
- let nextContent = prependMissingImports(content, ["import evlog from \"evlog/vite\";"]);
2288
+ const nextContent = prependMissingImports(content, ["import evlog from \"evlog/vite\";"]);
2282
2289
  if (nextContent.includes("evlog({")) return nextContent;
2283
- return nextContent.replace("sveltekit(),", `sveltekit(),\n evlog({ service: "${serviceName}" }),`);
2290
+ const source = ts.createSourceFile("vite.config.ts", nextContent, ts.ScriptTarget.Latest);
2291
+ let insertion;
2292
+ function visit(node) {
2293
+ if (insertion !== void 0) return;
2294
+ if (ts.isPropertyAssignment(node) && node.name.getText(source) === "plugins" && ts.isArrayLiteralExpression(node.initializer)) {
2295
+ const kitPlugin = node.initializer.elements.find((element) => ts.isCallExpression(element) && element.expression.getText(source) === "sveltekit");
2296
+ if (kitPlugin) {
2297
+ insertion = kitPlugin.getEnd();
2298
+ return;
2299
+ }
2300
+ }
2301
+ ts.forEachChild(node, visit);
2302
+ }
2303
+ visit(source);
2304
+ if (insertion === void 0) return nextContent;
2305
+ return `${nextContent.slice(0, insertion)},\n evlog({ service: ${JSON.stringify(serviceName)} })${nextContent.slice(insertion)}`;
2284
2306
  }
2285
2307
  function getSvelteEvlogHooksCall(fsDrain, axiom = false) {
2286
2308
  if (axiom) return "createEvlogHooks({ drain: createAxiomDrain() })";
@@ -2292,7 +2314,7 @@ function addSvelteHooksEvlogSetup(content, fsDrain, axiom) {
2292
2314
  ...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : [],
2293
2315
  ...axiom ? ["import { createAxiomDrain } from \"evlog/axiom\";"] : []
2294
2316
  ]);
2295
- if (fsDrain) nextContent = addNamedImport(nextContent, "$app/environment", ["dev"]);
2317
+ if (fsDrain) nextContent = addNamedImport(nextContent, "$app/env", ["dev"]);
2296
2318
  const hooksCall = getSvelteEvlogHooksCall(fsDrain, axiom);
2297
2319
  if (fsDrain || axiom) nextContent = nextContent.replaceAll("createEvlogHooks()", hooksCall);
2298
2320
  if (!nextContent.includes("export const handle") && !nextContent.includes("const authHandle")) {
@@ -2302,7 +2324,7 @@ function addSvelteHooksEvlogSetup(content, fsDrain, axiom) {
2302
2324
  nextContent = prependMissingImports(nextContent, ["import { sequence } from \"@sveltejs/kit/hooks\";"]);
2303
2325
  if (!nextContent.includes("const { handle: evlogHandle, handleError }")) nextContent = nextContent.replace(/((?:import .+\n)+)/, `$1\nconst { handle: evlogHandle, handleError } = ${hooksCall};\n\n`);
2304
2326
  nextContent = nextContent.replace(/export const handle(:\s*Handle)?\s*=\s*async/, (_match, typeAnnotation) => `const authHandle${typeAnnotation ?? ""} = async`);
2305
- if (!nextContent.includes("sequence(evlogHandle, authHandle)")) nextContent = `${nextContent.trimEnd()}\n\nexport const handle = sequence(evlogHandle as Handle, authHandle);\nexport { handleError };\n`;
2327
+ if (!nextContent.includes("sequence(evlogHandle, authHandle)")) nextContent = `${nextContent.trimEnd()}\n\nexport const handle = sequence(evlogHandle, authHandle);\nexport { handleError };\n`;
2306
2328
  return nextContent;
2307
2329
  }
2308
2330
  function addSvelteLocalsType(content) {
@@ -2398,7 +2420,7 @@ function addNextBetterAuthToRoute(content) {
2398
2420
  }
2399
2421
  function addSvelteBetterAuthEvlogSetup(content, config) {
2400
2422
  if (!content.includes("authHandle") || content.includes("evlogAuthHandle")) return content;
2401
- let nextContent = addNamedImport(content, "evlog/better-auth", ["createAuthMiddleware", "type BetterAuthInstance"]);
2423
+ let nextContent = addNamedImport(content, "evlog/better-auth", ["createAuthMiddleware"]);
2402
2424
  if (!nextContent.includes("from \"./services\"")) nextContent = prependMissingImports(nextContent, [getAuthImportLine(config, "./services")]);
2403
2425
  let environmentBinding = "ENV";
2404
2426
  if (usesCreateAuthFactory(config) && config.webDeploy === "cloudflare") {
@@ -2411,10 +2433,10 @@ function addSvelteBetterAuthEvlogSetup(content, config) {
2411
2433
  }
2412
2434
  const authExpression = getAuthExpression(config);
2413
2435
  const authOptions = "{ exclude: [\"/api/auth/**\"], maskEmail: true }";
2414
- const authHandleSnippet = usesCreateAuthFactory(config) && config.webDeploy === "cloudflare" ? `const evlogAuthHandle: Handle = async ({ event, resolve }) => {\n\tif (building) {\n\t\treturn resolve(event);\n\t}\n\n\tconst authEnv = event.platform?.env ?? ${environmentBinding};\n\tconst identifyUser = createAuthMiddleware((await createAuth(authEnv)) as BetterAuthInstance, ${authOptions});\n\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\n\treturn resolve(event);\n};\n\n` : `const identifyUser = createAuthMiddleware(${authExpression} as BetterAuthInstance, ${authOptions});\n\nconst evlogAuthHandle: Handle = async ({ event, resolve }) => {\n\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\n\treturn resolve(event);\n};\n\n`;
2436
+ const authHandleSnippet = usesCreateAuthFactory(config) && config.webDeploy === "cloudflare" ? `const evlogAuthHandle: Handle = async ({ event, resolve }) => {\n\tif (building) {\n\t\treturn resolve(event);\n\t}\n\n\tconst authEnv = ${environmentBinding};\n\tconst identifyUser = createAuthMiddleware(await createAuth(authEnv), ${authOptions});\n\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\n\treturn resolve(event);\n};\n\n` : `const identifyUser = createAuthMiddleware(${authExpression}, ${authOptions});\n\nconst evlogAuthHandle: Handle = async ({ event, resolve }) => {\n\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\n\treturn resolve(event);\n};\n\n`;
2415
2437
  const evlogHandleDeclaration = nextContent.match(/const \{ handle: evlogHandle, handleError \} = createEvlogHooks\([\s\S]*?\);\n\n/)?.[0];
2416
2438
  if (evlogHandleDeclaration) nextContent = insertAfterOnce(nextContent, evlogHandleDeclaration, authHandleSnippet, "evlogAuthHandle");
2417
- return nextContent.replace("sequence(evlogHandle as Handle, authHandle)", "sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)").replace("sequence(evlogHandle, authHandle)", "sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)");
2439
+ return nextContent.replace("sequence(evlogHandle as Handle, authHandle)", "sequence(evlogHandle, evlogAuthHandle, authHandle)").replace("sequence(evlogHandle, authHandle)", "sequence(evlogHandle, evlogAuthHandle, authHandle)");
2418
2440
  }
2419
2441
  function addAstroBetterAuthEvlogSetup(content, config) {
2420
2442
  if (content.includes("createAuthMiddleware(")) return content;
@@ -2447,14 +2469,16 @@ export const { register, onRequestError } = createInstrumentation({
2447
2469
  });
2448
2470
  `;
2449
2471
  }
2472
+ const NITRO_V3_PLUGIN_IMPORT = "import { definePlugin as defineNitroPlugin } from \"nitro\";\n";
2450
2473
  function getNitroEvlogDrainFile(axiom = false, nitroV3 = false) {
2451
- if (axiom) return `${nitroV3 ? "import { definePlugin as defineNitroPlugin } from \"nitro\";\n" : ""}import { createAxiomDrain } from "evlog/axiom";
2474
+ const pluginImport = nitroV3 ? NITRO_V3_PLUGIN_IMPORT : "";
2475
+ if (axiom) return `${pluginImport}import { createAxiomDrain } from "evlog/axiom";
2452
2476
 
2453
2477
  export default defineNitroPlugin((nitroApp) => {
2454
2478
  nitroApp.hooks.hook("evlog:drain", createAxiomDrain());
2455
2479
  });
2456
2480
  `;
2457
- return `import { createFsDrain } from "evlog/fs";
2481
+ return `${pluginImport}import { createFsDrain } from "evlog/fs";
2458
2482
 
2459
2483
  export default defineNitroPlugin((nitroApp) => {
2460
2484
  if (!import.meta.dev) return;
@@ -2505,31 +2529,32 @@ export async function identifyEvlogUser(request: Request) {
2505
2529
  }
2506
2530
  `;
2507
2531
  }
2508
- function getNitroEvlogAuthPluginFile(config) {
2532
+ function getTanstackStartEvlogAuthMiddlewareFile(config) {
2509
2533
  if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config, "../../src/services")}
2510
- import { createAuthIdentifier, type BetterAuthInstance } from "evlog/better-auth";
2534
+ import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
2535
+ import { useLogger } from "evlog/nitro/v3";
2536
+ import { defineHandler } from "nitro";
2511
2537
 
2512
- export default defineNitroPlugin((nitroApp) => {
2513
- nitroApp.hooks.hook("request", async (event) => {
2514
- const identify = createAuthIdentifier(${getAuthExpression(config)} as BetterAuthInstance, {
2515
- exclude: ["/api/auth/**"],
2516
- maskEmail: true,
2517
- });
2518
- await identify(event);
2538
+ export default defineHandler(async (event) => {
2539
+ const identifyUser = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
2540
+ exclude: ["/api/auth/**"],
2541
+ maskEmail: true,
2519
2542
  });
2543
+ await identifyUser(useLogger(event), event.req.headers, event.url.pathname);
2520
2544
  });
2521
2545
  `;
2522
2546
  return `${getAuthImportLine(config, "../../src/services")}
2523
- import { createAuthIdentifier, type BetterAuthInstance } from "evlog/better-auth";
2547
+ import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
2548
+ import { useLogger } from "evlog/nitro/v3";
2549
+ import { defineHandler } from "nitro";
2524
2550
 
2525
- export default defineNitroPlugin((nitroApp) => {
2526
- nitroApp.hooks.hook(
2527
- "request",
2528
- createAuthIdentifier(${getAuthExpression(config)} as BetterAuthInstance, {
2529
- exclude: ["/api/auth/**"],
2530
- maskEmail: true,
2531
- }),
2532
- );
2551
+ const identifyUser = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
2552
+ exclude: ["/api/auth/**"],
2553
+ maskEmail: true,
2554
+ });
2555
+
2556
+ export default defineHandler(async (event) => {
2557
+ await identifyUser(useLogger(event), event.req.headers, event.url.pathname);
2533
2558
  });
2534
2559
  `;
2535
2560
  }
@@ -2664,10 +2689,6 @@ async function setupNuxtEvlog(config, serviceName) {
2664
2689
  if (!await fs.pathExists(drainPath)) await writeFileIfChanged(drainPath, getNitroEvlogDrainFile(usesAxiom(config), config.frontend.includes("tanstack-start")));
2665
2690
  }
2666
2691
  if (shouldIdentifyWebAuth(config)) {
2667
- const oldAuthPluginPath = path.join(webDir, "server/plugins/evlog-auth.ts");
2668
- if (await fs.pathExists(oldAuthPluginPath)) {
2669
- if ((await fs.readFile(oldAuthPluginPath, "utf-8")).includes("evlog/better-auth")) await fs.remove(oldAuthPluginPath);
2670
- }
2671
2692
  const authMiddlewarePath = path.join(webDir, "server/middleware/evlog-auth.ts");
2672
2693
  if (!await fs.pathExists(authMiddlewarePath)) await writeFileIfChanged(authMiddlewarePath, getNuxtEvlogAuthMiddlewareFile(config));
2673
2694
  }
@@ -2683,7 +2704,7 @@ async function setupSvelteEvlog(config, serviceName) {
2683
2704
  else await writeFileIfChanged(hooksPath, `import { createEvlogHooks } from "evlog/sveltekit";
2684
2705
  ${fsDrain ? "import { createFsDrain } from \"evlog/fs\";\n" : ""}
2685
2706
  ${axiom ? "import { createAxiomDrain } from \"evlog/axiom\";\n" : ""}
2686
- ${fsDrain ? "import { dev } from \"$app/environment\";\n" : ""}
2707
+ ${fsDrain ? "import { dev } from \"$app/env\";\n" : ""}
2687
2708
 
2688
2709
  export const { handle, handleError } = ${getSvelteEvlogHooksCall(fsDrain, axiom)};
2689
2710
  `);
@@ -2725,8 +2746,8 @@ async function setupTanstackStartEvlog(config, serviceName) {
2725
2746
  if (!await fs.pathExists(drainPath)) await writeFileIfChanged(drainPath, getNitroEvlogDrainFile(usesAxiom(config), config.frontend.includes("tanstack-start")));
2726
2747
  }
2727
2748
  if (shouldIdentifyWebAuth(config)) {
2728
- const authPluginPath = path.join(webDir, "server/plugins/evlog-auth.ts");
2729
- if (!await fs.pathExists(authPluginPath)) await writeFileIfChanged(authPluginPath, getNitroEvlogAuthPluginFile(config));
2749
+ const authMiddlewarePath = path.join(webDir, "server/middleware/evlog-auth.ts");
2750
+ if (!await fs.pathExists(authMiddlewarePath)) await writeFileIfChanged(authMiddlewarePath, getTanstackStartEvlogAuthMiddlewareFile(config));
2730
2751
  }
2731
2752
  if (config.examples.includes("ai")) await updateFileIfExists(path.join(webDir, "src/routes/api/ai/$.ts"), addTanstackStartAiEvlogSetup);
2732
2753
  }
@@ -3607,7 +3628,7 @@ const SKILL_SOURCES = {
3607
3628
  "expo/skills": { label: "Expo" },
3608
3629
  "prisma/skills": { label: "Prisma" },
3609
3630
  "elysiajs/skills": { label: "ElysiaJS" },
3610
- "waynesutton/convexskills": { label: "Convex" },
3631
+ "get-convex/agent-skills": { label: "Convex" },
3611
3632
  "msmps/opentui-skill": { label: "OpenTUI Platform" },
3612
3633
  "haydenbleasel/ultracite": { label: "Ultracite" },
3613
3634
  "https://www.evlog.dev": { label: "evlog" }
@@ -3994,7 +4015,7 @@ function getRecommendedSourceKeys(config) {
3994
4015
  if (addons.includes("turborepo")) sources.push("vercel/turborepo");
3995
4016
  if (backend === "hono") sources.push("honojs/skills");
3996
4017
  if (backend === "elysia") sources.push("elysiajs/skills");
3997
- if (backend === "convex") sources.push("waynesutton/convexskills");
4018
+ if (backend === "convex") sources.push("get-convex/agent-skills");
3998
4019
  if (addons.includes("opentui")) sources.push("msmps/opentui-skill");
3999
4020
  if (addons.includes("ultracite")) sources.push("haydenbleasel/ultracite");
4000
4021
  if (addons.includes("evlog") || addons.includes("axiom")) sources.push("https://www.evlog.dev");
@@ -4051,36 +4072,32 @@ const CURATED_SKILLS_BY_SOURCE = {
4051
4072
  if (config.database === "mysql") return ["mysql", "vitess"];
4052
4073
  return [];
4053
4074
  },
4054
- "expo/skills": (config) => {
4055
- const skills = [
4056
- "expo-overview",
4057
- "expo-router",
4058
- "expo-dev-client",
4059
- "expo-native-ui",
4060
- "expo-data-fetching",
4061
- "eas-app-stores",
4062
- "eas-workflows"
4063
- ];
4064
- if (config.frontend.includes("native-uniwind")) skills.push("expo-tailwind-setup");
4065
- return skills;
4066
- },
4075
+ "expo/skills": () => [
4076
+ "expo-overview",
4077
+ "expo-router",
4078
+ "expo-dev-client",
4079
+ "expo-native-ui",
4080
+ "expo-data-fetching",
4081
+ "eas-app-stores",
4082
+ "eas-workflows"
4083
+ ],
4067
4084
  "prisma/skills": (config) => {
4068
4085
  const skills = [];
4069
- if (config.orm === "prisma") skills.push("prisma-cli", "prisma-client-api", "prisma-database-setup");
4070
- if (config.dbSetup === "prisma-postgres") skills.push("prisma-postgres");
4086
+ if (config.orm === "prisma") skills.push("prisma-cli", "prisma-client-api", "prisma-orm-setup");
4087
+ if (config.dbSetup === "prisma-postgres") skills.push("prisma-postgres-setup");
4071
4088
  return skills;
4072
4089
  },
4073
4090
  "elysiajs/skills": () => ["elysiajs"],
4074
- "waynesutton/convexskills": () => [
4075
- "convex-best-practices",
4076
- "convex-functions",
4077
- "convex-schema-validator",
4078
- "convex-realtime",
4079
- "convex-http-actions",
4080
- "convex-cron-jobs",
4081
- "convex-file-storage",
4082
- "convex-migrations",
4083
- "convex-security-check"
4091
+ "get-convex/agent-skills": () => [
4092
+ "convex",
4093
+ "convex-design",
4094
+ "convex-docs",
4095
+ "convex-reviewer",
4096
+ "convex-authz",
4097
+ "convex-crons",
4098
+ "convex-migrate",
4099
+ "convex-env",
4100
+ "convex-test"
4084
4101
  ],
4085
4102
  "msmps/opentui-skill": () => ["opentui"],
4086
4103
  "haydenbleasel/ultracite": () => ["ultracite"],
@@ -4189,31 +4206,43 @@ async function setupSkills(config) {
4189
4206
  const runner = getPackageRunnerPrefix(packageManager);
4190
4207
  const globalFlags = scope === "global" ? ["-g"] : [];
4191
4208
  const agentTargets = expandSkillsAgentTargets(selectedAgents);
4192
- for (const [source, skills] of Object.entries(skillsBySource)) if ((await Result.tryPromise({
4193
- try: async () => {
4194
- const args = [
4195
- ...runner,
4196
- "skills@latest",
4197
- "add",
4198
- source,
4199
- ...globalFlags,
4200
- "--skill",
4201
- ...skills,
4202
- "--agent",
4203
- ...agentTargets,
4204
- "-y"
4205
- ];
4206
- await $({
4207
- cwd: projectDir,
4208
- env: { CI: "true" }
4209
- })`${args}`;
4210
- },
4211
- catch: (e) => new AddonSetupError({
4209
+ const failures = [];
4210
+ for (const [source, skills] of Object.entries(skillsBySource)) {
4211
+ const installResult = await Result.tryPromise({
4212
+ try: async () => {
4213
+ const args = [
4214
+ ...runner,
4215
+ "skills@latest",
4216
+ "add",
4217
+ source,
4218
+ ...globalFlags,
4219
+ "--skill",
4220
+ ...skills,
4221
+ "--agent",
4222
+ ...agentTargets,
4223
+ "-y"
4224
+ ];
4225
+ await $({
4226
+ cwd: projectDir,
4227
+ env: { CI: "true" }
4228
+ })`${args}`;
4229
+ },
4230
+ catch: (e) => new AddonSetupError({
4231
+ addon: "skills",
4232
+ message: `Failed to install skills from ${source}: ${e instanceof Error ? e.message : String(e)}`,
4233
+ cause: e
4234
+ })
4235
+ });
4236
+ if (installResult.isErr()) failures.push(installResult.error);
4237
+ }
4238
+ if (failures.length > 0) {
4239
+ installSpinner.stop(pc.red("Failed to install skills"));
4240
+ return Result.err(new AddonSetupError({
4212
4241
  addon: "skills",
4213
- message: `Failed to install skills from ${source}: ${e instanceof Error ? e.message : String(e)}`,
4214
- cause: e
4215
- })
4216
- })).isErr()) cliLog.warn(pc.yellow(`Warning: Could not install skills from ${source}`));
4242
+ message: failures.map((failure) => failure.message).join("\n"),
4243
+ cause: failures.map((failure) => failure.cause)
4244
+ }));
4245
+ }
4217
4246
  installSpinner.stop("Skills installed");
4218
4247
  return Result.ok(void 0);
4219
4248
  }
@@ -5301,7 +5330,31 @@ async function addHandlerInternal(input) {
5301
5330
  });
5302
5331
  }
5303
5332
  });
5304
- if (setupResult.isErr()) return Result.err(setupResult.error);
5333
+ const cleanupReservedPackage = async () => {
5334
+ if (!reservedPackageDir) return void 0;
5335
+ const dir = reservedPackageDir;
5336
+ return (await Result.tryPromise({
5337
+ try: () => fs.remove(dir),
5338
+ catch: (cause) => new CLIError({
5339
+ message: `Failed to remove ${dir}`,
5340
+ cause
5341
+ })
5342
+ })).isErr() ? `Also failed to remove ${dir}; delete it before retrying.` : void 0;
5343
+ };
5344
+ if (setupResult.isErr()) {
5345
+ const cleanupNote = await cleanupReservedPackage();
5346
+ if (!cleanupNote) return Result.err(setupResult.error);
5347
+ return Result.err(new CLIError({
5348
+ message: `${setupResult.error.message}\n${cleanupNote}`,
5349
+ cause: setupResult.error
5350
+ }));
5351
+ }
5352
+ const stepFailures = getStepFailures();
5353
+ if (stepFailures.length > 0) {
5354
+ const cleanupNote = await cleanupReservedPackage();
5355
+ const message = isSilent() ? stepFailures.join("\n") : "Addon setup failed";
5356
+ return Result.err(new CLIError({ message: cleanupNote ? `${message}\n${cleanupNote}` : message }));
5357
+ }
5305
5358
  if (addonsToAdd.length > 0) {
5306
5359
  const configResult = await updateBtsConfig(projectDir, {
5307
5360
  addons: updatedAddons,
@@ -8078,19 +8131,21 @@ async function setupPrismaPostgres(config, cliInput) {
8078
8131
  }
8079
8132
  //#endregion
8080
8133
  //#region src/helpers/database-providers/supabase-setup.ts
8134
+ const SUPABASE_CLI_PACKAGE = "supabase@2.121.0";
8135
+ const supabaseEnv = { SUPABASE_EXPERIMENTAL_STACK: "0" };
8136
+ const statusSchema = z$1.object({ DB_URL: z$1.url().refine((value) => ["postgres:", "postgresql:"].includes(new URL(value).protocol)) });
8137
+ /** Save a verified database URL in the app that consumes the database. */
8081
8138
  async function writeSupabaseEnvFile(projectDir, backend, databaseUrl) {
8082
8139
  return Result.tryPromise({
8083
8140
  try: async () => {
8084
8141
  const targetApp = backend === "self" ? "apps/web" : "apps/server";
8085
- const envPath = path.join(projectDir, targetApp, ".env");
8086
- const dbUrlToUse = databaseUrl || "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
8087
- await addEnvVariablesToFile(envPath, [{
8142
+ await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
8088
8143
  key: "DATABASE_URL",
8089
- value: dbUrlToUse,
8144
+ value: databaseUrl,
8090
8145
  condition: true
8091
8146
  }, {
8092
8147
  key: "DIRECT_URL",
8093
- value: dbUrlToUse,
8148
+ value: databaseUrl,
8094
8149
  condition: true
8095
8150
  }]);
8096
8151
  },
@@ -8101,28 +8156,38 @@ async function writeSupabaseEnvFile(projectDir, backend, databaseUrl) {
8101
8156
  })
8102
8157
  });
8103
8158
  }
8104
- function extractDbUrl(statusEnv) {
8105
- return statusEnv.match(/^DB_URL="(postgresql:\/\/[^"]+)"$/m)?.[1] ?? null;
8106
- }
8159
+ /** Read Docker status with cancellation and a bounded wait. */
8107
8160
  async function readSupabaseStatus(serverDir, packageManager) {
8108
- const statusArgs = getPackageExecutionArgs(packageManager, "supabase status --agent no -o env");
8109
- const result = await Result.tryPromise({
8161
+ const statusArgs = getPackageExecutionArgs(packageManager, `${SUPABASE_CLI_PACKAGE} status --agent no --output json`);
8162
+ return Result.tryPromise({
8110
8163
  try: async () => {
8111
- const { stdout } = await execa(statusArgs[0], statusArgs.slice(1), { cwd: serverDir });
8112
- return stdout;
8164
+ const { stdout } = await execa(statusArgs[0], statusArgs.slice(1), {
8165
+ cwd: serverDir,
8166
+ env: supabaseEnv,
8167
+ cancelSignal: getInterruptSignal(),
8168
+ timeout: 12e4
8169
+ });
8170
+ return statusSchema.parse(JSON.parse(stdout)).DB_URL;
8113
8171
  },
8114
- catch: () => ""
8172
+ catch: (e) => new DatabaseSetupError({
8173
+ provider: "supabase",
8174
+ message: "Could not read the Supabase database URL. Run Supabase status and configure DATABASE_URL and DIRECT_URL manually.",
8175
+ cause: e
8176
+ })
8115
8177
  });
8116
- return result.isOk() ? result.value : "";
8117
8178
  }
8179
+ /** Initialize missing configuration without overwriting an existing project. */
8118
8180
  async function initializeSupabase(serverDir, packageManager) {
8119
8181
  cliLog.info("Initializing Supabase project...");
8120
8182
  return Result.tryPromise({
8121
8183
  try: async () => {
8122
- const supabaseInitArgs = getPackageExecutionArgs(packageManager, "supabase init");
8184
+ if (await fs.pathExists(path.join(serverDir, "supabase/config.toml"))) return;
8185
+ const supabaseInitArgs = getPackageExecutionArgs(packageManager, `${SUPABASE_CLI_PACKAGE} init --agent no`);
8123
8186
  await execa(supabaseInitArgs[0], supabaseInitArgs.slice(1), {
8124
8187
  cwd: serverDir,
8125
- stdio: "inherit"
8188
+ stdio: "inherit",
8189
+ env: supabaseEnv,
8190
+ cancelSignal: getInterruptSignal()
8126
8191
  });
8127
8192
  cliLog.success("Supabase project initialized");
8128
8193
  },
@@ -8130,18 +8195,23 @@ async function initializeSupabase(serverDir, packageManager) {
8130
8195
  const error = e;
8131
8196
  return new DatabaseSetupError({
8132
8197
  provider: "supabase",
8133
- message: error.message?.includes("ENOENT") ? "Supabase CLI not found. Please install it globally (npm install -g supabase) or ensure it's in your PATH." : `Failed to initialize Supabase project: ${error.message ?? String(e)}`,
8198
+ message: error.message?.includes("ENOENT") ? "Could not run the Supabase CLI. Ensure your selected package manager is installed and available in PATH." : `Failed to initialize Supabase project: ${error.message ?? String(e)}`,
8134
8199
  cause: e
8135
8200
  });
8136
8201
  }
8137
8202
  });
8138
8203
  }
8204
+ /** Start the standard Docker runtime and stream its progress to the CLI. */
8139
8205
  async function startSupabase(serverDir, packageManager) {
8140
8206
  cliLog.info("Starting Supabase services (this may take a moment)...");
8141
- const supabaseStartArgs = getPackageExecutionArgs(packageManager, "supabase start --agent no");
8207
+ const supabaseStartArgs = getPackageExecutionArgs(packageManager, `${SUPABASE_CLI_PACKAGE} start --agent no`);
8142
8208
  return Result.tryPromise({
8143
8209
  try: async () => {
8144
- const subprocess = execa(supabaseStartArgs[0], supabaseStartArgs.slice(1), { cwd: serverDir });
8210
+ const subprocess = execa(supabaseStartArgs[0], supabaseStartArgs.slice(1), {
8211
+ cwd: serverDir,
8212
+ env: supabaseEnv,
8213
+ cancelSignal: getInterruptSignal()
8214
+ });
8145
8215
  let stdoutData = "";
8146
8216
  if (subprocess.stdout) subprocess.stdout.on("data", (data) => {
8147
8217
  const text = data.toString();
@@ -8150,7 +8220,6 @@ async function startSupabase(serverDir, packageManager) {
8150
8220
  });
8151
8221
  if (subprocess.stderr) subprocess.stderr.pipe(process.stderr);
8152
8222
  await subprocess;
8153
- await new Promise((resolve) => setTimeout(resolve, 100));
8154
8223
  return stdoutData;
8155
8224
  },
8156
8225
  catch: (e) => {
@@ -8163,18 +8232,21 @@ async function startSupabase(serverDir, packageManager) {
8163
8232
  }
8164
8233
  });
8165
8234
  }
8166
- function displayManualSupabaseInstructions(targetApp, output) {
8235
+ /** Print pinned setup commands without changing configuration or credentials. */
8236
+ function displayManualSupabaseInstructions(targetApp, packageManager, output) {
8237
+ const command = getPackageExecutionCommand(packageManager, SUPABASE_CLI_PACKAGE);
8167
8238
  cliLog.info(`"Manual Supabase Setup Instructions:"
8168
- 1. Ensure Docker is installed and running.
8169
- 2. Install the Supabase CLI (e.g., \`npm install -g supabase\`).
8170
- 3. Run \`supabase init\` in your project's \`packages/db\` directory.
8171
- 4. Run \`supabase start\` in your project's \`packages/db\` directory.
8172
- 5. Copy the database URL from the output.${output ? `
8239
+ 1. Open your project's \`packages/db\` directory.
8240
+ 2. Run \`${command} init\` if \`supabase/config.toml\` does not exist.
8241
+ 3. Ensure Docker or a Docker-compatible container runtime is installed and running.
8242
+ 4. Run \`${command} start\`.
8243
+ 5. Run \`${command} status --output json\` and copy \`DB_URL\`.${output ? `
8173
8244
  ${pc.bold("Relevant output from `supabase start`:")}
8174
8245
  ${pc.dim(output)}` : ""}
8175
- 6. Add the database URL to the .env file in \`${targetApp}/.env\` as \`DATABASE_URL\`:
8176
- ${pc.gray("DATABASE_URL=\"your_supabase_db_url\"")}`);
8246
+ 6. Add the database URL to the .env file in \`${targetApp}/.env\` as \`DATABASE_URL\` and \`DIRECT_URL\`:
8247
+ ${pc.gray("DATABASE_URL=\"your_supabase_db_url\"\nDIRECT_URL=\"your_supabase_db_url\"")}`);
8177
8248
  }
8249
+ /** Configure local Supabase, preserving credentials until automatic setup succeeds. */
8178
8250
  async function setupSupabase(config, cliInput) {
8179
8251
  const { projectDir, packageManager, backend } = config;
8180
8252
  const targetApp = backend === "self" ? "apps/web" : "apps/server";
@@ -8193,8 +8265,8 @@ async function setupSupabase(config, cliInput) {
8193
8265
  });
8194
8266
  if (ensureDirResult.isErr()) return ensureDirResult;
8195
8267
  if (setupMode === "manual") {
8196
- displayManualSupabaseInstructions(targetApp);
8197
- return writeSupabaseEnvFile(projectDir, backend, "");
8268
+ displayManualSupabaseInstructions(targetApp, packageManager);
8269
+ return Result.ok();
8198
8270
  }
8199
8271
  let mode = setupMode;
8200
8272
  if (!mode) {
@@ -8218,35 +8290,35 @@ async function setupSupabase(config, cliInput) {
8218
8290
  }
8219
8291
  }
8220
8292
  if (mode === "manual") {
8221
- displayManualSupabaseInstructions(targetApp);
8222
- return writeSupabaseEnvFile(projectDir, backend, "");
8293
+ displayManualSupabaseInstructions(targetApp, packageManager);
8294
+ return Result.ok();
8223
8295
  }
8224
8296
  const initResult = await initializeSupabase(serverDir, packageManager);
8225
8297
  if (initResult.isErr()) {
8226
8298
  cliLog.error(pc.red(initResult.error.message));
8227
- displayManualSupabaseInstructions(targetApp);
8228
- return writeSupabaseEnvFile(projectDir, backend, "");
8299
+ displayManualSupabaseInstructions(targetApp, packageManager);
8300
+ return initResult;
8229
8301
  }
8230
8302
  const startResult = await startSupabase(serverDir, packageManager);
8231
8303
  if (startResult.isErr()) {
8232
8304
  cliLog.error(pc.red(startResult.error.message));
8233
- displayManualSupabaseInstructions(targetApp);
8234
- return writeSupabaseEnvFile(projectDir, backend, "");
8305
+ displayManualSupabaseInstructions(targetApp, packageManager);
8306
+ return startResult;
8235
8307
  }
8236
8308
  const supabaseOutput = startResult.value;
8237
- const dbUrl = extractDbUrl(await readSupabaseStatus(serverDir, packageManager));
8238
- if (dbUrl) {
8239
- const envResult = await writeSupabaseEnvFile(projectDir, backend, dbUrl);
8309
+ const statusResult = await readSupabaseStatus(serverDir, packageManager);
8310
+ if (statusResult.isOk()) {
8311
+ const envResult = await writeSupabaseEnvFile(projectDir, backend, statusResult.value);
8240
8312
  if (envResult.isOk()) cliLog.success(pc.green("Supabase local development setup ready!"));
8241
8313
  else {
8242
8314
  cliLog.error(pc.red("Supabase setup completed, but failed to update .env automatically."));
8243
- displayManualSupabaseInstructions(targetApp, supabaseOutput);
8315
+ displayManualSupabaseInstructions(targetApp, packageManager, supabaseOutput);
8244
8316
  }
8245
8317
  return envResult;
8246
8318
  }
8247
8319
  cliLog.error(pc.yellow("Supabase started, but could not extract DB URL automatically."));
8248
- displayManualSupabaseInstructions(targetApp, supabaseOutput);
8249
- return databaseSetupError("supabase", "Could not extract database URL from Supabase output. Please configure manually.");
8320
+ displayManualSupabaseInstructions(targetApp, packageManager, supabaseOutput);
8321
+ return statusResult;
8250
8322
  }
8251
8323
  //#endregion
8252
8324
  //#region src/helpers/database-providers/turso-setup.ts
@@ -8766,7 +8838,7 @@ async function displayPostInstallInstructions(config) {
8766
8838
  let output = `${pc.cyan("1.")} ${cdCmd}\n`;
8767
8839
  let stepCounter = 2;
8768
8840
  if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
8769
- if (database === "sqlite" && dbSetup !== "d1") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:local\n${pc.dim(" (optional - starts local SQLite database)")}\n`;
8841
+ if ((0, types_exports.usesTursoDevServer)(config)) output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:local\n${pc.dim(dbSetup === "turso" ? " (optional - starts a local Turso database)" : " (starts the local SQLite server)")}\n`;
8770
8842
  const hasAlchemyD1 = dbSetup === "d1" && (serverDeploy === "cloudflare" || isBackendSelf && webDeploy === "cloudflare");
8771
8843
  const hasWranglerLocalD1 = (0, types_exports.getLocalD1Owner)(config) === "wrangler";
8772
8844
  if (orm === "prisma" && !hasAlchemyD1) output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:generate\n`;
@@ -9169,7 +9241,8 @@ async function* runPostScaffoldSteps(options, cliInput, projectDir, isConvex) {
9169
9241
  projectDir,
9170
9242
  install,
9171
9243
  installError,
9172
- interrupted: wasAnyStepInterrupted()
9244
+ interrupted: wasAnyStepInterrupted(),
9245
+ stepFailures: getStepFailures()
9173
9246
  });
9174
9247
  }
9175
9248
  async function setPackageManagerVersion(projectDir, packageManager, version) {
@@ -9438,6 +9511,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
9438
9511
  await trackProjectCreation(config, input.disableAnalytics, mode);
9439
9512
  const warnings = [];
9440
9513
  if (created.interrupted) warnings.push("One or more setup steps were cancelled.");
9514
+ warnings.push(...created.stepFailures);
9441
9515
  if (created.installError) {
9442
9516
  warnings.push(created.installError.message);
9443
9517
  await reportDiagnostic("cli_failed", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.46.1",
3
+ "version": "3.48.0",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "keywords": [
6
6
  "better-auth",
@@ -64,17 +64,17 @@
64
64
  "dev": "tsdown --watch",
65
65
  "check-types": "tsc --noEmit",
66
66
  "check-test-types": "tsc --noEmit -p tsconfig.tests.json",
67
- "test": "bun test",
68
- "test:ci": "bun run build && BTS_SKIP_EXTERNAL_COMMANDS=1 BTS_TEST_MODE=1 bun test --bail=5",
69
- "test:complete": "cd ../.. && bun run build && cd apps/cli && BTS_MATRIX_MODE=smoke BTS_BUILD_SAMPLES=1 bun test --bail=5",
67
+ "test": "bun test ./test",
68
+ "test:ci": "bun run build && BTS_SKIP_EXTERNAL_COMMANDS=1 BTS_TEST_MODE=1 bun run test --bail=5",
69
+ "test:complete": "cd ../.. && bun run build && cd apps/cli && BTS_BUILD_SAMPLES=1 bun run test --bail=5",
70
70
  "prepublishOnly": "npm run build"
71
71
  },
72
72
  "dependencies": {
73
- "@better-t-stack/template-generator": "^3.46.1",
74
- "@better-t-stack/types": "^3.46.1",
73
+ "@better-t-stack/template-generator": "^3.48.0",
74
+ "@better-t-stack/types": "^3.48.0",
75
75
  "@clack/core": "^1.5.0",
76
76
  "@clack/prompts": "^1.8.0",
77
- "@modelcontextprotocol/server": "2.0.0",
77
+ "@modelcontextprotocol/server": "2.3.1",
78
78
  "@orpc/server": "^1.15.6",
79
79
  "better-result": "^3.0.1",
80
80
  "consola": "^3.4.2",
@@ -94,9 +94,9 @@
94
94
  "zod": "^4.5.4"
95
95
  },
96
96
  "devDependencies": {
97
- "@modelcontextprotocol/client": "2.0.0",
97
+ "@modelcontextprotocol/client": "2.3.1",
98
98
  "@playwright/test": "1.63.0",
99
- "@types/bun": "^1.4.2",
99
+ "@types/bun": "^1.4.3",
100
100
  "@types/fs-extra": "^11.0.4",
101
101
  "@types/node": "^26.5.0",
102
102
  "@types/semver": "^7.8.0",