create-better-t-stack 3.19.5 → 3.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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-xjt9vVj6.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-xjt9vVj6.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,246 @@ 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 getRecommendedMcpServers(config) {
1452
+ const servers = [];
1453
+ servers.push({
1454
+ key: "context7",
1455
+ label: "Context7",
1456
+ name: "context7",
1457
+ target: "@upstash/context7-mcp"
1458
+ });
1459
+ if (config.runtime === "workers" || config.webDeploy === "cloudflare" || config.serverDeploy === "cloudflare") servers.push({
1460
+ key: "cloudflare-docs",
1461
+ label: "Cloudflare Docs",
1462
+ name: "cloudflare-docs",
1463
+ target: "https://docs.mcp.cloudflare.com/sse",
1464
+ transport: "sse"
1465
+ });
1466
+ if (config.backend === "convex") servers.push({
1467
+ key: "convex",
1468
+ label: "Convex",
1469
+ name: "convex",
1470
+ target: "npx -y convex@latest mcp start"
1471
+ });
1472
+ if (hasReactBasedFrontend$1(config.frontend)) servers.push({
1473
+ key: "shadcn",
1474
+ label: "shadcn/ui",
1475
+ name: "shadcn",
1476
+ target: "npx -y shadcn@latest mcp"
1477
+ });
1478
+ if (config.frontend.includes("next")) servers.push({
1479
+ key: "next-devtools",
1480
+ label: "Next Devtools",
1481
+ name: "next-devtools",
1482
+ target: "npx -y next-devtools-mcp@latest"
1483
+ });
1484
+ if (config.frontend.includes("nuxt")) servers.push({
1485
+ key: "nuxt-docs",
1486
+ label: "Nuxt Docs",
1487
+ name: "nuxt",
1488
+ target: "https://nuxt.com/mcp"
1489
+ }, {
1490
+ key: "nuxt-ui-docs",
1491
+ label: "Nuxt UI Docs",
1492
+ name: "nuxt-ui",
1493
+ target: "https://ui.nuxt.com/mcp"
1494
+ });
1495
+ if (config.frontend.includes("svelte")) servers.push({
1496
+ key: "svelte-docs",
1497
+ label: "Svelte Docs",
1498
+ name: "svelte",
1499
+ target: "https://mcp.svelte.dev/mcp"
1500
+ });
1501
+ if (config.frontend.includes("astro")) servers.push({
1502
+ key: "astro-docs",
1503
+ label: "Astro Docs",
1504
+ name: "astro-docs",
1505
+ target: "https://mcp.docs.astro.build/mcp"
1506
+ });
1507
+ if (config.dbSetup === "planetscale") servers.push({
1508
+ key: "planetscale",
1509
+ label: "PlanetScale",
1510
+ name: "planetscale",
1511
+ target: "https://mcp.pscale.dev/mcp/planetscale"
1512
+ });
1513
+ if (config.dbSetup === "neon") servers.push({
1514
+ key: "neon",
1515
+ label: "Neon",
1516
+ name: "neon",
1517
+ target: "https://mcp.neon.tech/mcp"
1518
+ });
1519
+ if (config.dbSetup === "supabase") servers.push({
1520
+ key: "supabase",
1521
+ label: "Supabase",
1522
+ name: "supabase",
1523
+ target: "https://mcp.supabase.com/mcp"
1524
+ });
1525
+ if (config.auth === "better-auth") servers.push({
1526
+ key: "better-auth",
1527
+ label: "Better Auth",
1528
+ name: "better-auth",
1529
+ target: "https://mcp.inkeep.com/better-auth/mcp"
1530
+ });
1531
+ if (config.payments === "polar") servers.push({
1532
+ key: "polar",
1533
+ label: "Polar",
1534
+ name: "polar",
1535
+ target: "https://mcp.polar.sh/mcp/polar-mcp"
1536
+ });
1537
+ return servers;
1538
+ }
1539
+ function filterAgentsForScope(scope) {
1540
+ return MCP_AGENTS.filter((a) => a.scope === "both" || a.scope === scope);
1541
+ }
1542
+ async function setupMcp(config) {
1543
+ if (shouldSkipExternalCommands()) return Result.ok(void 0);
1544
+ const { packageManager, projectDir } = config;
1545
+ log.info("Setting up MCP servers...");
1546
+ const scope = await select({
1547
+ message: "Where should MCP servers be installed?",
1548
+ options: [{
1549
+ value: "project",
1550
+ label: "Project",
1551
+ hint: "Writes to project config files (recommended for teams)"
1552
+ }, {
1553
+ value: "global",
1554
+ label: "Global",
1555
+ hint: "Writes to user-level config files (personal machine)"
1556
+ }],
1557
+ initialValue: "project"
1558
+ });
1559
+ if (isCancel(scope)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
1560
+ const recommendedServers = getRecommendedMcpServers(config);
1561
+ if (recommendedServers.length === 0) return Result.ok(void 0);
1562
+ const serverOptions = recommendedServers.map((s) => ({
1563
+ value: s.key,
1564
+ label: s.label,
1565
+ hint: s.target
1566
+ }));
1567
+ const selectedServerKeys = await multiselect({
1568
+ message: "Select MCP servers to install",
1569
+ options: serverOptions,
1570
+ required: false,
1571
+ initialValues: serverOptions.map((o) => o.value)
1572
+ });
1573
+ if (isCancel(selectedServerKeys)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
1574
+ if (selectedServerKeys.length === 0) return Result.ok(void 0);
1575
+ const agentOptions = filterAgentsForScope(scope).map((a) => ({
1576
+ value: a.value,
1577
+ label: a.label
1578
+ }));
1579
+ const selectedAgents = await multiselect({
1580
+ message: "Select agents to install MCP servers to",
1581
+ options: agentOptions,
1582
+ required: false,
1583
+ initialValues: uniqueValues$1([
1584
+ "cursor",
1585
+ "claude-code",
1586
+ "vscode"
1587
+ ].filter((a) => agentOptions.some((o) => o.value === a)))
1588
+ });
1589
+ if (isCancel(selectedAgents)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
1590
+ if (selectedAgents.length === 0) return Result.ok(void 0);
1591
+ const serversByKey = new Map(recommendedServers.map((s) => [s.key, s]));
1592
+ const selectedServers = [];
1593
+ for (const key of selectedServerKeys) {
1594
+ const server = serversByKey.get(key);
1595
+ if (server) selectedServers.push(server);
1596
+ }
1597
+ if (selectedServers.length === 0) return Result.ok(void 0);
1598
+ const installSpinner = spinner();
1599
+ installSpinner.start("Installing MCP servers...");
1600
+ const runner = getPackageRunnerPrefix(packageManager);
1601
+ const globalFlags = scope === "global" ? ["-g"] : [];
1602
+ for (const server of selectedServers) {
1603
+ const transportFlags = server.transport ? ["-t", server.transport] : [];
1604
+ const headerFlags = (server.headers ?? []).flatMap((h) => ["--header", h]);
1605
+ const agentFlags = selectedAgents.flatMap((a) => ["-a", a]);
1606
+ const args = [
1607
+ ...runner,
1608
+ "add-mcp@latest",
1609
+ server.target,
1610
+ "--name",
1611
+ server.name,
1612
+ ...transportFlags,
1613
+ ...headerFlags,
1614
+ ...agentFlags,
1615
+ ...globalFlags,
1616
+ "-y"
1617
+ ];
1618
+ if ((await Result.tryPromise({
1619
+ try: async () => {
1620
+ await $({
1621
+ cwd: projectDir,
1622
+ env: { CI: "true" }
1623
+ })`${args}`;
1624
+ },
1625
+ catch: (e) => new AddonSetupError({
1626
+ addon: "mcp",
1627
+ message: `Failed to install MCP server '${server.name}': ${e instanceof Error ? e.message : String(e)}`,
1628
+ cause: e
1629
+ })
1630
+ })).isErr()) log.warn(pc.yellow(`Warning: Could not install MCP server '${server.name}'`));
1631
+ }
1632
+ installSpinner.stop("MCP servers installed");
1633
+ return Result.ok(void 0);
1634
+ }
1635
+
1387
1636
  //#endregion
1388
1637
  //#region src/helpers/addons/oxlint-setup.ts
1389
1638
  async function setupOxlint(projectDir, packageManager) {
@@ -1532,58 +1781,20 @@ async function addRulerScriptToPackageJson(projectDir, packageManager) {
1532
1781
  //#endregion
1533
1782
  //#region src/helpers/addons/skills-setup.ts
1534
1783
  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
- }
1784
+ "vercel-labs/agent-skills": { label: "Vercel Agent Skills" },
1785
+ "vercel/ai": { label: "Vercel AI SDK" },
1786
+ "vercel/turborepo": { label: "Turborepo" },
1787
+ "yusukebe/hono-skill": { label: "Hono Backend" },
1788
+ "vercel-labs/next-skills": { label: "Next.js Best Practices" },
1789
+ "nuxt/ui": { label: "Nuxt UI" },
1790
+ "heroui-inc/heroui": { label: "HeroUI Native" },
1791
+ "better-auth/skills": { label: "Better Auth" },
1792
+ "neondatabase/agent-skills": { label: "Neon Database" },
1793
+ "supabase/agent-skills": { label: "Supabase" },
1794
+ "expo/skills": { label: "Expo" },
1795
+ "prisma/skills": { label: "Prisma" },
1796
+ "elysiajs/skills": { label: "ElysiaJS" },
1797
+ "waynesutton/convexskills": { label: "Convex" }
1587
1798
  };
1588
1799
  const AVAILABLE_AGENTS = [
1589
1800
  {
@@ -1687,19 +1898,24 @@ const AVAILABLE_AGENTS = [
1687
1898
  label: "MCPJam"
1688
1899
  }
1689
1900
  ];
1901
+ function hasReactBasedFrontend(frontend) {
1902
+ return frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("tanstack-start") || frontend.includes("next");
1903
+ }
1904
+ function hasNativeFrontend(frontend) {
1905
+ return frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
1906
+ }
1690
1907
  function getRecommendedSourceKeys(config) {
1691
1908
  const sources = [];
1692
1909
  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");
1910
+ if (hasReactBasedFrontend(frontend)) sources.push("vercel-labs/agent-skills");
1696
1911
  if (frontend.includes("next")) sources.push("vercel-labs/next-skills");
1912
+ if (frontend.includes("nuxt")) sources.push("nuxt/ui");
1697
1913
  if (frontend.includes("native-uniwind")) sources.push("heroui-inc/heroui");
1698
- if (hasNativeFrontend) sources.push("expo/skills");
1914
+ if (hasNativeFrontend(frontend)) sources.push("expo/skills");
1699
1915
  if (auth === "better-auth") sources.push("better-auth/skills");
1700
1916
  if (dbSetup === "neon") sources.push("neondatabase/agent-skills");
1701
1917
  if (dbSetup === "supabase") sources.push("supabase/agent-skills");
1702
- if (orm === "prisma") sources.push("prisma/skills");
1918
+ if (orm === "prisma" || dbSetup === "prisma-postgres") sources.push("prisma/skills");
1703
1919
  if (examples.includes("ai")) sources.push("vercel/ai");
1704
1920
  if (addons.includes("turborepo")) sources.push("vercel/turborepo");
1705
1921
  if (backend === "hono") sources.push("yusukebe/hono-skill");
@@ -1707,60 +1923,95 @@ function getRecommendedSourceKeys(config) {
1707
1923
  if (backend === "convex") sources.push("waynesutton/convexskills");
1708
1924
  return sources;
1709
1925
  }
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;
1926
+ const CURATED_SKILLS_BY_SOURCE = {
1927
+ "vercel-labs/agent-skills": (config) => {
1928
+ const skills = [
1929
+ "web-design-guidelines",
1930
+ "vercel-composition-patterns",
1931
+ "vercel-react-best-practices"
1932
+ ];
1933
+ if (hasNativeFrontend(config.frontend)) skills.push("vercel-react-native-skills");
1934
+ return skills;
1935
+ },
1936
+ "vercel/ai": () => ["ai-sdk"],
1937
+ "vercel/turborepo": () => ["turborepo"],
1938
+ "yusukebe/hono-skill": () => ["hono"],
1939
+ "vercel-labs/next-skills": () => ["next-best-practices", "next-cache-components"],
1940
+ "nuxt/ui": () => ["nuxt-ui"],
1941
+ "heroui-inc/heroui": () => ["heroui-native"],
1942
+ "better-auth/skills": () => ["better-auth-best-practices"],
1943
+ "neondatabase/agent-skills": () => ["neon-postgres"],
1944
+ "supabase/agent-skills": () => ["supabase-postgres-best-practices"],
1945
+ "expo/skills": (config) => {
1946
+ const skills = [
1947
+ "expo-dev-client",
1948
+ "building-native-ui",
1949
+ "native-data-fetching",
1950
+ "expo-deployment",
1951
+ "upgrading-expo",
1952
+ "expo-cicd-workflows"
1953
+ ];
1954
+ if (config.frontend.includes("native-uniwind")) skills.push("expo-tailwind-setup");
1955
+ return skills;
1956
+ },
1957
+ "prisma/skills": (config) => {
1958
+ const skills = [];
1959
+ if (config.orm === "prisma") skills.push("prisma-cli", "prisma-client-api", "prisma-database-setup");
1960
+ if (config.dbSetup === "prisma-postgres") skills.push("prisma-postgres");
1961
+ return skills;
1962
+ },
1963
+ "elysiajs/skills": () => ["elysiajs"],
1964
+ "waynesutton/convexskills": () => [
1965
+ "convex-best-practices",
1966
+ "convex-functions",
1967
+ "convex-schema-validator",
1968
+ "convex-realtime",
1969
+ "convex-http-actions",
1970
+ "convex-cron-jobs",
1971
+ "convex-file-storage",
1972
+ "convex-migrations",
1973
+ "convex-security-check"
1974
+ ]
1975
+ };
1976
+ function getCuratedSkillNamesForSourceKey(sourceKey, config) {
1977
+ return CURATED_SKILLS_BY_SOURCE[sourceKey](config);
1719
1978
  }
1720
1979
  function uniqueValues(values) {
1721
1980
  return Array.from(new Set(values));
1722
1981
  }
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
1982
  async function setupSkills(config) {
1735
1983
  if (shouldSkipExternalCommands()) return Result.ok(void 0);
1736
1984
  const { packageManager, projectDir } = config;
1737
1985
  const btsConfig = await readBtsConfig(projectDir);
1738
- const recommendedSourceKeys = getRecommendedSourceKeys(btsConfig ? {
1986
+ const fullConfig = btsConfig ? {
1739
1987
  ...config,
1740
1988
  addons: btsConfig.addons ?? config.addons
1741
- } : config);
1989
+ } : config;
1990
+ const recommendedSourceKeys = getRecommendedSourceKeys(fullConfig);
1742
1991
  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
- }));
1992
+ const skillOptions = uniqueValues(recommendedSourceKeys).flatMap((sourceKey) => {
1993
+ const source = SKILL_SOURCES[sourceKey];
1994
+ return getCuratedSkillNamesForSourceKey(sourceKey, fullConfig).map((skillName) => ({
1995
+ value: `${sourceKey}::${skillName}`,
1996
+ label: skillName,
1997
+ hint: source.label
1998
+ }));
1999
+ });
2000
+ if (skillOptions.length === 0) return Result.ok(void 0);
2001
+ const scope = await select({
2002
+ message: "Where should skills be installed?",
2003
+ options: [{
2004
+ value: "project",
2005
+ label: "Project",
2006
+ hint: "Writes to project config files (recommended for teams)"
2007
+ }, {
2008
+ value: "global",
2009
+ label: "Global",
2010
+ hint: "Writes to user-level config files (personal machine)"
2011
+ }],
2012
+ initialValue: "project"
2013
+ });
2014
+ if (isCancel(scope)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
1764
2015
  const selectedSkills = await multiselect({
1765
2016
  message: "Select skills to install",
1766
2017
  options: skillOptions,
@@ -1790,11 +2041,12 @@ async function setupSkills(config) {
1790
2041
  const installSpinner = spinner();
1791
2042
  installSpinner.start("Installing skills...");
1792
2043
  const agentFlags = selectedAgents.map((a) => `-a ${a}`).join(" ");
2044
+ const globalFlag = scope === "global" ? "-g" : "";
1793
2045
  for (const [source, skills] of Object.entries(skillsBySource)) {
1794
2046
  const skillFlags = skills.map((s) => `-s ${s}`).join(" ");
1795
2047
  if ((await Result.tryPromise({
1796
2048
  try: async () => {
1797
- const args = getPackageExecutionArgs(packageManager, `skills@latest add ${source} ${skillFlags} ${agentFlags} -y`);
2049
+ const args = getPackageExecutionArgs(packageManager, `skills@latest add ${source} ${globalFlag} ${skillFlags} ${agentFlags} -y`);
1798
2050
  await $({
1799
2051
  cwd: projectDir,
1800
2052
  env: { CI: "true" }
@@ -2294,6 +2546,7 @@ async function setupAddons(config) {
2294
2546
  if (addons.includes("wxt")) await runSetup(() => setupWxt(config));
2295
2547
  if (addons.includes("ruler")) await runSetup(() => setupRuler(config));
2296
2548
  if (addons.includes("skills")) await runSetup(() => setupSkills(config));
2549
+ if (addons.includes("mcp")) await runSetup(() => setupMcp(config));
2297
2550
  }
2298
2551
  async function setupBiome(projectDir) {
2299
2552
  await addPackageDependency({
@@ -5437,7 +5690,7 @@ async function setPackageManagerVersion(projectDir, packageManager) {
5437
5690
  if (!await fs.pathExists(pkgJsonPath)) return Result.ok(void 0);
5438
5691
  const versionResult = await Result.tryPromise({
5439
5692
  try: async () => {
5440
- const { stdout } = await $`${packageManager} -v`;
5693
+ const { stdout } = await $({ cwd: os$1.tmpdir() })`${packageManager} -v`;
5441
5694
  return stdout.trim();
5442
5695
  },
5443
5696
  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.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",
@@ -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.0",
74
+ "@better-t-stack/types": "^3.20.0",
75
75
  "@clack/core": "^1.0.0",
76
76
  "@clack/prompts": "^1.0.0",
77
77
  "@orpc/server": "^1.13.4",