create-better-t-stack 3.19.5 → 3.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { l as createBtsCli } from "./src-C8vduSK8.mjs";
2
+ import { l as createBtsCli } from "./src-BqpeME-D.mjs";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.d.mts CHANGED
@@ -182,6 +182,7 @@ declare const router: {
182
182
  lefthook: "lefthook";
183
183
  husky: "husky";
184
184
  ruler: "ruler";
185
+ mcp: "mcp";
185
186
  turborepo: "turborepo";
186
187
  fumadocs: "fumadocs";
187
188
  ultracite: "ultracite";
@@ -264,6 +265,7 @@ declare const router: {
264
265
  lefthook: "lefthook";
265
266
  husky: "husky";
266
267
  ruler: "ruler";
268
+ mcp: "mcp";
267
269
  turborepo: "turborepo";
268
270
  fumadocs: "fumadocs";
269
271
  ultracite: "ultracite";
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { _ as DatabaseSetupError, a as VirtualFileSystem, b as UserCancelledError, c as create, d as docs, f as generate, g as CompatibilityError, h as CLIError, i as TEMPLATE_COUNT, l as createBtsCli, m as sponsors, n as GeneratorError, o as add, p as router, r as Result, s as builder, t as EMBEDDED_TEMPLATES, u as createVirtual, v as DirectoryConflictError, x as ValidationError, y as ProjectCreationError } from "./src-C8vduSK8.mjs";
2
+ import { _ as DatabaseSetupError, a as VirtualFileSystem, b as UserCancelledError, c as create, d as docs, f as generate, g as CompatibilityError, h as CLIError, i as TEMPLATE_COUNT, l as createBtsCli, m as sponsors, n as GeneratorError, o as add, p as router, r as Result, s as builder, t as EMBEDDED_TEMPLATES, u as createVirtual, v as DirectoryConflictError, x as ValidationError, y as ProjectCreationError } from "./src-BqpeME-D.mjs";
3
3
 
4
4
  export { CLIError, CompatibilityError, DatabaseSetupError, DirectoryConflictError, EMBEDDED_TEMPLATES, GeneratorError, ProjectCreationError, Result, TEMPLATE_COUNT, UserCancelledError, ValidationError, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generate, router, sponsors };
@@ -18,8 +18,8 @@ import { writeTree } from "@better-t-stack/template-generator/fs-writer";
18
18
  import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
19
19
  import { AsyncLocalStorage } from "node:async_hooks";
20
20
  import { applyEdits, modify, parse } from "jsonc-parser";
21
- import { format } from "oxfmt";
22
21
  import os$1 from "node:os";
22
+ import { format } from "oxfmt";
23
23
 
24
24
  //#region src/utils/get-package-manager.ts
25
25
  const getUserPkgManager = () => {
@@ -86,6 +86,7 @@ const ADDON_COMPATIBILITY = {
86
86
  starlight: [],
87
87
  ultracite: [],
88
88
  ruler: [],
89
+ mcp: [],
89
90
  oxlint: [],
90
91
  fumadocs: [],
91
92
  opentui: [],
@@ -1024,6 +1025,10 @@ function getAddonDisplay(addon) {
1024
1025
  label = "Skills";
1025
1026
  hint = "AI coding agent skills for your stack";
1026
1027
  break;
1028
+ case "mcp":
1029
+ label = "MCP";
1030
+ hint = "Install MCP servers (docs, databases, SaaS) via add-mcp";
1031
+ break;
1027
1032
  default:
1028
1033
  label = addon;
1029
1034
  hint = `Add ${addon}`;
@@ -1049,7 +1054,11 @@ const ADDON_GROUPS = {
1049
1054
  "opentui",
1050
1055
  "wxt"
1051
1056
  ],
1052
- AI: ["ruler", "skills"]
1057
+ AI: [
1058
+ "ruler",
1059
+ "skills",
1060
+ "mcp"
1061
+ ]
1053
1062
  };
1054
1063
  async function getAddonsChoice(addons, frontends, auth) {
1055
1064
  if (addons !== void 0) return addons;
@@ -1384,6 +1393,261 @@ async function setupFumadocs(config) {
1384
1393
  return Result.ok(void 0);
1385
1394
  }
1386
1395
 
1396
+ //#endregion
1397
+ //#region src/helpers/addons/mcp-setup.ts
1398
+ const MCP_AGENTS = [
1399
+ {
1400
+ value: "cursor",
1401
+ label: "Cursor",
1402
+ scope: "both"
1403
+ },
1404
+ {
1405
+ value: "claude-code",
1406
+ label: "Claude Code",
1407
+ scope: "both"
1408
+ },
1409
+ {
1410
+ value: "codex",
1411
+ label: "Codex",
1412
+ scope: "both"
1413
+ },
1414
+ {
1415
+ value: "opencode",
1416
+ label: "OpenCode",
1417
+ scope: "both"
1418
+ },
1419
+ {
1420
+ value: "gemini-cli",
1421
+ label: "Gemini CLI",
1422
+ scope: "both"
1423
+ },
1424
+ {
1425
+ value: "vscode",
1426
+ label: "VS Code (GitHub Copilot)",
1427
+ scope: "both"
1428
+ },
1429
+ {
1430
+ value: "zed",
1431
+ label: "Zed",
1432
+ scope: "both"
1433
+ },
1434
+ {
1435
+ value: "claude-desktop",
1436
+ label: "Claude Desktop",
1437
+ scope: "global"
1438
+ },
1439
+ {
1440
+ value: "goose",
1441
+ label: "Goose",
1442
+ scope: "global"
1443
+ }
1444
+ ];
1445
+ function uniqueValues$1(values) {
1446
+ return Array.from(new Set(values));
1447
+ }
1448
+ function hasReactBasedFrontend$1(frontend) {
1449
+ return frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("tanstack-start") || frontend.includes("next");
1450
+ }
1451
+ function hasNativeFrontend$1(frontend) {
1452
+ return frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
1453
+ }
1454
+ function getRecommendedMcpServers(config) {
1455
+ const servers = [];
1456
+ servers.push({
1457
+ key: "context7",
1458
+ label: "Context7",
1459
+ name: "context7",
1460
+ target: "@upstash/context7-mcp"
1461
+ });
1462
+ if (config.runtime === "workers" || config.webDeploy === "cloudflare" || config.serverDeploy === "cloudflare") servers.push({
1463
+ key: "cloudflare-docs",
1464
+ label: "Cloudflare Docs",
1465
+ name: "cloudflare-docs",
1466
+ target: "https://docs.mcp.cloudflare.com/sse",
1467
+ transport: "sse"
1468
+ });
1469
+ if (config.backend === "convex") servers.push({
1470
+ key: "convex",
1471
+ label: "Convex",
1472
+ name: "convex",
1473
+ target: "npx -y convex@latest mcp start"
1474
+ });
1475
+ if (hasReactBasedFrontend$1(config.frontend)) servers.push({
1476
+ key: "shadcn",
1477
+ label: "shadcn/ui",
1478
+ name: "shadcn",
1479
+ target: "npx -y shadcn@latest mcp"
1480
+ });
1481
+ if (config.frontend.includes("next")) servers.push({
1482
+ key: "next-devtools",
1483
+ label: "Next Devtools",
1484
+ name: "next-devtools",
1485
+ target: "npx -y next-devtools-mcp@latest"
1486
+ });
1487
+ if (config.frontend.includes("nuxt")) servers.push({
1488
+ key: "nuxt-docs",
1489
+ label: "Nuxt Docs",
1490
+ name: "nuxt",
1491
+ target: "https://nuxt.com/mcp"
1492
+ }, {
1493
+ key: "nuxt-ui-docs",
1494
+ label: "Nuxt UI Docs",
1495
+ name: "nuxt-ui",
1496
+ target: "https://ui.nuxt.com/mcp"
1497
+ });
1498
+ if (config.frontend.includes("svelte")) servers.push({
1499
+ key: "svelte-docs",
1500
+ label: "Svelte Docs",
1501
+ name: "svelte",
1502
+ target: "https://mcp.svelte.dev/mcp"
1503
+ });
1504
+ if (config.frontend.includes("astro")) servers.push({
1505
+ key: "astro-docs",
1506
+ label: "Astro Docs",
1507
+ name: "astro-docs",
1508
+ target: "https://mcp.docs.astro.build/mcp"
1509
+ });
1510
+ if (config.dbSetup === "planetscale") servers.push({
1511
+ key: "planetscale",
1512
+ label: "PlanetScale",
1513
+ name: "planetscale",
1514
+ target: "https://mcp.pscale.dev/mcp/planetscale"
1515
+ });
1516
+ if (config.dbSetup === "neon") servers.push({
1517
+ key: "neon",
1518
+ label: "Neon",
1519
+ name: "neon",
1520
+ target: "https://mcp.neon.tech/mcp"
1521
+ });
1522
+ if (config.dbSetup === "supabase") servers.push({
1523
+ key: "supabase",
1524
+ label: "Supabase",
1525
+ name: "supabase",
1526
+ target: "https://mcp.supabase.com/mcp"
1527
+ });
1528
+ if (config.auth === "better-auth") servers.push({
1529
+ key: "better-auth",
1530
+ label: "Better Auth",
1531
+ name: "better-auth",
1532
+ target: "https://mcp.inkeep.com/better-auth/mcp"
1533
+ });
1534
+ if (config.auth === "clerk") servers.push({
1535
+ key: "clerk",
1536
+ label: "Clerk",
1537
+ name: "clerk",
1538
+ target: "https://mcp.clerk.com/mcp"
1539
+ });
1540
+ if (hasNativeFrontend$1(config.frontend)) servers.push({
1541
+ key: "expo",
1542
+ label: "Expo",
1543
+ name: "expo-mcp",
1544
+ target: "https://mcp.expo.dev/mcp"
1545
+ });
1546
+ if (config.payments === "polar") servers.push({
1547
+ key: "polar",
1548
+ label: "Polar",
1549
+ name: "polar",
1550
+ target: "https://mcp.polar.sh/mcp/polar-mcp"
1551
+ });
1552
+ return servers;
1553
+ }
1554
+ function filterAgentsForScope(scope) {
1555
+ return MCP_AGENTS.filter((a) => a.scope === "both" || a.scope === scope);
1556
+ }
1557
+ async function setupMcp(config) {
1558
+ if (shouldSkipExternalCommands()) return Result.ok(void 0);
1559
+ const { packageManager, projectDir } = config;
1560
+ log.info("Setting up MCP servers...");
1561
+ const scope = await select({
1562
+ message: "Where should MCP servers be installed?",
1563
+ options: [{
1564
+ value: "project",
1565
+ label: "Project",
1566
+ hint: "Writes to project config files (recommended for teams)"
1567
+ }, {
1568
+ value: "global",
1569
+ label: "Global",
1570
+ hint: "Writes to user-level config files (personal machine)"
1571
+ }],
1572
+ initialValue: "project"
1573
+ });
1574
+ if (isCancel(scope)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
1575
+ const recommendedServers = getRecommendedMcpServers(config);
1576
+ if (recommendedServers.length === 0) return Result.ok(void 0);
1577
+ const serverOptions = recommendedServers.map((s) => ({
1578
+ value: s.key,
1579
+ label: s.label,
1580
+ hint: s.target
1581
+ }));
1582
+ const selectedServerKeys = await multiselect({
1583
+ message: "Select MCP servers to install",
1584
+ options: serverOptions,
1585
+ required: false,
1586
+ initialValues: serverOptions.map((o) => o.value)
1587
+ });
1588
+ if (isCancel(selectedServerKeys)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
1589
+ if (selectedServerKeys.length === 0) return Result.ok(void 0);
1590
+ const agentOptions = filterAgentsForScope(scope).map((a) => ({
1591
+ value: a.value,
1592
+ label: a.label
1593
+ }));
1594
+ const selectedAgents = await multiselect({
1595
+ message: "Select agents to install MCP servers to",
1596
+ options: agentOptions,
1597
+ required: false,
1598
+ initialValues: uniqueValues$1([
1599
+ "cursor",
1600
+ "claude-code",
1601
+ "vscode"
1602
+ ].filter((a) => agentOptions.some((o) => o.value === a)))
1603
+ });
1604
+ if (isCancel(selectedAgents)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
1605
+ if (selectedAgents.length === 0) return Result.ok(void 0);
1606
+ const serversByKey = new Map(recommendedServers.map((s) => [s.key, s]));
1607
+ const selectedServers = [];
1608
+ for (const key of selectedServerKeys) {
1609
+ const server = serversByKey.get(key);
1610
+ if (server) selectedServers.push(server);
1611
+ }
1612
+ if (selectedServers.length === 0) return Result.ok(void 0);
1613
+ const installSpinner = spinner();
1614
+ installSpinner.start("Installing MCP servers...");
1615
+ const runner = getPackageRunnerPrefix(packageManager);
1616
+ const globalFlags = scope === "global" ? ["-g"] : [];
1617
+ for (const server of selectedServers) {
1618
+ const transportFlags = server.transport ? ["-t", server.transport] : [];
1619
+ const headerFlags = (server.headers ?? []).flatMap((h) => ["--header", h]);
1620
+ const agentFlags = selectedAgents.flatMap((a) => ["-a", a]);
1621
+ const args = [
1622
+ ...runner,
1623
+ "add-mcp@latest",
1624
+ server.target,
1625
+ "--name",
1626
+ server.name,
1627
+ ...transportFlags,
1628
+ ...headerFlags,
1629
+ ...agentFlags,
1630
+ ...globalFlags,
1631
+ "-y"
1632
+ ];
1633
+ if ((await Result.tryPromise({
1634
+ try: async () => {
1635
+ await $({
1636
+ cwd: projectDir,
1637
+ env: { CI: "true" }
1638
+ })`${args}`;
1639
+ },
1640
+ catch: (e) => new AddonSetupError({
1641
+ addon: "mcp",
1642
+ message: `Failed to install MCP server '${server.name}': ${e instanceof Error ? e.message : String(e)}`,
1643
+ cause: e
1644
+ })
1645
+ })).isErr()) log.warn(pc.yellow(`Warning: Could not install MCP server '${server.name}'`));
1646
+ }
1647
+ installSpinner.stop("MCP servers installed");
1648
+ return Result.ok(void 0);
1649
+ }
1650
+
1387
1651
  //#endregion
1388
1652
  //#region src/helpers/addons/oxlint-setup.ts
1389
1653
  async function setupOxlint(projectDir, packageManager) {
@@ -1532,58 +1796,20 @@ async function addRulerScriptToPackageJson(projectDir, packageManager) {
1532
1796
  //#endregion
1533
1797
  //#region src/helpers/addons/skills-setup.ts
1534
1798
  const SKILL_SOURCES = {
1535
- "vercel-labs/agent-skills": {
1536
- source: "vercel-labs/agent-skills",
1537
- label: "Vercel Agent Skills"
1538
- },
1539
- "vercel/ai": {
1540
- source: "vercel/ai",
1541
- label: "Vercel AI SDK"
1542
- },
1543
- "vercel/turborepo": {
1544
- source: "vercel/turborepo",
1545
- label: "Turborepo"
1546
- },
1547
- "yusukebe/hono-skill": {
1548
- source: "yusukebe/hono-skill",
1549
- label: "Hono Backend"
1550
- },
1551
- "vercel-labs/next-skills": {
1552
- source: "vercel-labs/next-skills",
1553
- label: "Next.js Best Practices"
1554
- },
1555
- "heroui-inc/heroui": {
1556
- source: "heroui-inc/heroui",
1557
- label: "HeroUI Native"
1558
- },
1559
- "better-auth/skills": {
1560
- source: "better-auth/skills",
1561
- label: "Better Auth"
1562
- },
1563
- "neondatabase/agent-skills": {
1564
- source: "neondatabase/agent-skills",
1565
- label: "Neon Database"
1566
- },
1567
- "supabase/agent-skills": {
1568
- source: "supabase/agent-skills",
1569
- label: "Supabase"
1570
- },
1571
- "expo/skills": {
1572
- source: "expo/skills",
1573
- label: "Expo"
1574
- },
1575
- "prisma/skills": {
1576
- source: "prisma/skills",
1577
- label: "Prisma"
1578
- },
1579
- "elysiajs/skills": {
1580
- source: "elysiajs/skills",
1581
- label: "ElysiaJS"
1582
- },
1583
- "waynesutton/convexskills": {
1584
- source: "waynesutton/convexskills",
1585
- label: "Convex"
1586
- }
1799
+ "vercel-labs/agent-skills": { label: "Vercel Agent Skills" },
1800
+ "vercel/ai": { label: "Vercel AI SDK" },
1801
+ "vercel/turborepo": { label: "Turborepo" },
1802
+ "yusukebe/hono-skill": { label: "Hono Backend" },
1803
+ "vercel-labs/next-skills": { label: "Next.js Best Practices" },
1804
+ "nuxt/ui": { label: "Nuxt UI" },
1805
+ "heroui-inc/heroui": { label: "HeroUI Native" },
1806
+ "better-auth/skills": { label: "Better Auth" },
1807
+ "neondatabase/agent-skills": { label: "Neon Database" },
1808
+ "supabase/agent-skills": { label: "Supabase" },
1809
+ "expo/skills": { label: "Expo" },
1810
+ "prisma/skills": { label: "Prisma" },
1811
+ "elysiajs/skills": { label: "ElysiaJS" },
1812
+ "waynesutton/convexskills": { label: "Convex" }
1587
1813
  };
1588
1814
  const AVAILABLE_AGENTS = [
1589
1815
  {
@@ -1687,19 +1913,24 @@ const AVAILABLE_AGENTS = [
1687
1913
  label: "MCPJam"
1688
1914
  }
1689
1915
  ];
1916
+ function hasReactBasedFrontend(frontend) {
1917
+ return frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("tanstack-start") || frontend.includes("next");
1918
+ }
1919
+ function hasNativeFrontend(frontend) {
1920
+ return frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
1921
+ }
1690
1922
  function getRecommendedSourceKeys(config) {
1691
1923
  const sources = [];
1692
1924
  const { frontend, backend, dbSetup, auth, examples, addons, orm } = config;
1693
- const hasReactBasedFrontend = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("tanstack-start") || frontend.includes("next");
1694
- const hasNativeFrontend = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
1695
- if (hasReactBasedFrontend) sources.push("vercel-labs/agent-skills");
1925
+ if (hasReactBasedFrontend(frontend)) sources.push("vercel-labs/agent-skills");
1696
1926
  if (frontend.includes("next")) sources.push("vercel-labs/next-skills");
1927
+ if (frontend.includes("nuxt")) sources.push("nuxt/ui");
1697
1928
  if (frontend.includes("native-uniwind")) sources.push("heroui-inc/heroui");
1698
- if (hasNativeFrontend) sources.push("expo/skills");
1929
+ if (hasNativeFrontend(frontend)) sources.push("expo/skills");
1699
1930
  if (auth === "better-auth") sources.push("better-auth/skills");
1700
1931
  if (dbSetup === "neon") sources.push("neondatabase/agent-skills");
1701
1932
  if (dbSetup === "supabase") sources.push("supabase/agent-skills");
1702
- if (orm === "prisma") sources.push("prisma/skills");
1933
+ if (orm === "prisma" || dbSetup === "prisma-postgres") sources.push("prisma/skills");
1703
1934
  if (examples.includes("ai")) sources.push("vercel/ai");
1704
1935
  if (addons.includes("turborepo")) sources.push("vercel/turborepo");
1705
1936
  if (backend === "hono") sources.push("yusukebe/hono-skill");
@@ -1707,60 +1938,95 @@ function getRecommendedSourceKeys(config) {
1707
1938
  if (backend === "convex") sources.push("waynesutton/convexskills");
1708
1939
  return sources;
1709
1940
  }
1710
- function parseSkillsFromOutput(output) {
1711
- const skills = [];
1712
- const lines = output.split("\n");
1713
- const ansiRegex = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
1714
- for (const line of lines) {
1715
- const match = line.replace(ansiRegex, "").match(/^│\s{4}([a-z][a-z0-9-]*)$/);
1716
- if (match) skills.push(match[1]);
1717
- }
1718
- return skills;
1941
+ const CURATED_SKILLS_BY_SOURCE = {
1942
+ "vercel-labs/agent-skills": (config) => {
1943
+ const skills = [
1944
+ "web-design-guidelines",
1945
+ "vercel-composition-patterns",
1946
+ "vercel-react-best-practices"
1947
+ ];
1948
+ if (hasNativeFrontend(config.frontend)) skills.push("vercel-react-native-skills");
1949
+ return skills;
1950
+ },
1951
+ "vercel/ai": () => ["ai-sdk"],
1952
+ "vercel/turborepo": () => ["turborepo"],
1953
+ "yusukebe/hono-skill": () => ["hono"],
1954
+ "vercel-labs/next-skills": () => ["next-best-practices", "next-cache-components"],
1955
+ "nuxt/ui": () => ["nuxt-ui"],
1956
+ "heroui-inc/heroui": () => ["heroui-native"],
1957
+ "better-auth/skills": () => ["better-auth-best-practices"],
1958
+ "neondatabase/agent-skills": () => ["neon-postgres"],
1959
+ "supabase/agent-skills": () => ["supabase-postgres-best-practices"],
1960
+ "expo/skills": (config) => {
1961
+ const skills = [
1962
+ "expo-dev-client",
1963
+ "building-native-ui",
1964
+ "native-data-fetching",
1965
+ "expo-deployment",
1966
+ "upgrading-expo",
1967
+ "expo-cicd-workflows"
1968
+ ];
1969
+ if (config.frontend.includes("native-uniwind")) skills.push("expo-tailwind-setup");
1970
+ return skills;
1971
+ },
1972
+ "prisma/skills": (config) => {
1973
+ const skills = [];
1974
+ if (config.orm === "prisma") skills.push("prisma-cli", "prisma-client-api", "prisma-database-setup");
1975
+ if (config.dbSetup === "prisma-postgres") skills.push("prisma-postgres");
1976
+ return skills;
1977
+ },
1978
+ "elysiajs/skills": () => ["elysiajs"],
1979
+ "waynesutton/convexskills": () => [
1980
+ "convex-best-practices",
1981
+ "convex-functions",
1982
+ "convex-schema-validator",
1983
+ "convex-realtime",
1984
+ "convex-http-actions",
1985
+ "convex-cron-jobs",
1986
+ "convex-file-storage",
1987
+ "convex-migrations",
1988
+ "convex-security-check"
1989
+ ]
1990
+ };
1991
+ function getCuratedSkillNamesForSourceKey(sourceKey, config) {
1992
+ return CURATED_SKILLS_BY_SOURCE[sourceKey](config);
1719
1993
  }
1720
1994
  function uniqueValues(values) {
1721
1995
  return Array.from(new Set(values));
1722
1996
  }
1723
- async function fetchSkillsFromSource(source, packageManager, projectDir) {
1724
- try {
1725
- const args = getPackageExecutionArgs(packageManager, `skills@latest add ${source.source} --list`);
1726
- return parseSkillsFromOutput((await $({
1727
- cwd: projectDir,
1728
- env: { CI: "true" }
1729
- })`${args}`).stdout);
1730
- } catch {
1731
- return [];
1732
- }
1733
- }
1734
1997
  async function setupSkills(config) {
1735
1998
  if (shouldSkipExternalCommands()) return Result.ok(void 0);
1736
1999
  const { packageManager, projectDir } = config;
1737
2000
  const btsConfig = await readBtsConfig(projectDir);
1738
- const recommendedSourceKeys = getRecommendedSourceKeys(btsConfig ? {
2001
+ const fullConfig = btsConfig ? {
1739
2002
  ...config,
1740
2003
  addons: btsConfig.addons ?? config.addons
1741
- } : config);
2004
+ } : config;
2005
+ const recommendedSourceKeys = getRecommendedSourceKeys(fullConfig);
1742
2006
  if (recommendedSourceKeys.length === 0) return Result.ok(void 0);
1743
- const sourceKeys = uniqueValues(recommendedSourceKeys);
1744
- const s = spinner();
1745
- s.start("Fetching available skills...");
1746
- const allSkills = [];
1747
- const sources = sourceKeys.map((sourceKey) => SKILL_SOURCES[sourceKey]).filter((source) => Boolean(source));
1748
- const fetchedSkills = await Promise.all(sources.map(async (source) => ({
1749
- source,
1750
- skills: await fetchSkillsFromSource(source, packageManager, projectDir)
1751
- })));
1752
- for (const { source, skills } of fetchedSkills) for (const skillName of skills) allSkills.push({
1753
- name: skillName,
1754
- source: source.source,
1755
- sourceLabel: source.label
1756
- });
1757
- s.stop("Fetched available skills");
1758
- if (allSkills.length === 0) return Result.ok(void 0);
1759
- const skillOptions = allSkills.map((skill) => ({
1760
- value: `${skill.source}::${skill.name}`,
1761
- label: skill.name,
1762
- hint: skill.sourceLabel
1763
- }));
2007
+ const skillOptions = uniqueValues(recommendedSourceKeys).flatMap((sourceKey) => {
2008
+ const source = SKILL_SOURCES[sourceKey];
2009
+ return getCuratedSkillNamesForSourceKey(sourceKey, fullConfig).map((skillName) => ({
2010
+ value: `${sourceKey}::${skillName}`,
2011
+ label: skillName,
2012
+ hint: source.label
2013
+ }));
2014
+ });
2015
+ if (skillOptions.length === 0) return Result.ok(void 0);
2016
+ const scope = await select({
2017
+ message: "Where should skills be installed?",
2018
+ options: [{
2019
+ value: "project",
2020
+ label: "Project",
2021
+ hint: "Writes to project config files (recommended for teams)"
2022
+ }, {
2023
+ value: "global",
2024
+ label: "Global",
2025
+ hint: "Writes to user-level config files (personal machine)"
2026
+ }],
2027
+ initialValue: "project"
2028
+ });
2029
+ if (isCancel(scope)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
1764
2030
  const selectedSkills = await multiselect({
1765
2031
  message: "Select skills to install",
1766
2032
  options: skillOptions,
@@ -1790,11 +2056,12 @@ async function setupSkills(config) {
1790
2056
  const installSpinner = spinner();
1791
2057
  installSpinner.start("Installing skills...");
1792
2058
  const agentFlags = selectedAgents.map((a) => `-a ${a}`).join(" ");
2059
+ const globalFlag = scope === "global" ? "-g" : "";
1793
2060
  for (const [source, skills] of Object.entries(skillsBySource)) {
1794
2061
  const skillFlags = skills.map((s) => `-s ${s}`).join(" ");
1795
2062
  if ((await Result.tryPromise({
1796
2063
  try: async () => {
1797
- const args = getPackageExecutionArgs(packageManager, `skills@latest add ${source} ${skillFlags} ${agentFlags} -y`);
2064
+ const args = getPackageExecutionArgs(packageManager, `skills@latest add ${source} ${globalFlag} ${skillFlags} ${agentFlags} -y`);
1798
2065
  await $({
1799
2066
  cwd: projectDir,
1800
2067
  env: { CI: "true" }
@@ -2294,6 +2561,7 @@ async function setupAddons(config) {
2294
2561
  if (addons.includes("wxt")) await runSetup(() => setupWxt(config));
2295
2562
  if (addons.includes("ruler")) await runSetup(() => setupRuler(config));
2296
2563
  if (addons.includes("skills")) await runSetup(() => setupSkills(config));
2564
+ if (addons.includes("mcp")) await runSetup(() => setupMcp(config));
2297
2565
  }
2298
2566
  async function setupBiome(projectDir) {
2299
2567
  await addPackageDependency({
@@ -5437,7 +5705,7 @@ async function setPackageManagerVersion(projectDir, packageManager) {
5437
5705
  if (!await fs.pathExists(pkgJsonPath)) return Result.ok(void 0);
5438
5706
  const versionResult = await Result.tryPromise({
5439
5707
  try: async () => {
5440
- const { stdout } = await $`${packageManager} -v`;
5708
+ const { stdout } = await $({ cwd: os$1.tmpdir() })`${packageManager} -v`;
5441
5709
  return stdout.trim();
5442
5710
  },
5443
5711
  catch: () => null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.19.5",
3
+ "version": "3.20.1",
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",
@@ -70,8 +70,8 @@
70
70
  "prepublishOnly": "npm run build"
71
71
  },
72
72
  "dependencies": {
73
- "@better-t-stack/template-generator": "^3.19.5",
74
- "@better-t-stack/types": "^3.19.5",
73
+ "@better-t-stack/template-generator": "^3.20.1",
74
+ "@better-t-stack/types": "^3.20.1",
75
75
  "@clack/core": "^1.0.0",
76
76
  "@clack/prompts": "^1.0.0",
77
77
  "@orpc/server": "^1.13.4",