create-better-t-stack 3.27.4 → 3.27.5

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 { _ as types_exports, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as getLatestCLIVersion } from "./src-nmBsmday.mjs";
2
+ import { _ as types_exports, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as getLatestCLIVersion } from "./src-VGvTc2ik.mjs";
3
3
  import z from "zod";
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/dist/index.d.mts CHANGED
@@ -225,6 +225,9 @@ declare const router: _$_trpc_server0.TRPCBuiltRouter<{
225
225
  fumadocs?: {
226
226
  template: "react-router" | "tanstack-start" | "next-mdx" | "next-mdx-static" | "waku" | "react-router-spa" | "tanstack-start-spa";
227
227
  devPort?: number | undefined;
228
+ search?: "orama" | "orama-cloud" | undefined;
229
+ ogImage?: "next-og" | "takumi" | undefined;
230
+ aiChat?: "openrouter" | "inkeep" | undefined;
228
231
  } | undefined;
229
232
  opentui?: {
230
233
  template: "solid" | "react" | "core";
@@ -244,9 +247,9 @@ declare const router: _$_trpc_server0.TRPCBuiltRouter<{
244
247
  } | undefined;
245
248
  ultracite?: {
246
249
  linter?: "biome" | "oxlint" | "eslint" | undefined;
247
- editors?: ("void" | "antigravity" | "cursor" | "vscode" | "zed" | "windsurf" | "trae" | "kiro")[] | undefined;
248
- agents?: ("cline" | "codex" | "opencode" | "goose" | "amp" | "droid" | "claude" | "jules" | "copilot" | "aider" | "firebase-studio" | "open-hands" | "gemini" | "junie" | "augmentcode" | "kilo-code" | "roo-code" | "warp" | "crush" | "qwen" | "amazon-q-cli" | "firebender" | "cursor-cli" | "mistral-vibe" | "vercel")[] | undefined;
249
- hooks?: ("cursor" | "windsurf" | "claude")[] | undefined;
250
+ editors?: ("void" | "antigravity" | "cursor" | "vscode" | "zed" | "windsurf" | "trae" | "codebuddy" | "bob" | "kiro")[] | undefined;
251
+ agents?: ("cline" | "codex" | "opencode" | "goose" | "amp" | "pi" | "qoder" | "droid" | "zencoder" | "mcpjam" | "bob" | "universal" | "claude" | "jules" | "replit" | "devin" | "lovable" | "ona" | "openclaw" | "continue" | "snowflake-cortex" | "deepagents" | "kimi-cli" | "mux" | "adal" | "copilot" | "aider" | "firebase-studio" | "open-hands" | "gemini" | "junie" | "augmentcode" | "kilo-code" | "roo-code" | "warp" | "crush" | "qwen" | "amazon-q-cli" | "firebender" | "cursor-cli" | "mistral-vibe" | "vercel")[] | undefined;
252
+ hooks?: ("cursor" | "windsurf" | "codebuddy" | "claude" | "copilot")[] | undefined;
250
253
  } | undefined;
251
254
  } | undefined;
252
255
  dbSetupOptions?: {
@@ -332,6 +335,9 @@ declare const router: _$_trpc_server0.TRPCBuiltRouter<{
332
335
  fumadocs?: {
333
336
  template: "react-router" | "tanstack-start" | "next-mdx" | "next-mdx-static" | "waku" | "react-router-spa" | "tanstack-start-spa";
334
337
  devPort?: number | undefined;
338
+ search?: "orama" | "orama-cloud" | undefined;
339
+ ogImage?: "next-og" | "takumi" | undefined;
340
+ aiChat?: "openrouter" | "inkeep" | undefined;
335
341
  } | undefined;
336
342
  opentui?: {
337
343
  template: "solid" | "react" | "core";
@@ -351,9 +357,9 @@ declare const router: _$_trpc_server0.TRPCBuiltRouter<{
351
357
  } | undefined;
352
358
  ultracite?: {
353
359
  linter?: "biome" | "oxlint" | "eslint" | undefined;
354
- editors?: ("void" | "antigravity" | "cursor" | "vscode" | "zed" | "windsurf" | "trae" | "kiro")[] | undefined;
355
- agents?: ("cline" | "codex" | "opencode" | "goose" | "amp" | "droid" | "claude" | "jules" | "copilot" | "aider" | "firebase-studio" | "open-hands" | "gemini" | "junie" | "augmentcode" | "kilo-code" | "roo-code" | "warp" | "crush" | "qwen" | "amazon-q-cli" | "firebender" | "cursor-cli" | "mistral-vibe" | "vercel")[] | undefined;
356
- hooks?: ("cursor" | "windsurf" | "claude")[] | undefined;
360
+ editors?: ("void" | "antigravity" | "cursor" | "vscode" | "zed" | "windsurf" | "trae" | "codebuddy" | "bob" | "kiro")[] | undefined;
361
+ agents?: ("cline" | "codex" | "opencode" | "goose" | "amp" | "pi" | "qoder" | "droid" | "zencoder" | "mcpjam" | "bob" | "universal" | "claude" | "jules" | "replit" | "devin" | "lovable" | "ona" | "openclaw" | "continue" | "snowflake-cortex" | "deepagents" | "kimi-cli" | "mux" | "adal" | "copilot" | "aider" | "firebase-studio" | "open-hands" | "gemini" | "junie" | "augmentcode" | "kilo-code" | "roo-code" | "warp" | "crush" | "qwen" | "amazon-q-cli" | "firebender" | "cursor-cli" | "mistral-vibe" | "vercel")[] | undefined;
362
+ hooks?: ("cursor" | "windsurf" | "codebuddy" | "claude" | "copilot")[] | undefined;
357
363
  } | undefined;
358
364
  } | undefined;
359
365
  webDeploy?: "none" | "cloudflare" | undefined;
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { C as ProjectCreationError, S as DirectoryConflictError, T as ValidationError, a as TEMPLATE_COUNT, b as CompatibilityError, 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 UserCancelledError, x as DatabaseSetupError, y as CLIError } from "./src-nmBsmday.mjs";
2
+ import { C as ProjectCreationError, S as DirectoryConflictError, T as ValidationError, a as TEMPLATE_COUNT, b as CompatibilityError, 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 UserCancelledError, x as DatabaseSetupError, y as CLIError } from "./src-VGvTc2ik.mjs";
3
3
  export { CLIError, CompatibilityError, DatabaseSetupError, DirectoryConflictError, EMBEDDED_TEMPLATES, GeneratorError, ProjectCreationError, Result, SchemaNameSchema, TEMPLATE_COUNT, UserCancelledError, ValidationError, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generate, getSchemaResult, router, sponsors };
@@ -5,7 +5,7 @@ import { initTRPC } from "@trpc/server";
5
5
  import { Result, Result as Result$1, TaggedError } from "better-result";
6
6
  import { createCli } from "trpc-cli";
7
7
  import z from "zod";
8
- import { cancel, confirm, group, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
8
+ import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
9
9
  import pc from "picocolors";
10
10
  import path from "node:path";
11
11
  import envPaths from "env-paths";
@@ -1381,6 +1381,62 @@ const addPackageDependency = async (opts) => {
1381
1381
  await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
1382
1382
  };
1383
1383
  //#endregion
1384
+ //#region src/prompts/navigable-group.ts
1385
+ /**
1386
+ * Navigable group - a group of prompts that allows going back
1387
+ */
1388
+ /**
1389
+ * Define a group of prompts that supports going back to previous prompts.
1390
+ * Returns a result object with all the values, or handles cancel/go-back navigation.
1391
+ */
1392
+ async function navigableGroup(prompts, opts) {
1393
+ const results = {};
1394
+ const promptNames = Object.keys(prompts);
1395
+ let currentIndex = 0;
1396
+ let goingBack = false;
1397
+ while (currentIndex < promptNames.length) {
1398
+ const name = promptNames[currentIndex];
1399
+ const prompt = prompts[name];
1400
+ setIsFirstPrompt$1(currentIndex === 0);
1401
+ setLastPromptShownUI(false);
1402
+ const result = await prompt({ results })?.catch((e) => {
1403
+ throw e;
1404
+ });
1405
+ if (isGoBack(result)) {
1406
+ goingBack = true;
1407
+ if (currentIndex > 0) {
1408
+ const prevName = promptNames[currentIndex - 1];
1409
+ delete results[prevName];
1410
+ currentIndex--;
1411
+ continue;
1412
+ }
1413
+ goingBack = false;
1414
+ continue;
1415
+ }
1416
+ if (isCancel$1(result)) {
1417
+ if (typeof opts?.onCancel === "function") {
1418
+ results[name] = "canceled";
1419
+ opts.onCancel({ results });
1420
+ }
1421
+ setIsFirstPrompt$1(false);
1422
+ return results;
1423
+ }
1424
+ if (goingBack && !didLastPromptShowUI()) {
1425
+ if (currentIndex > 0) {
1426
+ const prevName = promptNames[currentIndex - 1];
1427
+ delete results[prevName];
1428
+ currentIndex--;
1429
+ continue;
1430
+ }
1431
+ }
1432
+ goingBack = false;
1433
+ results[name] = result;
1434
+ currentIndex++;
1435
+ }
1436
+ setIsFirstPrompt$1(false);
1437
+ return results;
1438
+ }
1439
+ //#endregion
1384
1440
  //#region src/utils/external-commands.ts
1385
1441
  function shouldSkipExternalCommands() {
1386
1442
  return process.env.BTS_SKIP_EXTERNAL_COMMANDS === "1" || process.env.BTS_TEST_MODE === "1";
@@ -1484,64 +1540,143 @@ function getPackageRunnerPrefix(packageManager) {
1484
1540
  const TEMPLATES$2 = {
1485
1541
  "next-mdx": {
1486
1542
  label: "Next.js: Fumadocs MDX",
1487
- hint: "Recommended template with MDX support",
1543
+ hint: "recommended",
1488
1544
  value: "+next+fuma-docs-mdx"
1489
1545
  },
1490
1546
  "next-mdx-static": {
1491
- label: "Next.js: Fumadocs MDX (Static)",
1492
- hint: "Static export template with MDX support",
1547
+ label: "Next.js Static: Fumadocs MDX",
1493
1548
  value: "+next+fuma-docs-mdx+static"
1494
1549
  },
1495
1550
  waku: {
1496
- label: "Waku: Content Collections",
1497
- hint: "Template using Waku with content collections",
1551
+ label: "Waku: Fumadocs MDX",
1498
1552
  value: "waku"
1499
1553
  },
1500
1554
  "react-router": {
1501
- label: "React Router: MDX Remote",
1502
- hint: "Template for React Router with MDX remote",
1555
+ label: "React Router: Fumadocs MDX (not RSC)",
1503
1556
  value: "react-router"
1504
1557
  },
1505
1558
  "react-router-spa": {
1506
- label: "React Router: SPA",
1507
- hint: "Template for React Router SPA",
1559
+ label: "React Router SPA: Fumadocs MDX (not RSC)",
1560
+ hint: "SPA mode allows you to host the site statically, compatible with a CDN.",
1508
1561
  value: "react-router-spa"
1509
1562
  },
1510
1563
  "tanstack-start": {
1511
- label: "Tanstack Start: MDX Remote",
1512
- hint: "Template for Tanstack Start with MDX remote",
1564
+ label: "Tanstack Start: Fumadocs MDX (not RSC)",
1513
1565
  value: "tanstack-start"
1514
1566
  },
1515
1567
  "tanstack-start-spa": {
1516
- label: "Tanstack Start: SPA",
1517
- hint: "Template for Tanstack Start SPA",
1568
+ label: "Tanstack Start SPA: Fumadocs MDX (not RSC)",
1569
+ hint: "SPA mode allows you to host the site statically, compatible with a CDN.",
1518
1570
  value: "tanstack-start-spa"
1519
1571
  }
1520
1572
  };
1521
1573
  const DEFAULT_TEMPLATE$2 = "next-mdx";
1522
1574
  const DEFAULT_DEV_PORT$1 = 4e3;
1575
+ function aiChatDisabledForTemplate(template) {
1576
+ return template === "next-mdx-static" || template.endsWith("-spa");
1577
+ }
1523
1578
  async function setupFumadocs(config) {
1524
1579
  if (shouldSkipExternalCommands()) return Result.ok(void 0);
1525
1580
  const { packageManager, projectDir } = config;
1526
1581
  cliLog.info("Setting up Fumadocs...");
1527
1582
  const configuredOptions = config.addonOptions?.fumadocs;
1528
1583
  let template = configuredOptions?.template;
1529
- if (!template) if (isSilent()) template = DEFAULT_TEMPLATE$2;
1584
+ let search = configuredOptions?.search;
1585
+ let ogImage = configuredOptions?.ogImage;
1586
+ let aiChat = configuredOptions?.aiChat;
1587
+ if (isSilent()) template = template ?? DEFAULT_TEMPLATE$2;
1530
1588
  else {
1531
- const selectedTemplate = await select({
1532
- message: "Choose a template",
1533
- options: Object.entries(TEMPLATES$2).map(([key, templateOption]) => ({
1534
- value: key,
1535
- label: templateOption.label,
1536
- hint: templateOption.hint
1537
- })),
1538
- initialValue: DEFAULT_TEMPLATE$2
1589
+ const promptResult = await Result.tryPromise({
1590
+ try: () => navigableGroup({
1591
+ template: async () => {
1592
+ if (template !== void 0) return template;
1593
+ return navigableSelect({
1594
+ message: "Choose a template",
1595
+ options: Object.entries(TEMPLATES$2).map(([key, t]) => ({
1596
+ value: key,
1597
+ label: t.label,
1598
+ hint: "hint" in t ? t.hint : void 0
1599
+ })),
1600
+ initialValue: DEFAULT_TEMPLATE$2
1601
+ });
1602
+ },
1603
+ search: async () => {
1604
+ if (search !== void 0) return search;
1605
+ return navigableSelect({
1606
+ message: "Choose a search solution?",
1607
+ options: [{
1608
+ value: "orama",
1609
+ label: "Default",
1610
+ hint: "local search powered by Orama, recommended"
1611
+ }, {
1612
+ value: "orama-cloud",
1613
+ label: "Orama Cloud",
1614
+ hint: "3rd party search solution, signup needed"
1615
+ }],
1616
+ initialValue: "orama"
1617
+ });
1618
+ },
1619
+ ogImage: async ({ results }) => {
1620
+ if (ogImage !== void 0) return ogImage;
1621
+ if (!(results.template ?? template ?? DEFAULT_TEMPLATE$2).startsWith("next-")) return "skip";
1622
+ return navigableSelect({
1623
+ message: "Configure Open Graph Image generation?",
1624
+ options: [{
1625
+ value: "next-og",
1626
+ label: "next/og",
1627
+ hint: "Next.js built-in solution"
1628
+ }, {
1629
+ value: "takumi",
1630
+ label: "Takumi",
1631
+ hint: "Output WebP format, framework-agnostic"
1632
+ }],
1633
+ initialValue: "next-og"
1634
+ });
1635
+ },
1636
+ aiChat: async ({ results }) => {
1637
+ if (aiChat !== void 0) return aiChat;
1638
+ if (aiChatDisabledForTemplate(results.template ?? template ?? DEFAULT_TEMPLATE$2)) return "none";
1639
+ return navigableSelect({
1640
+ message: "Configure AI Chat?",
1641
+ options: [
1642
+ {
1643
+ value: "none",
1644
+ label: "No"
1645
+ },
1646
+ {
1647
+ value: "openrouter",
1648
+ label: "AI SDK",
1649
+ hint: "default to OpenRouter"
1650
+ },
1651
+ {
1652
+ value: "inkeep",
1653
+ label: "Inkeep AI",
1654
+ hint: "API key required"
1655
+ }
1656
+ ],
1657
+ initialValue: "none"
1658
+ });
1659
+ }
1660
+ }),
1661
+ catch: (e) => new AddonSetupError({
1662
+ addon: "fumadocs",
1663
+ message: `Failed to run Fumadocs prompts: ${e instanceof Error ? e.message : String(e)}`,
1664
+ cause: e
1665
+ })
1539
1666
  });
1540
- if (isCancel(selectedTemplate)) return userCancelled("Operation cancelled");
1541
- template = selectedTemplate;
1542
- }
1543
- const templateArg = TEMPLATES$2[template].value;
1667
+ if (promptResult.isErr()) return Result.err(promptResult.error);
1668
+ const results = promptResult.value;
1669
+ if (results.template === void 0 || results.search === void 0 || results.ogImage === void 0 || results.aiChat === void 0) return userCancelled("Operation cancelled");
1670
+ template = results.template;
1671
+ search = results.search;
1672
+ ogImage = results.ogImage === "skip" ? void 0 : results.ogImage;
1673
+ aiChat = results.aiChat === "none" ? void 0 : results.aiChat;
1674
+ }
1675
+ if (!template) return userCancelled("Operation cancelled");
1544
1676
  const isNextTemplate = template.startsWith("next-");
1677
+ if (!isNextTemplate) ogImage = void 0;
1678
+ if (aiChatDisabledForTemplate(template)) aiChat = void 0;
1679
+ const templateArg = TEMPLATES$2[template].value;
1545
1680
  const devPort = configuredOptions?.devPort ?? DEFAULT_DEV_PORT$1;
1546
1681
  const options = [
1547
1682
  `--template ${templateArg}`,
@@ -1549,7 +1684,10 @@ async function setupFumadocs(config) {
1549
1684
  "--no-git"
1550
1685
  ];
1551
1686
  if (isNextTemplate) options.push("--src");
1552
- if (config.addons.includes("biome")) options.push("--linter biome");
1687
+ if (config.addons.includes("biome") || config.addons.includes("ultracite")) options.push("--linter biome");
1688
+ if (search) options.push(`--search ${search}`);
1689
+ if (ogImage) options.push(`--og-image ${ogImage}`);
1690
+ if (aiChat) options.push(`--ai-chat ${aiChat}`);
1553
1691
  const args = getPackageExecutionArgs(packageManager, `create-fumadocs-app@latest fumadocs ${options.join(" ")}`);
1554
1692
  const appsDir = path.join(projectDir, "apps");
1555
1693
  await fs.ensureDir(appsDir);
@@ -1812,67 +1950,84 @@ async function setupMcp(config) {
1812
1950
  if (shouldSkipExternalCommands()) return Result.ok(void 0);
1813
1951
  const { packageManager, projectDir } = config;
1814
1952
  cliLog.info("Setting up MCP servers...");
1815
- let scope = config.addonOptions?.mcp?.scope;
1816
- if (!scope) if (isSilent()) scope = DEFAULT_SCOPE$1;
1817
- else {
1818
- const selectedScope = await select({
1819
- message: "Where should MCP servers be installed?",
1820
- options: [{
1821
- value: "project",
1822
- label: "Project",
1823
- hint: "Writes to project config files (recommended for teams)"
1824
- }, {
1825
- value: "global",
1826
- label: "Global",
1827
- hint: "Writes to user-level config files (personal machine)"
1828
- }],
1829
- initialValue: DEFAULT_SCOPE$1
1830
- });
1831
- if (isCancel(selectedScope)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
1832
- scope = selectedScope;
1833
- }
1834
- const recommendedServers = getRecommendedMcpServers(config, scope);
1835
- if (recommendedServers.length === 0) return Result.ok(void 0);
1836
- const allServersByKey = new Map(getAllMcpServers(config).map((server) => [server.key, server]));
1837
- const serverOptions = recommendedServers.map((s) => ({
1838
- value: s.key,
1839
- label: s.label,
1840
- hint: s.target
1841
- }));
1953
+ const configuredScope = config.addonOptions?.mcp?.scope;
1842
1954
  const configuredServerKeys = config.addonOptions?.mcp?.servers;
1843
- const availableServerKeys = new Set(allServersByKey.keys());
1844
- let selectedServerKeys = configuredServerKeys?.filter((serverKey) => availableServerKeys.has(serverKey)) ?? [];
1845
- if (selectedServerKeys.length === 0 && configuredServerKeys === void 0) if (isSilent()) selectedServerKeys = serverOptions.map((o) => o.value);
1846
- else {
1847
- const promptedServerKeys = await multiselect({
1848
- message: "Select MCP servers to install",
1849
- options: serverOptions,
1850
- required: false,
1851
- initialValues: serverOptions.map((o) => o.value)
1852
- });
1853
- if (isCancel(promptedServerKeys)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
1854
- selectedServerKeys = [...promptedServerKeys];
1855
- }
1856
- if (selectedServerKeys.length === 0) return Result.ok(void 0);
1857
- const agentOptions = filterAgentsForScope(scope).map((a) => ({
1858
- value: a.value,
1859
- label: a.label
1860
- }));
1861
- const defaultAgents = uniqueValues$1(DEFAULT_AGENTS$2.filter((agent) => agentOptions.some((option) => option.value === agent)));
1862
1955
  const configuredAgents = config.addonOptions?.mcp?.agents;
1863
- let selectedAgents = configuredAgents?.filter((agent) => agentOptions.some((option) => option.value === agent)) ?? [];
1864
- if (selectedAgents.length === 0 && configuredAgents === void 0) if (isSilent()) selectedAgents = defaultAgents;
1865
- else {
1866
- const promptedAgents = await multiselect({
1867
- message: "Select agents to install MCP servers to",
1868
- options: agentOptions,
1869
- required: false,
1870
- initialValues: defaultAgents
1956
+ const allServersByKey = new Map(getAllMcpServers(config).map((server) => [server.key, server]));
1957
+ const availableServerKeys = new Set(allServersByKey.keys());
1958
+ let scope;
1959
+ let selectedServerKeys;
1960
+ let selectedAgents;
1961
+ if (isSilent()) {
1962
+ scope = configuredScope ?? DEFAULT_SCOPE$1;
1963
+ const recommendedServers = getRecommendedMcpServers(config, scope);
1964
+ if (recommendedServers.length === 0) return Result.ok(void 0);
1965
+ const serverOptions = recommendedServers.map((s) => s.key);
1966
+ selectedServerKeys = configuredServerKeys?.filter((k) => availableServerKeys.has(k)) ?? serverOptions;
1967
+ if (selectedServerKeys.length === 0) return Result.ok(void 0);
1968
+ const agentOptions = filterAgentsForScope(scope);
1969
+ const defaultAgents = uniqueValues$1(DEFAULT_AGENTS$2.filter((a) => agentOptions.some((o) => o.value === a)));
1970
+ selectedAgents = configuredAgents?.filter((a) => agentOptions.some((o) => o.value === a)) ?? defaultAgents;
1971
+ if (selectedAgents.length === 0) return Result.ok(void 0);
1972
+ } else {
1973
+ const results = await navigableGroup({
1974
+ scope: async () => {
1975
+ if (configuredScope !== void 0) return configuredScope;
1976
+ return navigableSelect({
1977
+ message: "Where should MCP servers be installed?",
1978
+ options: [{
1979
+ value: "project",
1980
+ label: "Project",
1981
+ hint: "Writes to project config files (recommended for teams)"
1982
+ }, {
1983
+ value: "global",
1984
+ label: "Global",
1985
+ hint: "Writes to user-level config files (personal machine)"
1986
+ }],
1987
+ initialValue: DEFAULT_SCOPE$1
1988
+ });
1989
+ },
1990
+ servers: async ({ results: r }) => {
1991
+ const recommended = getRecommendedMcpServers(config, r.scope ?? configuredScope ?? DEFAULT_SCOPE$1);
1992
+ if (recommended.length === 0) return [];
1993
+ const options = recommended.map((s) => ({
1994
+ value: s.key,
1995
+ label: s.label,
1996
+ hint: s.target
1997
+ }));
1998
+ if (configuredServerKeys !== void 0) return configuredServerKeys.filter((k) => availableServerKeys.has(k));
1999
+ return navigableMultiselect({
2000
+ message: "Select MCP servers to install",
2001
+ options,
2002
+ required: false,
2003
+ initialValues: options.map((o) => o.value)
2004
+ });
2005
+ },
2006
+ agents: async ({ results: r }) => {
2007
+ const currentScope = r.scope ?? configuredScope ?? DEFAULT_SCOPE$1;
2008
+ const currentServers = r.servers;
2009
+ if (currentServers !== void 0 && currentServers.length === 0) return [];
2010
+ const agentOpts = filterAgentsForScope(currentScope);
2011
+ if (agentOpts.length === 0) return [];
2012
+ const defaults = uniqueValues$1(DEFAULT_AGENTS$2.filter((a) => agentOpts.some((o) => o.value === a)));
2013
+ if (configuredAgents !== void 0) return configuredAgents.filter((a) => agentOpts.some((o) => o.value === a));
2014
+ return navigableMultiselect({
2015
+ message: "Select agents to install MCP servers to",
2016
+ options: agentOpts.map((a) => ({
2017
+ value: a.value,
2018
+ label: a.label
2019
+ })),
2020
+ required: false,
2021
+ initialValues: defaults
2022
+ });
2023
+ }
1871
2024
  });
1872
- if (isCancel(promptedAgents)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
1873
- selectedAgents = [...promptedAgents];
2025
+ if (results.scope === void 0 || results.servers === void 0 || results.agents === void 0) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
2026
+ scope = results.scope;
2027
+ selectedServerKeys = results.servers;
2028
+ selectedAgents = results.agents;
2029
+ if (selectedServerKeys.length === 0 || selectedAgents.length === 0) return Result.ok(void 0);
1874
2030
  }
1875
- if (selectedAgents.length === 0) return Result.ok(void 0);
1876
2031
  const selectedServers = [];
1877
2032
  for (const key of selectedServerKeys) {
1878
2033
  const server = allServersByKey.get(key);
@@ -2236,55 +2391,64 @@ async function setupSkills(config) {
2236
2391
  }));
2237
2392
  });
2238
2393
  if (skillOptions.length === 0) return Result.ok(void 0);
2239
- let scope = skillsOptions?.scope;
2240
- if (!scope) if (isSilent()) scope = DEFAULT_SCOPE;
2241
- else {
2242
- const selectedScope = await select({
2243
- message: "Where should skills be installed?",
2244
- options: [{
2245
- value: "project",
2246
- label: "Project",
2247
- hint: "Writes to project config files (recommended for teams)"
2248
- }, {
2249
- value: "global",
2250
- label: "Global",
2251
- hint: "Writes to user-level config files (personal machine)"
2252
- }],
2253
- initialValue: DEFAULT_SCOPE
2254
- });
2255
- if (isCancel(selectedScope)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
2256
- scope = selectedScope;
2257
- }
2258
- const allSkillValues = skillOptions.map((opt) => opt.value);
2394
+ const configuredScope = skillsOptions?.scope;
2259
2395
  const configuredSelections = skillsOptions?.selections;
2260
- let selectedSkills;
2261
- if (configuredSelections !== void 0) selectedSkills = configuredSelections.flatMap((selection) => selection.skills.map((skill) => `${selection.source}::${skill}`));
2262
- else if (isSilent()) selectedSkills = allSkillValues;
2263
- else {
2264
- const promptedSkills = await multiselect({
2265
- message: "Select skills to install",
2266
- options: skillOptions,
2267
- required: false,
2268
- initialValues: allSkillValues
2269
- });
2270
- if (isCancel(promptedSkills)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
2271
- selectedSkills = promptedSkills;
2272
- }
2273
- if (selectedSkills.length === 0) return Result.ok(void 0);
2274
2396
  const configuredAgents = skillsOptions?.agents;
2275
- let selectedAgents = configuredAgents ? [...configuredAgents] : [];
2276
- if (selectedAgents.length === 0 && configuredAgents === void 0) if (isSilent()) selectedAgents = [...DEFAULT_AGENTS$1];
2277
- else {
2278
- const promptedAgents = await multiselect({
2279
- message: "Select agents to install skills to",
2280
- options: AVAILABLE_AGENTS,
2281
- required: false,
2282
- initialValues: [...DEFAULT_AGENTS$1]
2397
+ const allSkillValues = skillOptions.map((opt) => opt.value);
2398
+ let scope;
2399
+ let selectedSkills;
2400
+ let selectedAgents;
2401
+ if (isSilent()) {
2402
+ scope = configuredScope ?? DEFAULT_SCOPE;
2403
+ selectedSkills = configuredSelections !== void 0 ? configuredSelections.flatMap((selection) => selection.skills.map((skill) => `${selection.source}::${skill}`)) : allSkillValues;
2404
+ if (selectedSkills.length === 0) return Result.ok(void 0);
2405
+ selectedAgents = configuredAgents ? [...configuredAgents] : [...DEFAULT_AGENTS$1];
2406
+ if (selectedAgents.length === 0) return Result.ok(void 0);
2407
+ } else {
2408
+ const results = await navigableGroup({
2409
+ scope: async () => {
2410
+ if (configuredScope !== void 0) return configuredScope;
2411
+ return navigableSelect({
2412
+ message: "Where should skills be installed?",
2413
+ options: [{
2414
+ value: "project",
2415
+ label: "Project",
2416
+ hint: "Writes to project config files (recommended for teams)"
2417
+ }, {
2418
+ value: "global",
2419
+ label: "Global",
2420
+ hint: "Writes to user-level config files (personal machine)"
2421
+ }],
2422
+ initialValue: DEFAULT_SCOPE
2423
+ });
2424
+ },
2425
+ skills: async () => {
2426
+ if (configuredSelections !== void 0) return configuredSelections.flatMap((selection) => selection.skills.map((skill) => `${selection.source}::${skill}`));
2427
+ return navigableMultiselect({
2428
+ message: "Select skills to install",
2429
+ options: skillOptions,
2430
+ required: false,
2431
+ initialValues: allSkillValues
2432
+ });
2433
+ },
2434
+ agents: async ({ results: r }) => {
2435
+ const pickedSkills = r.skills;
2436
+ if (pickedSkills !== void 0 && pickedSkills.length === 0) return [];
2437
+ if (configuredAgents !== void 0) return [...configuredAgents];
2438
+ return navigableMultiselect({
2439
+ message: "Select agents to install skills to",
2440
+ options: AVAILABLE_AGENTS,
2441
+ required: false,
2442
+ initialValues: [...DEFAULT_AGENTS$1]
2443
+ });
2444
+ }
2283
2445
  });
2284
- if (isCancel(promptedAgents)) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
2285
- selectedAgents = [...promptedAgents];
2446
+ if (results.scope === void 0 || results.skills === void 0 || results.agents === void 0) return Result.err(new UserCancelledError({ message: "Operation cancelled" }));
2447
+ scope = results.scope;
2448
+ selectedSkills = results.skills;
2449
+ selectedAgents = results.agents;
2450
+ if (selectedSkills.length === 0 || selectedAgents.length === 0) return Result.ok(void 0);
2286
2451
  }
2287
- if (selectedAgents.length === 0) return Result.ok(void 0);
2288
2452
  const skillsBySource = {};
2289
2453
  for (const skillKey of selectedSkills) {
2290
2454
  const [source, skillName] = skillKey.split("::");
@@ -2474,7 +2638,8 @@ async function setupTui(config) {
2474
2638
  cliLog.info("Setting up OpenTUI...");
2475
2639
  let template = resolveTuiTemplate(config);
2476
2640
  if (!template) {
2477
- const selectedTemplate = await select({
2641
+ setIsFirstPrompt(true);
2642
+ const selectedTemplate = await navigableSelect({
2478
2643
  message: "Choose a template",
2479
2644
  options: Object.entries(TEMPLATES$1).map(([key, templateOption]) => ({
2480
2645
  value: key,
@@ -2483,7 +2648,7 @@ async function setupTui(config) {
2483
2648
  })),
2484
2649
  initialValue: DEFAULT_TEMPLATE$1
2485
2650
  });
2486
- if (isCancel(selectedTemplate)) return userCancelled("Operation cancelled");
2651
+ if (isCancel$1(selectedTemplate)) return userCancelled("Operation cancelled");
2487
2652
  template = selectedTemplate;
2488
2653
  }
2489
2654
  const args = getPackageExecutionArgs(packageManager, `create-tui@latest --template ${template} --no-git --no-install tui`);
@@ -2563,42 +2728,40 @@ async function postProcessTuiWorkspace(tuiDir) {
2563
2728
  //#endregion
2564
2729
  //#region src/helpers/addons/ultracite-setup.ts
2565
2730
  const LINTERS = {
2566
- biome: {
2567
- label: "Biome",
2568
- hint: "Fast formatter and linter"
2569
- },
2570
- eslint: {
2571
- label: "ESLint",
2572
- hint: "Traditional JavaScript linter"
2573
- },
2574
- oxlint: {
2575
- label: "Oxlint",
2576
- hint: "Oxidation compiler linter"
2577
- }
2578
- };
2579
- const EDITORS = {
2580
- vscode: { label: "VS Code" },
2581
- cursor: { label: "Cursor" },
2582
- windsurf: { label: "Windsurf" },
2583
- antigravity: { label: "Antigravity" },
2584
- kiro: { label: "Kiro" },
2585
- trae: { label: "Trae" },
2586
- void: { label: "Void" },
2587
- zed: { label: "Zed" }
2731
+ biome: { label: "Biome (Recommended)" },
2732
+ eslint: { label: "ESLint + Prettier + Stylelint" },
2733
+ oxlint: { label: "Oxlint + Oxfmt" }
2588
2734
  };
2589
2735
  const AGENTS = {
2590
- claude: { label: "Claude" },
2736
+ universal: { label: "Universal (AGENTS.md — covers all agents)" },
2737
+ claude: { label: "Claude Code" },
2591
2738
  codex: { label: "Codex" },
2592
2739
  jules: { label: "Jules" },
2740
+ replit: { label: "Replit Agent" },
2741
+ devin: { label: "Devin" },
2742
+ lovable: { label: "Lovable" },
2743
+ zencoder: { label: "Zencoder" },
2744
+ ona: { label: "Ona" },
2745
+ openclaw: { label: "OpenClaw" },
2746
+ continue: { label: "Continue" },
2747
+ "snowflake-cortex": { label: "Snowflake Cortex" },
2748
+ deepagents: { label: "Deepagents" },
2749
+ qoder: { label: "Qoder" },
2750
+ "kimi-cli": { label: "Kimi CLI" },
2751
+ mcpjam: { label: "MCPJam" },
2752
+ mux: { label: "Mux" },
2753
+ pi: { label: "Pi" },
2754
+ adal: { label: "AdaL" },
2593
2755
  copilot: { label: "GitHub Copilot" },
2594
2756
  cline: { label: "Cline" },
2595
- amp: { label: "Amp" },
2757
+ amp: { label: "AMP" },
2596
2758
  aider: { label: "Aider" },
2597
2759
  "firebase-studio": { label: "Firebase Studio" },
2598
- "open-hands": { label: "Open Hands" },
2760
+ "open-hands": { label: "OpenHands" },
2599
2761
  gemini: { label: "Gemini" },
2600
2762
  junie: { label: "Junie" },
2601
- augmentcode: { label: "AugmentCode" },
2763
+ augmentcode: { label: "Augment Code" },
2764
+ bob: { label: "IBM Bob" },
2602
2765
  "kilo-code": { label: "Kilo Code" },
2603
2766
  goose: { label: "Goose" },
2604
2767
  "roo-code": { label: "Roo Code" },
@@ -2606,21 +2769,23 @@ const AGENTS = {
2606
2769
  droid: { label: "Droid" },
2607
2770
  opencode: { label: "OpenCode" },
2608
2771
  crush: { label: "Crush" },
2609
- qwen: { label: "Qwen" },
2772
+ qwen: { label: "Qwen Code" },
2610
2773
  "amazon-q-cli": { label: "Amazon Q CLI" },
2611
2774
  firebender: { label: "Firebender" },
2612
2775
  "cursor-cli": { label: "Cursor CLI" },
2613
2776
  "mistral-vibe": { label: "Mistral Vibe" },
2614
- vercel: { label: "Vercel" }
2777
+ vercel: { label: "Vercel Agent" }
2615
2778
  };
2616
2779
  const HOOKS = {
2617
2780
  cursor: { label: "Cursor" },
2618
2781
  windsurf: { label: "Windsurf" },
2619
- claude: { label: "Claude" }
2782
+ codebuddy: { label: "CodeBuddy" },
2783
+ claude: { label: "Claude Code" },
2784
+ copilot: { label: "GitHub Copilot" }
2620
2785
  };
2621
2786
  const DEFAULT_LINTER = "biome";
2622
- const DEFAULT_EDITORS = ["vscode", "cursor"];
2623
- const DEFAULT_AGENTS = ["claude", "codex"];
2787
+ const DEFAULT_EDITORS = ["vscode"];
2788
+ const DEFAULT_AGENTS = ["universal"];
2624
2789
  const DEFAULT_HOOKS = [];
2625
2790
  function getFrameworksFromFrontend(frontend) {
2626
2791
  const frameworkMap = {
@@ -2633,7 +2798,8 @@ function getFrameworksFromFrontend(frontend) {
2633
2798
  "native-uniwind": "react",
2634
2799
  "native-unistyles": "react",
2635
2800
  svelte: "svelte",
2636
- solid: "solid"
2801
+ solid: "solid",
2802
+ astro: "astro"
2637
2803
  };
2638
2804
  const frameworks = /* @__PURE__ */ new Set();
2639
2805
  for (const f of frontend) if (f !== "none" && frameworkMap[f]) frameworks.add(frameworkMap[f]);
@@ -2651,10 +2817,7 @@ function buildUltraciteInitArgs({ packageManager, linter, frameworks, editors, a
2651
2817
  if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
2652
2818
  if (agents.length > 0) ultraciteArgs.push("--agents", ...agents);
2653
2819
  if (hooks.length > 0) ultraciteArgs.push("--hooks", ...hooks);
2654
- if (gitHooks.length > 0) {
2655
- const integrations = gitHooks.includes("husky") ? [...new Set([...gitHooks, "lint-staged"])] : gitHooks;
2656
- ultraciteArgs.push("--integrations", ...integrations);
2657
- }
2820
+ if (gitHooks.length > 0) ultraciteArgs.push("--integrations", ...gitHooks);
2658
2821
  return [
2659
2822
  ...getPackageRunnerPrefix(packageManager),
2660
2823
  "ultracite@latest",
@@ -2678,67 +2841,60 @@ async function setupUltracite(config, gitHooks) {
2678
2841
  agents = agents ?? [...DEFAULT_AGENTS];
2679
2842
  hooks = hooks ?? [...DEFAULT_HOOKS];
2680
2843
  } else {
2681
- const groupResult = await Result.tryPromise({
2682
- try: async () => {
2683
- return await group({
2684
- linter: () => select({
2685
- message: "Choose linter/formatter",
2686
- options: Object.entries(LINTERS).map(([key, linterOption]) => ({
2687
- value: key,
2688
- label: linterOption.label,
2689
- hint: linterOption.hint
2690
- })),
2691
- initialValue: linter ?? DEFAULT_LINTER
2692
- }),
2693
- editors: () => multiselect({
2694
- message: "Choose editors",
2695
- required: false,
2696
- options: Object.entries(EDITORS).map(([key, editor]) => ({
2697
- value: key,
2698
- label: editor.label
2699
- })),
2700
- initialValues: editors ?? [...DEFAULT_EDITORS]
2701
- }),
2702
- agents: () => multiselect({
2703
- message: "Choose agents",
2704
- required: false,
2705
- options: Object.entries(AGENTS).map(([key, agent]) => ({
2706
- value: key,
2707
- label: agent.label
2708
- })),
2709
- initialValues: agents ?? [...DEFAULT_AGENTS]
2710
- }),
2711
- hooks: () => multiselect({
2712
- message: "Choose hooks",
2713
- required: false,
2714
- options: Object.entries(HOOKS).map(([key, hook]) => ({
2715
- value: key,
2716
- label: hook.label
2717
- })),
2718
- initialValues: hooks ?? [...DEFAULT_HOOKS]
2719
- })
2720
- }, { onCancel: () => {
2721
- throw new UserCancelledError({ message: "Operation cancelled" });
2722
- } });
2844
+ const results = await navigableGroup({
2845
+ linter: async () => {
2846
+ if (linter !== void 0) return linter;
2847
+ return navigableSelect({
2848
+ message: "Which linter do you want to use?",
2849
+ options: Object.entries(LINTERS).map(([key, linterOption]) => ({
2850
+ value: key,
2851
+ label: linterOption.label
2852
+ })),
2853
+ initialValue: linter ?? DEFAULT_LINTER
2854
+ });
2723
2855
  },
2724
- catch: (e) => {
2725
- if (e instanceof UserCancelledError) return e;
2726
- return new AddonSetupError({
2727
- addon: "ultracite",
2728
- message: `Failed to get user preferences: ${e instanceof Error ? e.message : String(e)}`,
2729
- cause: e
2856
+ editors: async () => {
2857
+ if (editors !== void 0) return editors;
2858
+ return navigableMultiselect({
2859
+ message: "Which editors do you want to configure (recommended)?",
2860
+ required: false,
2861
+ options: [{
2862
+ value: "vscode",
2863
+ label: "VSCode / Cursor / Windsurf"
2864
+ }, {
2865
+ value: "zed",
2866
+ label: "Zed"
2867
+ }]
2868
+ });
2869
+ },
2870
+ agents: async () => {
2871
+ if (agents !== void 0) return agents;
2872
+ return navigableMultiselect({
2873
+ message: "Which agent files do you want to add (optional)?",
2874
+ required: false,
2875
+ options: Object.entries(AGENTS).map(([key, agent]) => ({
2876
+ value: key,
2877
+ label: agent.label
2878
+ }))
2879
+ });
2880
+ },
2881
+ hooks: async () => {
2882
+ if (hooks !== void 0) return hooks;
2883
+ return navigableMultiselect({
2884
+ message: "Which agent hooks do you want to enable (optional)?",
2885
+ required: false,
2886
+ options: Object.entries(HOOKS).map(([key, hook]) => ({
2887
+ value: key,
2888
+ label: hook.label
2889
+ }))
2730
2890
  });
2731
2891
  }
2732
2892
  });
2733
- if (groupResult.isErr()) {
2734
- if (UserCancelledError.is(groupResult.error)) return userCancelled(groupResult.error.message);
2735
- cliLog.error(pc.red("Failed to set up Ultracite"));
2736
- return groupResult;
2737
- }
2738
- linter = groupResult.value.linter;
2739
- editors = groupResult.value.editors;
2740
- agents = groupResult.value.agents;
2741
- hooks = groupResult.value.hooks;
2893
+ if (results.linter === void 0 || results.editors === void 0 || results.agents === void 0 || results.hooks === void 0) return userCancelled("Operation cancelled");
2894
+ linter = results.linter;
2895
+ editors = results.editors;
2896
+ agents = results.agents;
2897
+ hooks = results.hooks;
2742
2898
  }
2743
2899
  const frameworks = getFrameworksFromFrontend(frontend);
2744
2900
  const args = buildUltraciteInitArgs({
@@ -2809,7 +2965,8 @@ async function setupWxt(config) {
2809
2965
  let template = configuredOptions?.template;
2810
2966
  if (!template) if (isSilent()) template = DEFAULT_TEMPLATE;
2811
2967
  else {
2812
- const selectedTemplate = await select({
2968
+ setIsFirstPrompt(true);
2969
+ const selectedTemplate = await navigableSelect({
2813
2970
  message: "Choose a template",
2814
2971
  options: Object.entries(TEMPLATES).map(([key, templateOption]) => ({
2815
2972
  value: key,
@@ -2818,7 +2975,7 @@ async function setupWxt(config) {
2818
2975
  })),
2819
2976
  initialValue: DEFAULT_TEMPLATE
2820
2977
  });
2821
- if (isCancel(selectedTemplate)) return userCancelled("Operation cancelled");
2978
+ if (isCancel$1(selectedTemplate)) return userCancelled("Operation cancelled");
2822
2979
  template = selectedTemplate;
2823
2980
  }
2824
2981
  const devPort = configuredOptions?.devPort ?? DEFAULT_DEV_PORT;
@@ -3648,62 +3805,6 @@ async function getinstallChoice(install) {
3648
3805
  return response;
3649
3806
  }
3650
3807
  //#endregion
3651
- //#region src/prompts/navigable-group.ts
3652
- /**
3653
- * Navigable group - a group of prompts that allows going back
3654
- */
3655
- /**
3656
- * Define a group of prompts that supports going back to previous prompts.
3657
- * Returns a result object with all the values, or handles cancel/go-back navigation.
3658
- */
3659
- async function navigableGroup(prompts, opts) {
3660
- const results = {};
3661
- const promptNames = Object.keys(prompts);
3662
- let currentIndex = 0;
3663
- let goingBack = false;
3664
- while (currentIndex < promptNames.length) {
3665
- const name = promptNames[currentIndex];
3666
- const prompt = prompts[name];
3667
- setIsFirstPrompt$1(currentIndex === 0);
3668
- setLastPromptShownUI(false);
3669
- const result = await prompt({ results })?.catch((e) => {
3670
- throw e;
3671
- });
3672
- if (isGoBack(result)) {
3673
- goingBack = true;
3674
- if (currentIndex > 0) {
3675
- const prevName = promptNames[currentIndex - 1];
3676
- delete results[prevName];
3677
- currentIndex--;
3678
- continue;
3679
- }
3680
- goingBack = false;
3681
- continue;
3682
- }
3683
- if (isCancel$1(result)) {
3684
- if (typeof opts?.onCancel === "function") {
3685
- results[name] = "canceled";
3686
- opts.onCancel({ results });
3687
- }
3688
- setIsFirstPrompt$1(false);
3689
- return results;
3690
- }
3691
- if (goingBack && !didLastPromptShowUI()) {
3692
- if (currentIndex > 0) {
3693
- const prevName = promptNames[currentIndex - 1];
3694
- delete results[prevName];
3695
- currentIndex--;
3696
- continue;
3697
- }
3698
- }
3699
- goingBack = false;
3700
- results[name] = result;
3701
- currentIndex++;
3702
- }
3703
- setIsFirstPrompt$1(false);
3704
- return results;
3705
- }
3706
- //#endregion
3707
3808
  //#region src/utils/config-validation.ts
3708
3809
  function validationErr(message) {
3709
3810
  return Result.err(new ValidationError({ message }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.27.4",
3
+ "version": "3.27.5",
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.27.4",
74
- "@better-t-stack/types": "^3.27.4",
73
+ "@better-t-stack/template-generator": "^3.27.5",
74
+ "@better-t-stack/types": "^3.27.5",
75
75
  "@clack/core": "^1.2.0",
76
76
  "@clack/prompts": "^1.2.0",
77
77
  "@modelcontextprotocol/sdk": "1.29.0",