claude-launchpad 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/cli.ts
4
4
  import { Command as Command5 } from "commander";
5
5
  import { access as access7 } from "fs/promises";
6
- import { join as join8 } from "path";
6
+ import { join as join9 } from "path";
7
7
 
8
8
  // src/commands/init/index.ts
9
9
  import { Command } from "commander";
@@ -324,9 +324,9 @@ async function fileExists(path) {
324
324
  }
325
325
  }
326
326
  async function globExists(dir, pattern) {
327
- const { readdir: readdir3 } = await import("fs/promises");
327
+ const { readdir: readdir4 } = await import("fs/promises");
328
328
  try {
329
- const entries = await readdir3(dir);
329
+ const entries = await readdir4(dir);
330
330
  return entries.some((e) => e.endsWith(pattern.replace("*", "")));
331
331
  } catch {
332
332
  return false;
@@ -947,34 +947,34 @@ async function analyzeSettings(config) {
947
947
  });
948
948
  return { name: "Settings", issues, score: 40 };
949
949
  }
950
- const plugins = config.settings.enabledPlugins;
951
- if (!plugins || Object.keys(plugins).length === 0) {
950
+ const hooks = config.settings.hooks;
951
+ if (!hooks || Object.keys(hooks).length === 0) {
952
952
  issues.push({
953
953
  analyzer: "Settings",
954
- severity: "low",
955
- message: "No plugins enabled",
956
- fix: "Consider enabling plugins for enhanced capabilities"
954
+ severity: "medium",
955
+ message: "settings.json has no hooks configured",
956
+ fix: "Run `claude-launchpad doctor --fix` to generate hooks"
957
957
  });
958
958
  }
959
- const permissions = config.settings.permissions;
960
- if (!permissions) {
959
+ const plugins = config.settings.enabledPlugins;
960
+ if (!plugins || Object.keys(plugins).length === 0) {
961
961
  issues.push({
962
962
  analyzer: "Settings",
963
- severity: "low",
964
- message: "No permission rules configured",
965
- fix: "Add permission rules to control which tools Claude can use automatically"
963
+ severity: "info",
964
+ message: "No plugins enabled \u2014 plugins are optional but can add capabilities"
966
965
  });
967
966
  }
968
- const env = config.settings.env;
969
- if (!env) {
967
+ const allowedTools = config.settings.allowedTools;
968
+ if (allowedTools && allowedTools.length > 0 && config.hooks.length === 0) {
970
969
  issues.push({
971
970
  analyzer: "Settings",
972
- severity: "info",
973
- message: "No environment variables defined in settings",
974
- fix: "Define env vars in settings.json for consistent development environments"
971
+ severity: "medium",
972
+ message: "Tools auto-allowed without any hooks \u2014 no safety net for dangerous operations",
973
+ fix: "Add PreToolUse hooks for security or remove allowedTools to use interactive prompting"
975
974
  });
976
975
  }
977
- const score = Math.max(0, 100 - issues.length * 15);
976
+ const actionableCount = issues.filter((i) => i.severity !== "info").length;
977
+ const score = Math.max(0, 100 - actionableCount * 20);
978
978
  return { name: "Settings", issues, score };
979
979
  }
980
980
 
@@ -1266,6 +1266,12 @@ async function applyFixes(issues, projectRoot) {
1266
1266
  return { fixed, skipped };
1267
1267
  }
1268
1268
  async function tryFix(issue, root, detected) {
1269
+ if (issue.analyzer === "Hooks" && issue.message.includes("No hooks configured")) {
1270
+ const a = await addEnvProtectionHook(root);
1271
+ const b = await addAutoFormatHook(root, detected);
1272
+ const c = await addForcePushProtection(root);
1273
+ return a || b || c;
1274
+ }
1269
1275
  if (issue.analyzer === "Hooks" && issue.message.includes(".env file protection")) {
1270
1276
  return addEnvProtectionHook(root);
1271
1277
  }
@@ -1434,9 +1440,93 @@ async function writeSettingsJson(root, settings) {
1434
1440
  await writeFile2(join4(dir, "settings.json"), JSON.stringify(settings, null, 2) + "\n");
1435
1441
  }
1436
1442
 
1443
+ // src/commands/doctor/watcher.ts
1444
+ import { watch } from "fs";
1445
+ import { join as join5 } from "path";
1446
+ async function watchConfig(projectRoot) {
1447
+ const claudeDir = join5(projectRoot, ".claude");
1448
+ const claudeMd = join5(projectRoot, "CLAUDE.md");
1449
+ const claudeignore = join5(projectRoot, ".claudeignore");
1450
+ await runAndDisplay(projectRoot);
1451
+ log.blank();
1452
+ log.info("Watching for changes... (Ctrl+C to stop)");
1453
+ log.blank();
1454
+ let debounce = null;
1455
+ const onChange = () => {
1456
+ if (debounce) clearTimeout(debounce);
1457
+ debounce = setTimeout(async () => {
1458
+ console.clear();
1459
+ await runAndDisplay(projectRoot);
1460
+ log.blank();
1461
+ log.info("Watching for changes... (Ctrl+C to stop)");
1462
+ log.blank();
1463
+ }, 300);
1464
+ };
1465
+ try {
1466
+ watch(claudeDir, { recursive: true }, onChange);
1467
+ } catch {
1468
+ }
1469
+ try {
1470
+ watch(claudeMd, onChange);
1471
+ } catch {
1472
+ }
1473
+ try {
1474
+ watch(claudeignore, onChange);
1475
+ } catch {
1476
+ }
1477
+ await new Promise(() => {
1478
+ });
1479
+ }
1480
+ async function runAndDisplay(projectRoot) {
1481
+ console.log("\x1B[36m\x1B[1m Claude Launchpad\x1B[0m");
1482
+ console.log("\x1B[2m Scaffold \xB7 Diagnose \xB7 Evaluate\x1B[0m");
1483
+ log.blank();
1484
+ const config = await parseClaudeConfig(projectRoot);
1485
+ if (config.claudeMdContent === null && config.settings === null) {
1486
+ log.error("No Claude Code configuration found.");
1487
+ return;
1488
+ }
1489
+ const results = await Promise.all([
1490
+ analyzeBudget(config),
1491
+ analyzeQuality(config),
1492
+ analyzeSettings(config),
1493
+ analyzeHooks(config),
1494
+ analyzeRules(config),
1495
+ analyzePermissions(config),
1496
+ analyzeMcp(config)
1497
+ ]);
1498
+ const overallScore = Math.round(
1499
+ results.reduce((sum, r) => sum + r.score, 0) / results.length
1500
+ );
1501
+ for (const result of results) {
1502
+ printScoreCard(result.name, result.score);
1503
+ }
1504
+ log.blank();
1505
+ printScoreCard("Overall", overallScore);
1506
+ log.blank();
1507
+ const allIssues = results.flatMap((r) => r.issues);
1508
+ const actionable = allIssues.filter((i) => i.severity !== "info");
1509
+ if (actionable.length === 0) {
1510
+ log.success("No issues found. Your configuration looks solid.");
1511
+ return;
1512
+ }
1513
+ const sorted = [...actionable].sort((a, b) => {
1514
+ const order = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
1515
+ return (order[a.severity] ?? 4) - (order[b.severity] ?? 4);
1516
+ });
1517
+ for (const issue of sorted) {
1518
+ printIssue(issue.severity, issue.analyzer, issue.message, issue.fix);
1519
+ }
1520
+ log.info(`${actionable.length} issue(s) found. Fix critical/high first.`);
1521
+ }
1522
+
1437
1523
  // src/commands/doctor/index.ts
1438
1524
  function createDoctorCommand() {
1439
- return new Command2("doctor").description("Diagnose your Claude Code configuration and report issues").option("-p, --path <path>", "Project root path", process.cwd()).option("--json", "Output as JSON").option("--min-score <n>", "Exit non-zero if overall score is below this threshold (for CI)").option("--fix", "Auto-apply deterministic fixes for detected issues").action(async (opts) => {
1525
+ return new Command2("doctor").description("Diagnose your Claude Code configuration and report issues").option("-p, --path <path>", "Project root path", process.cwd()).option("--json", "Output as JSON").option("--min-score <n>", "Exit non-zero if overall score is below this threshold (for CI)").option("--fix", "Auto-apply deterministic fixes for detected issues").option("--watch", "Watch for config changes and re-run automatically").action(async (opts) => {
1526
+ if (opts.watch) {
1527
+ await watchConfig(opts.path);
1528
+ return;
1529
+ }
1440
1530
  printBanner();
1441
1531
  log.step("Scanning Claude Code configuration...");
1442
1532
  log.blank();
@@ -1524,7 +1614,7 @@ import chalk2 from "chalk";
1524
1614
 
1525
1615
  // src/commands/eval/loader.ts
1526
1616
  import { readFile as readFile6, readdir as readdir2, access as access5 } from "fs/promises";
1527
- import { join as join5, resolve as resolve2, dirname } from "path";
1617
+ import { join as join6, resolve as resolve2, dirname } from "path";
1528
1618
  import { fileURLToPath } from "url";
1529
1619
  import { parse as parseYaml } from "yaml";
1530
1620
 
@@ -1574,7 +1664,7 @@ function validateChecks(raw, filePath) {
1574
1664
  throw new ScenarioError(filePath, `checks[${i}] must be an object`);
1575
1665
  }
1576
1666
  const check = c;
1577
- const validTypes = ["grep", "file-exists", "file-absent", "custom"];
1667
+ const validTypes = ["grep", "file-exists", "file-absent", "max-lines", "custom"];
1578
1668
  if (!validTypes.includes(check.type)) {
1579
1669
  throw new ScenarioError(filePath, `checks[${i}].type must be one of: ${validTypes.join(", ")}`);
1580
1670
  }
@@ -1647,7 +1737,7 @@ async function dirExists(path) {
1647
1737
  async function loadScenarios(options) {
1648
1738
  const { suite, customPath } = options;
1649
1739
  const scenarioDir = customPath ? resolve2(customPath) : await findScenariosDir();
1650
- const dirs = suite ? [join5(scenarioDir, suite)] : await getSubdirectories(scenarioDir);
1740
+ const dirs = suite ? [join6(scenarioDir, suite)] : await getSubdirectories(scenarioDir);
1651
1741
  const allDirs = [scenarioDir, ...dirs];
1652
1742
  const scenarios = [];
1653
1743
  for (const dir of allDirs) {
@@ -1669,7 +1759,7 @@ async function loadScenarios(options) {
1669
1759
  async function getSubdirectories(dir) {
1670
1760
  try {
1671
1761
  const entries = await readdir2(dir, { withFileTypes: true });
1672
- return entries.filter((e) => e.isDirectory()).map((e) => join5(dir, e.name));
1762
+ return entries.filter((e) => e.isDirectory()).map((e) => join6(dir, e.name));
1673
1763
  } catch {
1674
1764
  return [];
1675
1765
  }
@@ -1677,67 +1767,26 @@ async function getSubdirectories(dir) {
1677
1767
  async function listYamlFiles(dir) {
1678
1768
  try {
1679
1769
  const entries = await readdir2(dir, { withFileTypes: true });
1680
- return entries.filter((e) => e.isFile() && (e.name.endsWith(".yaml") || e.name.endsWith(".yml"))).map((e) => join5(dir, e.name));
1770
+ return entries.filter((e) => e.isFile() && (e.name.endsWith(".yaml") || e.name.endsWith(".yml"))).map((e) => join6(dir, e.name));
1681
1771
  } catch {
1682
1772
  return [];
1683
1773
  }
1684
1774
  }
1685
1775
 
1686
1776
  // src/commands/eval/runner.ts
1687
- import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile7, rm, cp } from "fs/promises";
1688
- import { join as join6, dirname as dirname2 } from "path";
1777
+ import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile7, readdir as readdir3, rm } from "fs/promises";
1778
+ import { join as join7, dirname as dirname2 } from "path";
1689
1779
  import { tmpdir } from "os";
1690
1780
  import { randomUUID } from "crypto";
1691
1781
  import { execFile } from "child_process";
1692
1782
  import { promisify } from "util";
1693
1783
  var exec = promisify(execFile);
1694
1784
  async function runScenario(scenario, options) {
1695
- const sandboxDir = join6(tmpdir(), `claude-eval-${randomUUID()}`);
1785
+ const sandboxDir = join7(tmpdir(), `claude-eval-${randomUUID()}`);
1696
1786
  try {
1697
- await mkdir3(sandboxDir, { recursive: true });
1698
- for (const file of scenario.setup.files) {
1699
- const filePath = join6(sandboxDir, file.path);
1700
- await mkdir3(dirname2(filePath), { recursive: true });
1701
- await writeFile3(filePath, file.content);
1702
- }
1703
- const claudeDir = join6(options.projectRoot, ".claude");
1704
- const sandboxClaudeDir = join6(sandboxDir, ".claude");
1705
- try {
1706
- await cp(claudeDir, sandboxClaudeDir, { recursive: true });
1707
- } catch {
1708
- }
1709
- if (scenario.setup.instructions) {
1710
- await writeFile3(
1711
- join6(sandboxDir, "CLAUDE.md"),
1712
- `# Eval Scenario
1713
-
1714
- ${scenario.setup.instructions}
1715
- `
1716
- );
1717
- }
1718
- await exec("git", ["init", "-q"], { cwd: sandboxDir });
1719
- await exec("git", ["add", "-A"], { cwd: sandboxDir });
1720
- await exec("git", [
1721
- "-c",
1722
- "user.name=eval",
1723
- "-c",
1724
- "user.email=eval@test",
1725
- "commit",
1726
- "-q",
1727
- "-m",
1728
- "eval setup"
1729
- ], { cwd: sandboxDir });
1730
- await runClaude(sandboxDir, scenario.prompt, options.timeout);
1731
- const checkResults = await evaluateChecks(scenario.checks, sandboxDir);
1732
- const score = checkResults.filter((c) => c.passed).reduce((sum, c) => sum + c.points, 0);
1733
- const maxScore = scenario.checks.reduce((sum, c) => sum + c.points, 0);
1734
- return {
1735
- scenario: scenario.name,
1736
- score,
1737
- maxScore,
1738
- passed: score >= scenario.passingScore,
1739
- checks: checkResults
1740
- };
1787
+ await setupSandbox(sandboxDir, scenario);
1788
+ await runClaudeInSandbox(sandboxDir, scenario.prompt, options.timeout);
1789
+ return await scoreResults(scenario, sandboxDir);
1741
1790
  } finally {
1742
1791
  if (options.debug) {
1743
1792
  console.log(` DEBUG: Sandbox preserved at ${sandboxDir}`);
@@ -1756,9 +1805,63 @@ async function runScenarioWithRetries(scenario, options) {
1756
1805
  const sorted = [...results].sort((a, b) => a.score - b.score);
1757
1806
  return sorted[Math.floor(sorted.length / 2)];
1758
1807
  }
1759
- async function runClaude(cwd, prompt, timeout) {
1808
+ async function setupSandbox(sandboxDir, scenario) {
1809
+ await mkdir3(sandboxDir, { recursive: true });
1810
+ for (const file of scenario.setup.files) {
1811
+ const filePath = join7(sandboxDir, file.path);
1812
+ await mkdir3(dirname2(filePath), { recursive: true });
1813
+ await writeFile3(filePath, file.content);
1814
+ }
1815
+ if (scenario.setup.instructions) {
1816
+ await writeFile3(
1817
+ join7(sandboxDir, "CLAUDE.md"),
1818
+ `# Eval Scenario
1819
+
1820
+ ${scenario.setup.instructions}
1821
+ `
1822
+ );
1823
+ }
1824
+ await exec("git", ["init", "-q"], { cwd: sandboxDir });
1825
+ await exec("git", ["add", "-A"], { cwd: sandboxDir });
1826
+ await exec("git", [
1827
+ "-c",
1828
+ "user.name=eval",
1829
+ "-c",
1830
+ "user.email=eval@test",
1831
+ "commit",
1832
+ "-q",
1833
+ "-m",
1834
+ "eval setup"
1835
+ ], { cwd: sandboxDir });
1836
+ }
1837
+ async function runClaudeInSandbox(cwd, prompt, timeout) {
1838
+ try {
1839
+ const sdk = await import("@anthropic-ai/claude-agent-sdk");
1840
+ const controller = new AbortController();
1841
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1842
+ try {
1843
+ for await (const _message of sdk.query({
1844
+ prompt,
1845
+ options: {
1846
+ cwd,
1847
+ allowedTools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep"],
1848
+ permissionMode: "dontAsk",
1849
+ settingSources: [],
1850
+ maxTurns: 20,
1851
+ abortController: controller
1852
+ }
1853
+ })) {
1854
+ }
1855
+ } finally {
1856
+ clearTimeout(timeoutId);
1857
+ }
1858
+ } catch {
1859
+ await runClaudeCli(cwd, prompt, timeout);
1860
+ }
1861
+ }
1862
+ async function runClaudeCli(cwd, prompt, timeout) {
1760
1863
  try {
1761
- return await exec(
1864
+ await exec(
1762
1865
  "claude",
1763
1866
  [
1764
1867
  "-p",
@@ -1769,29 +1872,34 @@ async function runClaude(cwd, prompt, timeout) {
1769
1872
  "20",
1770
1873
  "--dangerously-skip-permissions",
1771
1874
  "--allowedTools",
1875
+ "Bash",
1772
1876
  "Read",
1773
1877
  "Write",
1774
1878
  "Edit",
1775
- "Bash",
1776
1879
  "Glob",
1777
1880
  "Grep"
1778
1881
  ],
1779
- {
1780
- cwd,
1781
- timeout,
1782
- maxBuffer: 10 * 1024 * 1024
1783
- }
1882
+ { cwd, timeout, maxBuffer: 10 * 1024 * 1024 }
1784
1883
  );
1785
1884
  } catch (error) {
1786
1885
  if (error && typeof error === "object" && "stdout" in error) {
1787
- return {
1788
- stdout: String(error.stdout ?? ""),
1789
- stderr: String(error.stderr ?? "")
1790
- };
1886
+ return;
1791
1887
  }
1792
1888
  throw error;
1793
1889
  }
1794
1890
  }
1891
+ async function scoreResults(scenario, sandboxDir) {
1892
+ const checkResults = await evaluateChecks(scenario.checks, sandboxDir);
1893
+ const score = checkResults.filter((c) => c.passed).reduce((sum, c) => sum + c.points, 0);
1894
+ const maxScore = scenario.checks.reduce((sum, c) => sum + c.points, 0);
1895
+ return {
1896
+ scenario: scenario.name,
1897
+ score,
1898
+ maxScore,
1899
+ passed: score >= scenario.passingScore,
1900
+ checks: checkResults
1901
+ };
1902
+ }
1795
1903
  async function evaluateChecks(checks, sandboxDir) {
1796
1904
  const results = [];
1797
1905
  for (const check of checks) {
@@ -1802,37 +1910,81 @@ async function evaluateChecks(checks, sandboxDir) {
1802
1910
  }
1803
1911
  async function evaluateSingleCheck(check, sandboxDir) {
1804
1912
  switch (check.type) {
1805
- case "grep": {
1806
- if (!check.pattern) return false;
1807
- try {
1808
- const content = await readFile7(join6(sandboxDir, check.target), "utf-8");
1809
- const found = new RegExp(check.pattern).test(content);
1810
- return check.expect === "present" ? found : !found;
1811
- } catch {
1812
- return check.expect === "absent";
1813
- }
1913
+ case "grep":
1914
+ return checkGrep(check, sandboxDir);
1915
+ case "file-exists":
1916
+ return checkFileExists(check, sandboxDir);
1917
+ case "file-absent":
1918
+ return checkFileAbsent(check, sandboxDir);
1919
+ case "max-lines":
1920
+ return checkMaxLines(check, sandboxDir);
1921
+ case "custom":
1922
+ return false;
1923
+ default:
1924
+ return false;
1925
+ }
1926
+ }
1927
+ async function checkGrep(check, sandboxDir) {
1928
+ if (!check.pattern) return false;
1929
+ try {
1930
+ const content = await readFile7(join7(sandboxDir, check.target), "utf-8");
1931
+ let found;
1932
+ try {
1933
+ found = new RegExp(check.pattern).test(content);
1934
+ } catch {
1935
+ return false;
1814
1936
  }
1815
- case "file-exists": {
1816
- try {
1817
- await readFile7(join6(sandboxDir, check.target));
1818
- return check.expect === "present";
1819
- } catch {
1937
+ return check.expect === "present" ? found : !found;
1938
+ } catch {
1939
+ return check.expect === "absent";
1940
+ }
1941
+ }
1942
+ async function checkFileExists(check, sandboxDir) {
1943
+ try {
1944
+ await readFile7(join7(sandboxDir, check.target));
1945
+ return check.expect === "present";
1946
+ } catch {
1947
+ return check.expect === "absent";
1948
+ }
1949
+ }
1950
+ async function checkFileAbsent(check, sandboxDir) {
1951
+ try {
1952
+ await readFile7(join7(sandboxDir, check.target));
1953
+ return check.expect === "absent";
1954
+ } catch {
1955
+ return check.expect === "present";
1956
+ }
1957
+ }
1958
+ async function checkMaxLines(check, sandboxDir) {
1959
+ const maxLines = parseInt(check.pattern ?? "800", 10);
1960
+ try {
1961
+ const files = await listAllFiles(join7(sandboxDir, check.target));
1962
+ for (const file of files) {
1963
+ const content = await readFile7(file, "utf-8");
1964
+ if (content.split("\n").length > maxLines) {
1820
1965
  return check.expect === "absent";
1821
1966
  }
1822
1967
  }
1823
- case "file-absent": {
1824
- try {
1825
- await readFile7(join6(sandboxDir, check.target));
1826
- return check.expect === "absent";
1827
- } catch {
1828
- return check.expect === "present";
1968
+ return check.expect === "present";
1969
+ } catch {
1970
+ return check.expect === "absent";
1971
+ }
1972
+ }
1973
+ async function listAllFiles(dir) {
1974
+ const results = [];
1975
+ try {
1976
+ const entries = await readdir3(dir, { withFileTypes: true });
1977
+ for (const entry of entries) {
1978
+ const fullPath = join7(dir, entry.name);
1979
+ if (entry.isDirectory()) {
1980
+ results.push(...await listAllFiles(fullPath));
1981
+ } else {
1982
+ results.push(fullPath);
1829
1983
  }
1830
1984
  }
1831
- case "custom":
1832
- return false;
1833
- default:
1834
- return false;
1985
+ } catch {
1835
1986
  }
1987
+ return results;
1836
1988
  }
1837
1989
 
1838
1990
  // src/commands/eval/index.ts
@@ -1949,7 +2101,7 @@ import { Command as Command4 } from "commander";
1949
2101
  import { spawn, execFile as execFile2 } from "child_process";
1950
2102
  import { promisify as promisify2 } from "util";
1951
2103
  import { access as access6 } from "fs/promises";
1952
- import { join as join7 } from "path";
2104
+ import { join as join8 } from "path";
1953
2105
  var execAsync = promisify2(execFile2);
1954
2106
  var ENHANCE_PROMPT = `Read CLAUDE.md and the project's codebase, then update CLAUDE.md to fill in missing or incomplete sections:
1955
2107
 
@@ -1967,7 +2119,7 @@ function createEnhanceCommand() {
1967
2119
  return new Command4("enhance").description("Use Claude to analyze your codebase and complete CLAUDE.md").option("-p, --path <path>", "Project root path", process.cwd()).action(async (opts) => {
1968
2120
  printBanner();
1969
2121
  const root = opts.path;
1970
- const claudeMdPath = join7(root, "CLAUDE.md");
2122
+ const claudeMdPath = join8(root, "CLAUDE.md");
1971
2123
  try {
1972
2124
  await access6(claudeMdPath);
1973
2125
  } catch {
@@ -1996,8 +2148,8 @@ function createEnhanceCommand() {
1996
2148
  }
1997
2149
 
1998
2150
  // src/cli.ts
1999
- var program = new Command5().name("claude-launchpad").description("CLI toolkit that makes Claude Code setups measurably good").version("0.1.0").action(async () => {
2000
- const hasConfig = await fileExists3(join8(process.cwd(), "CLAUDE.md")) || await fileExists3(join8(process.cwd(), ".claude", "settings.json"));
2151
+ var program = new Command5().name("claude-launchpad").description("CLI toolkit that makes Claude Code setups measurably good").version("0.2.1").action(async () => {
2152
+ const hasConfig = await fileExists3(join9(process.cwd(), "CLAUDE.md")) || await fileExists3(join9(process.cwd(), ".claude", "settings.json"));
2001
2153
  if (hasConfig) {
2002
2154
  await program.commands.find((c) => c.name() === "doctor")?.parseAsync([], { from: "user" });
2003
2155
  } else {