@qazuor/claude-code-config 0.1.0 → 0.2.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/bin.js CHANGED
@@ -49,7 +49,7 @@ __export(fs_exports, {
49
49
  writeFile: () => writeFile,
50
50
  writeJson: () => writeJson
51
51
  });
52
- import path3 from "path";
52
+ import path2 from "path";
53
53
  import fs from "fs-extra";
54
54
  import { glob } from "glob";
55
55
  async function pathExists(filePath) {
@@ -75,18 +75,18 @@ async function readJson(filePath) {
75
75
  return fs.readJson(filePath);
76
76
  }
77
77
  async function writeJson(filePath, data, options) {
78
- await fs.ensureDir(path3.dirname(filePath));
78
+ await fs.ensureDir(path2.dirname(filePath));
79
79
  await fs.writeJson(filePath, data, { spaces: options?.spaces ?? 2 });
80
80
  }
81
81
  async function readFile(filePath) {
82
82
  return fs.readFile(filePath, "utf-8");
83
83
  }
84
84
  async function writeFile(filePath, content) {
85
- await fs.ensureDir(path3.dirname(filePath));
85
+ await fs.ensureDir(path2.dirname(filePath));
86
86
  await fs.writeFile(filePath, content, "utf-8");
87
87
  }
88
88
  async function copy(src, dest, options) {
89
- await fs.ensureDir(path3.dirname(dest));
89
+ await fs.ensureDir(path2.dirname(dest));
90
90
  await fs.copy(src, dest, { overwrite: options?.overwrite ?? false });
91
91
  }
92
92
  async function copyDir(src, dest, options) {
@@ -114,7 +114,7 @@ async function listDirs(pattern, options) {
114
114
  });
115
115
  const dirs = [];
116
116
  for (const match of matches) {
117
- const fullPath = options?.cwd ? path3.join(options.cwd, match) : match;
117
+ const fullPath = options?.cwd ? path2.join(options.cwd, match) : match;
118
118
  if (await isDirectory(fullPath)) {
119
119
  dirs.push(match);
120
120
  }
@@ -156,22 +156,22 @@ async function filesAreEqual(file1, file2) {
156
156
  }
157
157
  }
158
158
  function relativePath(from, to) {
159
- return path3.relative(from, to);
159
+ return path2.relative(from, to);
160
160
  }
161
161
  function resolvePath(...segments) {
162
- return path3.resolve(...segments);
162
+ return path2.resolve(...segments);
163
163
  }
164
164
  function joinPath(...segments) {
165
- return path3.join(...segments);
165
+ return path2.join(...segments);
166
166
  }
167
167
  function dirname(filePath) {
168
- return path3.dirname(filePath);
168
+ return path2.dirname(filePath);
169
169
  }
170
170
  function basename(filePath, ext) {
171
- return path3.basename(filePath, ext);
171
+ return path2.basename(filePath, ext);
172
172
  }
173
173
  function extname(filePath) {
174
- return path3.extname(filePath);
174
+ return path2.extname(filePath);
175
175
  }
176
176
  async function backup(src, suffix = ".backup") {
177
177
  const backupPath = `${src}${suffix}`;
@@ -187,7 +187,7 @@ async function makeExecutable(filePath) {
187
187
  async function createTempDir(prefix = "claude-config-") {
188
188
  const os4 = await import("os");
189
189
  const tempBase = os4.tmpdir();
190
- const tempDir = path3.join(tempBase, `${prefix}${Date.now()}`);
190
+ const tempDir = path2.join(tempBase, `${prefix}${Date.now()}`);
191
191
  await ensureDir(tempDir);
192
192
  return tempDir;
193
193
  }
@@ -1327,12 +1327,350 @@ function getBundleCategoryName(category) {
1327
1327
  return BUNDLE_CATEGORY_NAMES[category] ?? category;
1328
1328
  }
1329
1329
 
1330
+ // src/lib/ci-cd/index.ts
1331
+ init_esm_shims();
1332
+
1333
+ // src/lib/ci-cd/github-actions-generator.ts
1334
+ init_esm_shims();
1335
+ init_fs();
1336
+
1337
+ // src/lib/utils/spinner.ts
1338
+ init_esm_shims();
1339
+ import ora from "ora";
1340
+ import pc from "picocolors";
1341
+ var SpinnerManager = class {
1342
+ spinner = null;
1343
+ silent = false;
1344
+ configure(options) {
1345
+ if (options.silent !== void 0) this.silent = options.silent;
1346
+ }
1347
+ /**
1348
+ * Start a spinner with a message
1349
+ */
1350
+ start(text, options) {
1351
+ if (this.silent) return null;
1352
+ this.stop();
1353
+ this.spinner = ora({
1354
+ text,
1355
+ color: options?.color || "cyan",
1356
+ spinner: "dots"
1357
+ }).start();
1358
+ return this.spinner;
1359
+ }
1360
+ /**
1361
+ * Update spinner text
1362
+ */
1363
+ text(text) {
1364
+ if (this.spinner) {
1365
+ this.spinner.text = text;
1366
+ }
1367
+ }
1368
+ /**
1369
+ * Stop spinner with success message
1370
+ */
1371
+ succeed(text) {
1372
+ if (this.spinner) {
1373
+ this.spinner.succeed(text);
1374
+ this.spinner = null;
1375
+ }
1376
+ }
1377
+ /**
1378
+ * Stop spinner with failure message
1379
+ */
1380
+ fail(text) {
1381
+ if (this.spinner) {
1382
+ this.spinner.fail(text);
1383
+ this.spinner = null;
1384
+ }
1385
+ }
1386
+ /**
1387
+ * Stop spinner with warning message
1388
+ */
1389
+ warn(text) {
1390
+ if (this.spinner) {
1391
+ this.spinner.warn(text);
1392
+ this.spinner = null;
1393
+ }
1394
+ }
1395
+ /**
1396
+ * Stop spinner with info message
1397
+ */
1398
+ info(text) {
1399
+ if (this.spinner) {
1400
+ this.spinner.info(text);
1401
+ this.spinner = null;
1402
+ }
1403
+ }
1404
+ /**
1405
+ * Stop spinner without message
1406
+ */
1407
+ stop() {
1408
+ if (this.spinner) {
1409
+ this.spinner.stop();
1410
+ this.spinner = null;
1411
+ }
1412
+ }
1413
+ /**
1414
+ * Check if spinner is running
1415
+ */
1416
+ isRunning() {
1417
+ return this.spinner?.isSpinning ?? false;
1418
+ }
1419
+ };
1420
+ var spinner = new SpinnerManager();
1421
+ async function withSpinner(text, operation, options) {
1422
+ if (options?.silent) {
1423
+ return operation();
1424
+ }
1425
+ spinner.start(text);
1426
+ try {
1427
+ const result = await operation();
1428
+ spinner.succeed(options?.successText || text);
1429
+ return result;
1430
+ } catch (error) {
1431
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1432
+ spinner.fail(options?.failText || `${text} - ${pc.red(errorMessage)}`);
1433
+ throw error;
1434
+ }
1435
+ }
1436
+
1437
+ // src/lib/ci-cd/github-actions-generator.ts
1438
+ function getInstallCommand(packageManager) {
1439
+ switch (packageManager) {
1440
+ case "npm":
1441
+ return "npm ci";
1442
+ case "yarn":
1443
+ return "yarn install --frozen-lockfile";
1444
+ case "pnpm":
1445
+ return "pnpm install --frozen-lockfile";
1446
+ case "bun":
1447
+ return "bun install --frozen-lockfile";
1448
+ default:
1449
+ return "npm ci";
1450
+ }
1451
+ }
1452
+ function getRunCommand(packageManager, script) {
1453
+ switch (packageManager) {
1454
+ case "npm":
1455
+ return `npm run ${script}`;
1456
+ case "yarn":
1457
+ return `yarn ${script}`;
1458
+ case "pnpm":
1459
+ return `pnpm ${script}`;
1460
+ case "bun":
1461
+ return `bun run ${script}`;
1462
+ default:
1463
+ return `npm run ${script}`;
1464
+ }
1465
+ }
1466
+ function getCacheConfig(packageManager) {
1467
+ switch (packageManager) {
1468
+ case "npm":
1469
+ return {
1470
+ path: "~/.npm",
1471
+ key: "npm-${{ hashFiles('**/package-lock.json') }}"
1472
+ };
1473
+ case "yarn":
1474
+ return {
1475
+ path: ".yarn/cache",
1476
+ key: "yarn-${{ hashFiles('**/yarn.lock') }}"
1477
+ };
1478
+ case "pnpm":
1479
+ return {
1480
+ path: "~/.local/share/pnpm/store",
1481
+ key: "pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}"
1482
+ };
1483
+ case "bun":
1484
+ return {
1485
+ path: "~/.bun/install/cache",
1486
+ key: "bun-${{ hashFiles('**/bun.lockb') }}"
1487
+ };
1488
+ default:
1489
+ return {
1490
+ path: "~/.npm",
1491
+ key: "npm-${{ hashFiles('**/package-lock.json') }}"
1492
+ };
1493
+ }
1494
+ }
1495
+ function generateCIWorkflow(config) {
1496
+ const { packageManager, nodeVersion, enableCaching, runTests, runLint, runTypecheck, runBuild } = config;
1497
+ const cache = getCacheConfig(packageManager);
1498
+ const installCmd = getInstallCommand(packageManager);
1499
+ const steps = [];
1500
+ steps.push(` - name: Checkout
1501
+ uses: actions/checkout@v4`);
1502
+ if (packageManager === "pnpm") {
1503
+ steps.push(`
1504
+ - name: Setup pnpm
1505
+ uses: pnpm/action-setup@v4
1506
+ with:
1507
+ version: latest`);
1508
+ }
1509
+ if (packageManager === "bun") {
1510
+ steps.push(`
1511
+ - name: Setup Bun
1512
+ uses: oven-sh/setup-bun@v2
1513
+ with:
1514
+ bun-version: latest`);
1515
+ }
1516
+ if (packageManager !== "bun") {
1517
+ steps.push(`
1518
+ - name: Setup Node.js
1519
+ uses: actions/setup-node@v4
1520
+ with:
1521
+ node-version: '${nodeVersion}'${enableCaching ? `
1522
+ cache: '${packageManager}'` : ""}`);
1523
+ }
1524
+ if (enableCaching && packageManager === "bun") {
1525
+ steps.push(`
1526
+ - name: Cache dependencies
1527
+ uses: actions/cache@v4
1528
+ with:
1529
+ path: ${cache.path}
1530
+ key: ${cache.key}`);
1531
+ }
1532
+ steps.push(`
1533
+ - name: Install dependencies
1534
+ run: ${installCmd}`);
1535
+ if (runLint) {
1536
+ steps.push(`
1537
+ - name: Lint
1538
+ run: ${getRunCommand(packageManager, "lint")}`);
1539
+ }
1540
+ if (runTypecheck) {
1541
+ steps.push(`
1542
+ - name: Type check
1543
+ run: ${getRunCommand(packageManager, "typecheck")}`);
1544
+ }
1545
+ if (runTests) {
1546
+ steps.push(`
1547
+ - name: Run tests
1548
+ run: ${getRunCommand(packageManager, "test")}`);
1549
+ }
1550
+ if (runBuild) {
1551
+ steps.push(`
1552
+ - name: Build
1553
+ run: ${getRunCommand(packageManager, "build")}`);
1554
+ }
1555
+ return `name: CI
1556
+
1557
+ on:
1558
+ push:
1559
+ branches: [main, master]
1560
+ pull_request:
1561
+ branches: [main, master]
1562
+
1563
+ jobs:
1564
+ ci:
1565
+ runs-on: ubuntu-latest
1566
+
1567
+ steps:
1568
+ ${steps.join("\n")}
1569
+ `;
1570
+ }
1571
+ function generateReleaseWorkflow(config) {
1572
+ const { packageManager, nodeVersion } = config;
1573
+ const installCmd = getInstallCommand(packageManager);
1574
+ let setupSteps = "";
1575
+ if (packageManager === "pnpm") {
1576
+ setupSteps = `
1577
+ - name: Setup pnpm
1578
+ uses: pnpm/action-setup@v4
1579
+ with:
1580
+ version: latest
1581
+ `;
1582
+ }
1583
+ return `name: Release
1584
+
1585
+ on:
1586
+ push:
1587
+ tags:
1588
+ - 'v*'
1589
+
1590
+ jobs:
1591
+ release:
1592
+ runs-on: ubuntu-latest
1593
+ permissions:
1594
+ contents: write
1595
+
1596
+ steps:
1597
+ - name: Checkout
1598
+ uses: actions/checkout@v4
1599
+ with:
1600
+ fetch-depth: 0
1601
+ ${setupSteps}
1602
+ - name: Setup Node.js
1603
+ uses: actions/setup-node@v4
1604
+ with:
1605
+ node-version: '${nodeVersion}'
1606
+ cache: '${packageManager}'
1607
+ registry-url: 'https://registry.npmjs.org'
1608
+
1609
+ - name: Install dependencies
1610
+ run: ${installCmd}
1611
+
1612
+ - name: Build
1613
+ run: ${getRunCommand(packageManager, "build")}
1614
+
1615
+ - name: Create GitHub Release
1616
+ uses: softprops/action-gh-release@v1
1617
+ with:
1618
+ generate_release_notes: true
1619
+ `;
1620
+ }
1621
+ async function installCICD(projectPath, config, options) {
1622
+ const result = {
1623
+ created: [],
1624
+ skipped: [],
1625
+ errors: []
1626
+ };
1627
+ if (!config.enabled) {
1628
+ return result;
1629
+ }
1630
+ const workflowsDir = joinPath(projectPath, ".github", "workflows");
1631
+ try {
1632
+ await ensureDir(workflowsDir);
1633
+ if (config.ci) {
1634
+ const ciPath = joinPath(workflowsDir, "ci.yml");
1635
+ if (!await pathExists(ciPath) || options?.overwrite) {
1636
+ const content = generateCIWorkflow(config);
1637
+ await writeFile(ciPath, content);
1638
+ result.created.push("ci.yml");
1639
+ } else {
1640
+ result.skipped.push("ci.yml");
1641
+ }
1642
+ }
1643
+ if (config.cd) {
1644
+ const releasePath = joinPath(workflowsDir, "release.yml");
1645
+ if (!await pathExists(releasePath) || options?.overwrite) {
1646
+ const content = generateReleaseWorkflow(config);
1647
+ await writeFile(releasePath, content);
1648
+ result.created.push("release.yml");
1649
+ } else {
1650
+ result.skipped.push("release.yml");
1651
+ }
1652
+ }
1653
+ } catch (error) {
1654
+ result.errors.push(error instanceof Error ? error.message : String(error));
1655
+ }
1656
+ return result;
1657
+ }
1658
+ async function installCICDWithSpinner(projectPath, config, options) {
1659
+ return withSpinner(
1660
+ "Installing GitHub Actions workflows...",
1661
+ () => installCICD(projectPath, config, options),
1662
+ {
1663
+ successText: "Installed GitHub Actions workflows"
1664
+ }
1665
+ );
1666
+ }
1667
+
1330
1668
  // src/lib/code-style/index.ts
1331
1669
  init_esm_shims();
1332
1670
 
1333
1671
  // src/lib/code-style/installer.ts
1334
1672
  init_esm_shims();
1335
- import path2 from "path";
1673
+ import path3 from "path";
1336
1674
  import fse from "fs-extra";
1337
1675
 
1338
1676
  // src/constants/code-style-defaults.ts
@@ -1703,107 +2041,7 @@ var colors = {
1703
2041
  underline: chalk.underline
1704
2042
  };
1705
2043
 
1706
- // src/lib/utils/spinner.ts
1707
- init_esm_shims();
1708
- import ora from "ora";
1709
- import pc from "picocolors";
1710
- var SpinnerManager = class {
1711
- spinner = null;
1712
- silent = false;
1713
- configure(options) {
1714
- if (options.silent !== void 0) this.silent = options.silent;
1715
- }
1716
- /**
1717
- * Start a spinner with a message
1718
- */
1719
- start(text, options) {
1720
- if (this.silent) return null;
1721
- this.stop();
1722
- this.spinner = ora({
1723
- text,
1724
- color: options?.color || "cyan",
1725
- spinner: "dots"
1726
- }).start();
1727
- return this.spinner;
1728
- }
1729
- /**
1730
- * Update spinner text
1731
- */
1732
- text(text) {
1733
- if (this.spinner) {
1734
- this.spinner.text = text;
1735
- }
1736
- }
1737
- /**
1738
- * Stop spinner with success message
1739
- */
1740
- succeed(text) {
1741
- if (this.spinner) {
1742
- this.spinner.succeed(text);
1743
- this.spinner = null;
1744
- }
1745
- }
1746
- /**
1747
- * Stop spinner with failure message
1748
- */
1749
- fail(text) {
1750
- if (this.spinner) {
1751
- this.spinner.fail(text);
1752
- this.spinner = null;
1753
- }
1754
- }
1755
- /**
1756
- * Stop spinner with warning message
1757
- */
1758
- warn(text) {
1759
- if (this.spinner) {
1760
- this.spinner.warn(text);
1761
- this.spinner = null;
1762
- }
1763
- }
1764
- /**
1765
- * Stop spinner with info message
1766
- */
1767
- info(text) {
1768
- if (this.spinner) {
1769
- this.spinner.info(text);
1770
- this.spinner = null;
1771
- }
1772
- }
1773
- /**
1774
- * Stop spinner without message
1775
- */
1776
- stop() {
1777
- if (this.spinner) {
1778
- this.spinner.stop();
1779
- this.spinner = null;
1780
- }
1781
- }
1782
- /**
1783
- * Check if spinner is running
1784
- */
1785
- isRunning() {
1786
- return this.spinner?.isSpinning ?? false;
1787
- }
1788
- };
1789
- var spinner = new SpinnerManager();
1790
- async function withSpinner(text, operation, options) {
1791
- if (options?.silent) {
1792
- return operation();
1793
- }
1794
- spinner.start(text);
1795
- try {
1796
- const result = await operation();
1797
- spinner.succeed(options?.successText || text);
1798
- return result;
1799
- } catch (error) {
1800
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
1801
- spinner.fail(options?.failText || `${text} - ${pc.red(errorMessage)}`);
1802
- throw error;
1803
- }
1804
- }
1805
-
1806
- // src/lib/code-style/generator.ts
2044
+ // src/lib/code-style/generator.ts
1807
2045
  init_esm_shims();
1808
2046
  function generateEditorConfig(options) {
1809
2047
  const indent = options.indentStyle === "tab" ? "indent_style = tab" : "indent_style = space";
@@ -1983,7 +2221,7 @@ async function installCodeStyle(targetPath, config, options) {
1983
2221
  spinner.start("Installing code style configurations...");
1984
2222
  if (config.editorconfig) {
1985
2223
  const filename = CODE_STYLE_FILES.editorconfig;
1986
- const destPath = path2.join(targetPath, filename);
2224
+ const destPath = path3.join(targetPath, filename);
1987
2225
  try {
1988
2226
  if (await fse.pathExists(destPath) && !options?.overwrite) {
1989
2227
  result.skipped.push(filename);
@@ -2000,7 +2238,7 @@ async function installCodeStyle(targetPath, config, options) {
2000
2238
  }
2001
2239
  if (config.biome) {
2002
2240
  const filename = CODE_STYLE_FILES.biome;
2003
- const destPath = path2.join(targetPath, filename);
2241
+ const destPath = path3.join(targetPath, filename);
2004
2242
  try {
2005
2243
  if (await fse.pathExists(destPath) && !options?.overwrite) {
2006
2244
  result.skipped.push(filename);
@@ -2017,7 +2255,7 @@ async function installCodeStyle(targetPath, config, options) {
2017
2255
  }
2018
2256
  if (config.prettier) {
2019
2257
  const filename = CODE_STYLE_FILES.prettier;
2020
- const destPath = path2.join(targetPath, filename);
2258
+ const destPath = path3.join(targetPath, filename);
2021
2259
  try {
2022
2260
  if (await fse.pathExists(destPath) && !options?.overwrite) {
2023
2261
  result.skipped.push(filename);
@@ -2027,7 +2265,7 @@ async function installCodeStyle(targetPath, config, options) {
2027
2265
  await fse.writeFile(destPath, content, "utf-8");
2028
2266
  result.installed.push(filename);
2029
2267
  }
2030
- const ignoreDest = path2.join(targetPath, PRETTIER_IGNORE);
2268
+ const ignoreDest = path3.join(targetPath, PRETTIER_IGNORE);
2031
2269
  if (!await fse.pathExists(ignoreDest) || options?.overwrite) {
2032
2270
  const ignoreContent = generatePrettierIgnore();
2033
2271
  await fse.writeFile(ignoreDest, ignoreContent, "utf-8");
@@ -2040,7 +2278,7 @@ async function installCodeStyle(targetPath, config, options) {
2040
2278
  }
2041
2279
  if (config.commitlint) {
2042
2280
  const filename = CODE_STYLE_FILES.commitlint;
2043
- const destPath = path2.join(targetPath, filename);
2281
+ const destPath = path3.join(targetPath, filename);
2044
2282
  try {
2045
2283
  if (await fse.pathExists(destPath) && !options?.overwrite) {
2046
2284
  result.skipped.push(filename);
@@ -2118,6 +2356,214 @@ function showCodeStyleInstructions(config) {
2118
2356
  }
2119
2357
  }
2120
2358
 
2359
+ // src/lib/code-style/vscode-installer.ts
2360
+ init_esm_shims();
2361
+ init_fs();
2362
+ function generateVSCodeSettings(config) {
2363
+ const settings = {};
2364
+ if (config.biome) {
2365
+ settings["editor.defaultFormatter"] = "biomejs.biome";
2366
+ settings["editor.formatOnSave"] = true;
2367
+ settings["[javascript]"] = {
2368
+ "editor.defaultFormatter": "biomejs.biome"
2369
+ };
2370
+ settings["[javascriptreact]"] = {
2371
+ "editor.defaultFormatter": "biomejs.biome"
2372
+ };
2373
+ settings["[typescript]"] = {
2374
+ "editor.defaultFormatter": "biomejs.biome"
2375
+ };
2376
+ settings["[typescriptreact]"] = {
2377
+ "editor.defaultFormatter": "biomejs.biome"
2378
+ };
2379
+ settings["[json]"] = {
2380
+ "editor.defaultFormatter": "biomejs.biome"
2381
+ };
2382
+ settings["[jsonc]"] = {
2383
+ "editor.defaultFormatter": "biomejs.biome"
2384
+ };
2385
+ settings["eslint.enable"] = false;
2386
+ settings["biome.enabled"] = true;
2387
+ settings["biome.lintOnSave"] = true;
2388
+ }
2389
+ if (config.prettier && !config.biome) {
2390
+ settings["editor.defaultFormatter"] = "esbenp.prettier-vscode";
2391
+ settings["editor.formatOnSave"] = true;
2392
+ settings["[javascript]"] = {
2393
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
2394
+ };
2395
+ settings["[javascriptreact]"] = {
2396
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
2397
+ };
2398
+ settings["[typescript]"] = {
2399
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
2400
+ };
2401
+ settings["[typescriptreact]"] = {
2402
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
2403
+ };
2404
+ settings["[json]"] = {
2405
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
2406
+ };
2407
+ settings["[markdown]"] = {
2408
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
2409
+ };
2410
+ }
2411
+ if (config.editorconfig) {
2412
+ settings["editor.detectIndentation"] = false;
2413
+ }
2414
+ return settings;
2415
+ }
2416
+ function generateVSCodeExtensions(config) {
2417
+ const recommendations = [];
2418
+ if (config.biome) {
2419
+ recommendations.push("biomejs.biome");
2420
+ }
2421
+ if (config.prettier && !config.biome) {
2422
+ recommendations.push("esbenp.prettier-vscode");
2423
+ }
2424
+ if (config.editorconfig) {
2425
+ recommendations.push("EditorConfig.EditorConfig");
2426
+ }
2427
+ recommendations.push("dbaeumer.vscode-eslint");
2428
+ return { recommendations };
2429
+ }
2430
+ async function installVSCodeSettings(projectPath, config, options) {
2431
+ if (!config.enabled) {
2432
+ return {
2433
+ created: false,
2434
+ updated: false,
2435
+ skipped: true,
2436
+ path: ""
2437
+ };
2438
+ }
2439
+ const vscodeDir = joinPath(projectPath, ".vscode");
2440
+ const settingsPath = joinPath(vscodeDir, "settings.json");
2441
+ try {
2442
+ const newSettings = generateVSCodeSettings(config);
2443
+ const exists = await pathExists(settingsPath);
2444
+ if (exists) {
2445
+ if (options?.merge) {
2446
+ const existingContent = await readFile(settingsPath);
2447
+ let existingSettings = {};
2448
+ try {
2449
+ existingSettings = JSON.parse(existingContent);
2450
+ } catch {
2451
+ logger.warn("Could not parse existing settings.json, will overwrite");
2452
+ }
2453
+ const mergedSettings = { ...existingSettings, ...newSettings };
2454
+ await writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2));
2455
+ return {
2456
+ created: false,
2457
+ updated: true,
2458
+ skipped: false,
2459
+ path: settingsPath
2460
+ };
2461
+ }
2462
+ if (!options?.overwrite) {
2463
+ return {
2464
+ created: false,
2465
+ updated: false,
2466
+ skipped: true,
2467
+ path: settingsPath
2468
+ };
2469
+ }
2470
+ }
2471
+ await ensureDir(vscodeDir);
2472
+ await writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
2473
+ return {
2474
+ created: !exists,
2475
+ updated: exists,
2476
+ skipped: false,
2477
+ path: settingsPath
2478
+ };
2479
+ } catch (error) {
2480
+ return {
2481
+ created: false,
2482
+ updated: false,
2483
+ skipped: false,
2484
+ path: settingsPath,
2485
+ error: error instanceof Error ? error.message : String(error)
2486
+ };
2487
+ }
2488
+ }
2489
+ async function installVSCodeExtensions(projectPath, config, options) {
2490
+ if (!config.enabled) {
2491
+ return {
2492
+ created: false,
2493
+ updated: false,
2494
+ skipped: true,
2495
+ path: ""
2496
+ };
2497
+ }
2498
+ const vscodeDir = joinPath(projectPath, ".vscode");
2499
+ const extensionsPath = joinPath(vscodeDir, "extensions.json");
2500
+ try {
2501
+ const newExtensions = generateVSCodeExtensions(config);
2502
+ const exists = await pathExists(extensionsPath);
2503
+ if (exists) {
2504
+ if (options?.merge) {
2505
+ const existingContent = await readFile(extensionsPath);
2506
+ let existingExtensions = { recommendations: [] };
2507
+ try {
2508
+ existingExtensions = JSON.parse(existingContent);
2509
+ } catch {
2510
+ logger.warn("Could not parse existing extensions.json, will overwrite");
2511
+ }
2512
+ const mergedRecommendations = [
2513
+ .../* @__PURE__ */ new Set([
2514
+ ...existingExtensions.recommendations || [],
2515
+ ...newExtensions.recommendations
2516
+ ])
2517
+ ];
2518
+ await writeFile(
2519
+ extensionsPath,
2520
+ JSON.stringify({ recommendations: mergedRecommendations }, null, 2)
2521
+ );
2522
+ return {
2523
+ created: false,
2524
+ updated: true,
2525
+ skipped: false,
2526
+ path: extensionsPath
2527
+ };
2528
+ }
2529
+ if (!options?.overwrite) {
2530
+ return {
2531
+ created: false,
2532
+ updated: false,
2533
+ skipped: true,
2534
+ path: extensionsPath
2535
+ };
2536
+ }
2537
+ }
2538
+ await ensureDir(vscodeDir);
2539
+ await writeFile(extensionsPath, JSON.stringify(newExtensions, null, 2));
2540
+ return {
2541
+ created: !exists,
2542
+ updated: exists,
2543
+ skipped: false,
2544
+ path: extensionsPath
2545
+ };
2546
+ } catch (error) {
2547
+ return {
2548
+ created: false,
2549
+ updated: false,
2550
+ skipped: false,
2551
+ path: extensionsPath,
2552
+ error: error instanceof Error ? error.message : String(error)
2553
+ };
2554
+ }
2555
+ }
2556
+ async function installVSCodeConfig(projectPath, config, options) {
2557
+ const [settingsResult, extensionsResult] = await Promise.all([
2558
+ installVSCodeSettings(projectPath, config, options),
2559
+ installVSCodeExtensions(projectPath, config, options)
2560
+ ]);
2561
+ return {
2562
+ settings: settingsResult,
2563
+ extensions: extensionsResult
2564
+ };
2565
+ }
2566
+
2121
2567
  // src/lib/config/index.ts
2122
2568
  init_esm_shims();
2123
2569
 
@@ -2782,6 +3228,171 @@ function formatManualInstallInstructions(report) {
2782
3228
  return lines;
2783
3229
  }
2784
3230
 
3231
+ // src/lib/git-hooks/index.ts
3232
+ init_esm_shims();
3233
+
3234
+ // src/lib/git-hooks/husky-installer.ts
3235
+ init_esm_shims();
3236
+ init_fs();
3237
+ function generateCommitMsgHook() {
3238
+ return `#!/usr/bin/env sh
3239
+ . "$(dirname -- "$0")/_/husky.sh"
3240
+
3241
+ npx --no -- commitlint --edit "\${1}"
3242
+ `;
3243
+ }
3244
+ function generatePreCommitHook(lintCommand) {
3245
+ const command = lintCommand || "pnpm lint-staged";
3246
+ return `#!/usr/bin/env sh
3247
+ . "$(dirname -- "$0")/_/husky.sh"
3248
+
3249
+ ${command}
3250
+ `;
3251
+ }
3252
+ function generatePrePushHook(testCommand) {
3253
+ const command = testCommand || "pnpm test";
3254
+ return `#!/usr/bin/env sh
3255
+ . "$(dirname -- "$0")/_/husky.sh"
3256
+
3257
+ ${command}
3258
+ `;
3259
+ }
3260
+ function generateHuskyScript() {
3261
+ return `#!/usr/bin/env sh
3262
+ if [ -z "$husky_skip_init" ]; then
3263
+ debug () {
3264
+ if [ "$HUSKY_DEBUG" = "1" ]; then
3265
+ echo "husky (debug) - $1"
3266
+ fi
3267
+ }
3268
+
3269
+ readonly hook_name="$(basename -- "$0")"
3270
+ debug "starting $hook_name..."
3271
+
3272
+ if [ "$HUSKY" = "0" ]; then
3273
+ debug "HUSKY env variable is set to 0, skipping hook"
3274
+ exit 0
3275
+ fi
3276
+
3277
+ if [ -f ~/.huskyrc ]; then
3278
+ debug "sourcing ~/.huskyrc"
3279
+ . ~/.huskyrc
3280
+ fi
3281
+
3282
+ readonly husky_skip_init=1
3283
+ export husky_skip_init
3284
+ sh -e "$0" "$@"
3285
+ exitCode="$?"
3286
+
3287
+ if [ $exitCode != 0 ]; then
3288
+ echo "husky - $hook_name hook exited with code $exitCode (error)"
3289
+ fi
3290
+
3291
+ if [ $exitCode = 127 ]; then
3292
+ echo "husky - command not found in PATH=$PATH"
3293
+ fi
3294
+
3295
+ exit $exitCode
3296
+ fi
3297
+ `;
3298
+ }
3299
+ function generateHuskyGitignore() {
3300
+ return `_
3301
+ `;
3302
+ }
3303
+ async function installHusky(projectPath, config, options) {
3304
+ const result = {
3305
+ created: [],
3306
+ skipped: [],
3307
+ errors: [],
3308
+ initialized: false
3309
+ };
3310
+ const huskyDir = joinPath(projectPath, ".husky");
3311
+ const huskyInternalDir = joinPath(huskyDir, "_");
3312
+ try {
3313
+ await ensureDir(huskyDir);
3314
+ await ensureDir(huskyInternalDir);
3315
+ result.initialized = true;
3316
+ const huskyScriptPath = joinPath(huskyInternalDir, "husky.sh");
3317
+ if (!await pathExists(huskyScriptPath) || options?.overwrite) {
3318
+ await writeFile(huskyScriptPath, generateHuskyScript());
3319
+ await makeExecutable(huskyScriptPath);
3320
+ result.created.push("_/husky.sh");
3321
+ } else {
3322
+ result.skipped.push("_/husky.sh");
3323
+ }
3324
+ const gitignorePath = joinPath(huskyInternalDir, ".gitignore");
3325
+ if (!await pathExists(gitignorePath) || options?.overwrite) {
3326
+ await writeFile(gitignorePath, generateHuskyGitignore());
3327
+ result.created.push("_/.gitignore");
3328
+ }
3329
+ if (config.commitlint) {
3330
+ const commitMsgPath = joinPath(huskyDir, "commit-msg");
3331
+ if (!await pathExists(commitMsgPath) || options?.overwrite) {
3332
+ await writeFile(commitMsgPath, generateCommitMsgHook());
3333
+ await makeExecutable(commitMsgPath);
3334
+ result.created.push("commit-msg");
3335
+ } else {
3336
+ result.skipped.push("commit-msg");
3337
+ }
3338
+ }
3339
+ if (config.preCommit) {
3340
+ const preCommitPath = joinPath(huskyDir, "pre-commit");
3341
+ if (!await pathExists(preCommitPath) || options?.overwrite) {
3342
+ await writeFile(preCommitPath, generatePreCommitHook(config.lintCommand));
3343
+ await makeExecutable(preCommitPath);
3344
+ result.created.push("pre-commit");
3345
+ } else {
3346
+ result.skipped.push("pre-commit");
3347
+ }
3348
+ }
3349
+ if (config.prePush) {
3350
+ const prePushPath = joinPath(huskyDir, "pre-push");
3351
+ if (!await pathExists(prePushPath) || options?.overwrite) {
3352
+ await writeFile(prePushPath, generatePrePushHook(config.testCommand));
3353
+ await makeExecutable(prePushPath);
3354
+ result.created.push("pre-push");
3355
+ } else {
3356
+ result.skipped.push("pre-push");
3357
+ }
3358
+ }
3359
+ } catch (error) {
3360
+ result.errors.push(error instanceof Error ? error.message : String(error));
3361
+ }
3362
+ return result;
3363
+ }
3364
+ async function installHuskyWithSpinner(projectPath, config, options) {
3365
+ return withSpinner(
3366
+ "Installing Husky hooks...",
3367
+ () => installHusky(projectPath, config, options),
3368
+ {
3369
+ successText: "Installed Husky hooks"
3370
+ }
3371
+ );
3372
+ }
3373
+ function deriveHuskyConfigFromCodeStyle(codeStyle) {
3374
+ if (!codeStyle.enabled) {
3375
+ return null;
3376
+ }
3377
+ const commitlintEnabled = codeStyle.commitlint && codeStyle.commitlintOptions?.huskyIntegration;
3378
+ if (!commitlintEnabled) {
3379
+ return null;
3380
+ }
3381
+ let lintCommand;
3382
+ if (codeStyle.biome) {
3383
+ lintCommand = "pnpm biome check --staged";
3384
+ } else if (codeStyle.prettier) {
3385
+ lintCommand = "pnpm lint-staged";
3386
+ }
3387
+ return {
3388
+ commitlint: true,
3389
+ preCommit: !!lintCommand,
3390
+ lintCommand,
3391
+ prePush: false
3392
+ // Don't run tests on push by default
3393
+ };
3394
+ }
3395
+
2785
3396
  // src/lib/hooks/index.ts
2786
3397
  init_esm_shims();
2787
3398
 
@@ -2985,44 +3596,72 @@ import * as os2 from "os";
2985
3596
  init_esm_shims();
2986
3597
  var MCP_SERVERS = [
2987
3598
  // ============================================
2988
- // DOCUMENTATION
3599
+ // DOCUMENTATION & AI TOOLS
2989
3600
  // ============================================
2990
3601
  {
2991
3602
  id: "context7",
2992
3603
  name: "Context7",
2993
- description: "Documentation lookup for libraries and frameworks",
2994
- package: "@anthropic/context7-mcp",
3604
+ description: "Up-to-date documentation lookup for libraries and frameworks",
3605
+ package: "@upstash/context7-mcp",
2995
3606
  category: "documentation",
2996
- requiresConfig: false
3607
+ requiresConfig: false,
3608
+ installInstructions: "API keys provide higher rate limits. Get one at https://context7.com/dashboard"
3609
+ },
3610
+ {
3611
+ id: "perplexity",
3612
+ name: "Perplexity",
3613
+ description: "Web search without leaving the MCP ecosystem via Sonar API",
3614
+ package: "@chatmcp/server-perplexity-ask",
3615
+ category: "search",
3616
+ requiresConfig: true,
3617
+ configFields: [
3618
+ {
3619
+ name: "apiKey",
3620
+ type: "string",
3621
+ required: true,
3622
+ description: "Perplexity/Sonar API Key",
3623
+ envVar: "PERPLEXITY_API_KEY"
3624
+ }
3625
+ ],
3626
+ installInstructions: "Get API key at https://www.perplexity.ai/settings/api"
3627
+ },
3628
+ {
3629
+ id: "sequential-thinking",
3630
+ name: "Sequential Thinking",
3631
+ description: "Dynamic problem-solving through structured thinking process",
3632
+ package: "@modelcontextprotocol/server-sequential-thinking",
3633
+ category: "ai",
3634
+ requiresConfig: false,
3635
+ installInstructions: "Helps break down complex problems into manageable steps."
2997
3636
  },
2998
3637
  // ============================================
2999
3638
  // TESTING & BROWSER AUTOMATION
3000
3639
  // ============================================
3001
3640
  {
3002
- id: "chrome-devtools",
3003
- name: "Chrome DevTools",
3004
- description: "Browser automation, debugging, and performance profiling via Chrome DevTools Protocol",
3005
- package: "@anthropic/chrome-devtools-mcp",
3641
+ id: "puppeteer",
3642
+ name: "Puppeteer",
3643
+ description: "Headless Chrome automation for testing and scraping",
3644
+ package: "@modelcontextprotocol/server-puppeteer",
3006
3645
  category: "testing",
3007
- requiresConfig: false,
3008
- installInstructions: "Requires Chrome/Chromium browser installed on the system."
3646
+ requiresConfig: false
3009
3647
  },
3010
3648
  {
3011
3649
  id: "playwright",
3012
3650
  name: "Playwright",
3013
- description: "Cross-browser end-to-end testing and automation",
3014
- package: "@anthropic/playwright-mcp",
3651
+ description: "Browser automation via accessibility snapshots, not screenshots",
3652
+ package: "@playwright/mcp",
3015
3653
  category: "testing",
3016
3654
  requiresConfig: false,
3017
- installInstructions: "Run `npx playwright install` after setup to install browsers."
3655
+ installInstructions: "Requires Node.js 18+. Supports Chrome, Firefox, WebKit."
3018
3656
  },
3019
3657
  {
3020
- id: "puppeteer",
3021
- name: "Puppeteer",
3022
- description: "Headless Chrome automation for testing and scraping",
3023
- package: "@anthropic/puppeteer-mcp",
3658
+ id: "chrome-devtools",
3659
+ name: "Chrome DevTools",
3660
+ description: "Control and inspect live Chrome browser with DevTools Protocol",
3661
+ package: "chrome-devtools-mcp",
3024
3662
  category: "testing",
3025
- requiresConfig: false
3663
+ requiresConfig: false,
3664
+ installInstructions: "Requires Chrome installed. For Claude Code: claude mcp add chrome-devtools npx chrome-devtools-mcp@latest"
3026
3665
  },
3027
3666
  // ============================================
3028
3667
  // VERSION CONTROL
@@ -3030,7 +3669,7 @@ var MCP_SERVERS = [
3030
3669
  {
3031
3670
  id: "github",
3032
3671
  name: "GitHub",
3033
- description: "GitHub API integration (issues, PRs, repos)",
3672
+ description: "GitHub API integration (issues, PRs, repos, file operations)",
3034
3673
  package: "@modelcontextprotocol/server-github",
3035
3674
  category: "version-control",
3036
3675
  requiresConfig: true,
@@ -3043,32 +3682,33 @@ var MCP_SERVERS = [
3043
3682
  envVar: "GITHUB_TOKEN"
3044
3683
  }
3045
3684
  ],
3046
- installInstructions: "Create a Personal Access Token at https://github.com/settings/tokens with repo, issues, and pull_request scopes."
3685
+ installInstructions: "Create a Personal Access Token at https://github.com/settings/tokens with repo scope."
3047
3686
  },
3048
3687
  {
3049
3688
  id: "gitlab",
3050
3689
  name: "GitLab",
3051
- description: "GitLab API integration (issues, MRs, repos)",
3052
- package: "@anthropic/gitlab-mcp",
3690
+ description: "GitLab API for project management, issues, and merge requests",
3691
+ package: "@modelcontextprotocol/server-gitlab",
3053
3692
  category: "version-control",
3054
3693
  requiresConfig: true,
3055
3694
  configFields: [
3056
3695
  {
3057
3696
  name: "token",
3058
3697
  type: "string",
3059
- required: false,
3698
+ required: true,
3060
3699
  description: "GitLab Personal Access Token",
3061
- envVar: "GITLAB_TOKEN"
3700
+ envVar: "GITLAB_PERSONAL_ACCESS_TOKEN"
3062
3701
  },
3063
3702
  {
3064
- name: "baseUrl",
3703
+ name: "apiUrl",
3065
3704
  type: "string",
3066
3705
  required: false,
3067
- description: "GitLab instance URL (default: https://gitlab.com)",
3068
- default: "https://gitlab.com"
3706
+ description: "GitLab API URL (default: https://gitlab.com/api/v4)",
3707
+ envVar: "GITLAB_API_URL",
3708
+ default: "https://gitlab.com/api/v4"
3069
3709
  }
3070
3710
  ],
3071
- installInstructions: "Create a Personal Access Token at GitLab Settings > Access Tokens with api scope."
3711
+ installInstructions: "Create PAT at GitLab User Settings > Access Tokens with api, read_repository, write_repository scopes."
3072
3712
  },
3073
3713
  // ============================================
3074
3714
  // DATABASES
@@ -3076,7 +3716,7 @@ var MCP_SERVERS = [
3076
3716
  {
3077
3717
  id: "postgres",
3078
3718
  name: "PostgreSQL",
3079
- description: "Direct PostgreSQL database access",
3719
+ description: "Read-only PostgreSQL database access with schema inspection",
3080
3720
  package: "@modelcontextprotocol/server-postgres",
3081
3721
  category: "database",
3082
3722
  requiresConfig: true,
@@ -3092,63 +3732,45 @@ var MCP_SERVERS = [
3092
3732
  installInstructions: "Connection string format: postgresql://user:password@host:port/database"
3093
3733
  },
3094
3734
  {
3095
- id: "neon",
3096
- name: "Neon",
3097
- description: "Neon serverless PostgreSQL",
3098
- package: "@neondatabase/mcp-server-neon",
3099
- category: "database",
3100
- requiresConfig: true,
3101
- configFields: [
3102
- {
3103
- name: "apiKey",
3104
- type: "string",
3105
- required: false,
3106
- description: "Neon API Key",
3107
- envVar: "NEON_API_KEY"
3108
- }
3109
- ],
3110
- installInstructions: "Get your API key from https://console.neon.tech/app/settings/api-keys"
3111
- },
3112
- {
3113
- id: "mongodb",
3114
- name: "MongoDB",
3115
- description: "MongoDB document database access",
3116
- package: "@anthropic/mongodb-mcp",
3735
+ id: "mysql",
3736
+ name: "MySQL",
3737
+ description: "Read-only MySQL/MariaDB database access",
3738
+ package: "@modelcontextprotocol/server-mysql",
3117
3739
  category: "database",
3118
3740
  requiresConfig: true,
3119
3741
  configFields: [
3120
3742
  {
3121
3743
  name: "connectionString",
3122
3744
  type: "string",
3123
- required: false,
3124
- description: "MongoDB connection string",
3125
- envVar: "MONGODB_URI"
3745
+ required: true,
3746
+ description: "MySQL connection URL",
3747
+ envVar: "MYSQL_URL"
3126
3748
  }
3127
3749
  ],
3128
- installInstructions: "Connection string format: mongodb://user:password@host:port/database or mongodb+srv://..."
3750
+ installInstructions: "Connection format: mysql://user:password@host:port/database"
3129
3751
  },
3130
3752
  {
3131
- id: "mysql",
3132
- name: "MySQL",
3133
- description: "MySQL/MariaDB database access",
3134
- package: "@anthropic/mysql-mcp",
3753
+ id: "neon",
3754
+ name: "Neon",
3755
+ description: "Neon serverless PostgreSQL with branch management",
3756
+ package: "@neondatabase/mcp-server-neon",
3135
3757
  category: "database",
3136
3758
  requiresConfig: true,
3137
3759
  configFields: [
3138
3760
  {
3139
- name: "connectionString",
3761
+ name: "apiKey",
3140
3762
  type: "string",
3141
3763
  required: false,
3142
- description: "MySQL connection string",
3143
- envVar: "MYSQL_URL"
3764
+ description: "Neon API Key",
3765
+ envVar: "NEON_API_KEY"
3144
3766
  }
3145
3767
  ],
3146
- installInstructions: "Connection string format: mysql://user:password@host:port/database"
3768
+ installInstructions: "Get your API key from https://console.neon.tech/app/settings/api-keys"
3147
3769
  },
3148
3770
  {
3149
3771
  id: "sqlite",
3150
3772
  name: "SQLite",
3151
- description: "SQLite local database access",
3773
+ description: "SQLite database interaction and business intelligence",
3152
3774
  package: "@modelcontextprotocol/server-sqlite",
3153
3775
  category: "database",
3154
3776
  requiresConfig: true,
@@ -3165,27 +3787,20 @@ var MCP_SERVERS = [
3165
3787
  {
3166
3788
  id: "supabase",
3167
3789
  name: "Supabase",
3168
- description: "Supabase backend-as-a-service (DB, Auth, Storage)",
3169
- package: "@supabase/mcp-server",
3790
+ description: "Supabase projects, database, Edge Functions, and type generation",
3791
+ package: "@supabase/mcp-server-supabase",
3170
3792
  category: "database",
3171
3793
  requiresConfig: true,
3172
3794
  configFields: [
3173
3795
  {
3174
- name: "url",
3175
- type: "string",
3176
- required: false,
3177
- description: "Supabase project URL",
3178
- envVar: "SUPABASE_URL"
3179
- },
3180
- {
3181
- name: "anonKey",
3796
+ name: "accessToken",
3182
3797
  type: "string",
3183
3798
  required: false,
3184
- description: "Supabase anon/public key",
3185
- envVar: "SUPABASE_ANON_KEY"
3799
+ description: "Supabase Personal Access Token",
3800
+ envVar: "SUPABASE_ACCESS_TOKEN"
3186
3801
  }
3187
3802
  ],
3188
- installInstructions: "Find your project URL and anon key in Supabase Dashboard > Settings > API"
3803
+ installInstructions: "Get your access token from https://supabase.com/dashboard/account/tokens"
3189
3804
  },
3190
3805
  // ============================================
3191
3806
  // CACHE & KEY-VALUE STORES
@@ -3193,8 +3808,8 @@ var MCP_SERVERS = [
3193
3808
  {
3194
3809
  id: "redis",
3195
3810
  name: "Redis",
3196
- description: "Redis cache and key-value store",
3197
- package: "@anthropic/redis-mcp",
3811
+ description: "Redis key-value store operations (set, get, delete, list)",
3812
+ package: "@modelcontextprotocol/server-redis",
3198
3813
  category: "cache",
3199
3814
  requiresConfig: true,
3200
3815
  configFields: [
@@ -3207,29 +3822,29 @@ var MCP_SERVERS = [
3207
3822
  default: "redis://localhost:6379"
3208
3823
  }
3209
3824
  ],
3210
- installInstructions: "Connection URL format: redis://[[user]:password@]host[:port][/db]"
3825
+ installInstructions: "Pass Redis URL as argument: npx @modelcontextprotocol/server-redis redis://localhost:6379"
3211
3826
  },
3212
3827
  {
3213
3828
  id: "upstash",
3214
3829
  name: "Upstash",
3215
- description: "Upstash serverless Redis and Kafka",
3830
+ description: "Upstash Redis database management and commands",
3216
3831
  package: "@upstash/mcp-server",
3217
3832
  category: "cache",
3218
3833
  requiresConfig: true,
3219
3834
  configFields: [
3220
3835
  {
3221
- name: "url",
3836
+ name: "email",
3222
3837
  type: "string",
3223
3838
  required: false,
3224
- description: "Upstash Redis REST URL",
3225
- envVar: "UPSTASH_REDIS_REST_URL"
3839
+ description: "Upstash account email",
3840
+ envVar: "UPSTASH_EMAIL"
3226
3841
  },
3227
3842
  {
3228
- name: "token",
3843
+ name: "apiKey",
3229
3844
  type: "string",
3230
3845
  required: false,
3231
- description: "Upstash Redis REST token",
3232
- envVar: "UPSTASH_REDIS_REST_TOKEN"
3846
+ description: "Upstash API Key",
3847
+ envVar: "UPSTASH_API_KEY"
3233
3848
  }
3234
3849
  ],
3235
3850
  installInstructions: "Get credentials from https://console.upstash.com"
@@ -3237,57 +3852,14 @@ var MCP_SERVERS = [
3237
3852
  // ============================================
3238
3853
  // DEPLOYMENT & INFRASTRUCTURE
3239
3854
  // ============================================
3240
- {
3241
- id: "vercel",
3242
- name: "Vercel",
3243
- description: "Vercel deployment and project management",
3244
- package: "@vercel/mcp",
3245
- category: "deployment",
3246
- requiresConfig: true,
3247
- configFields: [
3248
- {
3249
- name: "token",
3250
- type: "string",
3251
- required: false,
3252
- description: "Vercel Access Token",
3253
- envVar: "VERCEL_TOKEN"
3254
- }
3255
- ],
3256
- installInstructions: "Create an access token at https://vercel.com/account/tokens"
3257
- },
3258
- {
3259
- id: "netlify",
3260
- name: "Netlify",
3261
- description: "Netlify deployment and site management",
3262
- package: "@anthropic/netlify-mcp",
3263
- category: "deployment",
3264
- requiresConfig: true,
3265
- configFields: [
3266
- {
3267
- name: "token",
3268
- type: "string",
3269
- required: false,
3270
- description: "Netlify Personal Access Token",
3271
- envVar: "NETLIFY_TOKEN"
3272
- }
3273
- ],
3274
- installInstructions: "Create a token at https://app.netlify.com/user/applications#personal-access-tokens"
3275
- },
3276
3855
  {
3277
3856
  id: "cloudflare",
3278
3857
  name: "Cloudflare",
3279
- description: "Cloudflare Workers, Pages, and DNS management",
3280
- package: "@cloudflare/mcp-server",
3858
+ description: "Cloudflare Workers, D1, KV, R2, and DNS management",
3859
+ package: "@cloudflare/mcp-server-cloudflare",
3281
3860
  category: "deployment",
3282
3861
  requiresConfig: true,
3283
3862
  configFields: [
3284
- {
3285
- name: "apiToken",
3286
- type: "string",
3287
- required: false,
3288
- description: "Cloudflare API Token",
3289
- envVar: "CLOUDFLARE_API_TOKEN"
3290
- },
3291
3863
  {
3292
3864
  name: "accountId",
3293
3865
  type: "string",
@@ -3296,437 +3868,246 @@ var MCP_SERVERS = [
3296
3868
  envVar: "CLOUDFLARE_ACCOUNT_ID"
3297
3869
  }
3298
3870
  ],
3299
- installInstructions: "Create an API token at https://dash.cloudflare.com/profile/api-tokens"
3300
- },
3301
- {
3302
- id: "aws",
3303
- name: "AWS",
3304
- description: "Amazon Web Services integration",
3305
- package: "@anthropic/aws-mcp",
3306
- category: "infrastructure",
3307
- requiresConfig: true,
3308
- configFields: [
3309
- {
3310
- name: "accessKeyId",
3311
- type: "string",
3312
- required: false,
3313
- description: "AWS Access Key ID",
3314
- envVar: "AWS_ACCESS_KEY_ID"
3315
- },
3316
- {
3317
- name: "secretAccessKey",
3318
- type: "string",
3319
- required: false,
3320
- description: "AWS Secret Access Key",
3321
- envVar: "AWS_SECRET_ACCESS_KEY"
3322
- },
3323
- {
3324
- name: "region",
3325
- type: "string",
3326
- required: false,
3327
- description: "AWS Region",
3328
- envVar: "AWS_REGION",
3329
- default: "us-east-1"
3330
- }
3331
- ],
3332
- installInstructions: "Create credentials in AWS IAM Console with appropriate permissions."
3871
+ installInstructions: "Run: npx @cloudflare/mcp-server-cloudflare init"
3333
3872
  },
3334
3873
  {
3335
- id: "docker",
3336
- name: "Docker",
3337
- description: "Docker container management",
3338
- package: "@anthropic/docker-mcp",
3339
- category: "infrastructure",
3340
- requiresConfig: false,
3341
- installInstructions: "Requires Docker Desktop or Docker Engine installed."
3342
- },
3343
- {
3344
- id: "kubernetes",
3345
- name: "Kubernetes",
3346
- description: "Kubernetes cluster management",
3347
- package: "@anthropic/kubernetes-mcp",
3348
- category: "infrastructure",
3874
+ id: "vercel",
3875
+ name: "Vercel",
3876
+ description: "Vercel deployments, DNS records, and project management",
3877
+ package: "vercel-mcp",
3878
+ category: "deployment",
3349
3879
  requiresConfig: true,
3350
3880
  configFields: [
3351
3881
  {
3352
- name: "kubeconfig",
3353
- type: "string",
3354
- required: false,
3355
- description: "Path to kubeconfig file",
3356
- default: "~/.kube/config"
3357
- },
3358
- {
3359
- name: "context",
3882
+ name: "apiKey",
3360
3883
  type: "string",
3361
- required: false,
3362
- description: "Kubernetes context to use"
3884
+ required: true,
3885
+ description: "Vercel API Key",
3886
+ envVar: "VERCEL_API_KEY"
3363
3887
  }
3364
3888
  ],
3365
- installInstructions: "Requires kubectl installed and configured."
3889
+ installInstructions: "Get API key at https://vercel.com/account/tokens"
3366
3890
  },
3367
3891
  {
3368
3892
  id: "filesystem",
3369
3893
  name: "Filesystem",
3370
- description: "Enhanced filesystem operations",
3894
+ description: "Secure file operations with configurable access controls",
3371
3895
  package: "@modelcontextprotocol/server-filesystem",
3372
3896
  category: "infrastructure",
3373
3897
  requiresConfig: false
3374
3898
  },
3375
- // ============================================
3376
- // PROJECT MANAGEMENT
3377
- // ============================================
3378
- {
3379
- id: "linear",
3380
- name: "Linear",
3381
- description: "Linear project management integration",
3382
- package: "@anthropic/linear-mcp",
3383
- category: "project-mgmt",
3384
- requiresConfig: true,
3385
- configFields: [
3386
- {
3387
- name: "apiKey",
3388
- type: "string",
3389
- required: false,
3390
- description: "Linear API Key",
3391
- envVar: "LINEAR_API_KEY"
3392
- }
3393
- ],
3394
- installInstructions: "Create an API key at https://linear.app/settings/api"
3395
- },
3396
3899
  {
3397
- id: "jira",
3398
- name: "Jira",
3399
- description: "Atlassian Jira issue tracking",
3400
- package: "@anthropic/jira-mcp",
3401
- category: "project-mgmt",
3402
- requiresConfig: true,
3403
- configFields: [
3404
- {
3405
- name: "host",
3406
- type: "string",
3407
- required: false,
3408
- description: "Jira instance URL",
3409
- envVar: "JIRA_HOST"
3410
- },
3411
- {
3412
- name: "email",
3413
- type: "string",
3414
- required: false,
3415
- description: "Jira account email",
3416
- envVar: "JIRA_EMAIL"
3417
- },
3418
- {
3419
- name: "token",
3420
- type: "string",
3421
- required: false,
3422
- description: "Jira API Token",
3423
- envVar: "JIRA_TOKEN"
3424
- }
3425
- ],
3426
- installInstructions: "Create an API token at https://id.atlassian.com/manage-profile/security/api-tokens"
3900
+ id: "memory",
3901
+ name: "Memory",
3902
+ description: "Knowledge graph-based persistent memory system",
3903
+ package: "@modelcontextprotocol/server-memory",
3904
+ category: "infrastructure",
3905
+ requiresConfig: false,
3906
+ installInstructions: "Memory is stored in memory.jsonl in the server directory by default."
3427
3907
  },
3908
+ // ============================================
3909
+ // PROJECT MANAGEMENT & PRODUCTIVITY
3910
+ // ============================================
3428
3911
  {
3429
3912
  id: "notion",
3430
3913
  name: "Notion",
3431
- description: "Notion workspace, pages, and databases",
3432
- package: "@anthropic/notion-mcp",
3914
+ description: "Official Notion API for pages, databases, and workspace",
3915
+ package: "@notionhq/notion-mcp-server",
3433
3916
  category: "project-mgmt",
3434
3917
  requiresConfig: true,
3435
3918
  configFields: [
3436
3919
  {
3437
3920
  name: "token",
3438
3921
  type: "string",
3439
- required: false,
3922
+ required: true,
3440
3923
  description: "Notion Integration Token",
3441
3924
  envVar: "NOTION_TOKEN"
3442
3925
  }
3443
3926
  ],
3444
- installInstructions: "Create an integration at https://www.notion.so/my-integrations and share pages with it."
3927
+ installInstructions: "Create integration at https://www.notion.so/profile/integrations and share pages with it."
3445
3928
  },
3446
3929
  {
3447
- id: "asana",
3448
- name: "Asana",
3449
- description: "Asana project and task management",
3450
- package: "@anthropic/asana-mcp",
3930
+ id: "obsidian",
3931
+ name: "Obsidian",
3932
+ description: "Read and search Obsidian vaults and Markdown directories",
3933
+ package: "mcp-obsidian",
3451
3934
  category: "project-mgmt",
3452
3935
  requiresConfig: true,
3453
3936
  configFields: [
3454
3937
  {
3455
- name: "token",
3456
- type: "string",
3457
- required: false,
3458
- description: "Asana Personal Access Token",
3459
- envVar: "ASANA_TOKEN"
3460
- }
3461
- ],
3462
- installInstructions: "Create a PAT at https://app.asana.com/0/my-apps"
3463
- },
3464
- // ============================================
3465
- // MONITORING & OBSERVABILITY
3466
- // ============================================
3467
- {
3468
- id: "sentry",
3469
- name: "Sentry",
3470
- description: "Error monitoring and tracking",
3471
- package: "@sentry/mcp-server",
3472
- category: "monitoring",
3473
- requiresConfig: true,
3474
- configFields: [
3475
- {
3476
- name: "authToken",
3477
- type: "string",
3478
- required: false,
3479
- description: "Sentry Auth Token",
3480
- envVar: "SENTRY_AUTH_TOKEN"
3481
- },
3482
- {
3483
- name: "org",
3484
- type: "string",
3485
- required: false,
3486
- description: "Sentry Organization slug"
3487
- }
3488
- ],
3489
- installInstructions: "Create an auth token at https://sentry.io/settings/account/api/auth-tokens/"
3490
- },
3491
- {
3492
- id: "datadog",
3493
- name: "Datadog",
3494
- description: "Datadog monitoring and APM",
3495
- package: "@anthropic/datadog-mcp",
3496
- category: "monitoring",
3497
- requiresConfig: true,
3498
- configFields: [
3499
- {
3500
- name: "apiKey",
3938
+ name: "vaultPath",
3501
3939
  type: "string",
3502
- required: false,
3503
- description: "Datadog API Key",
3504
- envVar: "DD_API_KEY"
3505
- },
3506
- {
3507
- name: "appKey",
3508
- type: "string",
3509
- required: false,
3510
- description: "Datadog Application Key",
3511
- envVar: "DD_APP_KEY"
3512
- },
3513
- {
3514
- name: "site",
3515
- type: "string",
3516
- required: false,
3517
- description: "Datadog site (e.g., datadoghq.com)",
3518
- default: "datadoghq.com"
3940
+ required: true,
3941
+ description: "Path to Obsidian vault"
3519
3942
  }
3520
3943
  ],
3521
- installInstructions: "Find keys at https://app.datadoghq.com/organization-settings/api-keys"
3944
+ installInstructions: "Works with any Markdown directory. Point to your vault path."
3522
3945
  },
3523
- // ============================================
3524
- // COMMUNICATION
3525
- // ============================================
3526
3946
  {
3527
- id: "slack",
3528
- name: "Slack",
3529
- description: "Slack messaging and channel management",
3530
- package: "@anthropic/slack-mcp",
3531
- category: "communication",
3532
- requiresConfig: true,
3533
- configFields: [
3534
- {
3535
- name: "token",
3536
- type: "string",
3537
- required: false,
3538
- description: "Slack Bot Token (xoxb-...)",
3539
- envVar: "SLACK_BOT_TOKEN"
3540
- }
3541
- ],
3542
- installInstructions: "Create a Slack app at https://api.slack.com/apps and install to your workspace."
3543
- },
3544
- {
3545
- id: "discord",
3546
- name: "Discord",
3547
- description: "Discord bot and server management",
3548
- package: "@anthropic/discord-mcp",
3549
- category: "communication",
3550
- requiresConfig: true,
3551
- configFields: [
3552
- {
3553
- name: "token",
3554
- type: "string",
3555
- required: false,
3556
- description: "Discord Bot Token",
3557
- envVar: "DISCORD_BOT_TOKEN"
3558
- }
3559
- ],
3560
- installInstructions: "Create a bot at https://discord.com/developers/applications"
3947
+ id: "n8n",
3948
+ name: "n8n",
3949
+ description: "n8n workflow automation node documentation and management",
3950
+ package: "n8n-mcp",
3951
+ category: "project-mgmt",
3952
+ requiresConfig: false,
3953
+ installInstructions: "Provides access to 543 n8n nodes documentation."
3561
3954
  },
3562
3955
  // ============================================
3563
- // DESIGN
3956
+ // MONITORING & OBSERVABILITY
3564
3957
  // ============================================
3565
3958
  {
3566
- id: "figma",
3567
- name: "Figma",
3568
- description: "Figma design file access and inspection",
3569
- package: "@anthropic/figma-mcp",
3570
- category: "design",
3959
+ id: "sentry",
3960
+ name: "Sentry",
3961
+ description: "Query Sentry errors, issues, and project information",
3962
+ package: "@sentry/mcp-server",
3963
+ category: "monitoring",
3571
3964
  requiresConfig: true,
3572
3965
  configFields: [
3573
3966
  {
3574
- name: "token",
3967
+ name: "authToken",
3575
3968
  type: "string",
3576
3969
  required: false,
3577
- description: "Figma Personal Access Token",
3578
- envVar: "FIGMA_TOKEN"
3970
+ description: "Sentry Auth Token",
3971
+ envVar: "SENTRY_AUTH_TOKEN"
3579
3972
  }
3580
3973
  ],
3581
- installInstructions: "Create a token at https://www.figma.com/developers/api#access-tokens"
3974
+ installInstructions: "For Claude Code: claude mcp add --transport http sentry https://mcp.sentry.dev/mcp"
3582
3975
  },
3583
3976
  // ============================================
3584
- // PAYMENTS
3977
+ // COMMUNICATION
3585
3978
  // ============================================
3586
3979
  {
3587
- id: "stripe",
3588
- name: "Stripe",
3589
- description: "Stripe payments API integration",
3590
- package: "@stripe/mcp-server",
3591
- category: "payments",
3980
+ id: "slack",
3981
+ name: "Slack",
3982
+ description: "Slack messaging, channels, and workspace interaction",
3983
+ package: "@modelcontextprotocol/server-slack",
3984
+ category: "communication",
3592
3985
  requiresConfig: true,
3593
3986
  configFields: [
3594
3987
  {
3595
- name: "secretKey",
3988
+ name: "token",
3596
3989
  type: "string",
3597
3990
  required: false,
3598
- description: "Stripe Secret Key",
3599
- envVar: "STRIPE_SECRET_KEY"
3991
+ description: "Slack Bot Token (xoxb-...)",
3992
+ envVar: "SLACK_BOT_TOKEN"
3600
3993
  }
3601
3994
  ],
3602
- installInstructions: "Find your API keys at https://dashboard.stripe.com/apikeys (use test keys for development)"
3995
+ installInstructions: "Create a Slack app at https://api.slack.com/apps and install to your workspace."
3603
3996
  },
3604
3997
  // ============================================
3605
- // SEARCH
3998
+ // DESIGN
3606
3999
  // ============================================
3607
4000
  {
3608
- id: "algolia",
3609
- name: "Algolia",
3610
- description: "Algolia search and discovery",
3611
- package: "@anthropic/algolia-mcp",
3612
- category: "search",
4001
+ id: "figma",
4002
+ name: "Figma",
4003
+ description: "Figma layout information for AI coding agents",
4004
+ package: "figma-developer-mcp",
4005
+ category: "design",
3613
4006
  requiresConfig: true,
3614
4007
  configFields: [
3615
- {
3616
- name: "appId",
3617
- type: "string",
3618
- required: false,
3619
- description: "Algolia Application ID",
3620
- envVar: "ALGOLIA_APP_ID"
3621
- },
3622
4008
  {
3623
4009
  name: "apiKey",
3624
4010
  type: "string",
3625
- required: false,
3626
- description: "Algolia Admin API Key",
3627
- envVar: "ALGOLIA_API_KEY"
4011
+ required: true,
4012
+ description: "Figma Personal Access Token",
4013
+ envVar: "FIGMA_API_KEY"
3628
4014
  }
3629
4015
  ],
3630
- installInstructions: "Find credentials at https://www.algolia.com/account/api-keys/"
4016
+ installInstructions: "Create token at https://www.figma.com/developers/api#access-tokens"
3631
4017
  },
3632
4018
  {
3633
- id: "elasticsearch",
3634
- name: "Elasticsearch",
3635
- description: "Elasticsearch search engine",
3636
- package: "@anthropic/elasticsearch-mcp",
3637
- category: "search",
4019
+ id: "shadcn",
4020
+ name: "shadcn/ui",
4021
+ description: "shadcn/ui component docs, installation, and code generation",
4022
+ package: "@heilgar/shadcn-ui-mcp-server",
4023
+ category: "ui-library",
4024
+ requiresConfig: false,
4025
+ installInstructions: "Supports npm, pnpm, yarn, and bun package managers."
4026
+ },
4027
+ {
4028
+ id: "magic-ui",
4029
+ name: "21st.dev Magic",
4030
+ description: "AI-driven UI component generation through natural language",
4031
+ package: "@21st-dev/magic",
4032
+ category: "ui-library",
3638
4033
  requiresConfig: true,
3639
4034
  configFields: [
3640
- {
3641
- name: "node",
3642
- type: "string",
3643
- required: false,
3644
- description: "Elasticsearch node URL",
3645
- envVar: "ELASTICSEARCH_NODE",
3646
- default: "http://localhost:9200"
3647
- },
3648
4035
  {
3649
4036
  name: "apiKey",
3650
4037
  type: "string",
3651
- required: false,
3652
- description: "Elasticsearch API Key (optional)",
3653
- envVar: "ELASTICSEARCH_API_KEY"
4038
+ required: true,
4039
+ description: "21st.dev Magic API Key",
4040
+ envVar: "TWENTYFIRST_API_KEY"
3654
4041
  }
3655
- ]
4042
+ ],
4043
+ installInstructions: "Get API key at https://21st.dev/magic/console"
3656
4044
  },
3657
4045
  // ============================================
3658
- // AI & ML
4046
+ // PAYMENTS
3659
4047
  // ============================================
3660
4048
  {
3661
- id: "openai",
3662
- name: "OpenAI",
3663
- description: "OpenAI API for GPT and embeddings",
3664
- package: "@anthropic/openai-mcp",
3665
- category: "ai",
4049
+ id: "stripe",
4050
+ name: "Stripe",
4051
+ description: "Stripe payments API with MCP support via Agent Toolkit",
4052
+ package: "@stripe/agent-toolkit",
4053
+ category: "payments",
3666
4054
  requiresConfig: true,
3667
4055
  configFields: [
3668
4056
  {
3669
- name: "apiKey",
4057
+ name: "secretKey",
3670
4058
  type: "string",
3671
4059
  required: false,
3672
- description: "OpenAI API Key",
3673
- envVar: "OPENAI_API_KEY"
4060
+ description: "Stripe Secret Key",
4061
+ envVar: "STRIPE_SECRET_KEY"
3674
4062
  }
3675
4063
  ],
3676
- installInstructions: "Get your API key at https://platform.openai.com/api-keys"
4064
+ installInstructions: "Get API keys at https://dashboard.stripe.com/apikeys. Run: npx -y @stripe/mcp --tools=all --api-key=YOUR_KEY"
3677
4065
  },
3678
4066
  {
3679
- id: "pinecone",
3680
- name: "Pinecone",
3681
- description: "Pinecone vector database for embeddings",
3682
- package: "@anthropic/pinecone-mcp",
3683
- category: "ai",
4067
+ id: "mercadopago",
4068
+ name: "Mercado Pago",
4069
+ description: "Mercado Pago payments, refunds, and customer management",
4070
+ package: "mercado-pago-mcp",
4071
+ category: "payments",
3684
4072
  requiresConfig: true,
3685
4073
  configFields: [
3686
4074
  {
3687
- name: "apiKey",
4075
+ name: "accessToken",
3688
4076
  type: "string",
3689
- required: false,
3690
- description: "Pinecone API Key",
3691
- envVar: "PINECONE_API_KEY"
4077
+ required: true,
4078
+ description: "Mercado Pago Access Token",
4079
+ envVar: "MERCADOPAGO_ACCESS_TOKEN"
3692
4080
  },
3693
4081
  {
3694
4082
  name: "environment",
3695
4083
  type: "string",
3696
4084
  required: false,
3697
- description: "Pinecone environment",
3698
- envVar: "PINECONE_ENVIRONMENT"
4085
+ description: "Environment (sandbox or production)",
4086
+ default: "sandbox"
3699
4087
  }
3700
4088
  ],
3701
- installInstructions: "Get credentials at https://app.pinecone.io/"
4089
+ installInstructions: "Get credentials at https://www.mercadopago.com/developers/panel/app"
3702
4090
  },
3703
4091
  // ============================================
3704
- // SECURITY
4092
+ // SEARCH
3705
4093
  // ============================================
3706
4094
  {
3707
- id: "vault",
3708
- name: "HashiCorp Vault",
3709
- description: "Secrets management with HashiCorp Vault",
3710
- package: "@anthropic/vault-mcp",
3711
- category: "security",
4095
+ id: "brave-search",
4096
+ name: "Brave Search",
4097
+ description: "Web and local search using Brave Search API",
4098
+ package: "@modelcontextprotocol/server-brave-search",
4099
+ category: "search",
3712
4100
  requiresConfig: true,
3713
4101
  configFields: [
3714
4102
  {
3715
- name: "address",
3716
- type: "string",
3717
- required: false,
3718
- description: "Vault server address",
3719
- envVar: "VAULT_ADDR",
3720
- default: "http://127.0.0.1:8200"
3721
- },
3722
- {
3723
- name: "token",
4103
+ name: "apiKey",
3724
4104
  type: "string",
3725
4105
  required: false,
3726
- description: "Vault token",
3727
- envVar: "VAULT_TOKEN"
4106
+ description: "Brave Search API Key",
4107
+ envVar: "BRAVE_API_KEY"
3728
4108
  }
3729
- ]
4109
+ ],
4110
+ installInstructions: "Get an API key at https://brave.com/search/api/"
3730
4111
  }
3731
4112
  ];
3732
4113
  function getMcpServer(id) {
@@ -4064,6 +4445,29 @@ async function installModules(category, modules, options) {
4064
4445
  }
4065
4446
  return result;
4066
4447
  }
4448
+ async function installCategoryReadme(category, options) {
4449
+ const sourceReadme = joinPath(options.templatesPath, category, "README.md");
4450
+ const targetReadme = joinPath(options.targetPath, ".claude", category, "README.md");
4451
+ if (!await pathExists(sourceReadme)) {
4452
+ logger.debug(`No README found for category: ${category}`);
4453
+ return;
4454
+ }
4455
+ const targetExists = await pathExists(targetReadme);
4456
+ if (targetExists && !options.overwrite) {
4457
+ logger.debug(`README already exists for ${category}, skipping`);
4458
+ return;
4459
+ }
4460
+ if (options.dryRun) {
4461
+ logger.debug(`Would install README for ${category}`);
4462
+ return;
4463
+ }
4464
+ try {
4465
+ await copy(sourceReadme, targetReadme, { overwrite: options.overwrite });
4466
+ logger.debug(`Installed README for ${category}`);
4467
+ } catch (error) {
4468
+ logger.debug(`Failed to install README for ${category}: ${error}`);
4469
+ }
4470
+ }
4067
4471
  async function installAllModules(modulesByCategory, options) {
4068
4472
  const results = {};
4069
4473
  const categories = ["agents", "skills", "commands", "docs"];
@@ -4086,6 +4490,7 @@ async function installAllModules(modulesByCategory, options) {
4086
4490
  silent: options.dryRun
4087
4491
  }
4088
4492
  );
4493
+ await installCategoryReadme(category, options);
4089
4494
  }
4090
4495
  return results;
4091
4496
  }
@@ -4596,7 +5001,7 @@ async function updatePackageJson(projectPath, changes, options = {}) {
4596
5001
  return result;
4597
5002
  }
4598
5003
  }
4599
- function getInstallCommand(packageManager) {
5004
+ function getInstallCommand2(packageManager) {
4600
5005
  switch (packageManager) {
4601
5006
  case "npm":
4602
5007
  return "npm install";
@@ -5301,6 +5706,218 @@ function showReplacementReport(report) {
5301
5706
  }
5302
5707
  }
5303
5708
 
5709
+ // src/lib/scaffold/claude-md-generator.ts
5710
+ init_esm_shims();
5711
+ init_fs();
5712
+
5713
+ // src/lib/utils/paths.ts
5714
+ init_esm_shims();
5715
+ import fs3 from "fs";
5716
+ import path5 from "path";
5717
+ import { fileURLToPath as fileURLToPath2 } from "url";
5718
+ function getPackageRoot() {
5719
+ const currentFilePath = fileURLToPath2(import.meta.url);
5720
+ let currentDir = path5.dirname(currentFilePath);
5721
+ while (currentDir !== path5.dirname(currentDir)) {
5722
+ const packageJsonPath = path5.join(currentDir, "package.json");
5723
+ if (fs3.existsSync(packageJsonPath)) {
5724
+ return currentDir;
5725
+ }
5726
+ currentDir = path5.dirname(currentDir);
5727
+ }
5728
+ throw new Error("Could not find package root (no package.json found in parent directories)");
5729
+ }
5730
+ function getTemplatesPath() {
5731
+ return path5.join(getPackageRoot(), "templates");
5732
+ }
5733
+
5734
+ // src/lib/scaffold/claude-md-generator.ts
5735
+ async function generateClaudeMd(projectPath, projectInfo, options) {
5736
+ const claudeMdPath = joinPath(projectPath, "CLAUDE.md");
5737
+ const exists = await pathExists(claudeMdPath);
5738
+ if (exists && !options?.overwrite) {
5739
+ return {
5740
+ created: false,
5741
+ skipped: true,
5742
+ path: claudeMdPath
5743
+ };
5744
+ }
5745
+ try {
5746
+ let template;
5747
+ if (options?.customTemplate) {
5748
+ template = options.customTemplate;
5749
+ } else {
5750
+ const templatePath = joinPath(getTemplatesPath(), "CLAUDE.md.template");
5751
+ if (await pathExists(templatePath)) {
5752
+ template = await readFile(templatePath);
5753
+ } else {
5754
+ template = getMinimalTemplate();
5755
+ }
5756
+ }
5757
+ const content = processTemplate(template, projectInfo, options);
5758
+ await writeFile(claudeMdPath, content);
5759
+ return {
5760
+ created: true,
5761
+ skipped: false,
5762
+ path: claudeMdPath
5763
+ };
5764
+ } catch (error) {
5765
+ return {
5766
+ created: false,
5767
+ skipped: false,
5768
+ path: claudeMdPath,
5769
+ error: error instanceof Error ? error.message : String(error)
5770
+ };
5771
+ }
5772
+ }
5773
+ async function generateClaudeMdWithSpinner(projectPath, projectInfo, options) {
5774
+ return withSpinner(
5775
+ "Generating CLAUDE.md...",
5776
+ () => generateClaudeMd(projectPath, projectInfo, options),
5777
+ {
5778
+ successText: "Created CLAUDE.md"
5779
+ }
5780
+ );
5781
+ }
5782
+ function processTemplate(template, projectInfo, options) {
5783
+ let content = template;
5784
+ const techStack = options?.templateConfig?.techStack;
5785
+ const commands = options?.templateConfig?.commands;
5786
+ const targets = options?.templateConfig?.targets;
5787
+ const preferences = options?.claudeConfig?.preferences;
5788
+ content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectInfo.name).replace(/\{\{PROJECT_DESCRIPTION\}\}/g, projectInfo.description).replace(/\{\{ORG\}\}/g, projectInfo.org).replace(/\{\{REPO\}\}/g, projectInfo.repo).replace(/\{\{ENTITY_TYPE\}\}/g, projectInfo.entityType).replace(/\{\{ENTITY_TYPE_PLURAL\}\}/g, projectInfo.entityTypePlural).replace(/\{\{LOCATION\}\}/g, projectInfo.location || "");
5789
+ const packageManager = preferences?.packageManager || "pnpm";
5790
+ content = content.replace(/\{\{PACKAGE_MANAGER\}\}/g, packageManager);
5791
+ const coverageTarget = targets && "coverage" in targets ? String(targets.coverage) : "90";
5792
+ content = content.replace(/\{\{COVERAGE_TARGET\}\}/g, coverageTarget);
5793
+ if (projectInfo.domain) {
5794
+ content = content.replace(/\{\{#if DOMAIN\}\}/g, "").replace(/\{\{\/if\}\}/g, "").replace(/\{\{DOMAIN\}\}/g, projectInfo.domain);
5795
+ } else {
5796
+ content = content.replace(/\{\{#if DOMAIN\}\}[\s\S]*?\{\{\/if\}\}/g, "");
5797
+ }
5798
+ if (techStack && Object.keys(techStack).length > 0) {
5799
+ const techStackContent = generateTechStackSection(techStack);
5800
+ content = content.replace(/\{\{#if TECH_STACK\}\}/g, "").replace(/\{\{TECH_STACK\}\}\n\{\{else\}\}[\s\S]*?\{\{\/if\}\}/g, techStackContent);
5801
+ } else {
5802
+ content = content.replace(/\{\{#if TECH_STACK\}\}[\s\S]*?\{\{else\}\}/g, "").replace(/\{\{\/if\}\}/g, "");
5803
+ }
5804
+ if (commands && Object.keys(commands).length > 0) {
5805
+ const commandsContent = generateCommandsSection(commands, packageManager);
5806
+ content = content.replace(/\{\{#if COMMANDS\}\}/g, "").replace(/\{\{COMMANDS\}\}\n\{\{else\}\}[\s\S]*?\{\{\/if\}\}/g, commandsContent);
5807
+ } else {
5808
+ content = content.replace(/\{\{#if COMMANDS\}\}[\s\S]*?\{\{else\}\}/g, "").replace(/\{\{\/if\}\}/g, "");
5809
+ }
5810
+ content = content.replace(/\{\{#if PROJECT_STRUCTURE\}\}[\s\S]*?\{\{else\}\}/g, "").replace(/\{\{\/if\}\}/g, "");
5811
+ return content;
5812
+ }
5813
+ function generateTechStackSection(techStack) {
5814
+ const lines = [];
5815
+ if (techStack.frontendFramework && techStack.frontendFramework !== "None") {
5816
+ lines.push("**Frontend:**");
5817
+ lines.push(`- Framework: ${techStack.frontendFramework}`);
5818
+ if (techStack.stateManagement && techStack.stateManagement !== "None") {
5819
+ lines.push(`- State: ${techStack.stateManagement}`);
5820
+ }
5821
+ lines.push("");
5822
+ }
5823
+ if (techStack.apiFramework && techStack.apiFramework !== "None") {
5824
+ lines.push("**Backend:**");
5825
+ lines.push(`- API: ${techStack.apiFramework}`);
5826
+ if (techStack.validationLibrary && techStack.validationLibrary !== "None") {
5827
+ lines.push(`- Validation: ${techStack.validationLibrary}`);
5828
+ }
5829
+ lines.push("");
5830
+ }
5831
+ if (techStack.databaseOrm && techStack.databaseOrm !== "None") {
5832
+ lines.push("**Database:**");
5833
+ lines.push(`- ORM: ${techStack.databaseOrm}`);
5834
+ lines.push("");
5835
+ }
5836
+ if (techStack.authPattern && techStack.authPattern !== "None") {
5837
+ lines.push("**Authentication:**");
5838
+ lines.push(`- Provider: ${techStack.authPattern}`);
5839
+ lines.push("");
5840
+ }
5841
+ if (techStack.testFramework && techStack.testFramework !== "None") {
5842
+ lines.push("**Testing:**");
5843
+ lines.push(`- Framework: ${techStack.testFramework}`);
5844
+ lines.push("");
5845
+ }
5846
+ if (techStack.bundler && techStack.bundler !== "None") {
5847
+ lines.push("**Build:**");
5848
+ lines.push(`- Bundler: ${techStack.bundler}`);
5849
+ lines.push("");
5850
+ }
5851
+ return lines.join("\n");
5852
+ }
5853
+ function generateCommandsSection(commands, packageManager) {
5854
+ const lines = ["```bash"];
5855
+ lines.push("# Development");
5856
+ lines.push(`${packageManager} dev # Start development server`);
5857
+ lines.push("");
5858
+ lines.push("# Testing");
5859
+ if (commands.test) {
5860
+ lines.push(`${commands.test} # Run tests`);
5861
+ } else {
5862
+ lines.push(`${packageManager} test # Run tests`);
5863
+ }
5864
+ if (commands.coverage) {
5865
+ lines.push(`${commands.coverage} # Run tests with coverage`);
5866
+ } else {
5867
+ lines.push(`${packageManager} test:coverage # Run tests with coverage`);
5868
+ }
5869
+ lines.push("");
5870
+ lines.push("# Quality");
5871
+ if (commands.lint) {
5872
+ lines.push(`${commands.lint} # Run linter`);
5873
+ } else {
5874
+ lines.push(`${packageManager} lint # Run linter`);
5875
+ }
5876
+ if (commands.typecheck) {
5877
+ lines.push(`${commands.typecheck} # Type checking`);
5878
+ } else {
5879
+ lines.push(`${packageManager} typecheck # Type checking`);
5880
+ }
5881
+ if (commands.build) {
5882
+ lines.push("");
5883
+ lines.push("# Build");
5884
+ lines.push(`${commands.build} # Build for production`);
5885
+ }
5886
+ lines.push("```");
5887
+ return lines.join("\n");
5888
+ }
5889
+ function getMinimalTemplate() {
5890
+ return `# CLAUDE.md
5891
+
5892
+ ## Project Overview
5893
+
5894
+ **{{PROJECT_NAME}}** - {{PROJECT_DESCRIPTION}}
5895
+
5896
+ ## Repository
5897
+
5898
+ - **GitHub:** https://github.com/{{ORG}}/{{REPO}}
5899
+
5900
+ ## Quick Commands
5901
+
5902
+ \`\`\`bash
5903
+ {{PACKAGE_MANAGER}} dev # Start development
5904
+ {{PACKAGE_MANAGER}} test # Run tests
5905
+ {{PACKAGE_MANAGER}} lint # Run linter
5906
+ {{PACKAGE_MANAGER}} build # Build project
5907
+ \`\`\`
5908
+
5909
+ ## Claude Configuration
5910
+
5911
+ This project uses \`@qazuor/claude-code-config\` for AI-assisted development.
5912
+
5913
+ See \`.claude/docs/quick-start.md\` for getting started.
5914
+
5915
+ ---
5916
+
5917
+ *Generated by [@qazuor/claude-code-config](https://github.com/qazuor/claude-code-config)*
5918
+ `;
5919
+ }
5920
+
5304
5921
  // src/lib/scaffold/index.ts
5305
5922
  init_esm_shims();
5306
5923
 
@@ -5816,8 +6433,8 @@ async function generateScaffoldWithProgress(projectPath, options) {
5816
6433
 
5817
6434
  // src/lib/templates/config-replacer.ts
5818
6435
  init_esm_shims();
5819
- import * as fs3 from "fs/promises";
5820
- import * as path5 from "path";
6436
+ import * as fs4 from "fs/promises";
6437
+ import * as path6 from "path";
5821
6438
  import ora2 from "ora";
5822
6439
 
5823
6440
  // src/constants/template-placeholders.ts
@@ -6231,16 +6848,19 @@ var TEMPLATE_PLACEHOLDERS = [
6231
6848
  choices: [
6232
6849
  { name: "React", value: "React" },
6233
6850
  { name: "Next.js", value: "Next.js" },
6851
+ { name: "TanStack Start", value: "TanStack Start" },
6234
6852
  { name: "Vue", value: "Vue" },
6235
6853
  { name: "Nuxt", value: "Nuxt" },
6236
6854
  { name: "Svelte", value: "Svelte" },
6237
6855
  { name: "SvelteKit", value: "SvelteKit" },
6238
6856
  { name: "Astro", value: "Astro" },
6239
6857
  { name: "SolidJS", value: "SolidJS" },
6858
+ { name: "Remix", value: "Remix" },
6240
6859
  { name: "Angular", value: "Angular" },
6241
6860
  { name: "None", value: "None" }
6242
6861
  ],
6243
6862
  default: (ctx) => {
6863
+ if (hasDependency(ctx, "@tanstack/start")) return "TanStack Start";
6244
6864
  if (hasDependency(ctx, "next")) return "Next.js";
6245
6865
  if (hasDependency(ctx, "nuxt")) return "Nuxt";
6246
6866
  if (hasDependency(ctx, "vue")) return "Vue";
@@ -6248,6 +6868,7 @@ var TEMPLATE_PLACEHOLDERS = [
6248
6868
  if (hasDependency(ctx, "@sveltejs/kit")) return "SvelteKit";
6249
6869
  if (hasDependency(ctx, "astro")) return "Astro";
6250
6870
  if (hasDependency(ctx, "solid-js")) return "SolidJS";
6871
+ if (hasDependency(ctx, "@remix-run/react")) return "Remix";
6251
6872
  if (hasDependency(ctx, "@angular/core")) return "Angular";
6252
6873
  if (hasDependency(ctx, "react")) return "React";
6253
6874
  return "None";
@@ -6319,26 +6940,32 @@ var TEMPLATE_PLACEHOLDERS = [
6319
6940
  description: "Authentication approach",
6320
6941
  inputType: "select",
6321
6942
  choices: [
6943
+ { name: "Better Auth", value: "Better Auth" },
6322
6944
  { name: "Clerk", value: "Clerk" },
6323
6945
  { name: "Auth.js (NextAuth)", value: "Auth.js" },
6324
6946
  { name: "Lucia", value: "Lucia" },
6325
6947
  { name: "Firebase Auth", value: "Firebase" },
6326
6948
  { name: "Supabase Auth", value: "Supabase" },
6949
+ { name: "Kinde", value: "Kinde" },
6950
+ { name: "WorkOS", value: "WorkOS" },
6327
6951
  { name: "Custom JWT", value: "JWT" },
6328
6952
  { name: "Session-based", value: "Session" },
6329
6953
  { name: "None", value: "None" }
6330
6954
  ],
6331
6955
  default: (ctx) => {
6956
+ if (hasDependency(ctx, "better-auth")) return "Better Auth";
6332
6957
  if (hasDependency(ctx, "@clerk/nextjs") || hasDependency(ctx, "@clerk/clerk-react"))
6333
6958
  return "Clerk";
6334
6959
  if (hasDependency(ctx, "next-auth") || hasDependency(ctx, "@auth/core")) return "Auth.js";
6335
6960
  if (hasDependency(ctx, "lucia")) return "Lucia";
6336
6961
  if (hasDependency(ctx, "firebase")) return "Firebase";
6337
6962
  if (hasDependency(ctx, "@supabase/supabase-js")) return "Supabase";
6963
+ if (hasDependency(ctx, "@kinde-oss/kinde-auth-nextjs")) return "Kinde";
6964
+ if (hasDependency(ctx, "@workos-inc/authkit-nextjs")) return "WorkOS";
6338
6965
  return "None";
6339
6966
  },
6340
6967
  required: false,
6341
- example: "Clerk"
6968
+ example: "Better Auth"
6342
6969
  },
6343
6970
  {
6344
6971
  key: "STATE_MANAGEMENT",
@@ -6715,7 +7342,7 @@ function flattenTemplateConfig(config) {
6715
7342
  return flattened;
6716
7343
  }
6717
7344
  function shouldProcessFile(filePath) {
6718
- const ext = path5.extname(filePath).toLowerCase();
7345
+ const ext = path6.extname(filePath).toLowerCase();
6719
7346
  return PROCESSABLE_EXTENSIONS.includes(ext);
6720
7347
  }
6721
7348
  function shouldSkipDirectory(dirName) {
@@ -6724,9 +7351,9 @@ function shouldSkipDirectory(dirName) {
6724
7351
  async function getAllFiles(dir) {
6725
7352
  const files = [];
6726
7353
  try {
6727
- const entries = await fs3.readdir(dir, { withFileTypes: true });
7354
+ const entries = await fs4.readdir(dir, { withFileTypes: true });
6728
7355
  for (const entry of entries) {
6729
- const fullPath = path5.join(dir, entry.name);
7356
+ const fullPath = path6.join(dir, entry.name);
6730
7357
  if (entry.isDirectory()) {
6731
7358
  if (!shouldSkipDirectory(entry.name)) {
6732
7359
  const subFiles = await getAllFiles(fullPath);
@@ -6743,7 +7370,7 @@ async function getAllFiles(dir) {
6743
7370
  async function replaceInFile2(filePath, replacements) {
6744
7371
  const changes = [];
6745
7372
  try {
6746
- let content = await fs3.readFile(filePath, "utf-8");
7373
+ let content = await fs4.readFile(filePath, "utf-8");
6747
7374
  let modified = false;
6748
7375
  for (const [placeholder, value] of Object.entries(replacements)) {
6749
7376
  if (content.includes(placeholder)) {
@@ -6753,7 +7380,7 @@ async function replaceInFile2(filePath, replacements) {
6753
7380
  }
6754
7381
  }
6755
7382
  if (modified) {
6756
- await fs3.writeFile(filePath, content, "utf-8");
7383
+ await fs4.writeFile(filePath, content, "utf-8");
6757
7384
  }
6758
7385
  } catch {
6759
7386
  }
@@ -6774,7 +7401,7 @@ async function replaceTemplatePlaceholders(dir, config) {
6774
7401
  report.filesModified++;
6775
7402
  for (const change of changes) {
6776
7403
  report.replacements.push({
6777
- file: path5.relative(dir, file),
7404
+ file: path6.relative(dir, file),
6778
7405
  placeholder: change.placeholder,
6779
7406
  value: change.value
6780
7407
  });
@@ -6845,11 +7472,11 @@ async function previewReplacements(dir, config) {
6845
7472
  const preview = [];
6846
7473
  for (const file of files) {
6847
7474
  try {
6848
- const content = await fs3.readFile(file, "utf-8");
7475
+ const content = await fs4.readFile(file, "utf-8");
6849
7476
  for (const [placeholder, value] of Object.entries(replacements)) {
6850
7477
  if (content.includes(placeholder)) {
6851
7478
  preview.push({
6852
- file: path5.relative(dir, file),
7479
+ file: path6.relative(dir, file),
6853
7480
  placeholder,
6854
7481
  value
6855
7482
  });
@@ -6864,27 +7491,6 @@ async function previewReplacements(dir, config) {
6864
7491
  // src/cli/commands/init.ts
6865
7492
  init_fs();
6866
7493
 
6867
- // src/lib/utils/paths.ts
6868
- init_esm_shims();
6869
- import fs4 from "fs";
6870
- import path6 from "path";
6871
- import { fileURLToPath as fileURLToPath2 } from "url";
6872
- function getPackageRoot() {
6873
- const currentFilePath = fileURLToPath2(import.meta.url);
6874
- let currentDir = path6.dirname(currentFilePath);
6875
- while (currentDir !== path6.dirname(currentDir)) {
6876
- const packageJsonPath = path6.join(currentDir, "package.json");
6877
- if (fs4.existsSync(packageJsonPath)) {
6878
- return currentDir;
6879
- }
6880
- currentDir = path6.dirname(currentDir);
6881
- }
6882
- throw new Error("Could not find package root (no package.json found in parent directories)");
6883
- }
6884
- function getTemplatesPath() {
6885
- return path6.join(getPackageRoot(), "templates");
6886
- }
6887
-
6888
7494
  // src/lib/utils/prompt-cancel.ts
6889
7495
  init_esm_shims();
6890
7496
  import * as readline from "readline";
@@ -7362,6 +7968,124 @@ async function editBundleSelection(currentBundleIds) {
7362
7968
  return selected;
7363
7969
  }
7364
7970
 
7971
+ // src/cli/prompts/ci-cd-config.ts
7972
+ init_esm_shims();
7973
+ async function promptCICDConfig(options) {
7974
+ logger.section("CI/CD Configuration", "\u{1F680}");
7975
+ logger.info("Configure continuous integration and deployment workflows");
7976
+ logger.newline();
7977
+ const enableCICD = await confirm({
7978
+ message: "Would you like to set up GitHub Actions workflows?",
7979
+ default: true
7980
+ });
7981
+ if (!enableCICD) {
7982
+ return {
7983
+ enabled: false,
7984
+ provider: "github-actions",
7985
+ ci: false,
7986
+ cd: false,
7987
+ packageManager: options?.packageManager || "pnpm",
7988
+ nodeVersion: "22",
7989
+ enableCaching: true,
7990
+ runTests: true,
7991
+ runLint: true,
7992
+ runTypecheck: true,
7993
+ runBuild: true
7994
+ };
7995
+ }
7996
+ const workflows = await checkbox({
7997
+ message: "Which workflows would you like to create?",
7998
+ choices: [
7999
+ {
8000
+ name: "CI (Continuous Integration) - Lint, test, build on PRs",
8001
+ value: "ci",
8002
+ checked: true
8003
+ },
8004
+ {
8005
+ name: "Release - Create releases on version tags",
8006
+ value: "cd",
8007
+ checked: false
8008
+ }
8009
+ ]
8010
+ });
8011
+ if (workflows.length === 0) {
8012
+ return {
8013
+ enabled: false,
8014
+ provider: "github-actions",
8015
+ ci: false,
8016
+ cd: false,
8017
+ packageManager: options?.packageManager || "pnpm",
8018
+ nodeVersion: "22",
8019
+ enableCaching: true,
8020
+ runTests: true,
8021
+ runLint: true,
8022
+ runTypecheck: true,
8023
+ runBuild: true
8024
+ };
8025
+ }
8026
+ const hasCi = workflows.includes("ci");
8027
+ const hasCd = workflows.includes("cd");
8028
+ let runTests = true;
8029
+ let runLint = true;
8030
+ let runTypecheck = true;
8031
+ let runBuild = true;
8032
+ if (hasCi) {
8033
+ const ciSteps = await checkbox({
8034
+ message: "Which steps should the CI workflow run?",
8035
+ choices: [
8036
+ { name: "Lint", value: "lint", checked: true },
8037
+ { name: "Type checking", value: "typecheck", checked: true },
8038
+ { name: "Tests", value: "tests", checked: true },
8039
+ { name: "Build", value: "build", checked: true }
8040
+ ]
8041
+ });
8042
+ runTests = ciSteps.includes("tests");
8043
+ runLint = ciSteps.includes("lint");
8044
+ runTypecheck = ciSteps.includes("typecheck");
8045
+ runBuild = ciSteps.includes("build");
8046
+ }
8047
+ const nodeVersion = await select({
8048
+ message: "Node.js version:",
8049
+ choices: [
8050
+ { name: "22 (LTS - Recommended)", value: "22" },
8051
+ { name: "20 (LTS)", value: "20" },
8052
+ { name: "18 (LTS)", value: "18" },
8053
+ { name: "Custom", value: "custom" }
8054
+ ],
8055
+ default: "22"
8056
+ });
8057
+ let finalNodeVersion = nodeVersion;
8058
+ if (nodeVersion === "custom") {
8059
+ finalNodeVersion = await input({
8060
+ message: "Enter Node.js version:",
8061
+ default: "22",
8062
+ validate: (v) => {
8063
+ if (!/^\d+(\.\d+)?$/.test(v)) {
8064
+ return 'Please enter a valid version (e.g., "20" or "20.10")';
8065
+ }
8066
+ return true;
8067
+ }
8068
+ });
8069
+ }
8070
+ const enableCaching = await confirm({
8071
+ message: "Enable dependency caching for faster builds?",
8072
+ default: true
8073
+ });
8074
+ return {
8075
+ enabled: true,
8076
+ provider: "github-actions",
8077
+ ci: hasCi,
8078
+ cd: hasCd,
8079
+ packageManager: options?.packageManager || "pnpm",
8080
+ nodeVersion: finalNodeVersion,
8081
+ enableCaching,
8082
+ runTests,
8083
+ runLint,
8084
+ runTypecheck,
8085
+ runBuild
8086
+ };
8087
+ }
8088
+
7365
8089
  // src/cli/prompts/folder-preferences.ts
7366
8090
  init_esm_shims();
7367
8091
 
@@ -7976,18 +8700,38 @@ async function promptProjectInfo(options) {
7976
8700
  return true;
7977
8701
  }
7978
8702
  });
7979
- const entityType = await input({
7980
- message: "Primary entity type (e.g., product, article, user):",
7981
- default: options?.defaults?.entityType || "item",
7982
- validate: (value) => {
7983
- if (!value.trim()) return "Entity type is required";
7984
- return true;
7985
- }
8703
+ const author = await input({
8704
+ message: 'Author (name or "Name <email>"):',
8705
+ default: options?.defaults?.author || ""
7986
8706
  });
7987
- const entityTypePlural = await input({
7988
- message: "Entity type plural:",
7989
- default: options?.defaults?.entityTypePlural || pluralize(entityType)
8707
+ let entityType = "item";
8708
+ let entityTypePlural = "items";
8709
+ const wantEntityConfig = await confirm({
8710
+ message: "Configure primary entity type? (Used for code examples and templates)",
8711
+ default: false
7990
8712
  });
8713
+ if (wantEntityConfig) {
8714
+ logger.info("The entity type is used in code examples and templates throughout the project.");
8715
+ logger.info(
8716
+ 'For example, if your project manages "products", code examples will use product-related names.'
8717
+ );
8718
+ logger.newline();
8719
+ entityType = await input({
8720
+ message: "Primary entity type (e.g., product, article, user, listing):",
8721
+ default: options?.defaults?.entityType || "item",
8722
+ validate: (value) => {
8723
+ if (!value.trim()) return "Entity type is required";
8724
+ return true;
8725
+ }
8726
+ });
8727
+ entityTypePlural = await input({
8728
+ message: "Entity type plural:",
8729
+ default: options?.defaults?.entityTypePlural || pluralize(entityType)
8730
+ });
8731
+ } else if (options?.defaults?.entityType) {
8732
+ entityType = options.defaults.entityType;
8733
+ entityTypePlural = options.defaults.entityTypePlural || pluralize(entityType);
8734
+ }
7991
8735
  let domain;
7992
8736
  let location;
7993
8737
  if (!options?.skipOptional) {
@@ -8026,7 +8770,8 @@ async function promptProjectInfo(options) {
8026
8770
  domain,
8027
8771
  entityType: entityType.trim().toLowerCase(),
8028
8772
  entityTypePlural: entityTypePlural.trim().toLowerCase(),
8029
- location
8773
+ location,
8774
+ author: author.trim() || void 0
8030
8775
  };
8031
8776
  }
8032
8777
  function pluralize(word) {
@@ -8045,6 +8790,7 @@ async function confirmProjectInfo(info) {
8045
8790
  logger.keyValue("Name", info.name);
8046
8791
  logger.keyValue("Description", info.description);
8047
8792
  logger.keyValue("GitHub", `${info.org}/${info.repo}`);
8793
+ if (info.author) logger.keyValue("Author", info.author);
8048
8794
  logger.keyValue("Entity", `${info.entityType} / ${info.entityTypePlural}`);
8049
8795
  if (info.domain) logger.keyValue("Domain", info.domain);
8050
8796
  if (info.location) logger.keyValue("Location", info.location);
@@ -9993,6 +10739,17 @@ function placeholderKeyToConfigKey(key, category) {
9993
10739
  async function promptTemplateConfig(options) {
9994
10740
  const { context, mode = "quick", category, requiredOnly = false } = options;
9995
10741
  logger.section("Template Configuration", "\u{1F4DD}");
10742
+ logger.newline();
10743
+ logger.info("This step personalizes all Claude Code configuration files for YOUR project.");
10744
+ logger.info("Your answers will be used to:");
10745
+ logger.newline();
10746
+ logger.item("Replace {{PLACEHOLDERS}} in agents, commands, and skills");
10747
+ logger.item("Configure CLAUDE.md with your tech stack and commands");
10748
+ logger.item("Set up quality targets (coverage, performance, accessibility)");
10749
+ logger.item("Customize code examples to match your project structure");
10750
+ logger.newline();
10751
+ logger.info("Accurate configuration means better AI assistance tailored to your codebase!");
10752
+ logger.newline();
9996
10753
  const hasDefaults = await hasGlobalDefaults();
9997
10754
  const globalDefaults = hasDefaults ? await getGlobalTemplateConfig() : void 0;
9998
10755
  if (mode === "quick") {
@@ -10265,7 +11022,7 @@ async function runInit(path8, options) {
10265
11022
  logger.warn("Configuration cancelled");
10266
11023
  return;
10267
11024
  }
10268
- const { config, skippedMcpConfigs, templateConfig } = buildResult;
11025
+ const { config, skippedMcpConfigs, templateConfig, cicdConfig } = buildResult;
10269
11026
  if (templateConfig) {
10270
11027
  config.templateConfig = templateConfig;
10271
11028
  }
@@ -10282,7 +11039,7 @@ async function runInit(path8, options) {
10282
11039
  showConfigSummary(config);
10283
11040
  return;
10284
11041
  }
10285
- await executeInstallation(projectPath, config, registry, templatesPath, options);
11042
+ await executeInstallation(projectPath, config, registry, templatesPath, options, cicdConfig);
10286
11043
  if (templateConfig && !options.noPlaceholders) {
10287
11044
  const claudePath = joinPath(projectPath, ".claude");
10288
11045
  await replaceTemplateConfigWithSpinner(claudePath, templateConfig);
@@ -10462,6 +11219,9 @@ async function buildInteractiveConfig(projectPath, detection, registry, options)
10462
11219
  }
10463
11220
  const permissionsConfig = await promptPermissionsConfig();
10464
11221
  const codeStyleConfig = await promptCodeStyleConfig();
11222
+ const cicdConfig = await promptCICDConfig({
11223
+ packageManager: preferences.packageManager
11224
+ });
10465
11225
  const folderPreferences = await promptQuickFolderPreferences({
10466
11226
  selectedBundles: bundleSelection.selectedBundles,
10467
11227
  technologies: detection.detectedTechnologies || []
@@ -10519,7 +11279,8 @@ async function buildInteractiveConfig(projectPath, detection, registry, options)
10519
11279
  return {
10520
11280
  config,
10521
11281
  skippedMcpConfigs,
10522
- templateConfig: templateConfigResult
11282
+ templateConfig: templateConfigResult,
11283
+ cicdConfig
10523
11284
  };
10524
11285
  }
10525
11286
  async function selectModulesWithBundles(registry, suggestedBundles) {
@@ -10567,7 +11328,7 @@ async function selectModulesWithBundles(registry, suggestedBundles) {
10567
11328
  }
10568
11329
  return result;
10569
11330
  }
10570
- async function executeInstallation(projectPath, config, registry, templatesPath, options) {
11331
+ async function executeInstallation(projectPath, config, registry, templatesPath, options, cicdConfig) {
10571
11332
  logger.newline();
10572
11333
  logger.title("Installing Configuration");
10573
11334
  if (config.scaffold.type === "full-project") {
@@ -10579,6 +11340,16 @@ async function executeInstallation(projectPath, config, registry, templatesPath,
10579
11340
  ...scaffoldResult.createdFiles
10580
11341
  ];
10581
11342
  }
11343
+ const claudeMdResult = await generateClaudeMdWithSpinner(projectPath, config.project, {
11344
+ overwrite: options.force,
11345
+ templateConfig: config.templateConfig,
11346
+ claudeConfig: config
11347
+ });
11348
+ if (claudeMdResult.error) {
11349
+ logger.warn(`CLAUDE.md generation warning: ${claudeMdResult.error}`);
11350
+ } else if (claudeMdResult.skipped) {
11351
+ logger.info("CLAUDE.md already exists, skipped");
11352
+ }
10582
11353
  const modulesByCategory = {
10583
11354
  agents: filterModules(registry, "agents", config.modules.agents.selected),
10584
11355
  skills: filterModules(registry, "skills", config.modules.skills.selected),
@@ -10639,6 +11410,33 @@ async function executeInstallation(projectPath, config, registry, templatesPath,
10639
11410
  if (codeStyleResult.errors.length > 0) {
10640
11411
  logger.warn(`Code style installation warnings: ${codeStyleResult.errors.join(", ")}`);
10641
11412
  }
11413
+ const vscodeResult = await installVSCodeConfig(projectPath, config.extras.codeStyle, {
11414
+ overwrite: options.force,
11415
+ merge: !options.force
11416
+ // Merge with existing settings if not forcing
11417
+ });
11418
+ if (vscodeResult.settings.error) {
11419
+ logger.warn(`VSCode settings warning: ${vscodeResult.settings.error}`);
11420
+ } else if (vscodeResult.settings.created || vscodeResult.settings.updated) {
11421
+ logger.info("VSCode settings configured for code style tools");
11422
+ }
11423
+ const huskyConfig = deriveHuskyConfigFromCodeStyle(config.extras.codeStyle);
11424
+ if (huskyConfig) {
11425
+ const huskyResult = await installHuskyWithSpinner(projectPath, huskyConfig, {
11426
+ overwrite: options.force
11427
+ });
11428
+ if (huskyResult.errors.length > 0) {
11429
+ logger.warn(`Husky installation warnings: ${huskyResult.errors.join(", ")}`);
11430
+ }
11431
+ }
11432
+ }
11433
+ if (cicdConfig?.enabled) {
11434
+ const cicdResult = await installCICDWithSpinner(projectPath, cicdConfig, {
11435
+ overwrite: options.force
11436
+ });
11437
+ if (cicdResult.errors.length > 0) {
11438
+ logger.warn(`CI/CD installation warnings: ${cicdResult.errors.join(", ")}`);
11439
+ }
10642
11440
  }
10643
11441
  await writeConfig(projectPath, config);
10644
11442
  logger.newline();
@@ -10667,6 +11465,7 @@ async function handlePackageJsonUpdate(projectPath, config, options) {
10667
11465
  project: {
10668
11466
  name: config.project.name,
10669
11467
  description: config.project.description,
11468
+ author: config.project.author,
10670
11469
  repository: config.project.org && config.project.repo ? `https://github.com/${config.project.org}/${config.project.repo}` : void 0
10671
11470
  }
10672
11471
  };
@@ -10736,7 +11535,7 @@ async function handlePackageJsonUpdate(projectPath, config, options) {
10736
11535
  logger.newline();
10737
11536
  logger.subtitle("Next Steps");
10738
11537
  logger.info("Run the following command to install dependencies:");
10739
- logger.raw(` ${getInstallCommand(packageManager)}`);
11538
+ logger.raw(` ${getInstallCommand2(packageManager)}`);
10740
11539
  const setupInstructions = getSetupInstructions(toolSelection);
10741
11540
  if (setupInstructions.length > 0) {
10742
11541
  logger.newline();