codebyplan 1.13.25 → 1.13.27

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
@@ -14,7 +14,7 @@ var VERSION, PACKAGE_NAME;
14
14
  var init_version = __esm({
15
15
  "src/lib/version.ts"() {
16
16
  "use strict";
17
- VERSION = "1.13.25";
17
+ VERSION = "1.13.27";
18
18
  PACKAGE_NAME = "codebyplan";
19
19
  }
20
20
  });
@@ -149,6 +149,7 @@ var init_gitignore_block = __esm({
149
149
  ".codebyplan/device.local.json",
150
150
  ".codebyplan/statusline.local.json",
151
151
  ".codebyplan/worktree.local.json",
152
+ ".codebyplan/todo/",
152
153
  ".codebyplan/claude-status.local.json",
153
154
  ".codebyplan.local.json"
154
155
  ];
@@ -1568,6 +1569,18 @@ function stripBaseSettingsFromSettings(settings, base) {
1568
1569
  }
1569
1570
  return settings;
1570
1571
  }
1572
+ function mergeEnabledPluginsIntoSettings(settings, plugins) {
1573
+ if (!settings.enabledPlugins) {
1574
+ settings.enabledPlugins = {};
1575
+ }
1576
+ for (const plugin of plugins) {
1577
+ const key = `${plugin}@claude-plugins-official`;
1578
+ if (!(key in settings.enabledPlugins)) {
1579
+ settings.enabledPlugins[key] = true;
1580
+ }
1581
+ }
1582
+ return settings;
1583
+ }
1571
1584
  function stripOwnedHooksFromSettings(settings) {
1572
1585
  if (!settings.hooks) {
1573
1586
  return settings;
@@ -1636,1518 +1649,1903 @@ var init_settings_merge = __esm({
1636
1649
  }
1637
1650
  });
1638
1651
 
1639
- // src/cli/claude/install.ts
1640
- var install_exports = {};
1641
- __export(install_exports, {
1642
- resolveTemplatesDir: () => resolveTemplatesDir,
1643
- runInstall: () => runInstall
1644
- });
1645
- import * as fs3 from "node:fs";
1646
- import * as os2 from "node:os";
1647
- import * as path4 from "node:path";
1648
- import { fileURLToPath } from "node:url";
1649
- function resolveTemplatesDir() {
1650
- const here = path4.dirname(fileURLToPath(import.meta.url));
1651
- const candidates = [
1652
- path4.resolve(here, "..", "templates"),
1653
- path4.resolve(here, "..", "..", "templates"),
1654
- path4.resolve(here, "..", "..", "..", "templates")
1655
- ];
1656
- for (const c of candidates) {
1657
- if (fs3.existsSync(c) && fs3.statSync(c).isDirectory()) {
1658
- return c;
1652
+ // src/lib/flags.ts
1653
+ import { readFile as readFile6 } from "node:fs/promises";
1654
+ import { join as join8, resolve } from "node:path";
1655
+ async function findCodebyplanConfig(startDir, maxDepth = 20) {
1656
+ let cursor = resolve(startDir);
1657
+ for (let depth = 0; depth < maxDepth; depth++) {
1658
+ const sentinelPath2 = join8(cursor, ".codebyplan", "repo.json");
1659
+ try {
1660
+ const raw = await readFile6(sentinelPath2, "utf-8");
1661
+ const parsed = JSON.parse(raw);
1662
+ return { path: sentinelPath2, contents: parsed };
1663
+ } catch {
1659
1664
  }
1665
+ const legacyPath = join8(cursor, ".codebyplan.json");
1666
+ try {
1667
+ const raw = await readFile6(legacyPath, "utf-8");
1668
+ const parsed = JSON.parse(raw);
1669
+ return { path: legacyPath, contents: parsed };
1670
+ } catch {
1671
+ }
1672
+ const parent = resolve(cursor, "..");
1673
+ if (parent === cursor) return null;
1674
+ cursor = parent;
1660
1675
  }
1661
- throw new Error(
1662
- `codebyplan: could not locate templates/ directory. Probed:
1663
- ${candidates.join(
1664
- "\n "
1665
- )}`
1666
- );
1676
+ return null;
1667
1677
  }
1668
- async function runInstall(opts, deps = {}) {
1669
- await Promise.resolve();
1670
- const scope = opts.scope ?? "project";
1671
- if (scope === "user") {
1672
- if (opts.renderer) {
1673
- console.warn(
1674
- "codebyplan claude install: --bash/--node/--python is ignored for --scope user (no project root for statusline.local.json)."
1675
- );
1678
+ function parseFlags(startIndex) {
1679
+ const flags = {};
1680
+ const args = process.argv.slice(startIndex);
1681
+ for (let i = 0; i < args.length; i++) {
1682
+ const arg = args[i];
1683
+ if (arg.startsWith("--") && i + 1 < args.length) {
1684
+ const key = arg.slice(2);
1685
+ flags[key] = args[++i];
1676
1686
  }
1677
- runInstallUser(opts, deps);
1678
- return;
1679
1687
  }
1680
- const projectDir = deps.projectDir ?? process.cwd();
1681
- let templatesDir;
1682
- try {
1683
- templatesDir = deps.templatesDir ?? resolveTemplatesDir();
1684
- } catch (err) {
1685
- console.error(
1686
- err instanceof Error ? err.message : `codebyplan claude install: ${String(err)}`
1688
+ return flags;
1689
+ }
1690
+ function hasFlag(name, startIndex) {
1691
+ return process.argv.slice(startIndex).includes(`--${name}`);
1692
+ }
1693
+ async function resolveConfig(flags) {
1694
+ const projectPath = flags["path"] ?? process.cwd();
1695
+ let repoId = flags["repo-id"] ?? process.env.CODEBYPLAN_REPO_ID;
1696
+ let worktreeId = flags["worktree-id"] ?? process.env.CODEBYPLAN_WORKTREE_ID;
1697
+ if (!repoId) {
1698
+ const found = await findCodebyplanConfig(projectPath);
1699
+ if (found) {
1700
+ repoId = found.contents.repo_id;
1701
+ if (!worktreeId) worktreeId = found.contents.worktree_id;
1702
+ }
1703
+ }
1704
+ if (!repoId) {
1705
+ throw new Error(
1706
+ `Could not determine repo_id.
1707
+
1708
+ Provide it via one of:
1709
+ --repo-id <uuid> CLI flag
1710
+ CODEBYPLAN_REPO_ID=<uuid> environment variable
1711
+ .codebyplan/repo.json { "repo_id": "<uuid>" } in project root
1712
+ Run 'codebyplan setup' to initialize this project`
1687
1713
  );
1688
- process.exitCode = 1;
1689
- return;
1690
1714
  }
1715
+ return { repoId, worktreeId, projectPath };
1716
+ }
1717
+ var init_flags = __esm({
1718
+ "src/lib/flags.ts"() {
1719
+ "use strict";
1720
+ }
1721
+ });
1722
+
1723
+ // src/lib/tech-detect.ts
1724
+ import { readFile as readFile7, access, readdir } from "node:fs/promises";
1725
+ import { join as join9, relative as relative2 } from "node:path";
1726
+ async function fileExists(filePath) {
1691
1727
  try {
1692
- const files = walkTemplates(templatesDir);
1693
- const manifestEntries = [];
1694
- for (const f of files) {
1695
- const absDest = path4.join(projectDir, ".claude", f.dest);
1696
- const absSrc = path4.join(templatesDir, f.src);
1697
- if (opts.dryRun) {
1698
- if (opts.verbose) {
1699
- console.log(`[dry-run] would copy ${f.src} \u2192 .claude/${f.dest}`);
1700
- }
1701
- } else {
1702
- fs3.mkdirSync(path4.dirname(absDest), { recursive: true });
1703
- fs3.copyFileSync(absSrc, absDest);
1704
- if (opts.verbose) {
1705
- console.log(`copied ${f.src} \u2192 .claude/${f.dest}`);
1706
- }
1707
- }
1708
- manifestEntries.push({ src: f.src, dest: f.dest, hash: f.hash });
1709
- }
1710
- const hooksJsonPath = path4.join(templatesDir, "hooks", "hooks.json");
1711
- const baseSettingsPath = path4.join(
1712
- templatesDir,
1713
- "settings.project.base.json"
1714
- );
1715
- const hasHooks = fs3.existsSync(hooksJsonPath);
1716
- const hasBase = fs3.existsSync(baseSettingsPath);
1717
- const settingsPath = path4.join(projectDir, ".claude", "settings.json");
1718
- const existingSettings = fs3.existsSync(settingsPath) ? JSON.parse(fs3.readFileSync(settingsPath, "utf8")) : {};
1719
- if (hasBase) {
1720
- const base = JSON.parse(
1721
- fs3.readFileSync(baseSettingsPath, "utf8")
1722
- );
1723
- mergeBaseSettingsIntoSettings(existingSettings, base);
1724
- }
1725
- if (hasHooks) {
1726
- const hooksJson = JSON.parse(
1727
- fs3.readFileSync(hooksJsonPath, "utf8")
1728
- );
1729
- mergeHooksIntoSettings(existingSettings, hooksJson);
1730
- }
1731
- if (!opts.dryRun) {
1732
- fs3.mkdirSync(path4.dirname(settingsPath), { recursive: true });
1733
- fs3.writeFileSync(
1734
- settingsPath,
1735
- JSON.stringify(existingSettings, null, 2) + "\n",
1736
- "utf8"
1737
- );
1738
- } else if (opts.verbose) {
1739
- console.log(
1740
- `[dry-run] would merge settings into ${path4.relative(projectDir, settingsPath)}`
1741
- );
1742
- }
1743
- const gitignoreAction = await ensureManagedGitignoreBlock(
1744
- projectDir,
1745
- opts.dryRun
1728
+ await access(filePath);
1729
+ return true;
1730
+ } catch {
1731
+ return false;
1732
+ }
1733
+ }
1734
+ async function discoverMonorepoApps(projectPath) {
1735
+ const apps = [];
1736
+ const patterns = [];
1737
+ try {
1738
+ const raw = await readFile7(
1739
+ join9(projectPath, "pnpm-workspace.yaml"),
1740
+ "utf-8"
1746
1741
  );
1747
- if (opts.verbose && gitignoreAction !== "unchanged") {
1748
- console.log(
1749
- `${opts.dryRun ? "[dry-run] would " : ""}${gitignoreAction} managed .gitignore block in ${path4.relative(projectDir, path4.join(projectDir, ".gitignore"))}`
1750
- );
1742
+ const matches = raw.match(/^\s*-\s*['"]?([^'"#\n]+)['"]?/gm);
1743
+ if (matches) {
1744
+ for (const m of matches) {
1745
+ const pattern = m.replace(/^\s*-\s*['"]?/, "").replace(/['"]?\s*$/, "").trim();
1746
+ if (pattern) patterns.push(pattern);
1747
+ }
1751
1748
  }
1752
- if (!opts.dryRun) {
1753
- const manifest = defaultManifest();
1754
- manifest.files = manifestEntries;
1755
- writeManifest(projectDir, manifest);
1749
+ } catch {
1750
+ }
1751
+ if (patterns.length === 0) {
1752
+ try {
1753
+ const raw = await readFile7(join9(projectPath, "package.json"), "utf-8");
1754
+ const pkg = JSON.parse(raw);
1755
+ const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
1756
+ if (ws) patterns.push(...ws);
1757
+ } catch {
1756
1758
  }
1757
- console.log(
1758
- `codebyplan claude install${opts.dryRun ? " (dry-run)" : ""}: ${manifestEntries.length} files, ${countHookEntries(templatesDir)} hook entries.`
1759
- );
1760
- if (opts.renderer && !opts.dryRun) {
1761
- await writeStatuslineLocalConfig(projectDir, { renderer: opts.renderer });
1759
+ }
1760
+ for (const pattern of patterns) {
1761
+ if (pattern.endsWith("/*")) {
1762
+ const dir = pattern.slice(0, -2);
1763
+ const absDir = join9(projectPath, dir);
1764
+ try {
1765
+ const entries = await readdir(absDir, { withFileTypes: true });
1766
+ for (const entry of entries) {
1767
+ if (entry.isDirectory()) {
1768
+ const relPath = join9(dir, entry.name);
1769
+ const absPath = join9(absDir, entry.name);
1770
+ if (await fileExists(join9(absPath, "package.json"))) {
1771
+ apps.push({ name: entry.name, path: relPath, absPath });
1772
+ }
1773
+ }
1774
+ }
1775
+ } catch {
1776
+ }
1762
1777
  }
1763
- } catch (err) {
1764
- console.error(
1765
- `codebyplan claude install failed: ${err instanceof Error ? err.message : String(err)}`
1766
- );
1767
- process.exitCode = 1;
1768
1778
  }
1779
+ return apps;
1769
1780
  }
1770
- function runInstallUser(opts, deps) {
1771
- let templatesDir;
1781
+ async function hasJsxFile(dir, depth = 0) {
1782
+ if (depth > 6) return false;
1772
1783
  try {
1773
- templatesDir = deps.templatesDir ?? resolveTemplatesDir();
1784
+ const entries = await readdir(dir, { withFileTypes: true });
1785
+ for (const entry of entries) {
1786
+ const name = entry.name;
1787
+ if (entry.isDirectory()) {
1788
+ if (SKIP_DIRS.has(name) || JSX_SKIP_DIRS.has(name)) continue;
1789
+ if (await hasJsxFile(join9(dir, name), depth + 1)) return true;
1790
+ } else if (entry.isFile()) {
1791
+ if (JSX_TEST_PATTERN.test(name)) continue;
1792
+ if (name.endsWith(".tsx") || name.endsWith(".jsx")) return true;
1793
+ }
1794
+ }
1774
1795
  } catch (err) {
1796
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1797
+ return false;
1798
+ }
1775
1799
  console.error(
1776
- err instanceof Error ? err.message : `codebyplan claude install: ${String(err)}`
1800
+ `detectCapabilities: readdir failed for ${dir}: ${err instanceof Error ? err.message : String(err)}`
1777
1801
  );
1778
- process.exitCode = 1;
1779
- return;
1780
1802
  }
1781
- try {
1782
- const userDir = deps.userDir ?? path4.join(os2.homedir(), ".claude");
1783
- const settingsPath = path4.join(userDir, "settings.json");
1784
- const userBaseSettingsPath = path4.join(
1785
- templatesDir,
1786
- "settings.user.base.json"
1787
- );
1788
- if (!fs3.existsSync(userBaseSettingsPath)) {
1789
- console.error(
1790
- "codebyplan claude install: settings.user.base.json not found in templates."
1791
- );
1792
- process.exitCode = 1;
1793
- return;
1794
- }
1795
- const userBase = JSON.parse(
1796
- fs3.readFileSync(userBaseSettingsPath, "utf8")
1797
- );
1798
- const existingSettings = fs3.existsSync(settingsPath) ? JSON.parse(fs3.readFileSync(settingsPath, "utf8")) : {};
1799
- mergeBaseSettingsIntoSettings(existingSettings, userBase);
1800
- if (!opts.dryRun) {
1801
- fs3.mkdirSync(userDir, { recursive: true });
1802
- fs3.writeFileSync(
1803
- settingsPath,
1804
- JSON.stringify(existingSettings, null, 2) + "\n",
1805
- "utf8"
1806
- );
1807
- const manifest = defaultManifest();
1808
- manifest.files = [];
1809
- writeManifestForScope("user", manifest, userDir);
1810
- } else if (opts.verbose) {
1811
- console.log(
1812
- `[dry-run] would merge user base settings into ${settingsPath}`
1813
- );
1803
+ return false;
1804
+ }
1805
+ async function detectCapabilities(dirPath, pkgJson) {
1806
+ const caps = /* @__PURE__ */ new Set();
1807
+ for (const sub of JSX_SCAN_DIRS) {
1808
+ if (await hasJsxFile(join9(dirPath, sub))) {
1809
+ caps.add("jsx");
1810
+ break;
1814
1811
  }
1815
- console.log(
1816
- `codebyplan claude install --scope user${opts.dryRun ? " (dry-run)" : ""}: settings.json updated, 0 template files copied.`
1817
- );
1818
- } catch (err) {
1819
- console.error(
1820
- `codebyplan claude install failed: ${err instanceof Error ? err.message : String(err)}`
1821
- );
1822
- process.exitCode = 1;
1823
1812
  }
1824
- }
1825
- function countHookEntries(templatesDir) {
1826
- const p = path4.join(templatesDir, "hooks", "hooks.json");
1827
- if (!fs3.existsSync(p)) return 0;
1828
- try {
1829
- const j = JSON.parse(fs3.readFileSync(p, "utf8"));
1830
- let n = 0;
1831
- for (const blocks of Object.values(j.hooks)) {
1832
- for (const b of blocks) n += b.hooks.length;
1813
+ if (pkgJson) {
1814
+ const allDeps = {
1815
+ ...pkgJson.dependencies ?? {},
1816
+ ...pkgJson.devDependencies ?? {}
1817
+ };
1818
+ for (const dep of Object.keys(allDeps)) {
1819
+ if (SERVER_FRAMEWORK_DEPS.has(dep)) {
1820
+ caps.add("node-server");
1821
+ break;
1822
+ }
1823
+ }
1824
+ if (!caps.has("node-server") && typeof pkgJson.main === "string") {
1825
+ if (SERVER_MAIN_ENTRIES.has(pkgJson.main.trim())) {
1826
+ caps.add("node-server");
1827
+ }
1833
1828
  }
1834
- return n;
1835
- } catch (err) {
1836
- console.error(
1837
- `codebyplan: could not count hook entries in hooks.json: ${err instanceof Error ? err.message : String(err)}`
1838
- );
1839
- return 0;
1840
1829
  }
1841
- }
1842
- var init_install = __esm({
1843
- "src/cli/claude/install.ts"() {
1844
- "use strict";
1845
- init_template_walker();
1846
- init_gitignore_block();
1847
- init_manifest();
1848
- init_settings_merge();
1849
- init_statusline_config();
1830
+ if (!caps.has("node-server") && await fileExists(join9(dirPath, "src", "main.ts"))) {
1831
+ caps.add("node-server");
1850
1832
  }
1851
- });
1852
-
1853
- // src/cli/setup.ts
1854
- var setup_exports = {};
1855
- __export(setup_exports, {
1856
- runSetup: () => runSetup
1857
- });
1858
- import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile6 } from "node:fs/promises";
1859
- import { homedir as homedir4 } from "node:os";
1860
- import { join as join9 } from "node:path";
1861
- import { stdin, stdout as stdout2 } from "node:process";
1862
- import { createInterface } from "node:readline/promises";
1863
- function getConfigPath(scope) {
1864
- return scope === "user" ? join9(homedir4(), ".claude.json") : join9(process.cwd(), ".mcp.json");
1833
+ if (pkgJson && pkgJson.bin) {
1834
+ if (typeof pkgJson.bin === "string" && pkgJson.bin.trim().length > 0) {
1835
+ caps.add("cli-bin");
1836
+ } else if (typeof pkgJson.bin === "object" && pkgJson.bin !== null && Object.keys(pkgJson.bin).length > 0) {
1837
+ caps.add("cli-bin");
1838
+ }
1839
+ }
1840
+ return caps;
1865
1841
  }
1866
- async function readConfig(path10) {
1842
+ async function detectFromDirectory(dirPath) {
1843
+ const seen = /* @__PURE__ */ new Map();
1844
+ let pkgJson = null;
1867
1845
  try {
1868
- const raw = await readFile6(path10, "utf-8");
1869
- const parsed = JSON.parse(raw);
1870
- if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
1871
- return parsed;
1846
+ const raw = await readFile7(join9(dirPath, "package.json"), "utf-8");
1847
+ pkgJson = JSON.parse(raw);
1848
+ const allDeps = {
1849
+ ...pkgJson.dependencies ?? {},
1850
+ ...pkgJson.devDependencies ?? {}
1851
+ };
1852
+ for (const depName of Object.keys(allDeps)) {
1853
+ const rule = PACKAGE_MAP[depName];
1854
+ if (rule) {
1855
+ const key = rule.name.toLowerCase();
1856
+ if (!seen.has(key)) {
1857
+ seen.set(key, { name: rule.name, category: rule.category });
1858
+ }
1859
+ continue;
1860
+ }
1861
+ for (const { prefix, rule: prefixRule } of PACKAGE_PREFIX_MAP) {
1862
+ if (depName.startsWith(prefix)) {
1863
+ const key = prefixRule.name.toLowerCase();
1864
+ if (!seen.has(key)) {
1865
+ seen.set(key, {
1866
+ name: prefixRule.name,
1867
+ category: prefixRule.category
1868
+ });
1869
+ }
1870
+ break;
1871
+ }
1872
+ }
1872
1873
  }
1873
- return {};
1874
1874
  } catch {
1875
- return {};
1876
1875
  }
1877
- }
1878
- function buildMcpEntry() {
1879
- return { type: "http", url: mcpEndpoint() };
1880
- }
1881
- async function writeMcpConfig(scope) {
1882
- const configPath = getConfigPath(scope);
1883
- const config = await readConfig(configPath);
1884
- if (typeof config.mcpServers !== "object" || config.mcpServers === null || Array.isArray(config.mcpServers)) {
1885
- config.mcpServers = {};
1886
- }
1887
- config.mcpServers.codebyplan = buildMcpEntry();
1888
- await writeFile6(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
1889
- return configPath;
1890
- }
1891
- async function fetchRepos(auth) {
1892
- const baseUrl3 = (process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com").replace(/\/$/, "");
1893
- const headers = auth.kind === "oauth" ? { Authorization: `Bearer ${await getAccessToken()}` } : { "x-api-key": auth.apiKey };
1894
- const res = await fetch(`${baseUrl3}/api/repos`, {
1895
- headers,
1896
- signal: AbortSignal.timeout(1e4)
1897
- });
1898
- if (res.status === 401) {
1899
- if (auth.kind === "oauth") {
1900
- throw new Error(
1901
- "Session rejected. Run `codebyplan logout && codebyplan login`."
1902
- );
1876
+ for (const { file, rule } of CONFIG_FILE_MAP) {
1877
+ const key = rule.name.toLowerCase();
1878
+ if (!seen.has(key) && await fileExists(join9(dirPath, file))) {
1879
+ seen.set(key, { name: rule.name, category: rule.category });
1903
1880
  }
1904
- throw new Error("Invalid API key. Please check and try again.");
1905
1881
  }
1906
- if (!res.ok) {
1907
- console.log(
1908
- ` Warning: API returned status ${res.status}, but continuing.
1909
- `
1882
+ const capabilities = await detectCapabilities(dirPath, pkgJson);
1883
+ const capsArray = Array.from(capabilities).sort();
1884
+ const entries = Array.from(seen.values()).map((entry) => {
1885
+ const isBearer = CAPABILITY_BEARER_NAMES.has(entry.name.toLowerCase());
1886
+ return {
1887
+ ...entry,
1888
+ capabilities: isBearer ? capsArray : []
1889
+ };
1890
+ });
1891
+ if (capsArray.length > 0) {
1892
+ const hasBearerWithCaps = entries.some(
1893
+ (e) => CAPABILITY_BEARER_NAMES.has(e.name.toLowerCase()) && (e.capabilities?.some((c) => c.length > 0) ?? false)
1910
1894
  );
1911
- return [];
1895
+ if (!hasBearerWithCaps) {
1896
+ entries.push({
1897
+ name: SYNTHETIC_CARRIER_NAME,
1898
+ category: "tool",
1899
+ capabilities: capsArray
1900
+ });
1901
+ }
1912
1902
  }
1913
- const body = await res.json();
1914
- return body.data ?? [];
1903
+ return entries.sort(compareByCategoryThenName);
1915
1904
  }
1916
- async function chooseAuthMode(rl) {
1917
- console.log(" How would you like to authenticate?\n");
1918
- console.log(" 1. OAuth \u2014 open browser to sign in (recommended)");
1919
- console.log(
1920
- " 2. Legacy API key \u2014 for the 30-day shim (deprecated 2026-06-30)\n"
1921
- );
1922
- const choice = (await rl.question(" Select (1/2, default: 1): ")).trim();
1923
- if (choice === "" || choice === "1") {
1924
- try {
1925
- await runLogin();
1926
- return { kind: "oauth" };
1927
- } catch (err) {
1928
- const msg = err instanceof Error ? err.message : String(err);
1929
- console.log(`
1930
- OAuth login failed: ${msg}
1931
- `);
1932
- return null;
1905
+ function collectCapabilities(entries) {
1906
+ const set = /* @__PURE__ */ new Set();
1907
+ for (const entry of entries) {
1908
+ if (!entry.capabilities) continue;
1909
+ for (const cap of entry.capabilities) {
1910
+ if (typeof cap === "string" && cap.length > 0) {
1911
+ set.add(cap.toLowerCase());
1912
+ }
1933
1913
  }
1934
1914
  }
1935
- const apiKey = (await rl.question(" Enter your API key: ")).trim();
1936
- if (!apiKey) {
1937
- console.log("\n No API key provided. Aborting setup.\n");
1938
- return null;
1939
- }
1940
- return { kind: "legacy", apiKey };
1915
+ return Array.from(set).sort();
1941
1916
  }
1942
- async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
1943
- const codebyplanDir = join9(projectPath, ".codebyplan");
1944
- await mkdir4(codebyplanDir, { recursive: true });
1945
- const repoJson = {
1946
- repo_id: selectedRepo.id
1947
- };
1948
- const repoAny = selectedRepo;
1949
- if (typeof repoAny.organization_id === "string") {
1950
- repoJson.organization_id = repoAny.organization_id;
1917
+ async function detectTechStack(projectPath) {
1918
+ const repo = await detectFromDirectory(projectPath);
1919
+ const discoveredApps = await discoverMonorepoApps(projectPath);
1920
+ const apps = [];
1921
+ for (const app of discoveredApps) {
1922
+ const stack = await detectFromDirectory(app.absPath);
1923
+ if (stack.length > 0) {
1924
+ apps.push({ name: app.name, path: app.path, stack });
1925
+ }
1951
1926
  }
1952
- if (typeof repoAny.project_id === "string") {
1953
- repoJson.project_id = repoAny.project_id;
1927
+ const flatMap = /* @__PURE__ */ new Map();
1928
+ for (const entry of repo) {
1929
+ flatMap.set(entry.name.toLowerCase(), entry);
1954
1930
  }
1955
- await writeFile6(
1956
- join9(codebyplanDir, "repo.json"),
1957
- JSON.stringify(repoJson, null, 2) + "\n",
1958
- "utf-8"
1931
+ for (const app of apps) {
1932
+ for (const entry of app.stack) {
1933
+ const key = entry.name.toLowerCase();
1934
+ const existing = flatMap.get(key);
1935
+ if (!existing) {
1936
+ flatMap.set(key, entry);
1937
+ } else if (entry.capabilities?.length) {
1938
+ const merged = Array.from(
1939
+ /* @__PURE__ */ new Set([...existing.capabilities ?? [], ...entry.capabilities])
1940
+ ).sort();
1941
+ flatMap.set(key, { ...existing, capabilities: merged });
1942
+ }
1943
+ }
1944
+ }
1945
+ const repoCleaned = stripSyntheticIfCovered(repo).sort(
1946
+ compareByCategoryThenName
1959
1947
  );
1960
- await writeFile6(
1961
- join9(codebyplanDir, "server.json"),
1962
- JSON.stringify(
1963
- {
1964
- server_port: null,
1965
- server_type: null,
1966
- auto_push_enabled: false,
1967
- port_allocations: []
1968
- },
1969
- null,
1970
- 2
1971
- ) + "\n",
1972
- "utf-8"
1973
- );
1974
- await writeFile6(
1975
- join9(codebyplanDir, "git.json"),
1976
- JSON.stringify({ git_branch: null, branch_config: null }, null, 2) + "\n",
1977
- "utf-8"
1978
- );
1979
- await writeFile6(
1980
- join9(codebyplanDir, "shipment.json"),
1981
- JSON.stringify({ shipment: null }, null, 2) + "\n",
1982
- "utf-8"
1983
- );
1984
- await writeFile6(
1985
- join9(codebyplanDir, "vendor.json"),
1986
- JSON.stringify({}, null, 2) + "\n",
1987
- "utf-8"
1988
- );
1989
- await writeFile6(
1990
- join9(codebyplanDir, "e2e.json"),
1991
- JSON.stringify({}, null, 2) + "\n",
1992
- "utf-8"
1993
- );
1994
- await writeFile6(
1995
- join9(codebyplanDir, "eslint.json"),
1996
- JSON.stringify({}, null, 2) + "\n",
1997
- "utf-8"
1948
+ const appsCleaned = apps.map((a) => ({
1949
+ ...a,
1950
+ stack: stripSyntheticIfCovered(a.stack).sort(compareByCategoryThenName)
1951
+ }));
1952
+ const flat = stripSyntheticIfCovered(Array.from(flatMap.values())).sort(
1953
+ compareByCategoryThenName
1998
1954
  );
1999
- const statuslinePath = join9(codebyplanDir, "statusline.json");
2000
- let statuslineExists = false;
2001
- try {
2002
- await readFile6(statuslinePath, "utf-8");
2003
- statuslineExists = true;
2004
- } catch {
1955
+ return { repo: repoCleaned, apps: appsCleaned, flat };
1956
+ }
1957
+ function stripSyntheticIfCovered(entries) {
1958
+ const synth = entries.find((e) => e.name === SYNTHETIC_CARRIER_NAME);
1959
+ if (!synth?.capabilities?.length) return entries;
1960
+ const realBearerCaps = /* @__PURE__ */ new Set();
1961
+ for (const e of entries) {
1962
+ if (e.name === SYNTHETIC_CARRIER_NAME) continue;
1963
+ for (const c of e.capabilities ?? []) realBearerCaps.add(c);
2005
1964
  }
2006
- if (!statuslineExists) {
2007
- await writeFile6(
2008
- statuslinePath,
2009
- JSON.stringify(STATUSLINE_DEFAULTS, null, 2) + "\n",
2010
- "utf-8"
1965
+ if (synth.capabilities.every((c) => realBearerCaps.has(c))) {
1966
+ return entries.filter((e) => e.name !== SYNTHETIC_CARRIER_NAME);
1967
+ }
1968
+ return entries;
1969
+ }
1970
+ function mergeTechStack(remote, detected) {
1971
+ const stripCarrier = (e) => e.name !== SYNTHETIC_CARRIER_NAME;
1972
+ const cleanDetected = {
1973
+ repo: detected.repo.filter(stripCarrier),
1974
+ apps: detected.apps.map((a) => ({
1975
+ ...a,
1976
+ stack: a.stack.filter(stripCarrier)
1977
+ })),
1978
+ flat: detected.flat.filter(stripCarrier)
1979
+ };
1980
+ const remoteResult = Array.isArray(remote) ? {
1981
+ repo: remote.filter(stripCarrier),
1982
+ apps: [],
1983
+ flat: remote.filter(stripCarrier)
1984
+ } : {
1985
+ repo: remote.repo.filter(stripCarrier),
1986
+ apps: remote.apps.map((a) => ({
1987
+ ...a,
1988
+ stack: a.stack.filter(stripCarrier)
1989
+ })),
1990
+ flat: remote.flat.filter(stripCarrier)
1991
+ };
1992
+ const seen = /* @__PURE__ */ new Map();
1993
+ for (const entry of remoteResult.flat) {
1994
+ seen.set(entry.name.toLowerCase(), entry);
1995
+ }
1996
+ const added = [];
1997
+ for (const entry of cleanDetected.flat) {
1998
+ const key = entry.name.toLowerCase();
1999
+ if (!seen.has(key)) {
2000
+ seen.set(key, entry);
2001
+ added.push(entry);
2002
+ }
2003
+ }
2004
+ const flat = Array.from(seen.values()).sort(compareByCategoryThenName);
2005
+ const merged = {
2006
+ repo: cleanDetected.repo,
2007
+ apps: cleanDetected.apps,
2008
+ flat
2009
+ };
2010
+ return { merged, added };
2011
+ }
2012
+ function parseTechStack(raw) {
2013
+ if (Array.isArray(raw)) {
2014
+ return raw.filter(
2015
+ (item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.category === "string"
2011
2016
  );
2012
2017
  }
2013
- await writeLocalConfig(projectPath, { device_id: deviceId });
2014
- console.log(` Created ${codebyplanDir}/`);
2015
- console.log(
2016
- ` repo.json, server.json, git.json, shipment.json, vendor.json, e2e.json, eslint.json, statusline.json`
2017
- );
2018
- console.log(` device.local.json (gitignored)`);
2019
- const gitignoreAction = await ensureManagedGitignoreBlock(projectPath);
2020
- if (gitignoreAction !== "unchanged") {
2021
- console.log(" Updated .gitignore (codebyplan managed block)");
2018
+ if (typeof raw === "object" && raw !== null && "flat" in raw) {
2019
+ return parseTechStack(raw.flat);
2022
2020
  }
2021
+ return [];
2023
2022
  }
2024
- async function runSetup() {
2025
- const rl = createInterface({ input: stdin, output: stdout2 });
2026
- console.log("\n CodeByPlan Setup\n");
2027
- console.log(" This will configure Claude Code to use CodeByPlan.\n");
2023
+ function parseAppTechStacks(raw) {
2024
+ if (!Array.isArray(raw)) return [];
2025
+ return raw.filter(
2026
+ (item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.path === "string" && Array.isArray(item.stack)
2027
+ ).map((item) => ({
2028
+ name: item.name,
2029
+ path: item.path,
2030
+ stack: parseTechStack(item.stack)
2031
+ }));
2032
+ }
2033
+ function parseTechStackResult(raw) {
2034
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw) && "flat" in raw) {
2035
+ const obj = raw;
2036
+ return {
2037
+ repo: parseTechStack(obj.repo),
2038
+ apps: parseAppTechStacks(obj.apps),
2039
+ flat: parseTechStack(obj.flat)
2040
+ };
2041
+ }
2042
+ const flat = parseTechStack(raw);
2043
+ return { repo: flat, apps: [], flat };
2044
+ }
2045
+ function categorizeDependency(depName) {
2046
+ const rule = PACKAGE_MAP[depName];
2047
+ if (rule) return rule.category;
2048
+ for (const { prefix, rule: prefixRule } of PACKAGE_PREFIX_MAP) {
2049
+ if (depName.startsWith(prefix)) return prefixRule.category;
2050
+ }
2051
+ return "other";
2052
+ }
2053
+ async function findPackageJsonFiles(dir, projectPath, depth = 0) {
2054
+ if (depth > 4) return [];
2055
+ const results = [];
2056
+ const pkgPath = join9(dir, "package.json");
2057
+ if (await fileExists(pkgPath)) {
2058
+ results.push(pkgPath);
2059
+ }
2028
2060
  try {
2029
- const auth = await chooseAuthMode(rl);
2030
- if (!auth) return;
2031
- console.log("\n Where should the MCP server be configured?\n");
2032
- console.log(" 1. Global \u2014 available in all projects (~/.claude.json)");
2033
- console.log(" 2. Project \u2014 only this project (.mcp.json)\n");
2034
- const scopeInput = (await rl.question(" Select (1/2, default: 1): ")).trim();
2035
- const scope = scopeInput === "2" ? "project" : "user";
2036
- console.log("\n Configuring MCP server...");
2037
- const configPath = await writeMcpConfig(scope);
2038
- console.log(` Done! Config written to ${configPath}
2039
- `);
2040
- let repos = [];
2041
- try {
2042
- repos = await fetchRepos(auth);
2043
- } catch (err) {
2044
- const msg = err instanceof Error ? err.message : String(err);
2045
- console.log(` ${msg}
2046
- `);
2047
- return;
2048
- }
2049
- if (repos.length === 0) {
2050
- console.log(
2051
- " No repositories found. Create one at https://codebyplan.com after setup.\n"
2052
- );
2053
- console.log(
2054
- " Setup complete! Start a new Claude Code session to begin.\n"
2055
- );
2056
- return;
2057
- }
2058
- console.log(" Initialize this project?\n");
2059
- const initAnswer = (await rl.question(" Link to a repository? (Y/n): ")).trim().toLowerCase();
2060
- if (initAnswer !== "" && initAnswer !== "y" && initAnswer !== "yes") {
2061
- console.log(
2062
- " Setup complete! Start a new Claude Code session to begin.\n"
2061
+ const entries = await readdir(dir, { withFileTypes: true });
2062
+ for (const entry of entries) {
2063
+ if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
2064
+ const subResults = await findPackageJsonFiles(
2065
+ join9(dir, entry.name),
2066
+ projectPath,
2067
+ depth + 1
2063
2068
  );
2064
- return;
2065
- }
2066
- if (auth.kind === "legacy") {
2067
- process.env.CODEBYPLAN_API_KEY = auth.apiKey;
2068
- }
2069
- console.log("\n Your repositories:\n");
2070
- for (let i = 0; i < repos.length; i++) {
2071
- console.log(` ${i + 1}. ${repos[i].name}`);
2072
- }
2073
- console.log();
2074
- const choice = (await rl.question(" Select a repository (number): ")).trim();
2075
- const index = parseInt(choice, 10) - 1;
2076
- if (isNaN(index) || index < 0 || index >= repos.length) {
2077
- console.log(" Invalid selection. Skipping project init.\n");
2078
- return;
2079
- }
2080
- const selectedRepo = repos[index];
2081
- console.log(`
2082
- Selected: ${selectedRepo.name}
2083
- `);
2084
- const projectPath = process.cwd();
2085
- const pathBasedId = await resolveAndCacheWorktreeId(
2086
- selectedRepo.id,
2087
- projectPath,
2088
- { skipWrite: true }
2089
- );
2090
- const deviceId = await getOrCreateDeviceId(projectPath);
2091
- let branch = "main";
2092
- try {
2093
- const { execSync: execSync9 } = await import("node:child_process");
2094
- branch = execSync9("git symbolic-ref --short HEAD", {
2095
- cwd: projectPath,
2096
- encoding: "utf-8"
2097
- }).trim();
2098
- } catch {
2069
+ results.push(...subResults);
2099
2070
  }
2100
- const tupleId = await resolveWorktreeId({
2101
- repoId: selectedRepo.id,
2102
- repoPath: projectPath,
2103
- branch,
2104
- deviceId
2105
- });
2106
- const _worktreeId = tupleId ?? pathBasedId;
2107
- void _worktreeId;
2108
- await writeCodebyplanDirectory(projectPath, selectedRepo, deviceId);
2109
- const priorExitCode = process.exitCode;
2110
- let installFailed = false;
2071
+ } catch {
2072
+ }
2073
+ return results;
2074
+ }
2075
+ async function scanAllDependencies(projectPath) {
2076
+ const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
2077
+ const dependencies = [];
2078
+ for (const pkgPath of packageJsonPaths) {
2111
2079
  try {
2112
- const { runInstall: runInstall2 } = await Promise.resolve().then(() => (init_install(), install_exports));
2113
- await runInstall2(
2114
- { yes: true, dryRun: false, verbose: false, scope: "project" },
2115
- { projectDir: projectPath }
2116
- );
2117
- installFailed = process.exitCode !== priorExitCode;
2080
+ const raw = await readFile7(pkgPath, "utf-8");
2081
+ const pkg = JSON.parse(raw);
2082
+ const sourcePath = relative2(projectPath, pkgPath);
2083
+ const depSections = [
2084
+ { deps: pkg.dependencies, depType: "production", isDev: false },
2085
+ { deps: pkg.devDependencies, depType: "dev", isDev: true },
2086
+ { deps: pkg.peerDependencies, depType: "peer", isDev: false },
2087
+ { deps: pkg.optionalDependencies, depType: "optional", isDev: false }
2088
+ ];
2089
+ for (const { deps, depType, isDev } of depSections) {
2090
+ if (!deps) continue;
2091
+ for (const [name, version] of Object.entries(deps)) {
2092
+ dependencies.push({
2093
+ name,
2094
+ version,
2095
+ category: categorizeDependency(name),
2096
+ source_path: sourcePath,
2097
+ is_dev: isDev,
2098
+ dep_type: depType
2099
+ });
2100
+ }
2101
+ }
2118
2102
  } catch {
2119
- installFailed = true;
2120
2103
  }
2121
- if (installFailed) {
2122
- process.exitCode = priorExitCode;
2123
- console.log(
2124
- " Warning: .claude/ install step failed. Run `codebyplan claude install` manually."
2125
- );
2126
- }
2127
- const existingRenderer = await readStatuslineLocalConfig(projectPath);
2128
- const isInteractive = process.stdin.isTTY === true;
2129
- if (!existingRenderer && isInteractive) {
2130
- const availability = detectAvailableRenderers();
2131
- const available = Object.entries(availability).filter(([, v]) => v).map(([k]) => k).join(", ");
2132
- console.log(
2133
- `
2134
- Which statusline renderer would you like to use? (available: ${available})`
2135
- );
2136
- console.log(" bash \u2014 shell script, zero dependencies (default)");
2137
- console.log(" node \u2014 Node.js renderer");
2138
- console.log(" python \u2014 Python 3 renderer");
2139
- const rendererInput = (await rl.question(
2140
- " Select renderer (bash/node/python, default: bash): "
2141
- )).trim().toLowerCase();
2142
- const chosen = rendererInput === "node" ? "node" : rendererInput === "python" ? "python" : "bash";
2143
- await writeStatuslineLocalConfig(projectPath, { renderer: chosen });
2144
- if (!availability[chosen]) {
2145
- console.log(
2146
- ` Warning: '${chosen}' was not detected on this machine. You can change it later with \`codebyplan statusline\`.`
2147
- );
2148
- } else {
2149
- console.log(` Renderer set to '${chosen}'.`);
2104
+ }
2105
+ return { dependencies };
2106
+ }
2107
+ var PACKAGE_MAP, PACKAGE_PREFIX_MAP, CONFIG_FILE_MAP, SYNTHETIC_CARRIER_NAME, CAPABILITY_BEARER_NAMES, JSX_TEST_PATTERN, JSX_SKIP_DIRS, JSX_SCAN_DIRS, SERVER_MAIN_ENTRIES, SERVER_FRAMEWORK_DEPS, compareByCategoryThenName, SKIP_DIRS;
2108
+ var init_tech_detect = __esm({
2109
+ "src/lib/tech-detect.ts"() {
2110
+ "use strict";
2111
+ PACKAGE_MAP = {
2112
+ // Frameworks
2113
+ next: { name: "Next.js", category: "framework" },
2114
+ nuxt: { name: "Nuxt", category: "framework" },
2115
+ gatsby: { name: "Gatsby", category: "framework" },
2116
+ express: { name: "Express", category: "framework" },
2117
+ fastify: { name: "Fastify", category: "framework" },
2118
+ hono: { name: "Hono", category: "framework" },
2119
+ "@remix-run/node": { name: "Remix", category: "framework" },
2120
+ svelte: { name: "Svelte", category: "framework" },
2121
+ astro: { name: "Astro", category: "framework" },
2122
+ "@angular/core": { name: "Angular", category: "framework" },
2123
+ "@nestjs/core": { name: "NestJS", category: "framework" },
2124
+ // Libraries (UI)
2125
+ react: { name: "React", category: "framework" },
2126
+ vue: { name: "Vue", category: "framework" },
2127
+ "solid-js": { name: "Solid", category: "framework" },
2128
+ preact: { name: "Preact", category: "framework" },
2129
+ // Languages (detected via devDeps)
2130
+ typescript: { name: "TypeScript", category: "language" },
2131
+ // Styling
2132
+ tailwindcss: { name: "Tailwind CSS", category: "styling" },
2133
+ sass: { name: "SCSS", category: "styling" },
2134
+ "styled-components": { name: "styled-components", category: "styling" },
2135
+ "@emotion/react": { name: "Emotion", category: "styling" },
2136
+ // Database
2137
+ prisma: { name: "Prisma", category: "database" },
2138
+ "@prisma/client": { name: "Prisma", category: "database" },
2139
+ "drizzle-orm": { name: "Drizzle", category: "database" },
2140
+ "@supabase/supabase-js": { name: "Supabase", category: "database" },
2141
+ mongoose: { name: "MongoDB", category: "database" },
2142
+ typeorm: { name: "TypeORM", category: "database" },
2143
+ knex: { name: "Knex", category: "database" },
2144
+ // Testing
2145
+ jest: { name: "Jest", category: "testing" },
2146
+ vitest: { name: "Vitest", category: "testing" },
2147
+ mocha: { name: "Mocha", category: "testing" },
2148
+ playwright: { name: "Playwright", category: "testing" },
2149
+ "@playwright/test": { name: "Playwright", category: "testing" },
2150
+ cypress: { name: "Cypress", category: "testing" },
2151
+ supertest: { name: "Supertest", category: "testing" },
2152
+ webdriverio: { name: "WebdriverIO", category: "testing" },
2153
+ "@wdio/cli": { name: "WebdriverIO", category: "testing" },
2154
+ "@vscode/test-cli": { name: "VS Code Test CLI", category: "testing" },
2155
+ // Build tools
2156
+ turbo: { name: "Turborepo", category: "build" },
2157
+ vite: { name: "Vite", category: "build" },
2158
+ webpack: { name: "Webpack", category: "build" },
2159
+ esbuild: { name: "esbuild", category: "build" },
2160
+ rollup: { name: "Rollup", category: "build" },
2161
+ nx: { name: "Nx", category: "build" },
2162
+ lerna: { name: "Lerna", category: "build" },
2163
+ tsup: { name: "tsup", category: "build" },
2164
+ "@swc/core": { name: "SWC", category: "build" },
2165
+ parcel: { name: "Parcel", category: "build" },
2166
+ // Tools
2167
+ eslint: { name: "ESLint", category: "tool" },
2168
+ prettier: { name: "Prettier", category: "tool" },
2169
+ "@biomejs/biome": { name: "Biome", category: "tool" },
2170
+ storybook: { name: "Storybook", category: "tool" },
2171
+ // Component libs
2172
+ "@mui/material": { name: "MUI", category: "component-lib" },
2173
+ "@chakra-ui/react": { name: "Chakra UI", category: "component-lib" },
2174
+ "@mantine/core": { name: "Mantine", category: "component-lib" },
2175
+ // GraphQL
2176
+ graphql: { name: "GraphQL", category: "graphql" },
2177
+ "@apollo/client": { name: "Apollo Client", category: "graphql" },
2178
+ urql: { name: "urql", category: "graphql" },
2179
+ "graphql-request": { name: "graphql-request", category: "graphql" },
2180
+ // Documentation
2181
+ typedoc: { name: "TypeDoc", category: "documentation" },
2182
+ "@docusaurus/core": { name: "Docusaurus", category: "documentation" },
2183
+ vitepress: { name: "VitePress", category: "documentation" },
2184
+ // Code quality
2185
+ husky: { name: "Husky", category: "quality" },
2186
+ "lint-staged": { name: "lint-staged", category: "quality" },
2187
+ commitlint: { name: "commitlint", category: "quality" },
2188
+ "@commitlint/cli": { name: "commitlint", category: "quality" },
2189
+ // Mobile
2190
+ "react-native": { name: "React Native", category: "mobile" },
2191
+ expo: { name: "Expo", category: "mobile" }
2192
+ };
2193
+ PACKAGE_PREFIX_MAP = [
2194
+ {
2195
+ prefix: "@radix-ui/",
2196
+ rule: { name: "Radix UI", category: "component-lib" }
2197
+ },
2198
+ { prefix: "@storybook/", rule: { name: "Storybook", category: "tool" } },
2199
+ {
2200
+ prefix: "@testing-library/",
2201
+ rule: { name: "Testing Library", category: "testing" }
2202
+ }
2203
+ ];
2204
+ CONFIG_FILE_MAP = [
2205
+ { file: "tsconfig.json", rule: { name: "TypeScript", category: "language" } },
2206
+ { file: "next.config.js", rule: { name: "Next.js", category: "framework" } },
2207
+ { file: "next.config.mjs", rule: { name: "Next.js", category: "framework" } },
2208
+ { file: "next.config.ts", rule: { name: "Next.js", category: "framework" } },
2209
+ {
2210
+ file: "tailwind.config.js",
2211
+ rule: { name: "Tailwind CSS", category: "styling" }
2212
+ },
2213
+ {
2214
+ file: "tailwind.config.ts",
2215
+ rule: { name: "Tailwind CSS", category: "styling" }
2216
+ },
2217
+ { file: "turbo.json", rule: { name: "Turborepo", category: "build" } },
2218
+ {
2219
+ file: "docker-compose.yml",
2220
+ rule: { name: "Docker", category: "deployment" }
2221
+ },
2222
+ {
2223
+ file: "docker-compose.yaml",
2224
+ rule: { name: "Docker", category: "deployment" }
2225
+ },
2226
+ { file: "Dockerfile", rule: { name: "Docker", category: "deployment" } },
2227
+ { file: "vercel.json", rule: { name: "Vercel", category: "deployment" } },
2228
+ { file: ".storybook/main.js", rule: { name: "Storybook", category: "tool" } },
2229
+ { file: ".storybook/main.ts", rule: { name: "Storybook", category: "tool" } },
2230
+ {
2231
+ file: ".storybook/main.mjs",
2232
+ rule: { name: "Storybook", category: "tool" }
2233
+ },
2234
+ {
2235
+ file: "components.json",
2236
+ rule: { name: "shadcn/ui", category: "component-lib" }
2237
+ },
2238
+ { file: "nx.json", rule: { name: "Nx", category: "build" } },
2239
+ { file: "lerna.json", rule: { name: "Lerna", category: "build" } },
2240
+ {
2241
+ file: "playwright.config.ts",
2242
+ rule: { name: "Playwright", category: "testing" }
2243
+ },
2244
+ {
2245
+ file: "playwright.config.js",
2246
+ rule: { name: "Playwright", category: "testing" }
2247
+ },
2248
+ {
2249
+ file: "playwright.config.mjs",
2250
+ rule: { name: "Playwright", category: "testing" }
2251
+ },
2252
+ { file: "wdio.conf.ts", rule: { name: "WebdriverIO", category: "testing" } },
2253
+ {
2254
+ file: "wdio.conf.js",
2255
+ rule: { name: "WebdriverIO", category: "testing" }
2256
+ },
2257
+ {
2258
+ file: "maestro/config.yaml",
2259
+ rule: { name: "Maestro", category: "testing" }
2150
2260
  }
2151
- }
2152
- console.log(
2153
- "\n Run `npx codebyplan config` to sync repo config from the DB."
2154
- );
2155
- console.log(
2156
- " Setup complete! Start a new Claude Code session to begin.\n"
2157
- );
2158
- } finally {
2159
- rl.close();
2160
- }
2161
- }
2162
- var init_setup = __esm({
2163
- "src/cli/setup.ts"() {
2164
- "use strict";
2165
- init_gitignore_block();
2166
- init_local_config();
2167
- init_statusline_config();
2168
- init_resolve_worktree();
2169
- init_token_refresh();
2170
- init_urls();
2171
- init_login();
2261
+ ];
2262
+ SYNTHETIC_CARRIER_NAME = "__capabilities__";
2263
+ CAPABILITY_BEARER_NAMES = /* @__PURE__ */ new Set([
2264
+ "react",
2265
+ "next.js",
2266
+ "vue",
2267
+ "svelte",
2268
+ "solid",
2269
+ "preact",
2270
+ "remix",
2271
+ "astro",
2272
+ "angular",
2273
+ "nestjs",
2274
+ "nuxt",
2275
+ "gatsby",
2276
+ "express",
2277
+ "fastify",
2278
+ "hono",
2279
+ "react native",
2280
+ "expo"
2281
+ ]);
2282
+ JSX_TEST_PATTERN = /\.(test|spec)\.(tsx|jsx)$/;
2283
+ JSX_SKIP_DIRS = /* @__PURE__ */ new Set(["__tests__", "test", "tests"]);
2284
+ JSX_SCAN_DIRS = ["src", "app", "pages"];
2285
+ SERVER_MAIN_ENTRIES = /* @__PURE__ */ new Set([
2286
+ "dist/main.js",
2287
+ "dist/server.js",
2288
+ "dist/index.js",
2289
+ "main.js",
2290
+ "server.js"
2291
+ ]);
2292
+ SERVER_FRAMEWORK_DEPS = /* @__PURE__ */ new Set([
2293
+ "@nestjs/core",
2294
+ "express",
2295
+ "fastify",
2296
+ "hono"
2297
+ ]);
2298
+ compareByCategoryThenName = (a, b) => {
2299
+ const catCmp = a.category.localeCompare(b.category);
2300
+ if (catCmp !== 0) return catCmp;
2301
+ return a.name.localeCompare(b.name);
2302
+ };
2303
+ SKIP_DIRS = /* @__PURE__ */ new Set([
2304
+ "node_modules",
2305
+ ".next",
2306
+ "dist",
2307
+ ".turbo",
2308
+ ".git",
2309
+ "coverage",
2310
+ "build",
2311
+ "out",
2312
+ ".vercel",
2313
+ ".expo"
2314
+ ]);
2172
2315
  }
2173
2316
  });
2174
2317
 
2175
- // src/lib/flags.ts
2176
- import { readFile as readFile7 } from "node:fs/promises";
2177
- import { join as join10, resolve as resolve2 } from "node:path";
2178
- async function findCodebyplanConfig(startDir, maxDepth = 20) {
2179
- let cursor = resolve2(startDir);
2180
- for (let depth = 0; depth < maxDepth; depth++) {
2181
- const sentinelPath2 = join10(cursor, ".codebyplan", "repo.json");
2182
- try {
2183
- const raw = await readFile7(sentinelPath2, "utf-8");
2184
- const parsed = JSON.parse(raw);
2185
- return { path: sentinelPath2, contents: parsed };
2186
- } catch {
2187
- }
2188
- const legacyPath = join10(cursor, ".codebyplan.json");
2189
- try {
2190
- const raw = await readFile7(legacyPath, "utf-8");
2191
- const parsed = JSON.parse(raw);
2192
- return { path: legacyPath, contents: parsed };
2193
- } catch {
2194
- }
2195
- const parent = resolve2(cursor, "..");
2196
- if (parent === cursor) return null;
2197
- cursor = parent;
2198
- }
2199
- return null;
2200
- }
2201
- function parseFlags(startIndex) {
2202
- const flags = {};
2203
- const args = process.argv.slice(startIndex);
2204
- for (let i = 0; i < args.length; i++) {
2205
- const arg = args[i];
2206
- if (arg.startsWith("--") && i + 1 < args.length) {
2207
- const key = arg.slice(2);
2208
- flags[key] = args[++i];
2209
- }
2318
+ // src/lib/lsp-detect.ts
2319
+ import { spawnSync as spawnSync2 } from "node:child_process";
2320
+ function mapTechStackToLsp(detectedNames) {
2321
+ const pluginIndex = new Map(
2322
+ LSP_TABLE.map((s) => [s.plugin, s])
2323
+ );
2324
+ const seen = /* @__PURE__ */ new Set();
2325
+ const result = [];
2326
+ for (const name of detectedNames) {
2327
+ const pluginId = ALIAS_MAP[name.toLowerCase()];
2328
+ if (!pluginId) continue;
2329
+ if (seen.has(pluginId)) continue;
2330
+ seen.add(pluginId);
2331
+ const server = pluginIndex.get(pluginId);
2332
+ if (server) result.push(server);
2210
2333
  }
2211
- return flags;
2334
+ return result;
2212
2335
  }
2213
- function hasFlag(name, startIndex) {
2214
- return process.argv.slice(startIndex).includes(`--${name}`);
2336
+ function binaryOnPath(binary) {
2337
+ return probeWith("which", binary) || probeWith("where", binary);
2215
2338
  }
2216
- async function resolveConfig(flags) {
2217
- const projectPath = flags["path"] ?? process.cwd();
2218
- let repoId = flags["repo-id"] ?? process.env.CODEBYPLAN_REPO_ID;
2219
- let worktreeId = flags["worktree-id"] ?? process.env.CODEBYPLAN_WORKTREE_ID;
2220
- if (!repoId) {
2221
- const found = await findCodebyplanConfig(projectPath);
2222
- if (found) {
2223
- repoId = found.contents.repo_id;
2224
- if (!worktreeId) worktreeId = found.contents.worktree_id;
2225
- }
2226
- }
2227
- if (!repoId) {
2228
- throw new Error(
2229
- `Could not determine repo_id.
2230
-
2231
- Provide it via one of:
2232
- --repo-id <uuid> CLI flag
2233
- CODEBYPLAN_REPO_ID=<uuid> environment variable
2234
- .codebyplan/repo.json { "repo_id": "<uuid>" } in project root
2235
- Run 'codebyplan setup' to initialize this project`
2236
- );
2339
+ function probeWith(probe, binary) {
2340
+ try {
2341
+ const result = spawnSync2(probe, [binary], {
2342
+ encoding: "utf-8",
2343
+ timeout: 5e3
2344
+ });
2345
+ if (result.error) return false;
2346
+ return result.status === 0;
2347
+ } catch {
2348
+ return false;
2237
2349
  }
2238
- return { repoId, worktreeId, projectPath };
2239
2350
  }
2240
- var init_flags = __esm({
2241
- "src/lib/flags.ts"() {
2351
+ var LSP_TABLE, ALIAS_MAP;
2352
+ var init_lsp_detect = __esm({
2353
+ "src/lib/lsp-detect.ts"() {
2242
2354
  "use strict";
2355
+ LSP_TABLE = [
2356
+ {
2357
+ language: "TypeScript",
2358
+ plugin: "typescript-lsp",
2359
+ binary: "typescript-language-server",
2360
+ npmPackage: "typescript-language-server",
2361
+ installHint: "npm install -g typescript-language-server"
2362
+ },
2363
+ {
2364
+ language: "Python",
2365
+ plugin: "pyright-lsp",
2366
+ binary: "pyright-langserver",
2367
+ npmPackage: "pyright",
2368
+ installHint: "npm install -g pyright"
2369
+ },
2370
+ {
2371
+ language: "PHP",
2372
+ plugin: "php-lsp",
2373
+ binary: "intelephense",
2374
+ npmPackage: "intelephense",
2375
+ installHint: "npm install -g intelephense"
2376
+ },
2377
+ {
2378
+ language: "Rust",
2379
+ plugin: "rust-analyzer-lsp",
2380
+ binary: "rust-analyzer",
2381
+ npmPackage: null,
2382
+ installHint: "rustup component add rust-analyzer"
2383
+ },
2384
+ {
2385
+ language: "Go",
2386
+ plugin: "gopls-lsp",
2387
+ binary: "gopls",
2388
+ npmPackage: null,
2389
+ installHint: "go install golang.org/x/tools/gopls@latest"
2390
+ },
2391
+ {
2392
+ language: "Swift",
2393
+ plugin: "swift-lsp",
2394
+ binary: "sourcekit-lsp",
2395
+ npmPackage: null,
2396
+ installHint: "Install Xcode and the Swift toolchain; sourcekit-lsp is included in the toolchain"
2397
+ },
2398
+ {
2399
+ language: "C/C++",
2400
+ plugin: "clangd-lsp",
2401
+ binary: "clangd",
2402
+ npmPackage: null,
2403
+ installHint: "brew install llvm # macOS\napt install clangd # Debian/Ubuntu"
2404
+ },
2405
+ {
2406
+ language: "C#",
2407
+ plugin: "csharp-lsp",
2408
+ binary: "csharp-ls",
2409
+ npmPackage: null,
2410
+ installHint: "dotnet tool install -g csharp-ls"
2411
+ },
2412
+ {
2413
+ language: "Java",
2414
+ plugin: "jdtls-lsp",
2415
+ binary: "jdtls",
2416
+ npmPackage: null,
2417
+ installHint: "Download Eclipse JDT LS from https://github.com/eclipse-jdtls/eclipse.jdt.ls/releases"
2418
+ },
2419
+ {
2420
+ language: "Kotlin",
2421
+ plugin: "kotlin-lsp",
2422
+ binary: "kotlin-language-server",
2423
+ npmPackage: null,
2424
+ installHint: "Download from https://github.com/fwcd/kotlin-language-server/releases"
2425
+ },
2426
+ {
2427
+ language: "Lua",
2428
+ plugin: "lua-lsp",
2429
+ binary: "lua-language-server",
2430
+ npmPackage: null,
2431
+ installHint: "brew install lua-language-server # macOS"
2432
+ }
2433
+ ];
2434
+ ALIAS_MAP = {
2435
+ // TypeScript / JavaScript
2436
+ typescript: "typescript-lsp",
2437
+ javascript: "typescript-lsp",
2438
+ "next.js": "typescript-lsp",
2439
+ react: "typescript-lsp",
2440
+ vue: "typescript-lsp",
2441
+ svelte: "typescript-lsp",
2442
+ angular: "typescript-lsp",
2443
+ nestjs: "typescript-lsp",
2444
+ // Python
2445
+ python: "pyright-lsp",
2446
+ // PHP
2447
+ php: "php-lsp",
2448
+ // Rust
2449
+ rust: "rust-analyzer-lsp",
2450
+ // Go
2451
+ go: "gopls-lsp",
2452
+ golang: "gopls-lsp",
2453
+ // Swift
2454
+ swift: "swift-lsp",
2455
+ // C/C++
2456
+ "c++": "clangd-lsp",
2457
+ "c/c++": "clangd-lsp",
2458
+ c: "clangd-lsp",
2459
+ clang: "clangd-lsp",
2460
+ cpp: "clangd-lsp",
2461
+ // C#
2462
+ "c#": "csharp-lsp",
2463
+ csharp: "csharp-lsp",
2464
+ ".net": "csharp-lsp",
2465
+ dotnet: "csharp-lsp",
2466
+ // Java
2467
+ java: "jdtls-lsp",
2468
+ // Kotlin
2469
+ kotlin: "kotlin-lsp",
2470
+ // Lua
2471
+ lua: "lua-lsp"
2472
+ };
2243
2473
  }
2244
2474
  });
2245
2475
 
2246
- // src/cli/statusline.ts
2247
- var statusline_exports = {};
2248
- __export(statusline_exports, {
2249
- runStatusline: () => runStatusline
2476
+ // src/cli/lsp.ts
2477
+ var lsp_exports = {};
2478
+ __export(lsp_exports, {
2479
+ runLsp: () => runLsp,
2480
+ runLspFull: () => runLspFull
2250
2481
  });
2251
- import { dirname as dirname5 } from "node:path";
2252
- async function resolveProjectPath() {
2253
- const found = await findCodebyplanConfig(process.cwd());
2254
- if (found) {
2255
- return dirname5(dirname5(found.path));
2482
+ import { readFile as readFile8, writeFile as writeFile6, mkdir as mkdir4, unlink as unlink2, access as access2 } from "node:fs/promises";
2483
+ import { join as join10 } from "node:path";
2484
+ import { spawnSync as spawnSync3 } from "node:child_process";
2485
+ async function fileExists2(filePath) {
2486
+ try {
2487
+ await access2(filePath);
2488
+ return true;
2489
+ } catch {
2490
+ return false;
2256
2491
  }
2257
- return process.cwd();
2258
2492
  }
2259
- function parseRendererArg(arg) {
2260
- const normalised = arg.startsWith("--") ? arg.slice(2) : arg;
2261
- if (VALID_RENDERERS2.has(normalised)) {
2262
- return normalised;
2493
+ async function readJsonFile(filePath) {
2494
+ try {
2495
+ const raw = await readFile8(filePath, "utf-8");
2496
+ return JSON.parse(raw);
2497
+ } catch {
2498
+ return null;
2263
2499
  }
2264
- return null;
2265
2500
  }
2266
- async function runStatusline(args) {
2267
- const positional = args.filter((a) => !a.startsWith("-"));
2268
- const flagArgs = args.filter((a) => a.startsWith("--"));
2269
- const rendererTokens = [...positional, ...flagArgs].filter(
2270
- (a) => parseRendererArg(a) !== null
2501
+ function tryNpmInstallGlobal(pkg) {
2502
+ try {
2503
+ const result = spawnSync3("npm", ["i", "-g", pkg], {
2504
+ encoding: "utf-8",
2505
+ timeout: 6e4,
2506
+ // "pipe" (not "inherit"): inheriting the parent's fds can surface EPIPE/EIO
2507
+ // in piped/CI contexts as result.error even when npm itself succeeded, and
2508
+ // interleaves npm output with our own console.log lines unpredictably.
2509
+ stdio: "pipe"
2510
+ });
2511
+ if (result.error) return false;
2512
+ return result.status === 0;
2513
+ } catch {
2514
+ return false;
2515
+ }
2516
+ }
2517
+ async function runLspFull(projectPath, opts = {}) {
2518
+ const dryRun = opts.dryRun ?? false;
2519
+ console.log("\n CodeByPlan LSP");
2520
+ console.log(` Path: ${projectPath}
2521
+ `);
2522
+ const rootDetected = await detectTechStack(projectPath);
2523
+ const allTechNames = /* @__PURE__ */ new Set();
2524
+ for (const entry of rootDetected.flat) {
2525
+ allTechNames.add(entry.name);
2526
+ }
2527
+ const servers = mapTechStackToLsp(Array.from(allTechNames));
2528
+ if (servers.length === 0) {
2529
+ console.log(" No LSP-relevant tech detected. Nothing to enable.\n");
2530
+ return;
2531
+ }
2532
+ console.log(
2533
+ ` Detected LSP servers: ${servers.map((s) => s.plugin).join(", ")}
2534
+ `
2271
2535
  );
2272
- if (rendererTokens.length > 1) {
2273
- process.stderr.write(
2274
- "codebyplan statusline: bash, node, and python are mutually exclusive; pass only one.\n"
2536
+ const settingsPath = join10(projectPath, ".claude", "settings.json");
2537
+ let settings = {};
2538
+ const existingSettingsRaw = await readJsonFile(settingsPath);
2539
+ if (existingSettingsRaw) {
2540
+ settings = existingSettingsRaw;
2541
+ }
2542
+ mergeEnabledPluginsIntoSettings(
2543
+ settings,
2544
+ servers.map((s) => s.plugin)
2545
+ );
2546
+ if (dryRun) {
2547
+ console.log(` [dry-run] would update ${settingsPath}`);
2548
+ } else {
2549
+ await mkdir4(join10(projectPath, ".claude"), { recursive: true });
2550
+ await writeFile6(
2551
+ settingsPath,
2552
+ JSON.stringify(settings, null, 2) + "\n",
2553
+ "utf-8"
2275
2554
  );
2276
- process.exitCode = 1;
2277
- return;
2555
+ console.log(` Updated ${settingsPath}`);
2278
2556
  }
2279
- let rendererArg;
2280
- for (const a of [...positional, ...flagArgs]) {
2281
- const parsed = parseRendererArg(a);
2282
- if (parsed !== null) {
2283
- rendererArg = a;
2284
- break;
2557
+ const lspJsonPath = join10(projectPath, ".codebyplan", "lsp.json");
2558
+ const lspJsonContent = {
2559
+ servers: servers.map((s) => ({
2560
+ plugin: s.plugin,
2561
+ binary: s.binary,
2562
+ install: s.npmPackage ? `npm i -g ${s.npmPackage}` : s.installHint
2563
+ }))
2564
+ };
2565
+ if (dryRun) {
2566
+ console.log(` [dry-run] would write ${lspJsonPath}`);
2567
+ } else {
2568
+ await mkdir4(join10(projectPath, ".codebyplan"), { recursive: true });
2569
+ await writeFile6(
2570
+ lspJsonPath,
2571
+ JSON.stringify(lspJsonContent, null, 2) + "\n",
2572
+ "utf-8"
2573
+ );
2574
+ console.log(` Wrote ${lspJsonPath}`);
2575
+ }
2576
+ const todoDir = join10(projectPath, ".codebyplan", "todo", "session-start");
2577
+ const autoInstalled = [];
2578
+ const nudged = [];
2579
+ for (const server of servers) {
2580
+ if (binaryOnPath(server.binary)) {
2581
+ continue;
2582
+ }
2583
+ if (server.npmPackage !== null) {
2584
+ if (dryRun) {
2585
+ console.log(` [dry-run] would install ${server.npmPackage}`);
2586
+ autoInstalled.push(server.plugin);
2587
+ } else {
2588
+ console.log(` Installing ${server.npmPackage} globally...`);
2589
+ const success = tryNpmInstallGlobal(server.npmPackage);
2590
+ if (success && binaryOnPath(server.binary)) {
2591
+ autoInstalled.push(server.plugin);
2592
+ console.log(` Installed ${server.npmPackage}.`);
2593
+ continue;
2594
+ }
2595
+ console.log(
2596
+ ` npm install failed for ${server.npmPackage}. Writing nudge file.`
2597
+ );
2598
+ await mkdir4(todoDir, { recursive: true });
2599
+ const nudgeFile = join10(todoDir, `install-${server.binary}.md`);
2600
+ const nudgeContent = `Run \`${server.installHint}\` to enable ${server.language} LSP support in Claude Code.
2601
+ `;
2602
+ await writeFile6(nudgeFile, nudgeContent, "utf-8");
2603
+ nudged.push(server.binary);
2604
+ }
2605
+ } else {
2606
+ if (dryRun) {
2607
+ console.log(` [dry-run] would write nudge for ${server.binary}`);
2608
+ nudged.push(server.binary);
2609
+ } else {
2610
+ await mkdir4(todoDir, { recursive: true });
2611
+ const nudgeFile = join10(todoDir, `install-${server.binary}.md`);
2612
+ const nudgeContent = `Run \`${server.installHint}\` to enable ${server.language} LSP support in Claude Code.
2613
+ `;
2614
+ await writeFile6(nudgeFile, nudgeContent, "utf-8");
2615
+ nudged.push(server.binary);
2616
+ }
2285
2617
  }
2286
2618
  }
2287
- const unknownPositional = args.filter(
2288
- (a) => !a.startsWith("-") && !VALID_RENDERERS2.has(a)
2619
+ const pluginNames = servers.map((s) => s.plugin);
2620
+ console.log(`
2621
+ Summary:`);
2622
+ console.log(
2623
+ ` Enabled plugins: ${pluginNames.length > 0 ? pluginNames.join(", ") : "none"}`
2289
2624
  );
2290
- if (unknownPositional.length > 0) {
2291
- process.stderr.write(
2292
- `codebyplan statusline: unknown argument '${unknownPositional[0]}'.
2293
-
2294
- Usage:
2295
- codebyplan statusline Show current renderer and line toggles
2296
- codebyplan statusline bash|node|python Set the renderer
2297
- codebyplan statusline --bash|--node|--python Set the renderer (flag form)
2298
- `
2625
+ console.log(
2626
+ ` (settings.json key format: <plugin>@claude-plugins-official)`
2627
+ );
2628
+ if (autoInstalled.length > 0) {
2629
+ console.log(
2630
+ ` ${dryRun ? "Would auto-install:" : "Auto-installed: "} ${autoInstalled.join(", ")}`
2299
2631
  );
2300
- process.exitCode = 1;
2301
- return;
2302
2632
  }
2303
- const unknownFlags = flagArgs.filter((a) => parseRendererArg(a) === null);
2304
- if (unknownFlags.length > 0) {
2305
- process.stderr.write(
2306
- `codebyplan statusline: unknown flag '${unknownFlags[0]}'.
2307
-
2308
- Usage:
2309
- codebyplan statusline Show current renderer and line toggles
2310
- codebyplan statusline bash|node|python Set the renderer
2311
- codebyplan statusline --bash|--node|--python Set the renderer (flag form)
2312
- `
2633
+ if (nudged.length > 0) {
2634
+ console.log(
2635
+ ` ${dryRun ? "Would write nudge for:" : "Manual install required for:"} ${nudged.join(", ")}`
2313
2636
  );
2314
- process.exitCode = 1;
2315
- return;
2316
- }
2317
- const projectPath = await resolveProjectPath();
2318
- if (rendererArg !== void 0) {
2319
- const chosen = parseRendererArg(rendererArg);
2320
- await writeStatuslineLocalConfig(projectPath, { renderer: chosen });
2321
- const availability2 = detectAvailableRenderers();
2322
- if (!availability2[chosen]) {
2323
- process.stderr.write(
2324
- `Warning: '${chosen}' was not detected on this machine. The renderer is saved but may not run.
2325
- `
2637
+ if (!dryRun) {
2638
+ console.log(
2639
+ ` See .codebyplan/todo/session-start/ for install instructions.`
2326
2640
  );
2327
2641
  }
2328
- console.log(`Statusline renderer set to '${chosen}'.`);
2329
- return;
2330
2642
  }
2331
- const availability = detectAvailableRenderers();
2332
- const [renderer, config] = await Promise.all([
2333
- resolveRenderer(projectPath),
2334
- readStatuslineConfig(projectPath)
2335
- ]);
2336
- const availStr = Object.entries(availability).map(([k, v]) => `${k}: ${v ? "yes" : "no"}`).join(", ");
2337
- console.log(`
2338
- Statusline configuration`);
2339
- console.log(` Renderer: ${renderer}`);
2340
- console.log(` Available: ${availStr}`);
2341
- console.log(` no_color: ${config.no_color}`);
2342
2643
  console.log(`
2343
- Line toggles:`);
2344
- for (const [key, val] of Object.entries(config.lines)) {
2345
- console.log(` ${key.padEnd(14)} ${val ? "on" : "off"}`);
2644
+ LSP setup complete.
2645
+ `);
2646
+ }
2647
+ async function runLspCheck(projectPath) {
2648
+ const lspJsonPath = join10(projectPath, ".codebyplan", "lsp.json");
2649
+ const lspJson = await readJsonFile(lspJsonPath);
2650
+ if (!lspJson || !Array.isArray(lspJson.servers)) {
2651
+ return;
2652
+ }
2653
+ const todoDir = join10(projectPath, ".codebyplan", "todo", "session-start");
2654
+ const stillMissing = [];
2655
+ for (const entry of lspJson.servers) {
2656
+ const { binary, plugin } = entry;
2657
+ const found = binaryOnPath(binary);
2658
+ const fullServer = LSP_TABLE.find((s) => s.plugin === plugin);
2659
+ if (found) {
2660
+ const nudgeFile = join10(todoDir, `install-${binary}.md`);
2661
+ if (await fileExists2(nudgeFile)) {
2662
+ try {
2663
+ await unlink2(nudgeFile);
2664
+ } catch {
2665
+ }
2666
+ }
2667
+ } else {
2668
+ const hint = fullServer?.installHint ?? entry.install;
2669
+ stillMissing.push(hint);
2670
+ }
2671
+ }
2672
+ for (const hint of stillMissing) {
2673
+ console.log(hint);
2346
2674
  }
2347
- console.log();
2348
2675
  }
2349
- var VALID_RENDERERS2;
2350
- var init_statusline = __esm({
2351
- "src/cli/statusline.ts"() {
2676
+ async function runLsp() {
2677
+ const checkMode = hasFlag("check", 3);
2678
+ const dryRun = hasFlag("dry-run", 3);
2679
+ const pathIdx = process.argv.indexOf("--path");
2680
+ const pathVal = pathIdx !== -1 ? process.argv[pathIdx + 1] : void 0;
2681
+ const projectPath = pathVal != null && !pathVal.startsWith("--") ? pathVal : process.cwd();
2682
+ if (checkMode) {
2683
+ await runLspCheck(projectPath);
2684
+ } else {
2685
+ await runLspFull(projectPath, { dryRun });
2686
+ }
2687
+ }
2688
+ var init_lsp = __esm({
2689
+ "src/cli/lsp.ts"() {
2352
2690
  "use strict";
2353
2691
  init_flags();
2354
- init_statusline_config();
2355
- VALID_RENDERERS2 = /* @__PURE__ */ new Set(["bash", "node", "python"]);
2692
+ init_tech_detect();
2693
+ init_lsp_detect();
2694
+ init_settings_merge();
2356
2695
  }
2357
2696
  });
2358
2697
 
2359
- // src/cli/logout.ts
2360
- var logout_exports = {};
2361
- __export(logout_exports, {
2362
- runLogout: () => runLogout
2698
+ // src/cli/claude/install.ts
2699
+ var install_exports = {};
2700
+ __export(install_exports, {
2701
+ resolveTemplatesDir: () => resolveTemplatesDir,
2702
+ runInstall: () => runInstall
2363
2703
  });
2364
- async function runLogout() {
2365
- await clearTokens();
2366
- await clearClient();
2367
- console.log("\n Logged out.\n");
2704
+ import * as fs3 from "node:fs";
2705
+ import * as os2 from "node:os";
2706
+ import * as path4 from "node:path";
2707
+ import { fileURLToPath } from "node:url";
2708
+ function resolveTemplatesDir() {
2709
+ const here = path4.dirname(fileURLToPath(import.meta.url));
2710
+ const candidates = [
2711
+ path4.resolve(here, "..", "templates"),
2712
+ path4.resolve(here, "..", "..", "templates"),
2713
+ path4.resolve(here, "..", "..", "..", "templates")
2714
+ ];
2715
+ for (const c of candidates) {
2716
+ if (fs3.existsSync(c) && fs3.statSync(c).isDirectory()) {
2717
+ return c;
2718
+ }
2719
+ }
2720
+ throw new Error(
2721
+ `codebyplan: could not locate templates/ directory. Probed:
2722
+ ${candidates.join(
2723
+ "\n "
2724
+ )}`
2725
+ );
2368
2726
  }
2369
- var init_logout = __esm({
2370
- "src/cli/logout.ts"() {
2371
- "use strict";
2372
- init_client_registration();
2373
- init_keychain();
2727
+ async function runInstall(opts, deps = {}) {
2728
+ await Promise.resolve();
2729
+ const scope = opts.scope ?? "project";
2730
+ if (scope === "user") {
2731
+ if (opts.renderer) {
2732
+ console.warn(
2733
+ "codebyplan claude install: --bash/--node/--python is ignored for --scope user (no project root for statusline.local.json)."
2734
+ );
2735
+ }
2736
+ runInstallUser(opts, deps);
2737
+ return;
2374
2738
  }
2375
- });
2376
-
2377
- // src/cli/whoami.ts
2378
- var whoami_exports = {};
2379
- __export(whoami_exports, {
2380
- runWhoami: () => runWhoami
2381
- });
2382
- async function fetchMe(accessToken) {
2739
+ const projectDir = deps.projectDir ?? process.cwd();
2740
+ let templatesDir;
2383
2741
  try {
2384
- const res = await fetch(meEndpoint(), {
2385
- headers: { Authorization: `Bearer ${accessToken}` },
2386
- signal: AbortSignal.timeout(1e4)
2387
- });
2388
- if (!res.ok) return null;
2389
- const body = await res.json();
2390
- return {
2391
- email: body.email ?? null,
2392
- username: body.username ?? null
2393
- };
2394
- } catch {
2395
- return null;
2742
+ templatesDir = deps.templatesDir ?? resolveTemplatesDir();
2743
+ } catch (err) {
2744
+ console.error(
2745
+ err instanceof Error ? err.message : `codebyplan claude install: ${String(err)}`
2746
+ );
2747
+ process.exitCode = 1;
2748
+ return;
2749
+ }
2750
+ try {
2751
+ const files = walkTemplates(templatesDir);
2752
+ const manifestEntries = [];
2753
+ for (const f of files) {
2754
+ const absDest = path4.join(projectDir, ".claude", f.dest);
2755
+ const absSrc = path4.join(templatesDir, f.src);
2756
+ if (opts.dryRun) {
2757
+ if (opts.verbose) {
2758
+ console.log(`[dry-run] would copy ${f.src} \u2192 .claude/${f.dest}`);
2759
+ }
2760
+ } else {
2761
+ fs3.mkdirSync(path4.dirname(absDest), { recursive: true });
2762
+ fs3.copyFileSync(absSrc, absDest);
2763
+ if (opts.verbose) {
2764
+ console.log(`copied ${f.src} \u2192 .claude/${f.dest}`);
2765
+ }
2766
+ }
2767
+ manifestEntries.push({ src: f.src, dest: f.dest, hash: f.hash });
2768
+ }
2769
+ const hooksJsonPath = path4.join(templatesDir, "hooks", "hooks.json");
2770
+ const baseSettingsPath = path4.join(
2771
+ templatesDir,
2772
+ "settings.project.base.json"
2773
+ );
2774
+ const hasHooks = fs3.existsSync(hooksJsonPath);
2775
+ const hasBase = fs3.existsSync(baseSettingsPath);
2776
+ const settingsPath = path4.join(projectDir, ".claude", "settings.json");
2777
+ const existingSettings = fs3.existsSync(settingsPath) ? JSON.parse(fs3.readFileSync(settingsPath, "utf8")) : {};
2778
+ if (hasBase) {
2779
+ const base = JSON.parse(
2780
+ fs3.readFileSync(baseSettingsPath, "utf8")
2781
+ );
2782
+ mergeBaseSettingsIntoSettings(existingSettings, base);
2783
+ }
2784
+ if (hasHooks) {
2785
+ const hooksJson = JSON.parse(
2786
+ fs3.readFileSync(hooksJsonPath, "utf8")
2787
+ );
2788
+ mergeHooksIntoSettings(existingSettings, hooksJson);
2789
+ }
2790
+ if (!opts.dryRun) {
2791
+ fs3.mkdirSync(path4.dirname(settingsPath), { recursive: true });
2792
+ fs3.writeFileSync(
2793
+ settingsPath,
2794
+ JSON.stringify(existingSettings, null, 2) + "\n",
2795
+ "utf8"
2796
+ );
2797
+ } else if (opts.verbose) {
2798
+ console.log(
2799
+ `[dry-run] would merge settings into ${path4.relative(projectDir, settingsPath)}`
2800
+ );
2801
+ }
2802
+ const gitignoreAction = await ensureManagedGitignoreBlock(
2803
+ projectDir,
2804
+ opts.dryRun
2805
+ );
2806
+ if (opts.verbose && gitignoreAction !== "unchanged") {
2807
+ console.log(
2808
+ `${opts.dryRun ? "[dry-run] would " : ""}${gitignoreAction} managed .gitignore block in ${path4.relative(projectDir, path4.join(projectDir, ".gitignore"))}`
2809
+ );
2810
+ }
2811
+ if (!opts.dryRun) {
2812
+ const manifest = defaultManifest();
2813
+ manifest.files = manifestEntries;
2814
+ writeManifest(projectDir, manifest);
2815
+ }
2816
+ console.log(
2817
+ `codebyplan claude install${opts.dryRun ? " (dry-run)" : ""}: ${manifestEntries.length} files, ${countHookEntries(templatesDir)} hook entries.`
2818
+ );
2819
+ if (opts.renderer && !opts.dryRun) {
2820
+ await writeStatuslineLocalConfig(projectDir, { renderer: opts.renderer });
2821
+ }
2822
+ try {
2823
+ const { runLspFull: runLspFull2 } = await Promise.resolve().then(() => (init_lsp(), lsp_exports));
2824
+ await runLspFull2(projectDir, { dryRun: opts.dryRun });
2825
+ } catch {
2826
+ }
2827
+ } catch (err) {
2828
+ console.error(
2829
+ `codebyplan claude install failed: ${err instanceof Error ? err.message : String(err)}`
2830
+ );
2831
+ process.exitCode = 1;
2396
2832
  }
2397
2833
  }
2398
- async function runWhoami(opts = {}) {
2399
- const tokens = await loadTokens();
2400
- if (opts.json) {
2401
- if (!tokens) {
2402
- console.log("null");
2834
+ function runInstallUser(opts, deps) {
2835
+ let templatesDir;
2836
+ try {
2837
+ templatesDir = deps.templatesDir ?? resolveTemplatesDir();
2838
+ } catch (err) {
2839
+ console.error(
2840
+ err instanceof Error ? err.message : `codebyplan claude install: ${String(err)}`
2841
+ );
2842
+ process.exitCode = 1;
2843
+ return;
2844
+ }
2845
+ try {
2846
+ const userDir = deps.userDir ?? path4.join(os2.homedir(), ".claude");
2847
+ const settingsPath = path4.join(userDir, "settings.json");
2848
+ const userBaseSettingsPath = path4.join(
2849
+ templatesDir,
2850
+ "settings.user.base.json"
2851
+ );
2852
+ if (!fs3.existsSync(userBaseSettingsPath)) {
2853
+ console.error(
2854
+ "codebyplan claude install: settings.user.base.json not found in templates."
2855
+ );
2403
2856
  process.exitCode = 1;
2404
2857
  return;
2405
2858
  }
2406
- const me2 = await fetchMe(tokens.access_token);
2859
+ const userBase = JSON.parse(
2860
+ fs3.readFileSync(userBaseSettingsPath, "utf8")
2861
+ );
2862
+ const existingSettings = fs3.existsSync(settingsPath) ? JSON.parse(fs3.readFileSync(settingsPath, "utf8")) : {};
2863
+ mergeBaseSettingsIntoSettings(existingSettings, userBase);
2864
+ if (!opts.dryRun) {
2865
+ fs3.mkdirSync(userDir, { recursive: true });
2866
+ fs3.writeFileSync(
2867
+ settingsPath,
2868
+ JSON.stringify(existingSettings, null, 2) + "\n",
2869
+ "utf8"
2870
+ );
2871
+ const manifest = defaultManifest();
2872
+ manifest.files = [];
2873
+ writeManifestForScope("user", manifest, userDir);
2874
+ } else if (opts.verbose) {
2875
+ console.log(
2876
+ `[dry-run] would merge user base settings into ${settingsPath}`
2877
+ );
2878
+ }
2407
2879
  console.log(
2408
- JSON.stringify({
2409
- user_id: tokens.user_id,
2410
- email: me2?.email ?? tokens.email,
2411
- username: me2?.username ?? null
2412
- })
2880
+ `codebyplan claude install --scope user${opts.dryRun ? " (dry-run)" : ""}: settings.json updated, 0 template files copied.`
2881
+ );
2882
+ } catch (err) {
2883
+ console.error(
2884
+ `codebyplan claude install failed: ${err instanceof Error ? err.message : String(err)}`
2413
2885
  );
2414
- return;
2415
- }
2416
- if (!tokens) {
2417
- console.log("\n Not logged in. Run `codebyplan login` to authenticate.\n");
2418
2886
  process.exitCode = 1;
2419
- return;
2420
2887
  }
2421
- const me = await fetchMe(tokens.access_token);
2422
- const degraded = me === null;
2423
- const email = me?.email ?? tokens.email;
2424
- const username = me?.username ?? null;
2425
- console.log(`
2426
- user_id: ${tokens.user_id}`);
2427
- console.log(` email: ${email ?? "(unknown)"}`);
2428
- console.log(` username: ${username ?? "(not set)"}`);
2429
- if (degraded) {
2430
- console.log(` (profile fetch failed \u2014 showing stored credentials)
2431
- `);
2432
- } else {
2433
- console.log();
2888
+ }
2889
+ function countHookEntries(templatesDir) {
2890
+ const p = path4.join(templatesDir, "hooks", "hooks.json");
2891
+ if (!fs3.existsSync(p)) return 0;
2892
+ try {
2893
+ const j = JSON.parse(fs3.readFileSync(p, "utf8"));
2894
+ let n = 0;
2895
+ for (const blocks of Object.values(j.hooks)) {
2896
+ for (const b of blocks) n += b.hooks.length;
2897
+ }
2898
+ return n;
2899
+ } catch (err) {
2900
+ console.error(
2901
+ `codebyplan: could not count hook entries in hooks.json: ${err instanceof Error ? err.message : String(err)}`
2902
+ );
2903
+ return 0;
2434
2904
  }
2435
2905
  }
2436
- var init_whoami = __esm({
2437
- "src/cli/whoami.ts"() {
2906
+ var init_install = __esm({
2907
+ "src/cli/claude/install.ts"() {
2438
2908
  "use strict";
2439
- init_keychain();
2440
- init_urls();
2909
+ init_template_walker();
2910
+ init_gitignore_block();
2911
+ init_manifest();
2912
+ init_settings_merge();
2913
+ init_statusline_config();
2441
2914
  }
2442
2915
  });
2443
2916
 
2444
- // src/cli/upgrade-auth.ts
2445
- var upgrade_auth_exports = {};
2446
- __export(upgrade_auth_exports, {
2447
- runUpgradeAuth: () => runUpgradeAuth
2917
+ // src/cli/setup.ts
2918
+ var setup_exports = {};
2919
+ __export(setup_exports, {
2920
+ runSetup: () => runSetup
2448
2921
  });
2449
- import { readFile as readFile8, writeFile as writeFile7 } from "node:fs/promises";
2450
- import { homedir as homedir5 } from "node:os";
2451
- import { join as join11 } from "node:path";
2452
- function configPaths() {
2453
- return [join11(homedir5(), ".claude.json"), join11(process.cwd(), ".mcp.json")];
2922
+ import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
2923
+ import { homedir as homedir4 } from "node:os";
2924
+ import { join as join12 } from "node:path";
2925
+ import { stdin, stdout as stdout2 } from "node:process";
2926
+ import { createInterface } from "node:readline/promises";
2927
+ function getConfigPath(scope) {
2928
+ return scope === "user" ? join12(homedir4(), ".claude.json") : join12(process.cwd(), ".mcp.json");
2454
2929
  }
2455
- async function readConfig2(path10) {
2930
+ async function readConfig(path10) {
2456
2931
  try {
2457
- const raw = await readFile8(path10, "utf-8");
2932
+ const raw = await readFile9(path10, "utf-8");
2458
2933
  const parsed = JSON.parse(raw);
2459
2934
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
2460
2935
  return parsed;
2461
2936
  }
2462
- return null;
2937
+ return {};
2463
2938
  } catch {
2464
- return null;
2939
+ return {};
2465
2940
  }
2466
2941
  }
2467
- function entryHasLegacyApiKey(entry) {
2468
- if (!entry || !entry.headers) return false;
2469
- return "x-api-key" in entry.headers;
2942
+ function buildMcpEntry() {
2943
+ return { type: "http", url: mcpEndpoint() };
2470
2944
  }
2471
- async function rewriteConfig(path10, config, newUrl) {
2472
- const servers = config.mcpServers;
2473
- if (!servers) return false;
2474
- const entry = servers.codebyplan;
2475
- if (!entry) return false;
2476
- if (!entryHasLegacyApiKey(entry) && entry.url === newUrl && entry.type === "http")
2477
- return false;
2478
- servers.codebyplan = { type: "http", url: newUrl };
2479
- await writeFile7(path10, JSON.stringify(config, null, 2) + "\n", "utf-8");
2480
- return true;
2945
+ async function writeMcpConfig(scope) {
2946
+ const configPath = getConfigPath(scope);
2947
+ const config = await readConfig(configPath);
2948
+ if (typeof config.mcpServers !== "object" || config.mcpServers === null || Array.isArray(config.mcpServers)) {
2949
+ config.mcpServers = {};
2950
+ }
2951
+ config.mcpServers.codebyplan = buildMcpEntry();
2952
+ await writeFile7(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
2953
+ return configPath;
2481
2954
  }
2482
- async function runUpgradeAuth() {
2483
- console.log("\n Upgrading codebyplan auth to OAuth...\n");
2484
- await runLogin();
2485
- const newUrl = mcpEndpoint();
2486
- let migrated = 0;
2487
- for (const path10 of configPaths()) {
2488
- const config = await readConfig2(path10);
2489
- if (!config) continue;
2490
- const changed = await rewriteConfig(path10, config, newUrl);
2491
- if (changed) {
2492
- console.log(` Updated ${path10}`);
2493
- migrated++;
2955
+ async function fetchRepos(auth) {
2956
+ const baseUrl3 = (process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com").replace(/\/$/, "");
2957
+ const headers = auth.kind === "oauth" ? { Authorization: `Bearer ${await getAccessToken()}` } : { "x-api-key": auth.apiKey };
2958
+ const res = await fetch(`${baseUrl3}/api/repos`, {
2959
+ headers,
2960
+ signal: AbortSignal.timeout(1e4)
2961
+ });
2962
+ if (res.status === 401) {
2963
+ if (auth.kind === "oauth") {
2964
+ throw new Error(
2965
+ "Session rejected. Run `codebyplan logout && codebyplan login`."
2966
+ );
2494
2967
  }
2968
+ throw new Error("Invalid API key. Please check and try again.");
2495
2969
  }
2496
- if (migrated === 0) {
2497
- console.log(
2498
- " All codebyplan MCP entries are already up to date \u2014 nothing to change."
2499
- );
2500
- } else {
2970
+ if (!res.ok) {
2501
2971
  console.log(
2502
- `
2503
- Done. Restart Claude Code to use the new endpoint: ${newUrl}
2972
+ ` Warning: API returned status ${res.status}, but continuing.
2504
2973
  `
2505
2974
  );
2975
+ return [];
2506
2976
  }
2977
+ const body = await res.json();
2978
+ return body.data ?? [];
2507
2979
  }
2508
- var init_upgrade_auth = __esm({
2509
- "src/cli/upgrade-auth.ts"() {
2510
- "use strict";
2511
- init_urls();
2512
- init_login();
2513
- }
2514
- });
2515
-
2516
- // src/cli/confirm.ts
2517
- var confirm_exports = {};
2518
- __export(confirm_exports, {
2519
- SyncCancelledError: () => SyncCancelledError,
2520
- confirmProceed: () => confirmProceed
2521
- });
2522
- import { createInterface as createInterface2 } from "node:readline/promises";
2523
- import { stdin as stdin2, stdout as stdout3 } from "node:process";
2524
- function isAbortError(err) {
2525
- return err instanceof Error && err.code === "ABORT_ERR";
2526
- }
2527
- async function confirmProceed(message) {
2528
- const rl = createInterface2({ input: stdin2, output: stdout3 });
2529
- try {
2530
- while (true) {
2531
- const answer = await rl.question(message ?? " Proceed? [Y/n] ");
2532
- const a = answer.trim().toLowerCase();
2533
- if (a === "" || a === "y" || a === "yes") return true;
2534
- if (a === "n" || a === "no") return false;
2535
- console.log(
2536
- ` Unknown option "${answer.trim()}". Valid: y/yes, n/no, or Enter for yes.`
2537
- );
2980
+ async function chooseAuthMode(rl) {
2981
+ console.log(" How would you like to authenticate?\n");
2982
+ console.log(" 1. OAuth \u2014 open browser to sign in (recommended)");
2983
+ console.log(
2984
+ " 2. Legacy API key \u2014 for the 30-day shim (deprecated 2026-06-30)\n"
2985
+ );
2986
+ const choice = (await rl.question(" Select (1/2, default: 1): ")).trim();
2987
+ if (choice === "" || choice === "1") {
2988
+ try {
2989
+ await runLogin();
2990
+ return { kind: "oauth" };
2991
+ } catch (err) {
2992
+ const msg = err instanceof Error ? err.message : String(err);
2993
+ console.log(`
2994
+ OAuth login failed: ${msg}
2995
+ `);
2996
+ return null;
2538
2997
  }
2539
- } catch (err) {
2540
- if (isAbortError(err)) throw new SyncCancelledError();
2541
- throw err;
2542
- } finally {
2543
- rl.close();
2544
2998
  }
2999
+ const apiKey = (await rl.question(" Enter your API key: ")).trim();
3000
+ if (!apiKey) {
3001
+ console.log("\n No API key provided. Aborting setup.\n");
3002
+ return null;
3003
+ }
3004
+ return { kind: "legacy", apiKey };
2545
3005
  }
2546
- var SyncCancelledError;
2547
- var init_confirm = __esm({
2548
- "src/cli/confirm.ts"() {
2549
- "use strict";
2550
- SyncCancelledError = class extends Error {
2551
- constructor() {
2552
- super("Sync cancelled");
2553
- this.name = "SyncCancelledError";
2554
- }
2555
- };
3006
+ async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
3007
+ const codebyplanDir = join12(projectPath, ".codebyplan");
3008
+ await mkdir5(codebyplanDir, { recursive: true });
3009
+ const repoJson = {
3010
+ repo_id: selectedRepo.id
3011
+ };
3012
+ const repoAny = selectedRepo;
3013
+ if (typeof repoAny.organization_id === "string") {
3014
+ repoJson.organization_id = repoAny.organization_id;
2556
3015
  }
2557
- });
2558
-
2559
- // src/lib/tech-detect.ts
2560
- import { readFile as readFile9, access, readdir } from "node:fs/promises";
2561
- import { join as join12, relative as relative3 } from "node:path";
2562
- async function fileExists(filePath) {
3016
+ if (typeof repoAny.project_id === "string") {
3017
+ repoJson.project_id = repoAny.project_id;
3018
+ }
3019
+ await writeFile7(
3020
+ join12(codebyplanDir, "repo.json"),
3021
+ JSON.stringify(repoJson, null, 2) + "\n",
3022
+ "utf-8"
3023
+ );
3024
+ await writeFile7(
3025
+ join12(codebyplanDir, "server.json"),
3026
+ JSON.stringify(
3027
+ {
3028
+ server_port: null,
3029
+ server_type: null,
3030
+ auto_push_enabled: false,
3031
+ port_allocations: []
3032
+ },
3033
+ null,
3034
+ 2
3035
+ ) + "\n",
3036
+ "utf-8"
3037
+ );
3038
+ await writeFile7(
3039
+ join12(codebyplanDir, "git.json"),
3040
+ JSON.stringify({ git_branch: null, branch_config: null }, null, 2) + "\n",
3041
+ "utf-8"
3042
+ );
3043
+ await writeFile7(
3044
+ join12(codebyplanDir, "shipment.json"),
3045
+ JSON.stringify({ shipment: null }, null, 2) + "\n",
3046
+ "utf-8"
3047
+ );
3048
+ await writeFile7(
3049
+ join12(codebyplanDir, "vendor.json"),
3050
+ JSON.stringify({}, null, 2) + "\n",
3051
+ "utf-8"
3052
+ );
3053
+ await writeFile7(
3054
+ join12(codebyplanDir, "e2e.json"),
3055
+ JSON.stringify({}, null, 2) + "\n",
3056
+ "utf-8"
3057
+ );
3058
+ await writeFile7(
3059
+ join12(codebyplanDir, "eslint.json"),
3060
+ JSON.stringify({}, null, 2) + "\n",
3061
+ "utf-8"
3062
+ );
3063
+ const statuslinePath = join12(codebyplanDir, "statusline.json");
3064
+ let statuslineExists = false;
2563
3065
  try {
2564
- await access(filePath);
2565
- return true;
3066
+ await readFile9(statuslinePath, "utf-8");
3067
+ statuslineExists = true;
2566
3068
  } catch {
2567
- return false;
2568
3069
  }
2569
- }
2570
- async function discoverMonorepoApps(projectPath) {
2571
- const apps = [];
2572
- const patterns = [];
2573
- try {
2574
- const raw = await readFile9(
2575
- join12(projectPath, "pnpm-workspace.yaml"),
3070
+ if (!statuslineExists) {
3071
+ await writeFile7(
3072
+ statuslinePath,
3073
+ JSON.stringify(STATUSLINE_DEFAULTS, null, 2) + "\n",
2576
3074
  "utf-8"
2577
3075
  );
2578
- const matches = raw.match(/^\s*-\s*['"]?([^'"#\n]+)['"]?/gm);
2579
- if (matches) {
2580
- for (const m of matches) {
2581
- const pattern = m.replace(/^\s*-\s*['"]?/, "").replace(/['"]?\s*$/, "").trim();
2582
- if (pattern) patterns.push(pattern);
2583
- }
2584
- }
2585
- } catch {
2586
- }
2587
- if (patterns.length === 0) {
2588
- try {
2589
- const raw = await readFile9(join12(projectPath, "package.json"), "utf-8");
2590
- const pkg = JSON.parse(raw);
2591
- const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
2592
- if (ws) patterns.push(...ws);
2593
- } catch {
2594
- }
2595
3076
  }
2596
- for (const pattern of patterns) {
2597
- if (pattern.endsWith("/*")) {
2598
- const dir = pattern.slice(0, -2);
2599
- const absDir = join12(projectPath, dir);
2600
- try {
2601
- const entries = await readdir(absDir, { withFileTypes: true });
2602
- for (const entry of entries) {
2603
- if (entry.isDirectory()) {
2604
- const relPath = join12(dir, entry.name);
2605
- const absPath = join12(absDir, entry.name);
2606
- if (await fileExists(join12(absPath, "package.json"))) {
2607
- apps.push({ name: entry.name, path: relPath, absPath });
2608
- }
2609
- }
2610
- }
2611
- } catch {
2612
- }
2613
- }
3077
+ await writeLocalConfig(projectPath, { device_id: deviceId });
3078
+ console.log(` Created ${codebyplanDir}/`);
3079
+ console.log(
3080
+ ` repo.json, server.json, git.json, shipment.json, vendor.json, e2e.json, eslint.json, statusline.json`
3081
+ );
3082
+ console.log(` device.local.json (gitignored)`);
3083
+ const gitignoreAction = await ensureManagedGitignoreBlock(projectPath);
3084
+ if (gitignoreAction !== "unchanged") {
3085
+ console.log(" Updated .gitignore (codebyplan managed block)");
2614
3086
  }
2615
- return apps;
2616
3087
  }
2617
- async function hasJsxFile(dir, depth = 0) {
2618
- if (depth > 6) return false;
3088
+ async function runSetup() {
3089
+ const rl = createInterface({ input: stdin, output: stdout2 });
3090
+ console.log("\n CodeByPlan Setup\n");
3091
+ console.log(" This will configure Claude Code to use CodeByPlan.\n");
2619
3092
  try {
2620
- const entries = await readdir(dir, { withFileTypes: true });
2621
- for (const entry of entries) {
2622
- const name = entry.name;
2623
- if (entry.isDirectory()) {
2624
- if (SKIP_DIRS.has(name) || JSX_SKIP_DIRS.has(name)) continue;
2625
- if (await hasJsxFile(join12(dir, name), depth + 1)) return true;
2626
- } else if (entry.isFile()) {
2627
- if (JSX_TEST_PATTERN.test(name)) continue;
2628
- if (name.endsWith(".tsx") || name.endsWith(".jsx")) return true;
2629
- }
3093
+ const auth = await chooseAuthMode(rl);
3094
+ if (!auth) return;
3095
+ console.log("\n Where should the MCP server be configured?\n");
3096
+ console.log(" 1. Global \u2014 available in all projects (~/.claude.json)");
3097
+ console.log(" 2. Project \u2014 only this project (.mcp.json)\n");
3098
+ const scopeInput = (await rl.question(" Select (1/2, default: 1): ")).trim();
3099
+ const scope = scopeInput === "2" ? "project" : "user";
3100
+ console.log("\n Configuring MCP server...");
3101
+ const configPath = await writeMcpConfig(scope);
3102
+ console.log(` Done! Config written to ${configPath}
3103
+ `);
3104
+ let repos = [];
3105
+ try {
3106
+ repos = await fetchRepos(auth);
3107
+ } catch (err) {
3108
+ const msg = err instanceof Error ? err.message : String(err);
3109
+ console.log(` ${msg}
3110
+ `);
3111
+ return;
2630
3112
  }
2631
- } catch (err) {
2632
- if (err instanceof Error && "code" in err && err.code === "ENOENT") {
2633
- return false;
3113
+ if (repos.length === 0) {
3114
+ console.log(
3115
+ " No repositories found. Create one at https://codebyplan.com after setup.\n"
3116
+ );
3117
+ console.log(
3118
+ " Setup complete! Start a new Claude Code session to begin.\n"
3119
+ );
3120
+ return;
2634
3121
  }
2635
- console.error(
2636
- `detectCapabilities: readdir failed for ${dir}: ${err instanceof Error ? err.message : String(err)}`
2637
- );
2638
- }
2639
- return false;
2640
- }
2641
- async function detectCapabilities(dirPath, pkgJson) {
2642
- const caps = /* @__PURE__ */ new Set();
2643
- for (const sub of JSX_SCAN_DIRS) {
2644
- if (await hasJsxFile(join12(dirPath, sub))) {
2645
- caps.add("jsx");
2646
- break;
3122
+ console.log(" Initialize this project?\n");
3123
+ const initAnswer = (await rl.question(" Link to a repository? (Y/n): ")).trim().toLowerCase();
3124
+ if (initAnswer !== "" && initAnswer !== "y" && initAnswer !== "yes") {
3125
+ console.log(
3126
+ " Setup complete! Start a new Claude Code session to begin.\n"
3127
+ );
3128
+ return;
2647
3129
  }
2648
- }
2649
- if (pkgJson) {
2650
- const allDeps = {
2651
- ...pkgJson.dependencies ?? {},
2652
- ...pkgJson.devDependencies ?? {}
2653
- };
2654
- for (const dep of Object.keys(allDeps)) {
2655
- if (SERVER_FRAMEWORK_DEPS.has(dep)) {
2656
- caps.add("node-server");
2657
- break;
2658
- }
3130
+ if (auth.kind === "legacy") {
3131
+ process.env.CODEBYPLAN_API_KEY = auth.apiKey;
2659
3132
  }
2660
- if (!caps.has("node-server") && typeof pkgJson.main === "string") {
2661
- if (SERVER_MAIN_ENTRIES.has(pkgJson.main.trim())) {
2662
- caps.add("node-server");
2663
- }
3133
+ console.log("\n Your repositories:\n");
3134
+ for (let i = 0; i < repos.length; i++) {
3135
+ console.log(` ${i + 1}. ${repos[i].name}`);
2664
3136
  }
2665
- }
2666
- if (!caps.has("node-server") && await fileExists(join12(dirPath, "src", "main.ts"))) {
2667
- caps.add("node-server");
2668
- }
2669
- if (pkgJson && pkgJson.bin) {
2670
- if (typeof pkgJson.bin === "string" && pkgJson.bin.trim().length > 0) {
2671
- caps.add("cli-bin");
2672
- } else if (typeof pkgJson.bin === "object" && pkgJson.bin !== null && Object.keys(pkgJson.bin).length > 0) {
2673
- caps.add("cli-bin");
3137
+ console.log();
3138
+ const choice = (await rl.question(" Select a repository (number): ")).trim();
3139
+ const index = parseInt(choice, 10) - 1;
3140
+ if (isNaN(index) || index < 0 || index >= repos.length) {
3141
+ console.log(" Invalid selection. Skipping project init.\n");
3142
+ return;
2674
3143
  }
2675
- }
2676
- return caps;
2677
- }
2678
- async function detectFromDirectory(dirPath) {
2679
- const seen = /* @__PURE__ */ new Map();
2680
- let pkgJson = null;
2681
- try {
2682
- const raw = await readFile9(join12(dirPath, "package.json"), "utf-8");
2683
- pkgJson = JSON.parse(raw);
2684
- const allDeps = {
2685
- ...pkgJson.dependencies ?? {},
2686
- ...pkgJson.devDependencies ?? {}
2687
- };
2688
- for (const depName of Object.keys(allDeps)) {
2689
- const rule = PACKAGE_MAP[depName];
2690
- if (rule) {
2691
- const key = rule.name.toLowerCase();
2692
- if (!seen.has(key)) {
2693
- seen.set(key, { name: rule.name, category: rule.category });
2694
- }
2695
- continue;
2696
- }
2697
- for (const { prefix, rule: prefixRule } of PACKAGE_PREFIX_MAP) {
2698
- if (depName.startsWith(prefix)) {
2699
- const key = prefixRule.name.toLowerCase();
2700
- if (!seen.has(key)) {
2701
- seen.set(key, {
2702
- name: prefixRule.name,
2703
- category: prefixRule.category
2704
- });
2705
- }
2706
- break;
2707
- }
2708
- }
3144
+ const selectedRepo = repos[index];
3145
+ console.log(`
3146
+ Selected: ${selectedRepo.name}
3147
+ `);
3148
+ const projectPath = process.cwd();
3149
+ const pathBasedId = await resolveAndCacheWorktreeId(
3150
+ selectedRepo.id,
3151
+ projectPath,
3152
+ { skipWrite: true }
3153
+ );
3154
+ const deviceId = await getOrCreateDeviceId(projectPath);
3155
+ let branch = "main";
3156
+ try {
3157
+ const { execSync: execSync9 } = await import("node:child_process");
3158
+ branch = execSync9("git symbolic-ref --short HEAD", {
3159
+ cwd: projectPath,
3160
+ encoding: "utf-8"
3161
+ }).trim();
3162
+ } catch {
2709
3163
  }
2710
- } catch {
2711
- }
2712
- for (const { file, rule } of CONFIG_FILE_MAP) {
2713
- const key = rule.name.toLowerCase();
2714
- if (!seen.has(key) && await fileExists(join12(dirPath, file))) {
2715
- seen.set(key, { name: rule.name, category: rule.category });
3164
+ const tupleId = await resolveWorktreeId({
3165
+ repoId: selectedRepo.id,
3166
+ repoPath: projectPath,
3167
+ branch,
3168
+ deviceId
3169
+ });
3170
+ const _worktreeId = tupleId ?? pathBasedId;
3171
+ void _worktreeId;
3172
+ await writeCodebyplanDirectory(projectPath, selectedRepo, deviceId);
3173
+ const priorExitCode = process.exitCode;
3174
+ let installFailed = false;
3175
+ try {
3176
+ const { runInstall: runInstall2 } = await Promise.resolve().then(() => (init_install(), install_exports));
3177
+ await runInstall2(
3178
+ { yes: true, dryRun: false, verbose: false, scope: "project" },
3179
+ { projectDir: projectPath }
3180
+ );
3181
+ installFailed = process.exitCode !== priorExitCode;
3182
+ } catch {
3183
+ installFailed = true;
2716
3184
  }
2717
- }
2718
- const capabilities = await detectCapabilities(dirPath, pkgJson);
2719
- const capsArray = Array.from(capabilities).sort();
2720
- const entries = Array.from(seen.values()).map((entry) => {
2721
- const isBearer = CAPABILITY_BEARER_NAMES.has(entry.name.toLowerCase());
2722
- return {
2723
- ...entry,
2724
- capabilities: isBearer ? capsArray : []
2725
- };
2726
- });
2727
- if (capsArray.length > 0) {
2728
- const hasBearerWithCaps = entries.some(
2729
- (e) => CAPABILITY_BEARER_NAMES.has(e.name.toLowerCase()) && (e.capabilities?.some((c) => c.length > 0) ?? false)
2730
- );
2731
- if (!hasBearerWithCaps) {
2732
- entries.push({
2733
- name: SYNTHETIC_CARRIER_NAME,
2734
- category: "tool",
2735
- capabilities: capsArray
2736
- });
3185
+ if (installFailed) {
3186
+ process.exitCode = priorExitCode;
3187
+ console.log(
3188
+ " Warning: .claude/ install step failed. Run `codebyplan claude install` manually."
3189
+ );
2737
3190
  }
2738
- }
2739
- return entries.sort(compareByCategoryThenName);
2740
- }
2741
- function collectCapabilities(entries) {
2742
- const set = /* @__PURE__ */ new Set();
2743
- for (const entry of entries) {
2744
- if (!entry.capabilities) continue;
2745
- for (const cap of entry.capabilities) {
2746
- if (typeof cap === "string" && cap.length > 0) {
2747
- set.add(cap.toLowerCase());
3191
+ const existingRenderer = await readStatuslineLocalConfig(projectPath);
3192
+ const isInteractive = process.stdin.isTTY === true;
3193
+ if (!existingRenderer && isInteractive) {
3194
+ const availability = detectAvailableRenderers();
3195
+ const available = Object.entries(availability).filter(([, v]) => v).map(([k]) => k).join(", ");
3196
+ console.log(
3197
+ `
3198
+ Which statusline renderer would you like to use? (available: ${available})`
3199
+ );
3200
+ console.log(" bash \u2014 shell script, zero dependencies (default)");
3201
+ console.log(" node \u2014 Node.js renderer");
3202
+ console.log(" python \u2014 Python 3 renderer");
3203
+ const rendererInput = (await rl.question(
3204
+ " Select renderer (bash/node/python, default: bash): "
3205
+ )).trim().toLowerCase();
3206
+ const chosen = rendererInput === "node" ? "node" : rendererInput === "python" ? "python" : "bash";
3207
+ await writeStatuslineLocalConfig(projectPath, { renderer: chosen });
3208
+ if (!availability[chosen]) {
3209
+ console.log(
3210
+ ` Warning: '${chosen}' was not detected on this machine. You can change it later with \`codebyplan statusline\`.`
3211
+ );
3212
+ } else {
3213
+ console.log(` Renderer set to '${chosen}'.`);
2748
3214
  }
2749
3215
  }
3216
+ console.log(
3217
+ "\n Run `npx codebyplan config` to sync repo config from the DB."
3218
+ );
3219
+ console.log(
3220
+ " Setup complete! Start a new Claude Code session to begin.\n"
3221
+ );
3222
+ } finally {
3223
+ rl.close();
2750
3224
  }
2751
- return Array.from(set).sort();
2752
3225
  }
2753
- async function detectTechStack(projectPath) {
2754
- const repo = await detectFromDirectory(projectPath);
2755
- const discoveredApps = await discoverMonorepoApps(projectPath);
2756
- const apps = [];
2757
- for (const app of discoveredApps) {
2758
- const stack = await detectFromDirectory(app.absPath);
2759
- if (stack.length > 0) {
2760
- apps.push({ name: app.name, path: app.path, stack });
2761
- }
2762
- }
2763
- const flatMap = /* @__PURE__ */ new Map();
2764
- for (const entry of repo) {
2765
- flatMap.set(entry.name.toLowerCase(), entry);
3226
+ var init_setup = __esm({
3227
+ "src/cli/setup.ts"() {
3228
+ "use strict";
3229
+ init_gitignore_block();
3230
+ init_local_config();
3231
+ init_statusline_config();
3232
+ init_resolve_worktree();
3233
+ init_token_refresh();
3234
+ init_urls();
3235
+ init_login();
2766
3236
  }
2767
- for (const app of apps) {
2768
- for (const entry of app.stack) {
2769
- const key = entry.name.toLowerCase();
2770
- const existing = flatMap.get(key);
2771
- if (!existing) {
2772
- flatMap.set(key, entry);
2773
- } else if (entry.capabilities?.length) {
2774
- const merged = Array.from(
2775
- /* @__PURE__ */ new Set([...existing.capabilities ?? [], ...entry.capabilities])
2776
- ).sort();
2777
- flatMap.set(key, { ...existing, capabilities: merged });
2778
- }
2779
- }
3237
+ });
3238
+
3239
+ // src/cli/statusline.ts
3240
+ var statusline_exports = {};
3241
+ __export(statusline_exports, {
3242
+ runStatusline: () => runStatusline
3243
+ });
3244
+ import { dirname as dirname5 } from "node:path";
3245
+ async function resolveProjectPath() {
3246
+ const found = await findCodebyplanConfig(process.cwd());
3247
+ if (found) {
3248
+ return dirname5(dirname5(found.path));
2780
3249
  }
2781
- const repoCleaned = stripSyntheticIfCovered(repo).sort(
2782
- compareByCategoryThenName
2783
- );
2784
- const appsCleaned = apps.map((a) => ({
2785
- ...a,
2786
- stack: stripSyntheticIfCovered(a.stack).sort(compareByCategoryThenName)
2787
- }));
2788
- const flat = stripSyntheticIfCovered(Array.from(flatMap.values())).sort(
2789
- compareByCategoryThenName
2790
- );
2791
- return { repo: repoCleaned, apps: appsCleaned, flat };
3250
+ return process.cwd();
2792
3251
  }
2793
- function stripSyntheticIfCovered(entries) {
2794
- const synth = entries.find((e) => e.name === SYNTHETIC_CARRIER_NAME);
2795
- if (!synth?.capabilities?.length) return entries;
2796
- const realBearerCaps = /* @__PURE__ */ new Set();
2797
- for (const e of entries) {
2798
- if (e.name === SYNTHETIC_CARRIER_NAME) continue;
2799
- for (const c of e.capabilities ?? []) realBearerCaps.add(c);
2800
- }
2801
- if (synth.capabilities.every((c) => realBearerCaps.has(c))) {
2802
- return entries.filter((e) => e.name !== SYNTHETIC_CARRIER_NAME);
3252
+ function parseRendererArg(arg) {
3253
+ const normalised = arg.startsWith("--") ? arg.slice(2) : arg;
3254
+ if (VALID_RENDERERS2.has(normalised)) {
3255
+ return normalised;
2803
3256
  }
2804
- return entries;
3257
+ return null;
2805
3258
  }
2806
- function mergeTechStack(remote, detected) {
2807
- const stripCarrier = (e) => e.name !== SYNTHETIC_CARRIER_NAME;
2808
- const cleanDetected = {
2809
- repo: detected.repo.filter(stripCarrier),
2810
- apps: detected.apps.map((a) => ({
2811
- ...a,
2812
- stack: a.stack.filter(stripCarrier)
2813
- })),
2814
- flat: detected.flat.filter(stripCarrier)
2815
- };
2816
- const remoteResult = Array.isArray(remote) ? {
2817
- repo: remote.filter(stripCarrier),
2818
- apps: [],
2819
- flat: remote.filter(stripCarrier)
2820
- } : {
2821
- repo: remote.repo.filter(stripCarrier),
2822
- apps: remote.apps.map((a) => ({
2823
- ...a,
2824
- stack: a.stack.filter(stripCarrier)
2825
- })),
2826
- flat: remote.flat.filter(stripCarrier)
2827
- };
2828
- const seen = /* @__PURE__ */ new Map();
2829
- for (const entry of remoteResult.flat) {
2830
- seen.set(entry.name.toLowerCase(), entry);
3259
+ async function runStatusline(args) {
3260
+ const positional = args.filter((a) => !a.startsWith("-"));
3261
+ const flagArgs = args.filter((a) => a.startsWith("--"));
3262
+ const rendererTokens = [...positional, ...flagArgs].filter(
3263
+ (a) => parseRendererArg(a) !== null
3264
+ );
3265
+ if (rendererTokens.length > 1) {
3266
+ process.stderr.write(
3267
+ "codebyplan statusline: bash, node, and python are mutually exclusive; pass only one.\n"
3268
+ );
3269
+ process.exitCode = 1;
3270
+ return;
2831
3271
  }
2832
- const added = [];
2833
- for (const entry of cleanDetected.flat) {
2834
- const key = entry.name.toLowerCase();
2835
- if (!seen.has(key)) {
2836
- seen.set(key, entry);
2837
- added.push(entry);
3272
+ let rendererArg;
3273
+ for (const a of [...positional, ...flagArgs]) {
3274
+ const parsed = parseRendererArg(a);
3275
+ if (parsed !== null) {
3276
+ rendererArg = a;
3277
+ break;
2838
3278
  }
2839
3279
  }
2840
- const flat = Array.from(seen.values()).sort(compareByCategoryThenName);
2841
- const merged = {
2842
- repo: cleanDetected.repo,
2843
- apps: cleanDetected.apps,
2844
- flat
2845
- };
2846
- return { merged, added };
2847
- }
2848
- function parseTechStack(raw) {
2849
- if (Array.isArray(raw)) {
2850
- return raw.filter(
2851
- (item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.category === "string"
3280
+ const unknownPositional = args.filter(
3281
+ (a) => !a.startsWith("-") && !VALID_RENDERERS2.has(a)
3282
+ );
3283
+ if (unknownPositional.length > 0) {
3284
+ process.stderr.write(
3285
+ `codebyplan statusline: unknown argument '${unknownPositional[0]}'.
3286
+
3287
+ Usage:
3288
+ codebyplan statusline Show current renderer and line toggles
3289
+ codebyplan statusline bash|node|python Set the renderer
3290
+ codebyplan statusline --bash|--node|--python Set the renderer (flag form)
3291
+ `
3292
+ );
3293
+ process.exitCode = 1;
3294
+ return;
3295
+ }
3296
+ const unknownFlags = flagArgs.filter((a) => parseRendererArg(a) === null);
3297
+ if (unknownFlags.length > 0) {
3298
+ process.stderr.write(
3299
+ `codebyplan statusline: unknown flag '${unknownFlags[0]}'.
3300
+
3301
+ Usage:
3302
+ codebyplan statusline Show current renderer and line toggles
3303
+ codebyplan statusline bash|node|python Set the renderer
3304
+ codebyplan statusline --bash|--node|--python Set the renderer (flag form)
3305
+ `
2852
3306
  );
3307
+ process.exitCode = 1;
3308
+ return;
2853
3309
  }
2854
- if (typeof raw === "object" && raw !== null && "flat" in raw) {
2855
- return parseTechStack(raw.flat);
3310
+ const projectPath = await resolveProjectPath();
3311
+ if (rendererArg !== void 0) {
3312
+ const chosen = parseRendererArg(rendererArg);
3313
+ await writeStatuslineLocalConfig(projectPath, { renderer: chosen });
3314
+ const availability2 = detectAvailableRenderers();
3315
+ if (!availability2[chosen]) {
3316
+ process.stderr.write(
3317
+ `Warning: '${chosen}' was not detected on this machine. The renderer is saved but may not run.
3318
+ `
3319
+ );
3320
+ }
3321
+ console.log(`Statusline renderer set to '${chosen}'.`);
3322
+ return;
2856
3323
  }
2857
- return [];
3324
+ const availability = detectAvailableRenderers();
3325
+ const [renderer, config] = await Promise.all([
3326
+ resolveRenderer(projectPath),
3327
+ readStatuslineConfig(projectPath)
3328
+ ]);
3329
+ const availStr = Object.entries(availability).map(([k, v]) => `${k}: ${v ? "yes" : "no"}`).join(", ");
3330
+ console.log(`
3331
+ Statusline configuration`);
3332
+ console.log(` Renderer: ${renderer}`);
3333
+ console.log(` Available: ${availStr}`);
3334
+ console.log(` no_color: ${config.no_color}`);
3335
+ console.log(`
3336
+ Line toggles:`);
3337
+ for (const [key, val] of Object.entries(config.lines)) {
3338
+ console.log(` ${key.padEnd(14)} ${val ? "on" : "off"}`);
3339
+ }
3340
+ console.log();
2858
3341
  }
2859
- function parseAppTechStacks(raw) {
2860
- if (!Array.isArray(raw)) return [];
2861
- return raw.filter(
2862
- (item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.path === "string" && Array.isArray(item.stack)
2863
- ).map((item) => ({
2864
- name: item.name,
2865
- path: item.path,
2866
- stack: parseTechStack(item.stack)
2867
- }));
3342
+ var VALID_RENDERERS2;
3343
+ var init_statusline = __esm({
3344
+ "src/cli/statusline.ts"() {
3345
+ "use strict";
3346
+ init_flags();
3347
+ init_statusline_config();
3348
+ VALID_RENDERERS2 = /* @__PURE__ */ new Set(["bash", "node", "python"]);
3349
+ }
3350
+ });
3351
+
3352
+ // src/cli/logout.ts
3353
+ var logout_exports = {};
3354
+ __export(logout_exports, {
3355
+ runLogout: () => runLogout
3356
+ });
3357
+ async function runLogout() {
3358
+ await clearTokens();
3359
+ await clearClient();
3360
+ console.log("\n Logged out.\n");
2868
3361
  }
2869
- function parseTechStackResult(raw) {
2870
- if (typeof raw === "object" && raw !== null && !Array.isArray(raw) && "flat" in raw) {
2871
- const obj = raw;
3362
+ var init_logout = __esm({
3363
+ "src/cli/logout.ts"() {
3364
+ "use strict";
3365
+ init_client_registration();
3366
+ init_keychain();
3367
+ }
3368
+ });
3369
+
3370
+ // src/cli/whoami.ts
3371
+ var whoami_exports = {};
3372
+ __export(whoami_exports, {
3373
+ runWhoami: () => runWhoami
3374
+ });
3375
+ async function fetchMe(accessToken) {
3376
+ try {
3377
+ const res = await fetch(meEndpoint(), {
3378
+ headers: { Authorization: `Bearer ${accessToken}` },
3379
+ signal: AbortSignal.timeout(1e4)
3380
+ });
3381
+ if (!res.ok) return null;
3382
+ const body = await res.json();
2872
3383
  return {
2873
- repo: parseTechStack(obj.repo),
2874
- apps: parseAppTechStacks(obj.apps),
2875
- flat: parseTechStack(obj.flat)
3384
+ email: body.email ?? null,
3385
+ username: body.username ?? null
2876
3386
  };
3387
+ } catch {
3388
+ return null;
2877
3389
  }
2878
- const flat = parseTechStack(raw);
2879
- return { repo: flat, apps: [], flat };
2880
3390
  }
2881
- function categorizeDependency(depName) {
2882
- const rule = PACKAGE_MAP[depName];
2883
- if (rule) return rule.category;
2884
- for (const { prefix, rule: prefixRule } of PACKAGE_PREFIX_MAP) {
2885
- if (depName.startsWith(prefix)) return prefixRule.category;
3391
+ async function runWhoami(opts = {}) {
3392
+ const tokens = await loadTokens();
3393
+ if (opts.json) {
3394
+ if (!tokens) {
3395
+ console.log("null");
3396
+ process.exitCode = 1;
3397
+ return;
3398
+ }
3399
+ const me2 = await fetchMe(tokens.access_token);
3400
+ console.log(
3401
+ JSON.stringify({
3402
+ user_id: tokens.user_id,
3403
+ email: me2?.email ?? tokens.email,
3404
+ username: me2?.username ?? null
3405
+ })
3406
+ );
3407
+ return;
3408
+ }
3409
+ if (!tokens) {
3410
+ console.log("\n Not logged in. Run `codebyplan login` to authenticate.\n");
3411
+ process.exitCode = 1;
3412
+ return;
3413
+ }
3414
+ const me = await fetchMe(tokens.access_token);
3415
+ const degraded = me === null;
3416
+ const email = me?.email ?? tokens.email;
3417
+ const username = me?.username ?? null;
3418
+ console.log(`
3419
+ user_id: ${tokens.user_id}`);
3420
+ console.log(` email: ${email ?? "(unknown)"}`);
3421
+ console.log(` username: ${username ?? "(not set)"}`);
3422
+ if (degraded) {
3423
+ console.log(` (profile fetch failed \u2014 showing stored credentials)
3424
+ `);
3425
+ } else {
3426
+ console.log();
2886
3427
  }
2887
- return "other";
2888
3428
  }
2889
- async function findPackageJsonFiles(dir, projectPath, depth = 0) {
2890
- if (depth > 4) return [];
2891
- const results = [];
2892
- const pkgPath = join12(dir, "package.json");
2893
- if (await fileExists(pkgPath)) {
2894
- results.push(pkgPath);
3429
+ var init_whoami = __esm({
3430
+ "src/cli/whoami.ts"() {
3431
+ "use strict";
3432
+ init_keychain();
3433
+ init_urls();
2895
3434
  }
3435
+ });
3436
+
3437
+ // src/cli/upgrade-auth.ts
3438
+ var upgrade_auth_exports = {};
3439
+ __export(upgrade_auth_exports, {
3440
+ runUpgradeAuth: () => runUpgradeAuth
3441
+ });
3442
+ import { readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
3443
+ import { homedir as homedir5 } from "node:os";
3444
+ import { join as join13 } from "node:path";
3445
+ function configPaths() {
3446
+ return [join13(homedir5(), ".claude.json"), join13(process.cwd(), ".mcp.json")];
3447
+ }
3448
+ async function readConfig2(path10) {
2896
3449
  try {
2897
- const entries = await readdir(dir, { withFileTypes: true });
2898
- for (const entry of entries) {
2899
- if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
2900
- const subResults = await findPackageJsonFiles(
2901
- join12(dir, entry.name),
2902
- projectPath,
2903
- depth + 1
2904
- );
2905
- results.push(...subResults);
3450
+ const raw = await readFile10(path10, "utf-8");
3451
+ const parsed = JSON.parse(raw);
3452
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
3453
+ return parsed;
2906
3454
  }
3455
+ return null;
2907
3456
  } catch {
3457
+ return null;
2908
3458
  }
2909
- return results;
2910
3459
  }
2911
- async function scanAllDependencies(projectPath) {
2912
- const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
2913
- const dependencies = [];
2914
- for (const pkgPath of packageJsonPaths) {
2915
- try {
2916
- const raw = await readFile9(pkgPath, "utf-8");
2917
- const pkg = JSON.parse(raw);
2918
- const sourcePath = relative3(projectPath, pkgPath);
2919
- const depSections = [
2920
- { deps: pkg.dependencies, depType: "production", isDev: false },
2921
- { deps: pkg.devDependencies, depType: "dev", isDev: true },
2922
- { deps: pkg.peerDependencies, depType: "peer", isDev: false },
2923
- { deps: pkg.optionalDependencies, depType: "optional", isDev: false }
2924
- ];
2925
- for (const { deps, depType, isDev } of depSections) {
2926
- if (!deps) continue;
2927
- for (const [name, version] of Object.entries(deps)) {
2928
- dependencies.push({
2929
- name,
2930
- version,
2931
- category: categorizeDependency(name),
2932
- source_path: sourcePath,
2933
- is_dev: isDev,
2934
- dep_type: depType
2935
- });
2936
- }
2937
- }
2938
- } catch {
3460
+ function entryHasLegacyApiKey(entry) {
3461
+ if (!entry || !entry.headers) return false;
3462
+ return "x-api-key" in entry.headers;
3463
+ }
3464
+ async function rewriteConfig(path10, config, newUrl) {
3465
+ const servers = config.mcpServers;
3466
+ if (!servers) return false;
3467
+ const entry = servers.codebyplan;
3468
+ if (!entry) return false;
3469
+ if (!entryHasLegacyApiKey(entry) && entry.url === newUrl && entry.type === "http")
3470
+ return false;
3471
+ servers.codebyplan = { type: "http", url: newUrl };
3472
+ await writeFile8(path10, JSON.stringify(config, null, 2) + "\n", "utf-8");
3473
+ return true;
3474
+ }
3475
+ async function runUpgradeAuth() {
3476
+ console.log("\n Upgrading codebyplan auth to OAuth...\n");
3477
+ await runLogin();
3478
+ const newUrl = mcpEndpoint();
3479
+ let migrated = 0;
3480
+ for (const path10 of configPaths()) {
3481
+ const config = await readConfig2(path10);
3482
+ if (!config) continue;
3483
+ const changed = await rewriteConfig(path10, config, newUrl);
3484
+ if (changed) {
3485
+ console.log(` Updated ${path10}`);
3486
+ migrated++;
3487
+ }
3488
+ }
3489
+ if (migrated === 0) {
3490
+ console.log(
3491
+ " All codebyplan MCP entries are already up to date \u2014 nothing to change."
3492
+ );
3493
+ } else {
3494
+ console.log(
3495
+ `
3496
+ Done. Restart Claude Code to use the new endpoint: ${newUrl}
3497
+ `
3498
+ );
3499
+ }
3500
+ }
3501
+ var init_upgrade_auth = __esm({
3502
+ "src/cli/upgrade-auth.ts"() {
3503
+ "use strict";
3504
+ init_urls();
3505
+ init_login();
3506
+ }
3507
+ });
3508
+
3509
+ // src/cli/confirm.ts
3510
+ var confirm_exports = {};
3511
+ __export(confirm_exports, {
3512
+ SyncCancelledError: () => SyncCancelledError,
3513
+ confirmProceed: () => confirmProceed
3514
+ });
3515
+ import { createInterface as createInterface2 } from "node:readline/promises";
3516
+ import { stdin as stdin2, stdout as stdout3 } from "node:process";
3517
+ function isAbortError(err) {
3518
+ return err instanceof Error && err.code === "ABORT_ERR";
3519
+ }
3520
+ async function confirmProceed(message) {
3521
+ const rl = createInterface2({ input: stdin2, output: stdout3 });
3522
+ try {
3523
+ while (true) {
3524
+ const answer = await rl.question(message ?? " Proceed? [Y/n] ");
3525
+ const a = answer.trim().toLowerCase();
3526
+ if (a === "" || a === "y" || a === "yes") return true;
3527
+ if (a === "n" || a === "no") return false;
3528
+ console.log(
3529
+ ` Unknown option "${answer.trim()}". Valid: y/yes, n/no, or Enter for yes.`
3530
+ );
2939
3531
  }
3532
+ } catch (err) {
3533
+ if (isAbortError(err)) throw new SyncCancelledError();
3534
+ throw err;
3535
+ } finally {
3536
+ rl.close();
2940
3537
  }
2941
- return { dependencies };
2942
3538
  }
2943
- var PACKAGE_MAP, PACKAGE_PREFIX_MAP, CONFIG_FILE_MAP, SYNTHETIC_CARRIER_NAME, CAPABILITY_BEARER_NAMES, JSX_TEST_PATTERN, JSX_SKIP_DIRS, JSX_SCAN_DIRS, SERVER_MAIN_ENTRIES, SERVER_FRAMEWORK_DEPS, compareByCategoryThenName, SKIP_DIRS;
2944
- var init_tech_detect = __esm({
2945
- "src/lib/tech-detect.ts"() {
3539
+ var SyncCancelledError;
3540
+ var init_confirm = __esm({
3541
+ "src/cli/confirm.ts"() {
2946
3542
  "use strict";
2947
- PACKAGE_MAP = {
2948
- // Frameworks
2949
- next: { name: "Next.js", category: "framework" },
2950
- nuxt: { name: "Nuxt", category: "framework" },
2951
- gatsby: { name: "Gatsby", category: "framework" },
2952
- express: { name: "Express", category: "framework" },
2953
- fastify: { name: "Fastify", category: "framework" },
2954
- hono: { name: "Hono", category: "framework" },
2955
- "@remix-run/node": { name: "Remix", category: "framework" },
2956
- svelte: { name: "Svelte", category: "framework" },
2957
- astro: { name: "Astro", category: "framework" },
2958
- "@angular/core": { name: "Angular", category: "framework" },
2959
- "@nestjs/core": { name: "NestJS", category: "framework" },
2960
- // Libraries (UI)
2961
- react: { name: "React", category: "framework" },
2962
- vue: { name: "Vue", category: "framework" },
2963
- "solid-js": { name: "Solid", category: "framework" },
2964
- preact: { name: "Preact", category: "framework" },
2965
- // Languages (detected via devDeps)
2966
- typescript: { name: "TypeScript", category: "language" },
2967
- // Styling
2968
- tailwindcss: { name: "Tailwind CSS", category: "styling" },
2969
- sass: { name: "SCSS", category: "styling" },
2970
- "styled-components": { name: "styled-components", category: "styling" },
2971
- "@emotion/react": { name: "Emotion", category: "styling" },
2972
- // Database
2973
- prisma: { name: "Prisma", category: "database" },
2974
- "@prisma/client": { name: "Prisma", category: "database" },
2975
- "drizzle-orm": { name: "Drizzle", category: "database" },
2976
- "@supabase/supabase-js": { name: "Supabase", category: "database" },
2977
- mongoose: { name: "MongoDB", category: "database" },
2978
- typeorm: { name: "TypeORM", category: "database" },
2979
- knex: { name: "Knex", category: "database" },
2980
- // Testing
2981
- jest: { name: "Jest", category: "testing" },
2982
- vitest: { name: "Vitest", category: "testing" },
2983
- mocha: { name: "Mocha", category: "testing" },
2984
- playwright: { name: "Playwright", category: "testing" },
2985
- "@playwright/test": { name: "Playwright", category: "testing" },
2986
- cypress: { name: "Cypress", category: "testing" },
2987
- supertest: { name: "Supertest", category: "testing" },
2988
- webdriverio: { name: "WebdriverIO", category: "testing" },
2989
- "@wdio/cli": { name: "WebdriverIO", category: "testing" },
2990
- "@vscode/test-cli": { name: "VS Code Test CLI", category: "testing" },
2991
- // Build tools
2992
- turbo: { name: "Turborepo", category: "build" },
2993
- vite: { name: "Vite", category: "build" },
2994
- webpack: { name: "Webpack", category: "build" },
2995
- esbuild: { name: "esbuild", category: "build" },
2996
- rollup: { name: "Rollup", category: "build" },
2997
- nx: { name: "Nx", category: "build" },
2998
- lerna: { name: "Lerna", category: "build" },
2999
- tsup: { name: "tsup", category: "build" },
3000
- "@swc/core": { name: "SWC", category: "build" },
3001
- parcel: { name: "Parcel", category: "build" },
3002
- // Tools
3003
- eslint: { name: "ESLint", category: "tool" },
3004
- prettier: { name: "Prettier", category: "tool" },
3005
- "@biomejs/biome": { name: "Biome", category: "tool" },
3006
- storybook: { name: "Storybook", category: "tool" },
3007
- // Component libs
3008
- "@mui/material": { name: "MUI", category: "component-lib" },
3009
- "@chakra-ui/react": { name: "Chakra UI", category: "component-lib" },
3010
- "@mantine/core": { name: "Mantine", category: "component-lib" },
3011
- // GraphQL
3012
- graphql: { name: "GraphQL", category: "graphql" },
3013
- "@apollo/client": { name: "Apollo Client", category: "graphql" },
3014
- urql: { name: "urql", category: "graphql" },
3015
- "graphql-request": { name: "graphql-request", category: "graphql" },
3016
- // Documentation
3017
- typedoc: { name: "TypeDoc", category: "documentation" },
3018
- "@docusaurus/core": { name: "Docusaurus", category: "documentation" },
3019
- vitepress: { name: "VitePress", category: "documentation" },
3020
- // Code quality
3021
- husky: { name: "Husky", category: "quality" },
3022
- "lint-staged": { name: "lint-staged", category: "quality" },
3023
- commitlint: { name: "commitlint", category: "quality" },
3024
- "@commitlint/cli": { name: "commitlint", category: "quality" },
3025
- // Mobile
3026
- "react-native": { name: "React Native", category: "mobile" },
3027
- expo: { name: "Expo", category: "mobile" }
3028
- };
3029
- PACKAGE_PREFIX_MAP = [
3030
- {
3031
- prefix: "@radix-ui/",
3032
- rule: { name: "Radix UI", category: "component-lib" }
3033
- },
3034
- { prefix: "@storybook/", rule: { name: "Storybook", category: "tool" } },
3035
- {
3036
- prefix: "@testing-library/",
3037
- rule: { name: "Testing Library", category: "testing" }
3038
- }
3039
- ];
3040
- CONFIG_FILE_MAP = [
3041
- { file: "tsconfig.json", rule: { name: "TypeScript", category: "language" } },
3042
- { file: "next.config.js", rule: { name: "Next.js", category: "framework" } },
3043
- { file: "next.config.mjs", rule: { name: "Next.js", category: "framework" } },
3044
- { file: "next.config.ts", rule: { name: "Next.js", category: "framework" } },
3045
- {
3046
- file: "tailwind.config.js",
3047
- rule: { name: "Tailwind CSS", category: "styling" }
3048
- },
3049
- {
3050
- file: "tailwind.config.ts",
3051
- rule: { name: "Tailwind CSS", category: "styling" }
3052
- },
3053
- { file: "turbo.json", rule: { name: "Turborepo", category: "build" } },
3054
- {
3055
- file: "docker-compose.yml",
3056
- rule: { name: "Docker", category: "deployment" }
3057
- },
3058
- {
3059
- file: "docker-compose.yaml",
3060
- rule: { name: "Docker", category: "deployment" }
3061
- },
3062
- { file: "Dockerfile", rule: { name: "Docker", category: "deployment" } },
3063
- { file: "vercel.json", rule: { name: "Vercel", category: "deployment" } },
3064
- { file: ".storybook/main.js", rule: { name: "Storybook", category: "tool" } },
3065
- { file: ".storybook/main.ts", rule: { name: "Storybook", category: "tool" } },
3066
- {
3067
- file: ".storybook/main.mjs",
3068
- rule: { name: "Storybook", category: "tool" }
3069
- },
3070
- {
3071
- file: "components.json",
3072
- rule: { name: "shadcn/ui", category: "component-lib" }
3073
- },
3074
- { file: "nx.json", rule: { name: "Nx", category: "build" } },
3075
- { file: "lerna.json", rule: { name: "Lerna", category: "build" } },
3076
- {
3077
- file: "playwright.config.ts",
3078
- rule: { name: "Playwright", category: "testing" }
3079
- },
3080
- {
3081
- file: "playwright.config.js",
3082
- rule: { name: "Playwright", category: "testing" }
3083
- },
3084
- {
3085
- file: "playwright.config.mjs",
3086
- rule: { name: "Playwright", category: "testing" }
3087
- },
3088
- { file: "wdio.conf.ts", rule: { name: "WebdriverIO", category: "testing" } },
3089
- {
3090
- file: "wdio.conf.js",
3091
- rule: { name: "WebdriverIO", category: "testing" }
3092
- },
3093
- {
3094
- file: "maestro/config.yaml",
3095
- rule: { name: "Maestro", category: "testing" }
3543
+ SyncCancelledError = class extends Error {
3544
+ constructor() {
3545
+ super("Sync cancelled");
3546
+ this.name = "SyncCancelledError";
3096
3547
  }
3097
- ];
3098
- SYNTHETIC_CARRIER_NAME = "__capabilities__";
3099
- CAPABILITY_BEARER_NAMES = /* @__PURE__ */ new Set([
3100
- "react",
3101
- "next.js",
3102
- "vue",
3103
- "svelte",
3104
- "solid",
3105
- "preact",
3106
- "remix",
3107
- "astro",
3108
- "angular",
3109
- "nestjs",
3110
- "nuxt",
3111
- "gatsby",
3112
- "express",
3113
- "fastify",
3114
- "hono",
3115
- "react native",
3116
- "expo"
3117
- ]);
3118
- JSX_TEST_PATTERN = /\.(test|spec)\.(tsx|jsx)$/;
3119
- JSX_SKIP_DIRS = /* @__PURE__ */ new Set(["__tests__", "test", "tests"]);
3120
- JSX_SCAN_DIRS = ["src", "app", "pages"];
3121
- SERVER_MAIN_ENTRIES = /* @__PURE__ */ new Set([
3122
- "dist/main.js",
3123
- "dist/server.js",
3124
- "dist/index.js",
3125
- "main.js",
3126
- "server.js"
3127
- ]);
3128
- SERVER_FRAMEWORK_DEPS = /* @__PURE__ */ new Set([
3129
- "@nestjs/core",
3130
- "express",
3131
- "fastify",
3132
- "hono"
3133
- ]);
3134
- compareByCategoryThenName = (a, b) => {
3135
- const catCmp = a.category.localeCompare(b.category);
3136
- if (catCmp !== 0) return catCmp;
3137
- return a.name.localeCompare(b.name);
3138
3548
  };
3139
- SKIP_DIRS = /* @__PURE__ */ new Set([
3140
- "node_modules",
3141
- ".next",
3142
- "dist",
3143
- ".turbo",
3144
- ".git",
3145
- "coverage",
3146
- "build",
3147
- "out",
3148
- ".vercel",
3149
- ".expo"
3150
- ]);
3151
3549
  }
3152
3550
  });
3153
3551
 
@@ -3537,11 +3935,11 @@ __export(eslint_exports, {
3537
3935
  eslintInit: () => eslintInit,
3538
3936
  runEslint: () => runEslint
3539
3937
  });
3540
- import { readFile as readFile10, writeFile as writeFile8, access as access2, readdir as readdir2 } from "node:fs/promises";
3541
- import { join as join13, relative as relative4 } from "node:path";
3542
- async function fileExists2(filePath) {
3938
+ import { readFile as readFile11, writeFile as writeFile9, access as access3, readdir as readdir2 } from "node:fs/promises";
3939
+ import { join as join14, relative as relative4 } from "node:path";
3940
+ async function fileExists3(filePath) {
3543
3941
  try {
3544
- await access2(filePath);
3942
+ await access3(filePath);
3545
3943
  return true;
3546
3944
  } catch {
3547
3945
  return false;
@@ -3549,7 +3947,7 @@ async function fileExists2(filePath) {
3549
3947
  }
3550
3948
  async function autoDetectIgnorePatterns(absPath) {
3551
3949
  const patterns = [];
3552
- if (await fileExists2(join13(absPath, "esbuild.js"))) {
3950
+ if (await fileExists3(join14(absPath, "esbuild.js"))) {
3553
3951
  patterns.push("esbuild.js");
3554
3952
  }
3555
3953
  let entries = [];
@@ -3569,19 +3967,19 @@ async function autoDetectIgnorePatterns(absPath) {
3569
3967
  }
3570
3968
  for (const ext of ["ts", "mts", "js", "mjs"]) {
3571
3969
  const candidate = `vitest.config.${ext}`;
3572
- if (await fileExists2(join13(absPath, candidate))) {
3970
+ if (await fileExists3(join14(absPath, candidate))) {
3573
3971
  patterns.push(candidate);
3574
3972
  break;
3575
3973
  }
3576
3974
  }
3577
3975
  for (const ext of ["ts", "mts", "js", "mjs"]) {
3578
3976
  const candidate = `vite.config.${ext}`;
3579
- if (await fileExists2(join13(absPath, candidate))) {
3977
+ if (await fileExists3(join14(absPath, candidate))) {
3580
3978
  patterns.push(candidate);
3581
3979
  break;
3582
3980
  }
3583
3981
  }
3584
- if (await fileExists2(join13(absPath, "tauri.conf.json"))) {
3982
+ if (await fileExists3(join14(absPath, "tauri.conf.json"))) {
3585
3983
  patterns.push("src-tauri/**");
3586
3984
  patterns.push("**/*.d.ts");
3587
3985
  }
@@ -3589,14 +3987,14 @@ async function autoDetectIgnorePatterns(absPath) {
3589
3987
  }
3590
3988
  function detectPackageManager(projectPath) {
3591
3989
  return (async () => {
3592
- if (await fileExists2(join13(projectPath, "pnpm-lock.yaml"))) return "pnpm";
3593
- if (await fileExists2(join13(projectPath, "yarn.lock"))) return "yarn";
3990
+ if (await fileExists3(join14(projectPath, "pnpm-lock.yaml"))) return "pnpm";
3991
+ if (await fileExists3(join14(projectPath, "yarn.lock"))) return "yarn";
3594
3992
  return "npm";
3595
3993
  })();
3596
3994
  }
3597
3995
  async function getInstalledDeps(pkgJsonPath) {
3598
3996
  try {
3599
- const raw = await readFile10(pkgJsonPath, "utf-8");
3997
+ const raw = await readFile11(pkgJsonPath, "utf-8");
3600
3998
  const pkg = JSON.parse(raw);
3601
3999
  const all = /* @__PURE__ */ new Set();
3602
4000
  for (const name of Object.keys(pkg.dependencies ?? {})) all.add(name);
@@ -3709,7 +4107,7 @@ async function eslintInit(repoId, projectPath) {
3709
4107
  ignorePatterns: detectedIgnores
3710
4108
  });
3711
4109
  const hash = hashConfig(content);
3712
- const configPath = join13(target.absPath, "eslint.config.mjs");
4110
+ const configPath = join14(target.absPath, "eslint.config.mjs");
3713
4111
  configsToWrite.push({
3714
4112
  target,
3715
4113
  presets,
@@ -3731,11 +4129,11 @@ async function eslintInit(repoId, projectPath) {
3731
4129
  return;
3732
4130
  }
3733
4131
  const pm = await detectPackageManager(projectPath);
3734
- const rootPkgJsonPath = join13(projectPath, "package.json");
4132
+ const rootPkgJsonPath = join14(projectPath, "package.json");
3735
4133
  const installed = await getInstalledDeps(rootPkgJsonPath);
3736
4134
  if (isMonorepo) {
3737
4135
  for (const { target } of configsToWrite) {
3738
- const appPkgJson = join13(target.absPath, "package.json");
4136
+ const appPkgJson = join14(target.absPath, "package.json");
3739
4137
  const appDeps = await getInstalledDeps(appPkgJson);
3740
4138
  for (const dep of appDeps) {
3741
4139
  installed.add(dep);
@@ -3785,9 +4183,9 @@ async function eslintInit(repoId, projectPath) {
3785
4183
  configPath,
3786
4184
  userOverrides
3787
4185
  } of configsToWrite) {
3788
- if (await fileExists2(configPath)) {
4186
+ if (await fileExists3(configPath)) {
3789
4187
  try {
3790
- const existing = await readFile10(configPath, "utf-8");
4188
+ const existing = await readFile11(configPath, "utf-8");
3791
4189
  const existingHash = hashConfig(existing);
3792
4190
  if (existingHash === hash) {
3793
4191
  console.log(
@@ -3807,7 +4205,7 @@ async function eslintInit(repoId, projectPath) {
3807
4205
  }
3808
4206
  }
3809
4207
  try {
3810
- await writeFile8(configPath, content, "utf-8");
4208
+ await writeFile9(configPath, content, "utf-8");
3811
4209
  } catch (err) {
3812
4210
  console.error(
3813
4211
  ` ${target.name}: Failed to write config: ${err instanceof Error ? err.message : String(err)}`
@@ -4107,16 +4505,16 @@ var init_sync_approvals = __esm({
4107
4505
  });
4108
4506
 
4109
4507
  // src/lib/worktree-cache.ts
4110
- import { mkdir as mkdir5, readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
4111
- import { dirname as dirname6, join as join14 } from "node:path";
4508
+ import { mkdir as mkdir6, readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
4509
+ import { dirname as dirname6, join as join15 } from "node:path";
4112
4510
  function worktreeCachePath(repoRoot) {
4113
- return join14(repoRoot, ".codebyplan", "worktree.local.json");
4511
+ return join15(repoRoot, ".codebyplan", "worktree.local.json");
4114
4512
  }
4115
4513
  async function readCachedWorktreeId(repoRoot, currentBranch) {
4116
4514
  const cachePath = worktreeCachePath(repoRoot);
4117
4515
  let raw;
4118
4516
  try {
4119
- raw = await readFile11(cachePath, "utf-8");
4517
+ raw = await readFile12(cachePath, "utf-8");
4120
4518
  } catch (err) {
4121
4519
  const code = err.code;
4122
4520
  if (code === "ENOENT") {
@@ -4161,8 +4559,8 @@ async function writeWorktreeCache(repoRoot, data) {
4161
4559
  resolved_at: (/* @__PURE__ */ new Date()).toISOString()
4162
4560
  };
4163
4561
  try {
4164
- await mkdir5(dirPath, { recursive: true });
4165
- await writeFile9(
4562
+ await mkdir6(dirPath, { recursive: true });
4563
+ await writeFile10(
4166
4564
  cachePath,
4167
4565
  JSON.stringify(payload, null, 2) + "\n",
4168
4566
  "utf-8"
@@ -4191,8 +4589,8 @@ __export(round_exports, {
4191
4589
  runRoundSyncApprovals: () => runRoundSyncApprovals,
4192
4590
  setRetryDelayMs: () => setRetryDelayMs
4193
4591
  });
4194
- import { access as access3 } from "node:fs/promises";
4195
- import { join as join15 } from "node:path";
4592
+ import { access as access4 } from "node:fs/promises";
4593
+ import { join as join16 } from "node:path";
4196
4594
  import { execSync as execSync2 } from "node:child_process";
4197
4595
  function setRetryDelayMs(ms) {
4198
4596
  RETRY_DELAY_MS = ms;
@@ -4373,7 +4771,7 @@ async function runRoundSyncApprovals(args) {
4373
4771
  "sync-approvals: git status failed; proceeding with empty diff\n"
4374
4772
  );
4375
4773
  }
4376
- const hookPath = join15(
4774
+ const hookPath = join16(
4377
4775
  repoRoot,
4378
4776
  ".claude",
4379
4777
  "hooks",
@@ -4381,7 +4779,7 @@ async function runRoundSyncApprovals(args) {
4381
4779
  );
4382
4780
  let lintFormatHookExists = false;
4383
4781
  try {
4384
- await access3(hookPath);
4782
+ await access4(hookPath);
4385
4783
  lintFormatHookExists = true;
4386
4784
  } catch {
4387
4785
  }
@@ -4464,8 +4862,8 @@ var init_round = __esm({
4464
4862
  });
4465
4863
 
4466
4864
  // src/lib/migrate-branch-model.ts
4467
- import { readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
4468
- import { join as join16 } from "node:path";
4865
+ import { readFile as readFile13, writeFile as writeFile11 } from "node:fs/promises";
4866
+ import { join as join17 } from "node:path";
4469
4867
  import { execSync as execSync3 } from "node:child_process";
4470
4868
  function assertValidBranchName(branch) {
4471
4869
  if (!/^[a-zA-Z0-9/_.-]+$/.test(branch)) {
@@ -4474,8 +4872,8 @@ function assertValidBranchName(branch) {
4474
4872
  );
4475
4873
  }
4476
4874
  }
4477
- async function readJsonFile(filePath) {
4478
- const raw = await readFile12(filePath, "utf-8");
4875
+ async function readJsonFile2(filePath) {
4876
+ const raw = await readFile13(filePath, "utf-8");
4479
4877
  const parsed = JSON.parse(raw);
4480
4878
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
4481
4879
  throw new Error(`${filePath} does not contain a JSON object`);
@@ -4544,17 +4942,17 @@ async function runBranchMigration(opts) {
4544
4942
  if (found) {
4545
4943
  if (found.path.endsWith("/repo.json")) {
4546
4944
  const dir = found.path.slice(0, found.path.lastIndexOf("/"));
4547
- configPath = join16(dir, "git.json");
4945
+ configPath = join17(dir, "git.json");
4548
4946
  } else {
4549
4947
  configPath = found.path;
4550
4948
  }
4551
4949
  } else {
4552
- configPath = join16(cwd, ".codebyplan", "git.json");
4950
+ configPath = join17(cwd, ".codebyplan", "git.json");
4553
4951
  }
4554
4952
  let fileRaw;
4555
4953
  let fileParsed;
4556
4954
  try {
4557
- const result = await readJsonFile(configPath);
4955
+ const result = await readJsonFile2(configPath);
4558
4956
  fileRaw = result.raw;
4559
4957
  fileParsed = result.parsed;
4560
4958
  } catch (err) {
@@ -4623,7 +5021,7 @@ async function runBranchMigration(opts) {
4623
5021
  const updatedParsed = { ...fileParsed, branch_config: after };
4624
5022
  const newJson = JSON.stringify(updatedParsed, null, 2) + "\n";
4625
5023
  if (newJson !== fileRaw) {
4626
- await writeFile10(configPath, newJson, "utf-8");
5024
+ await writeFile11(configPath, newJson, "utf-8");
4627
5025
  }
4628
5026
  }
4629
5027
  return {
@@ -4729,24 +5127,24 @@ var init_branch = __esm({
4729
5127
  });
4730
5128
 
4731
5129
  // src/lib/git-utils.ts
4732
- import { readFile as readFile13 } from "node:fs/promises";
4733
- import { join as join17 } from "node:path";
4734
- import { spawnSync as spawnSync2 } from "node:child_process";
5130
+ import { readFile as readFile14 } from "node:fs/promises";
5131
+ import { join as join18 } from "node:path";
5132
+ import { spawnSync as spawnSync4 } from "node:child_process";
4735
5133
  async function readBaseBranch(cwd) {
4736
5134
  const found = await findCodebyplanConfig(cwd);
4737
5135
  let gitJsonPath;
4738
5136
  if (found) {
4739
5137
  if (found.path.endsWith("/repo.json")) {
4740
5138
  const dir = found.path.slice(0, found.path.lastIndexOf("/"));
4741
- gitJsonPath = join17(dir, "git.json");
5139
+ gitJsonPath = join18(dir, "git.json");
4742
5140
  } else {
4743
5141
  gitJsonPath = found.path;
4744
5142
  }
4745
5143
  } else {
4746
- gitJsonPath = join17(cwd, ".codebyplan", "git.json");
5144
+ gitJsonPath = join18(cwd, ".codebyplan", "git.json");
4747
5145
  }
4748
5146
  try {
4749
- const raw = await readFile13(gitJsonPath, "utf-8");
5147
+ const raw = await readFile14(gitJsonPath, "utf-8");
4750
5148
  const parsed = JSON.parse(raw);
4751
5149
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
4752
5150
  return "main";
@@ -4766,7 +5164,7 @@ async function readBaseBranch(cwd) {
4766
5164
  function resolveBaseRef(cwd, baseBranch) {
4767
5165
  if (baseBranch.startsWith("origin/")) return baseBranch;
4768
5166
  const remoteRef = `origin/${baseBranch}`;
4769
- const check = spawnSync2(
5167
+ const check = spawnSync4(
4770
5168
  "git",
4771
5169
  ["rev-parse", "--verify", "--quiet", `${remoteRef}^{commit}`],
4772
5170
  { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
@@ -4784,9 +5182,9 @@ var init_git_utils = __esm({
4784
5182
  });
4785
5183
 
4786
5184
  // src/lib/bump.ts
4787
- import { readFile as readFile14, writeFile as writeFile11, access as access4, readdir as readdir3 } from "node:fs/promises";
4788
- import { join as join18, relative as relative5, resolve as resolve3 } from "node:path";
4789
- import { spawnSync as spawnSync3 } from "node:child_process";
5185
+ import { readFile as readFile15, writeFile as writeFile12, access as access5, readdir as readdir3 } from "node:fs/promises";
5186
+ import { join as join19, relative as relative5, resolve as resolve3 } from "node:path";
5187
+ import { spawnSync as spawnSync5 } from "node:child_process";
4790
5188
  function parsePnpmWorkspaceGlobs(raw) {
4791
5189
  const lines = raw.split("\n");
4792
5190
  const globs = [];
@@ -4815,18 +5213,18 @@ async function expandGlob(cwd, glob) {
4815
5213
  if (parts.length !== 2 || parts[1] !== "*") {
4816
5214
  return [];
4817
5215
  }
4818
- const parentDir = join18(cwd, parts[0]);
5216
+ const parentDir = join19(cwd, parts[0]);
4819
5217
  let dirs;
4820
5218
  try {
4821
5219
  const entries = await readdir3(parentDir, { withFileTypes: true });
4822
- dirs = entries.filter((e) => e.isDirectory()).map((e) => join18(parentDir, e.name));
5220
+ dirs = entries.filter((e) => e.isDirectory()).map((e) => join19(parentDir, e.name));
4823
5221
  } catch {
4824
5222
  return [];
4825
5223
  }
4826
5224
  const results = [];
4827
5225
  for (const dir of dirs) {
4828
5226
  try {
4829
- await access4(join18(dir, "package.json"));
5227
+ await access5(join19(dir, "package.json"));
4830
5228
  results.push(dir);
4831
5229
  } catch {
4832
5230
  }
@@ -4837,7 +5235,7 @@ async function buildPackageMap(cwd) {
4837
5235
  const map = /* @__PURE__ */ new Map();
4838
5236
  let globs = [];
4839
5237
  try {
4840
- const raw = await readFile14(join18(cwd, "pnpm-workspace.yaml"), "utf-8");
5238
+ const raw = await readFile15(join19(cwd, "pnpm-workspace.yaml"), "utf-8");
4841
5239
  globs = parsePnpmWorkspaceGlobs(raw);
4842
5240
  } catch {
4843
5241
  }
@@ -4845,7 +5243,7 @@ async function buildPackageMap(cwd) {
4845
5243
  const dirs = await expandGlob(cwd, glob);
4846
5244
  for (const dir of dirs) {
4847
5245
  try {
4848
- const pkgRaw = await readFile14(join18(dir, "package.json"), "utf-8");
5246
+ const pkgRaw = await readFile15(join19(dir, "package.json"), "utf-8");
4849
5247
  const pkg = JSON.parse(pkgRaw);
4850
5248
  const name = pkg.name ?? relative5(cwd, dir);
4851
5249
  map.set(dir, { name, dir });
@@ -4894,7 +5292,7 @@ function compareSemver(a, b) {
4894
5292
  return 0;
4895
5293
  }
4896
5294
  function gitShowFile(cwd, ref, relPath) {
4897
- const result = spawnSync3("git", ["show", `${ref}:${relPath}`], {
5295
+ const result = spawnSync5("git", ["show", `${ref}:${relPath}`], {
4898
5296
  cwd,
4899
5297
  encoding: "utf-8",
4900
5298
  stdio: ["pipe", "pipe", "pipe"]
@@ -4935,7 +5333,7 @@ function injectVersion(raw, nextVersion, filePath) {
4935
5333
  async function prependChangelog(changelogPath, packageName, nextVersion, now, dryRun) {
4936
5334
  let existing;
4937
5335
  try {
4938
- existing = await readFile14(changelogPath, "utf-8");
5336
+ existing = await readFile15(changelogPath, "utf-8");
4939
5337
  } catch {
4940
5338
  return false;
4941
5339
  }
@@ -4948,7 +5346,7 @@ async function prependChangelog(changelogPath, packageName, nextVersion, now, dr
4948
5346
  `;
4949
5347
  const updated = entry + existing;
4950
5348
  if (!dryRun) {
4951
- await writeFile11(changelogPath, updated, "utf-8");
5349
+ await writeFile12(changelogPath, updated, "utf-8");
4952
5350
  }
4953
5351
  return true;
4954
5352
  }
@@ -4958,7 +5356,7 @@ async function runBump(opts) {
4958
5356
  const now = opts?.now ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4959
5357
  const baseBranch = await readBaseBranch(cwd);
4960
5358
  const baseRef = resolveBaseRef(cwd, baseBranch);
4961
- const diffResult = spawnSync3(
5359
+ const diffResult = spawnSync5(
4962
5360
  "git",
4963
5361
  ["diff", "--name-only", `${baseRef}...HEAD`],
4964
5362
  {
@@ -4974,7 +5372,7 @@ async function runBump(opts) {
4974
5372
  const changedFiles = (diffResult.stdout ?? "").trim().split("\n").filter(Boolean).map((f) => resolve3(cwd, f));
4975
5373
  const packageMap = await buildPackageMap(cwd);
4976
5374
  const packageDirs = Array.from(packageMap.keys());
4977
- const rootPkgPath = join18(cwd, "package.json");
5375
+ const rootPkgPath = join19(cwd, "package.json");
4978
5376
  const changedPackageDirs = /* @__PURE__ */ new Set();
4979
5377
  for (const absFile of changedFiles) {
4980
5378
  const owner = findOwningPackage(absFile, packageDirs);
@@ -4985,28 +5383,28 @@ async function runBump(opts) {
4985
5383
  const entries = [];
4986
5384
  for (const pkgDir of changedPackageDirs) {
4987
5385
  const pkgInfo = packageMap.get(pkgDir);
4988
- const pkgJsonPath = join18(pkgDir, "package.json");
5386
+ const pkgJsonPath = join19(pkgDir, "package.json");
4989
5387
  if (pkgJsonPath === rootPkgPath) continue;
4990
5388
  const versionFileCandidates = [
4991
5389
  { abs: pkgJsonPath, rel: relative5(cwd, pkgJsonPath).replace(/\\/g, "/") }
4992
5390
  ];
4993
- const tauriConfPath = join18(pkgDir, "src-tauri", "tauri.conf.json");
5391
+ const tauriConfPath = join19(pkgDir, "src-tauri", "tauri.conf.json");
4994
5392
  const tauriRelPath = relative5(cwd, tauriConfPath).replace(/\\/g, "/");
4995
5393
  try {
4996
- await access4(tauriConfPath);
5394
+ await access5(tauriConfPath);
4997
5395
  versionFileCandidates.push({ abs: tauriConfPath, rel: tauriRelPath });
4998
5396
  } catch {
4999
5397
  }
5000
- const appJsonPath = join18(pkgDir, "app.json");
5398
+ const appJsonPath = join19(pkgDir, "app.json");
5001
5399
  const appJsonRelPath = relative5(cwd, appJsonPath).replace(/\\/g, "/");
5002
5400
  try {
5003
- await access4(appJsonPath);
5401
+ await access5(appJsonPath);
5004
5402
  versionFileCandidates.push({ abs: appJsonPath, rel: appJsonRelPath });
5005
5403
  } catch {
5006
5404
  }
5007
5405
  let currentPkgJsonRaw;
5008
5406
  try {
5009
- currentPkgJsonRaw = await readFile14(pkgJsonPath, "utf-8");
5407
+ currentPkgJsonRaw = await readFile15(pkgJsonPath, "utf-8");
5010
5408
  } catch (err) {
5011
5409
  console.warn(
5012
5410
  `runBump: could not read ${pkgJsonPath}: ${err instanceof Error ? err.message : String(err)}`
@@ -5051,7 +5449,7 @@ async function runBump(opts) {
5051
5449
  for (const { abs, rel } of versionFileCandidates) {
5052
5450
  let raw;
5053
5451
  try {
5054
- raw = await readFile14(abs, "utf-8");
5452
+ raw = await readFile15(abs, "utf-8");
5055
5453
  } catch {
5056
5454
  continue;
5057
5455
  }
@@ -5065,11 +5463,11 @@ async function runBump(opts) {
5065
5463
  }
5066
5464
  const updated = injectVersion(raw, nextVersion, abs);
5067
5465
  if (!dryRun) {
5068
- await writeFile11(abs, updated, "utf-8");
5466
+ await writeFile12(abs, updated, "utf-8");
5069
5467
  }
5070
5468
  updatedVersionFiles.push(rel);
5071
5469
  }
5072
- const changelogPath = join18(pkgDir, "CHANGELOG.md");
5470
+ const changelogPath = join19(pkgDir, "CHANGELOG.md");
5073
5471
  const changelogUpdated = await prependChangelog(
5074
5472
  changelogPath,
5075
5473
  pkgInfo.name,
@@ -5190,7 +5588,7 @@ var init_bump2 = __esm({
5190
5588
  });
5191
5589
 
5192
5590
  // src/lib/ship.ts
5193
- import { execSync as execSync4, spawnSync as spawnSync4 } from "node:child_process";
5591
+ import { execSync as execSync4, spawnSync as spawnSync6 } from "node:child_process";
5194
5592
  import { relative as relative6 } from "node:path";
5195
5593
  function assertValidBranchName2(branch, label) {
5196
5594
  if (!/^[a-zA-Z0-9/_.-]+$/.test(branch)) {
@@ -5215,7 +5613,7 @@ async function pollChecks(feat, timeoutSeconds, cwd, _sleepMs = (ms) => new Prom
5215
5613
  let totalGhErrors = 0;
5216
5614
  let iteration = 0;
5217
5615
  while (Date.now() < deadline) {
5218
- const ghResult = spawnSync4(
5616
+ const ghResult = spawnSync6(
5219
5617
  "gh",
5220
5618
  ["pr", "checks", feat, "--json", "name,state,conclusion"],
5221
5619
  {
@@ -5373,7 +5771,7 @@ ${statusOut.trim()}`
5373
5771
  ...e.changelogUpdated ? [relative6(cwd, e.packageDir).replace(/\\/g, "/") + "/CHANGELOG.md"] : []
5374
5772
  ]);
5375
5773
  if (toStage.length > 0) {
5376
- const addResult = spawnSync4("git", ["add", "--", ...toStage], {
5774
+ const addResult = spawnSync6("git", ["add", "--", ...toStage], {
5377
5775
  cwd,
5378
5776
  encoding: "utf-8",
5379
5777
  stdio: ["pipe", "pipe", "pipe"]
@@ -5383,13 +5781,13 @@ ${statusOut.trim()}`
5383
5781
  `Failed to stage bump files: ${addResult.stderr?.trim() ?? addResult.error?.message}`
5384
5782
  );
5385
5783
  }
5386
- const commitResult = spawnSync4(
5784
+ const commitResult = spawnSync6(
5387
5785
  "git",
5388
5786
  ["commit", "-m", "chore(release): bump versions"],
5389
5787
  { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5390
5788
  );
5391
5789
  if (commitResult.status !== 0 || commitResult.error) {
5392
- spawnSync4(
5790
+ spawnSync6(
5393
5791
  "git",
5394
5792
  ["restore", "--staged", "--worktree", "--", ...toStage],
5395
5793
  {
@@ -5434,7 +5832,7 @@ ${statusOut.trim()}`
5434
5832
  } else {
5435
5833
  createArgs.push("--body", defaultPrBody(feat, base));
5436
5834
  }
5437
- const createResult = spawnSync4("gh", createArgs, {
5835
+ const createResult = spawnSync6("gh", createArgs, {
5438
5836
  cwd,
5439
5837
  encoding: "utf-8",
5440
5838
  stdio: ["pipe", "pipe", "pipe"]
@@ -5465,7 +5863,7 @@ ${statusOut.trim()}`
5465
5863
  });
5466
5864
  let mergeCommit = null;
5467
5865
  try {
5468
- const prViewResult = spawnSync4(
5866
+ const prViewResult = spawnSync6(
5469
5867
  "gh",
5470
5868
  ["pr", "view", feat, "--json", "state,mergeCommit"],
5471
5869
  {
@@ -6255,7 +6653,7 @@ __export(version_status_exports, {
6255
6653
  });
6256
6654
  import { execFileSync, execSync as execSync6 } from "node:child_process";
6257
6655
  import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
6258
- import { dirname as dirname8, join as join20 } from "node:path";
6656
+ import { dirname as dirname8, join as join21 } from "node:path";
6259
6657
  function fetchLatestVersion() {
6260
6658
  try {
6261
6659
  return execFileSync("npm", ["view", "codebyplan", "version"], {
@@ -6294,9 +6692,9 @@ function detectPackageManager2(gitRoot) {
6294
6692
  let dir = process.cwd();
6295
6693
  const stopAt = gitRoot ?? null;
6296
6694
  while (true) {
6297
- if (existsSync4(join20(dir, "pnpm-lock.yaml"))) return "pnpm";
6298
- if (existsSync4(join20(dir, "yarn.lock"))) return "yarn";
6299
- if (existsSync4(join20(dir, "package-lock.json"))) return "npm";
6695
+ if (existsSync4(join21(dir, "pnpm-lock.yaml"))) return "pnpm";
6696
+ if (existsSync4(join21(dir, "yarn.lock"))) return "yarn";
6697
+ if (existsSync4(join21(dir, "package-lock.json"))) return "npm";
6300
6698
  if (stopAt !== null && dir === stopAt) break;
6301
6699
  const parent = dirname8(dir);
6302
6700
  if (parent === dir) break;
@@ -6316,7 +6714,7 @@ function buildInstallCommand2(pm) {
6316
6714
  }
6317
6715
  async function resolveGuard(gitRoot, currentBranch) {
6318
6716
  if (gitRoot !== null) {
6319
- const canonicalPkgPath = join20(
6717
+ const canonicalPkgPath = join21(
6320
6718
  gitRoot,
6321
6719
  "packages",
6322
6720
  "codebyplan-package",
@@ -6340,10 +6738,10 @@ async function resolveGuard(gitRoot, currentBranch) {
6340
6738
  let gitJsonPath;
6341
6739
  if (found.path.endsWith("/repo.json")) {
6342
6740
  const dir = found.path.slice(0, found.path.lastIndexOf("/"));
6343
- gitJsonPath = join20(dir, "git.json");
6741
+ gitJsonPath = join21(dir, "git.json");
6344
6742
  } else {
6345
6743
  const legacyDir = found.path.slice(0, found.path.lastIndexOf("/"));
6346
- gitJsonPath = join20(legacyDir, ".codebyplan", "git.json");
6744
+ gitJsonPath = join21(legacyDir, ".codebyplan", "git.json");
6347
6745
  }
6348
6746
  const raw = readFileSync5(gitJsonPath, "utf-8");
6349
6747
  const parsed = JSON.parse(raw);
@@ -6422,8 +6820,8 @@ var upload_e2e_images_exports = {};
6422
6820
  __export(upload_e2e_images_exports, {
6423
6821
  runUploadE2eImagesCommand: () => runUploadE2eImagesCommand
6424
6822
  });
6425
- import { readFile as readFile15 } from "node:fs/promises";
6426
- import { join as join21, basename, resolve as resolve5 } from "node:path";
6823
+ import { readFile as readFile16 } from "node:fs/promises";
6824
+ import { join as join22, basename, resolve as resolve5 } from "node:path";
6427
6825
  import { execSync as execSync7 } from "node:child_process";
6428
6826
  function baseUrl2() {
6429
6827
  return (process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com").replace(/\/$/, "");
@@ -6457,8 +6855,8 @@ function parseArgs(args) {
6457
6855
  }
6458
6856
  async function readE2eConfig(projectPath) {
6459
6857
  try {
6460
- const raw = await readFile15(
6461
- join21(projectPath, ".codebyplan", "e2e.json"),
6858
+ const raw = await readFile16(
6859
+ join22(projectPath, ".codebyplan", "e2e.json"),
6462
6860
  "utf-8"
6463
6861
  );
6464
6862
  return JSON.parse(raw);
@@ -6499,7 +6897,7 @@ function collectPngsFromGitDiff(projectPath, frameworkName, frameworkConfig, bas
6499
6897
  continue;
6500
6898
  const isNew = status === "A";
6501
6899
  results.push({
6502
- absolutePath: join21(projectPath, filePath),
6900
+ absolutePath: join22(projectPath, filePath),
6503
6901
  filename: basename(filePath),
6504
6902
  framework: frameworkName,
6505
6903
  is_new: isNew
@@ -6596,7 +6994,7 @@ async function runUploadE2eImagesCommand(args) {
6596
6994
  for (const png of allPngs) {
6597
6995
  let bytes;
6598
6996
  try {
6599
- bytes = await readFile15(png.absolutePath);
6997
+ bytes = await readFile16(png.absolutePath);
6600
6998
  } catch {
6601
6999
  process.stderr.write(
6602
7000
  `upload-e2e-images: could not read file: ${png.absolutePath}
@@ -6755,19 +7153,19 @@ var init_worktree_port_resolver = __esm({
6755
7153
  });
6756
7154
 
6757
7155
  // src/lib/migrate-local-config.ts
6758
- import { mkdir as mkdir6, readFile as readFile16, unlink as unlink2, writeFile as writeFile12 } from "node:fs/promises";
6759
- import { join as join22 } from "node:path";
7156
+ import { mkdir as mkdir7, readFile as readFile17, unlink as unlink3, writeFile as writeFile13 } from "node:fs/promises";
7157
+ import { join as join23 } from "node:path";
6760
7158
  function legacySharedPath(projectPath) {
6761
- return join22(projectPath, ".codebyplan.json");
7159
+ return join23(projectPath, ".codebyplan.json");
6762
7160
  }
6763
7161
  function legacyLocalPath(projectPath) {
6764
- return join22(projectPath, ".codebyplan.local.json");
7162
+ return join23(projectPath, ".codebyplan.local.json");
6765
7163
  }
6766
7164
  function newDirPath(projectPath) {
6767
- return join22(projectPath, ".codebyplan");
7165
+ return join23(projectPath, ".codebyplan");
6768
7166
  }
6769
7167
  function sentinelPath(projectPath) {
6770
- return join22(projectPath, ".codebyplan", "repo.json");
7168
+ return join23(projectPath, ".codebyplan", "repo.json");
6771
7169
  }
6772
7170
  async function statSafe(p) {
6773
7171
  const { stat: stat2 } = await import("node:fs/promises");
@@ -6806,7 +7204,7 @@ async function runLocalMigration(projectPath) {
6806
7204
  }
6807
7205
  let legacyRaw;
6808
7206
  try {
6809
- legacyRaw = await readFile16(legacySharedPath(projectPath), "utf-8");
7207
+ legacyRaw = await readFile17(legacySharedPath(projectPath), "utf-8");
6810
7208
  } catch {
6811
7209
  return {
6812
7210
  migrated: true,
@@ -6833,7 +7231,7 @@ async function runLocalMigration(projectPath) {
6833
7231
  let deviceId;
6834
7232
  let deviceWrittenByHelper = false;
6835
7233
  try {
6836
- const localRaw = await readFile16(legacyLocalPath(projectPath), "utf-8");
7234
+ const localRaw = await readFile17(legacyLocalPath(projectPath), "utf-8");
6837
7235
  const localParsed = JSON.parse(localRaw);
6838
7236
  if (typeof localParsed.device_id === "string") {
6839
7237
  deviceId = localParsed.device_id;
@@ -6841,7 +7239,7 @@ async function runLocalMigration(projectPath) {
6841
7239
  } catch {
6842
7240
  }
6843
7241
  try {
6844
- await mkdir6(newDirPath(projectPath), { recursive: true });
7242
+ await mkdir7(newDirPath(projectPath), { recursive: true });
6845
7243
  } catch (err) {
6846
7244
  const code = err.code;
6847
7245
  if (code === "ENOTDIR" || code === "EEXIST") {
@@ -6860,8 +7258,8 @@ async function runLocalMigration(projectPath) {
6860
7258
  if ("repo_id" in cfg) repoJson.repo_id = cfg.repo_id;
6861
7259
  if ("organization_id" in cfg) repoJson.organization_id = cfg.organization_id;
6862
7260
  if ("project_id" in cfg) repoJson.project_id = cfg.project_id;
6863
- await writeFile12(
6864
- join22(projectPath, ".codebyplan", "repo.json"),
7261
+ await writeFile13(
7262
+ join23(projectPath, ".codebyplan", "repo.json"),
6865
7263
  JSON.stringify(repoJson, null, 2) + "\n",
6866
7264
  "utf-8"
6867
7265
  );
@@ -6873,8 +7271,8 @@ async function runLocalMigration(projectPath) {
6873
7271
  serverJson.auto_push_enabled = cfg.auto_push_enabled;
6874
7272
  if ("port_allocations" in cfg)
6875
7273
  serverJson.port_allocations = cfg.port_allocations;
6876
- await writeFile12(
6877
- join22(projectPath, ".codebyplan", "server.json"),
7274
+ await writeFile13(
7275
+ join23(projectPath, ".codebyplan", "server.json"),
6878
7276
  JSON.stringify(serverJson, null, 2) + "\n",
6879
7277
  "utf-8"
6880
7278
  );
@@ -6882,44 +7280,44 @@ async function runLocalMigration(projectPath) {
6882
7280
  const gitJson = {};
6883
7281
  if ("git_branch" in cfg) gitJson.git_branch = cfg.git_branch;
6884
7282
  if ("branch_config" in cfg) gitJson.branch_config = cfg.branch_config;
6885
- await writeFile12(
6886
- join22(projectPath, ".codebyplan", "git.json"),
7283
+ await writeFile13(
7284
+ join23(projectPath, ".codebyplan", "git.json"),
6887
7285
  JSON.stringify(gitJson, null, 2) + "\n",
6888
7286
  "utf-8"
6889
7287
  );
6890
7288
  filesChanged.push(".codebyplan/git.json");
6891
7289
  const shipmentJson = {};
6892
7290
  if ("shipment" in cfg) shipmentJson.shipment = cfg.shipment;
6893
- await writeFile12(
6894
- join22(projectPath, ".codebyplan", "shipment.json"),
7291
+ await writeFile13(
7292
+ join23(projectPath, ".codebyplan", "shipment.json"),
6895
7293
  JSON.stringify(shipmentJson, null, 2) + "\n",
6896
7294
  "utf-8"
6897
7295
  );
6898
7296
  filesChanged.push(".codebyplan/shipment.json");
6899
7297
  const vendorJson = {};
6900
- await writeFile12(
6901
- join22(projectPath, ".codebyplan", "vendor.json"),
7298
+ await writeFile13(
7299
+ join23(projectPath, ".codebyplan", "vendor.json"),
6902
7300
  JSON.stringify(vendorJson, null, 2) + "\n",
6903
7301
  "utf-8"
6904
7302
  );
6905
7303
  filesChanged.push(".codebyplan/vendor.json");
6906
7304
  const e2eJson = {};
6907
- await writeFile12(
6908
- join22(projectPath, ".codebyplan", "e2e.json"),
7305
+ await writeFile13(
7306
+ join23(projectPath, ".codebyplan", "e2e.json"),
6909
7307
  JSON.stringify(e2eJson, null, 2) + "\n",
6910
7308
  "utf-8"
6911
7309
  );
6912
7310
  filesChanged.push(".codebyplan/e2e.json");
6913
7311
  const eslintJson = {};
6914
- await writeFile12(
6915
- join22(projectPath, ".codebyplan", "eslint.json"),
7312
+ await writeFile13(
7313
+ join23(projectPath, ".codebyplan", "eslint.json"),
6916
7314
  JSON.stringify(eslintJson, null, 2) + "\n",
6917
7315
  "utf-8"
6918
7316
  );
6919
7317
  filesChanged.push(".codebyplan/eslint.json");
6920
7318
  if (!deviceWrittenByHelper) {
6921
- await writeFile12(
6922
- join22(projectPath, ".codebyplan", "device.local.json"),
7319
+ await writeFile13(
7320
+ join23(projectPath, ".codebyplan", "device.local.json"),
6923
7321
  JSON.stringify({ device_id: deviceId }, null, 2) + "\n",
6924
7322
  "utf-8"
6925
7323
  );
@@ -6931,9 +7329,9 @@ async function runLocalMigration(projectPath) {
6931
7329
  "Migration write incomplete: .codebyplan/repo.json was not persisted. Re-run migration to retry from a clean state."
6932
7330
  );
6933
7331
  }
6934
- const gitignorePath = join22(projectPath, ".gitignore");
7332
+ const gitignorePath = join23(projectPath, ".gitignore");
6935
7333
  try {
6936
- const gitignoreContent = await readFile16(gitignorePath, "utf-8");
7334
+ const gitignoreContent = await readFile17(gitignorePath, "utf-8");
6937
7335
  const legacyLine = ".codebyplan.local.json";
6938
7336
  const newLine = ".codebyplan/device.local.json";
6939
7337
  const hasLegacy = gitignoreContent.split("\n").some((l) => l.trimEnd() === legacyLine);
@@ -6952,18 +7350,18 @@ async function runLocalMigration(projectPath) {
6952
7350
  updated = gitignoreContent;
6953
7351
  }
6954
7352
  if (updated !== gitignoreContent) {
6955
- await writeFile12(gitignorePath, updated, "utf-8");
7353
+ await writeFile13(gitignorePath, updated, "utf-8");
6956
7354
  filesChanged.push(".gitignore");
6957
7355
  }
6958
7356
  } catch {
6959
7357
  }
6960
7358
  try {
6961
- await unlink2(legacySharedPath(projectPath));
7359
+ await unlink3(legacySharedPath(projectPath));
6962
7360
  filesChanged.push(".codebyplan.json (deleted)");
6963
7361
  } catch {
6964
7362
  }
6965
7363
  try {
6966
- await unlink2(legacyLocalPath(projectPath));
7364
+ await unlink3(legacyLocalPath(projectPath));
6967
7365
  filesChanged.push(".codebyplan.local.json (deleted)");
6968
7366
  } catch {
6969
7367
  }
@@ -6993,8 +7391,8 @@ __export(config_exports, {
6993
7391
  readVendorConfig: () => readVendorConfig,
6994
7392
  runConfig: () => runConfig
6995
7393
  });
6996
- import { mkdir as mkdir7, readFile as readFile17, writeFile as writeFile13 } from "node:fs/promises";
6997
- import { join as join23 } from "node:path";
7394
+ import { mkdir as mkdir8, readFile as readFile18, writeFile as writeFile14 } from "node:fs/promises";
7395
+ import { join as join24 } from "node:path";
6998
7396
  async function runConfig() {
6999
7397
  const flags = parseFlags(3);
7000
7398
  const dryRun = hasFlag("dry-run", 3);
@@ -7027,7 +7425,7 @@ async function runConfig() {
7027
7425
  console.log("\n Config complete.\n");
7028
7426
  }
7029
7427
  async function syncConfigToFile(repoId, projectPath, dryRun) {
7030
- const codebyplanDir = join23(projectPath, ".codebyplan");
7428
+ const codebyplanDir = join24(projectPath, ".codebyplan");
7031
7429
  const {
7032
7430
  resolvedWorktreeId,
7033
7431
  portAllocations,
@@ -7099,7 +7497,7 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
7099
7497
  console.log(" Config would be updated (dry-run).");
7100
7498
  return;
7101
7499
  }
7102
- await mkdir7(codebyplanDir, { recursive: true });
7500
+ await mkdir8(codebyplanDir, { recursive: true });
7103
7501
  const files = [
7104
7502
  { name: "repo.json", payload: repoPayload },
7105
7503
  { name: "server.json", payload: serverPayload },
@@ -7111,16 +7509,16 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
7111
7509
  ];
7112
7510
  let anyUpdated = false;
7113
7511
  for (const { name, payload, createOnly } of files) {
7114
- const filePath = join23(codebyplanDir, name);
7512
+ const filePath = join24(codebyplanDir, name);
7115
7513
  const newJson = JSON.stringify(payload, null, 2) + "\n";
7116
7514
  let currentJson = "";
7117
7515
  try {
7118
- currentJson = await readFile17(filePath, "utf-8");
7516
+ currentJson = await readFile18(filePath, "utf-8");
7119
7517
  } catch {
7120
7518
  }
7121
7519
  if (createOnly && currentJson !== "") continue;
7122
7520
  if (currentJson === newJson) continue;
7123
- await writeFile13(filePath, newJson, "utf-8");
7521
+ await writeFile14(filePath, newJson, "utf-8");
7124
7522
  console.log(` Updated .codebyplan/${name}`);
7125
7523
  anyUpdated = true;
7126
7524
  }
@@ -7130,8 +7528,8 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
7130
7528
  }
7131
7529
  async function readRepoConfig(projectPath) {
7132
7530
  try {
7133
- const raw = await readFile17(
7134
- join23(projectPath, ".codebyplan", "repo.json"),
7531
+ const raw = await readFile18(
7532
+ join24(projectPath, ".codebyplan", "repo.json"),
7135
7533
  "utf-8"
7136
7534
  );
7137
7535
  return JSON.parse(raw);
@@ -7141,8 +7539,8 @@ async function readRepoConfig(projectPath) {
7141
7539
  }
7142
7540
  async function readServerConfig(projectPath) {
7143
7541
  try {
7144
- const raw = await readFile17(
7145
- join23(projectPath, ".codebyplan", "server.json"),
7542
+ const raw = await readFile18(
7543
+ join24(projectPath, ".codebyplan", "server.json"),
7146
7544
  "utf-8"
7147
7545
  );
7148
7546
  return JSON.parse(raw);
@@ -7152,8 +7550,8 @@ async function readServerConfig(projectPath) {
7152
7550
  }
7153
7551
  async function readGitConfig(projectPath) {
7154
7552
  try {
7155
- const raw = await readFile17(
7156
- join23(projectPath, ".codebyplan", "git.json"),
7553
+ const raw = await readFile18(
7554
+ join24(projectPath, ".codebyplan", "git.json"),
7157
7555
  "utf-8"
7158
7556
  );
7159
7557
  return JSON.parse(raw);
@@ -7163,8 +7561,8 @@ async function readGitConfig(projectPath) {
7163
7561
  }
7164
7562
  async function readShipmentConfig(projectPath) {
7165
7563
  try {
7166
- const raw = await readFile17(
7167
- join23(projectPath, ".codebyplan", "shipment.json"),
7564
+ const raw = await readFile18(
7565
+ join24(projectPath, ".codebyplan", "shipment.json"),
7168
7566
  "utf-8"
7169
7567
  );
7170
7568
  return JSON.parse(raw);
@@ -7174,8 +7572,8 @@ async function readShipmentConfig(projectPath) {
7174
7572
  }
7175
7573
  async function readVendorConfig(projectPath) {
7176
7574
  try {
7177
- const raw = await readFile17(
7178
- join23(projectPath, ".codebyplan", "vendor.json"),
7575
+ const raw = await readFile18(
7576
+ join24(projectPath, ".codebyplan", "vendor.json"),
7179
7577
  "utf-8"
7180
7578
  );
7181
7579
  return JSON.parse(raw);
@@ -7185,8 +7583,8 @@ async function readVendorConfig(projectPath) {
7185
7583
  }
7186
7584
  async function readE2eConfig2(projectPath) {
7187
7585
  try {
7188
- const raw = await readFile17(
7189
- join23(projectPath, ".codebyplan", "e2e.json"),
7586
+ const raw = await readFile18(
7587
+ join24(projectPath, ".codebyplan", "e2e.json"),
7190
7588
  "utf-8"
7191
7589
  );
7192
7590
  return JSON.parse(raw);
@@ -7196,8 +7594,8 @@ async function readE2eConfig2(projectPath) {
7196
7594
  }
7197
7595
  async function readServerLocalConfig(projectPath) {
7198
7596
  try {
7199
- const raw = await readFile17(
7200
- join23(projectPath, ".codebyplan", "server.local.json"),
7597
+ const raw = await readFile18(
7598
+ join24(projectPath, ".codebyplan", "server.local.json"),
7201
7599
  "utf-8"
7202
7600
  );
7203
7601
  return JSON.parse(raw);
@@ -7252,14 +7650,14 @@ var init_server_detect = __esm({
7252
7650
  });
7253
7651
 
7254
7652
  // src/lib/port-verify.ts
7255
- import { readFile as readFile18 } from "node:fs/promises";
7653
+ import { readFile as readFile19 } from "node:fs/promises";
7256
7654
  async function verifyPorts(projectPath, portAllocations) {
7257
7655
  const mismatches = [];
7258
7656
  const allocatedPorts = new Set(portAllocations.map((a) => a.port));
7259
7657
  const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
7260
7658
  for (const pkgPath of packageJsonPaths) {
7261
7659
  try {
7262
- const raw = await readFile18(pkgPath, "utf-8");
7660
+ const raw = await readFile19(pkgPath, "utf-8");
7263
7661
  const pkg = JSON.parse(raw);
7264
7662
  const scriptPort = detectPortFromScripts(pkg);
7265
7663
  if (scriptPort !== null && !allocatedPorts.has(scriptPort)) {
@@ -7322,7 +7720,7 @@ async function findUnallocatedApps(projectPath, portAllocations) {
7322
7720
  }
7323
7721
  let pkg;
7324
7722
  try {
7325
- const raw = await readFile18(`${app.absPath}/package.json`, "utf-8");
7723
+ const raw = await readFile19(`${app.absPath}/package.json`, "utf-8");
7326
7724
  pkg = JSON.parse(raw);
7327
7725
  } catch {
7328
7726
  continue;
@@ -7372,8 +7770,8 @@ __export(ports_exports, {
7372
7770
  parseEnvFile: () => parseEnvFile,
7373
7771
  runPorts: () => runPorts
7374
7772
  });
7375
- import { mkdir as mkdir8, readFile as readFile19, writeFile as writeFile14 } from "node:fs/promises";
7376
- import { join as join24 } from "node:path";
7773
+ import { mkdir as mkdir9, readFile as readFile20, writeFile as writeFile15 } from "node:fs/promises";
7774
+ import { join as join25 } from "node:path";
7377
7775
  async function runPorts() {
7378
7776
  const flags = parseFlags(3);
7379
7777
  const dryRun = hasFlag("dry-run", 3);
@@ -7494,12 +7892,12 @@ async function writeServerLocalConfig(repoId, projectPath, dryRun) {
7494
7892
  // and ServerLocalConfig.port_allocations is typed the same — honest end-to-end.
7495
7893
  port_allocations: portAllocations
7496
7894
  };
7497
- const codebyplanDir = join24(projectPath, ".codebyplan");
7498
- const filePath = join24(codebyplanDir, "server.local.json");
7895
+ const codebyplanDir = join25(projectPath, ".codebyplan");
7896
+ const filePath = join25(codebyplanDir, "server.local.json");
7499
7897
  const newJson = JSON.stringify(payload, null, 2) + "\n";
7500
7898
  let currentJson = "";
7501
7899
  try {
7502
- currentJson = await readFile19(filePath, "utf-8");
7900
+ currentJson = await readFile20(filePath, "utf-8");
7503
7901
  } catch {
7504
7902
  }
7505
7903
  if (currentJson === newJson) {
@@ -7510,18 +7908,18 @@ async function writeServerLocalConfig(repoId, projectPath, dryRun) {
7510
7908
  console.log(" Would update .codebyplan/server.local.json (dry-run).");
7511
7909
  return;
7512
7910
  }
7513
- await mkdir8(codebyplanDir, { recursive: true });
7514
- await writeFile14(filePath, newJson, "utf-8");
7911
+ await mkdir9(codebyplanDir, { recursive: true });
7912
+ await writeFile15(filePath, newJson, "utf-8");
7515
7913
  console.log(
7516
7914
  ` Updated .codebyplan/server.local.json (worktree ${resolvedWorktreeId ?? "\u2014"}, ${portAllocations.length} allocation${portAllocations.length === 1 ? "" : "s"}).`
7517
7915
  );
7518
7916
  }
7519
7917
  async function provisionE2eEnv(projectPath, dryRun) {
7520
- const relSource = join24("apps", "web", ".env.local");
7521
- const sourcePath = join24(projectPath, relSource);
7918
+ const relSource = join25("apps", "web", ".env.local");
7919
+ const sourcePath = join25(projectPath, relSource);
7522
7920
  let sourceRaw;
7523
7921
  try {
7524
- sourceRaw = await readFile19(sourcePath, "utf-8");
7922
+ sourceRaw = await readFile20(sourcePath, "utf-8");
7525
7923
  } catch {
7526
7924
  console.warn(
7527
7925
  ` Skipped .codebyplan/e2e.env \u2014 source ${relSource} not found.`
@@ -7550,12 +7948,12 @@ async function provisionE2eEnv(projectPath, dryRun) {
7550
7948
  );
7551
7949
  return;
7552
7950
  }
7553
- const codebyplanDir = join24(projectPath, ".codebyplan");
7554
- const filePath = join24(codebyplanDir, "e2e.env");
7951
+ const codebyplanDir = join25(projectPath, ".codebyplan");
7952
+ const filePath = join25(codebyplanDir, "e2e.env");
7555
7953
  const newContent = lines.join("\n") + "\n";
7556
7954
  let currentContent = "";
7557
7955
  try {
7558
- currentContent = await readFile19(filePath, "utf-8");
7956
+ currentContent = await readFile20(filePath, "utf-8");
7559
7957
  } catch {
7560
7958
  }
7561
7959
  if (currentContent === newContent) {
@@ -7566,8 +7964,8 @@ async function provisionE2eEnv(projectPath, dryRun) {
7566
7964
  console.log(" Would provision .codebyplan/e2e.env (dry-run).");
7567
7965
  return;
7568
7966
  }
7569
- await mkdir8(codebyplanDir, { recursive: true });
7570
- await writeFile14(filePath, newContent, "utf-8");
7967
+ await mkdir9(codebyplanDir, { recursive: true });
7968
+ await writeFile15(filePath, newContent, "utf-8");
7571
7969
  console.log(
7572
7970
  ` Provisioned .codebyplan/e2e.env (${lines.length} var${lines.length === 1 ? "" : "s"}).`
7573
7971
  );
@@ -8414,6 +8812,11 @@ async function runUpdate(opts, deps = {}) {
8414
8812
  if (opts.renderer && !opts.dryRun) {
8415
8813
  await writeStatuslineLocalConfig(projectDir, { renderer: opts.renderer });
8416
8814
  }
8815
+ try {
8816
+ const { runLspFull: runLspFull2 } = await Promise.resolve().then(() => (init_lsp(), lsp_exports));
8817
+ await runLspFull2(projectDir, { dryRun: opts.dryRun });
8818
+ } catch {
8819
+ }
8417
8820
  } catch (err) {
8418
8821
  console.error(
8419
8822
  `codebyplan claude update failed: ${err instanceof Error ? err.message : String(err)}`
@@ -8886,6 +9289,11 @@ void (async () => {
8886
9289
  await runTechStack2();
8887
9290
  process.exit(0);
8888
9291
  }
9292
+ if (arg === "lsp") {
9293
+ const { runLsp: runLsp2 } = await Promise.resolve().then(() => (init_lsp(), lsp_exports));
9294
+ await runLsp2();
9295
+ process.exit(0);
9296
+ }
8889
9297
  if (arg === "claude") {
8890
9298
  const subcommand = process.argv[3];
8891
9299
  const flagArgs = process.argv.slice(3);
@@ -8972,6 +9380,7 @@ void (async () => {
8972
9380
  codebyplan tech-stack Detect and sync tech stack dependencies
8973
9381
  (--full-tech-stack: sync every local worktree on this device)
8974
9382
  codebyplan eslint ESLint config management (init)
9383
+ codebyplan lsp Detect tech stack and enable LSP plugins in Claude Code
8975
9384
  codebyplan round sync-approvals Sync git diff and approvals with round/task state
8976
9385
  codebyplan bump Detect changed packages and patch-bump versions
8977
9386
  codebyplan ship Ship current feat branch to production via PR