@sightmap/sightmap 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1497,6 +1497,682 @@ async function processFile(absPath, relPath, opts, fileResults, diagnostics) {
1497
1497
  }
1498
1498
  }
1499
1499
 
1500
+ // src/cli/init/index.ts
1501
+ import { resolve as resolve21 } from "path";
1502
+ import { mkdir as mkdir4, readFile as readFile12, rm, writeFile as writeFile7 } from "fs/promises";
1503
+ import { tmpdir } from "os";
1504
+ import * as clack2 from "@clack/prompts";
1505
+
1506
+ // src/cli/init/detect/framework.ts
1507
+ import { readFile as readFile4, access } from "fs/promises";
1508
+ import { resolve as resolve11 } from "path";
1509
+ async function exists(path) {
1510
+ try {
1511
+ await access(path);
1512
+ return true;
1513
+ } catch {
1514
+ return false;
1515
+ }
1516
+ }
1517
+ async function detectFramework(cwd) {
1518
+ const pkgPath = resolve11(cwd, "package.json");
1519
+ if (!await exists(pkgPath)) return "unknown";
1520
+ const pkg = JSON.parse(await readFile4(pkgPath, "utf8"));
1521
+ const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
1522
+ if (deps.next) {
1523
+ if (await exists(resolve11(cwd, "app"))) return "next-app";
1524
+ if (await exists(resolve11(cwd, "pages"))) return "next-pages";
1525
+ return "next-app";
1526
+ }
1527
+ const rrVersion = deps["react-router"];
1528
+ const rr7 = rrVersion ? /^[\^~>=<]*7\./.test(rrVersion) : false;
1529
+ if (rr7 || await exists(resolve11(cwd, "react-router.config.ts"))) {
1530
+ return "react-router-7";
1531
+ }
1532
+ if (deps.react) {
1533
+ if (deps.vite || await exists(resolve11(cwd, "vite.config.ts")) || await exists(resolve11(cwd, "vite.config.js"))) {
1534
+ return "react-vite";
1535
+ }
1536
+ if (deps["react-scripts"]) return "react-cra";
1537
+ }
1538
+ return "unknown";
1539
+ }
1540
+
1541
+ // src/cli/init/detect/harness.ts
1542
+ import { access as access2 } from "fs/promises";
1543
+ import { resolve as resolve12 } from "path";
1544
+ async function exists2(path) {
1545
+ try {
1546
+ await access2(path);
1547
+ return true;
1548
+ } catch {
1549
+ return false;
1550
+ }
1551
+ }
1552
+ var HARNESS_ORDER = ["claude-code", "codex", "cursor", "opencode"];
1553
+ async function detectHarnesses(cwd) {
1554
+ const out = [];
1555
+ for (const h of HARNESS_ORDER) {
1556
+ let found = false;
1557
+ if (h === "claude-code") found = await exists2(resolve12(cwd, ".claude"));
1558
+ else if (h === "cursor") found = await exists2(resolve12(cwd, ".cursor"));
1559
+ else if (h === "codex") found = await exists2(resolve12(cwd, ".codex"));
1560
+ else if (h === "opencode") found = await exists2(resolve12(cwd, "opencode.json"));
1561
+ if (found) out.push(h);
1562
+ }
1563
+ return out;
1564
+ }
1565
+
1566
+ // src/cli/init/detect/vendor.ts
1567
+ import { readFile as readFile5, access as access3 } from "fs/promises";
1568
+ import { resolve as resolve13 } from "path";
1569
+
1570
+ // src/cli/init/vendor-allowlist.ts
1571
+ var KNOWN_SIGHTMAP_VENDORS = ["subtext"];
1572
+ function isKnownVendor(entry) {
1573
+ const haystacks = [entry.name, entry.command, ...entry.args];
1574
+ return KNOWN_SIGHTMAP_VENDORS.some(
1575
+ (vendor) => haystacks.some((h) => h.toLowerCase().includes(vendor.toLowerCase()))
1576
+ );
1577
+ }
1578
+
1579
+ // src/cli/init/detect/vendor.ts
1580
+ async function readJsonIfExists(path) {
1581
+ try {
1582
+ await access3(path);
1583
+ return JSON.parse(await readFile5(path, "utf8"));
1584
+ } catch {
1585
+ return null;
1586
+ }
1587
+ }
1588
+ function extractServers(parsed) {
1589
+ if (!parsed || typeof parsed !== "object") return [];
1590
+ const root = parsed;
1591
+ const servers = root.mcpServers ?? {};
1592
+ return Object.entries(servers).map(([name, def]) => ({
1593
+ name,
1594
+ command: def.command ?? "",
1595
+ args: def.args ?? []
1596
+ }));
1597
+ }
1598
+ async function detectSightmapAwareVendor(cwd, hosts) {
1599
+ const candidates = [];
1600
+ if (hosts.includes("claude-code")) candidates.push(resolve13(cwd, ".mcp.json"));
1601
+ if (hosts.includes("cursor")) candidates.push(resolve13(cwd, ".cursor/mcp.json"));
1602
+ if (hosts.includes("opencode")) candidates.push(resolve13(cwd, "opencode.json"));
1603
+ for (const path of candidates) {
1604
+ const parsed = await readJsonIfExists(path);
1605
+ if (!parsed) continue;
1606
+ for (const entry of extractServers(parsed)) {
1607
+ if (isKnownVendor(entry)) return true;
1608
+ }
1609
+ }
1610
+ return false;
1611
+ }
1612
+
1613
+ // src/cli/init/detect/sightmap-dir.ts
1614
+ import { access as access4, readdir as readdir4 } from "fs/promises";
1615
+ import { resolve as resolve14 } from "path";
1616
+ async function detectSightmapDir(cwd) {
1617
+ const dir = resolve14(cwd, ".sightmap");
1618
+ try {
1619
+ await access4(dir);
1620
+ } catch {
1621
+ return { present: false };
1622
+ }
1623
+ const entries = await readdir4(dir);
1624
+ const yamls = entries.filter((e) => e.endsWith(".yaml") || e.endsWith(".yml"));
1625
+ return { present: true, viewCount: yamls.length };
1626
+ }
1627
+
1628
+ // src/cli/init/tui.ts
1629
+ import * as clack from "@clack/prompts";
1630
+ import pc from "picocolors";
1631
+ var FRAMEWORK_LABELS = {
1632
+ "react-vite": "React (Vite)",
1633
+ "react-cra": "React (CRA)",
1634
+ "next-app": "Next.js (App Router)",
1635
+ "next-pages": "Next.js (Pages Router)",
1636
+ "react-router-7": "React Router 7",
1637
+ unknown: "unknown"
1638
+ };
1639
+ var HOST_LABELS = {
1640
+ "claude-code": "Claude Code",
1641
+ codex: "Codex",
1642
+ cursor: "Cursor",
1643
+ opencode: "OpenCode"
1644
+ };
1645
+ function formatDetectionSummary(d) {
1646
+ const fw = `Framework: ${FRAMEWORK_LABELS[d.framework]}`;
1647
+ const hosts = d.hosts.length > 0 ? `Coding agents: ${d.hosts.map((h) => HOST_LABELS[h]).join(", ")}` : "Coding agents: none detected";
1648
+ const vendor = d.sightmapAwareVendor ? "Browser MCP: Sightmap-aware vendor detected" : "Browser MCP: none detected";
1649
+ const dir = d.sightmapDir.present ? `.sightmap/: present (${d.sightmapDir.viewCount ?? 0} views)` : ".sightmap/: not present";
1650
+ return [fw, hosts, vendor, dir];
1651
+ }
1652
+ function intro2() {
1653
+ clack.intro(pc.cyan("\u25C6 Sightmap") + " curate your view inventory");
1654
+ }
1655
+ function outro2(message) {
1656
+ clack.outro(pc.green(message));
1657
+ }
1658
+ function showDetection(d) {
1659
+ clack.note(formatDetectionSummary(d).join("\n"), pc.dim("Detected"));
1660
+ }
1661
+ async function selectInstallPath(suggestion = "plugin") {
1662
+ return clack.select({
1663
+ message: "How would you like to install Sightmap?",
1664
+ initialValue: suggestion,
1665
+ options: [
1666
+ {
1667
+ value: "plugin",
1668
+ label: "Plugin (recommended)",
1669
+ hint: "Two copy-paste commands per host. Skills, hooks, MCP \u2014 all together."
1670
+ },
1671
+ {
1672
+ value: "manual",
1673
+ label: "Manual",
1674
+ hint: "Write MCP config + copy skills/hooks into this repo."
1675
+ }
1676
+ ]
1677
+ });
1678
+ }
1679
+ async function selectHosts(detected, defaults) {
1680
+ const options = ["claude-code", "codex", "cursor", "opencode"].map((h) => ({
1681
+ value: h,
1682
+ label: HOST_LABELS[h] + (detected.includes(h) ? pc.dim(" (detected)") : "")
1683
+ }));
1684
+ return clack.multiselect({
1685
+ message: "Which hosts?",
1686
+ options,
1687
+ initialValues: defaults,
1688
+ required: true
1689
+ });
1690
+ }
1691
+
1692
+ // src/cli/init/mcp-config/claude-code.ts
1693
+ import { readFile as readFile6, writeFile as writeFile2, access as access5 } from "fs/promises";
1694
+ import { resolve as resolve15 } from "path";
1695
+ async function readJsonIfExists2(path) {
1696
+ try {
1697
+ await access5(path);
1698
+ return JSON.parse(await readFile6(path, "utf8"));
1699
+ } catch {
1700
+ return null;
1701
+ }
1702
+ }
1703
+ async function writeClaudeCodeMcp(opts) {
1704
+ const path = resolve15(opts.cwd, ".mcp.json");
1705
+ const existing = await readJsonIfExists2(path) ?? {};
1706
+ const servers = existing.mcpServers ?? {};
1707
+ const next = {
1708
+ ...existing,
1709
+ mcpServers: {
1710
+ ...servers,
1711
+ [opts.serverName]: opts.serverDef
1712
+ }
1713
+ };
1714
+ await writeFile2(path, JSON.stringify(next, null, 2) + "\n", "utf8");
1715
+ }
1716
+
1717
+ // src/cli/init/mcp-config/cursor.ts
1718
+ import { readFile as readFile7, writeFile as writeFile3, access as access6, mkdir } from "fs/promises";
1719
+ import { resolve as resolve16, dirname as dirname2 } from "path";
1720
+ async function writeCursorMcp(opts) {
1721
+ const path = resolve16(opts.cwd, ".cursor/mcp.json");
1722
+ let existing = {};
1723
+ try {
1724
+ await access6(path);
1725
+ existing = JSON.parse(await readFile7(path, "utf8"));
1726
+ } catch {
1727
+ await mkdir(dirname2(path), { recursive: true });
1728
+ }
1729
+ const servers = existing.mcpServers ?? {};
1730
+ const next = {
1731
+ ...existing,
1732
+ mcpServers: { ...servers, [opts.serverName]: opts.serverDef }
1733
+ };
1734
+ await writeFile3(path, JSON.stringify(next, null, 2) + "\n", "utf8");
1735
+ }
1736
+
1737
+ // src/cli/init/mcp-config/opencode.ts
1738
+ import { readFile as readFile8, writeFile as writeFile4, access as access7 } from "fs/promises";
1739
+ import { resolve as resolve17 } from "path";
1740
+ async function writeOpenCodeMcp(opts) {
1741
+ const path = resolve17(opts.cwd, "opencode.json");
1742
+ let existing = {};
1743
+ try {
1744
+ await access7(path);
1745
+ existing = JSON.parse(await readFile8(path, "utf8"));
1746
+ } catch {
1747
+ }
1748
+ const mcp = existing.mcp ?? {};
1749
+ const next = {
1750
+ ...existing,
1751
+ mcp: { ...mcp, [opts.serverName]: opts.serverDef }
1752
+ };
1753
+ await writeFile4(path, JSON.stringify(next, null, 2) + "\n", "utf8");
1754
+ }
1755
+
1756
+ // src/cli/init/mcp-config/codex.ts
1757
+ import { readFile as readFile9, writeFile as writeFile5, access as access8, mkdir as mkdir2 } from "fs/promises";
1758
+ import { dirname as dirname3 } from "path";
1759
+ import { parse as parse2, stringify } from "smol-toml";
1760
+ async function writeCodexMcp(opts) {
1761
+ let existing = {};
1762
+ try {
1763
+ await access8(opts.configPath);
1764
+ existing = parse2(await readFile9(opts.configPath, "utf8"));
1765
+ } catch {
1766
+ await mkdir2(dirname3(opts.configPath), { recursive: true });
1767
+ }
1768
+ const servers = existing.mcp_servers ?? {};
1769
+ const next = {
1770
+ ...existing,
1771
+ mcp_servers: {
1772
+ ...servers,
1773
+ [opts.serverName]: opts.serverDef
1774
+ }
1775
+ };
1776
+ await writeFile5(opts.configPath, stringify(next), "utf8");
1777
+ }
1778
+
1779
+ // src/cli/init/tarball.ts
1780
+ import pacote from "pacote";
1781
+ import { readFile as readFile10 } from "fs/promises";
1782
+ import { resolve as resolve18 } from "path";
1783
+ async function fetchPluginTarball(opts) {
1784
+ const spec = `@sightmap/plugin@${opts.version}`;
1785
+ await pacote.extract(spec, opts.destDir);
1786
+ const manifestPath = resolve18(opts.destDir, ".claude-plugin/plugin.json");
1787
+ const manifest = JSON.parse(await readFile10(manifestPath, "utf8"));
1788
+ if (!manifest.compatibleMcpVersion) {
1789
+ throw new Error("Plugin tarball missing 'compatibleMcpVersion' in plugin.json");
1790
+ }
1791
+ return {
1792
+ destDir: opts.destDir,
1793
+ compatibleMcpVersion: manifest.compatibleMcpVersion,
1794
+ pluginVersion: manifest.version ?? "0.0.0"
1795
+ };
1796
+ }
1797
+
1798
+ // src/cli/init/skills-copy.ts
1799
+ import { cp, mkdir as mkdir3, readFile as readFile11, writeFile as writeFile6, access as access9 } from "fs/promises";
1800
+ import { resolve as resolve19 } from "path";
1801
+ async function copyPluginAssets(opts) {
1802
+ const targets = resolveTargets(opts.host, opts.projectDir);
1803
+ await mkdir3(targets.skills, { recursive: true });
1804
+ await cp(resolve19(opts.tarballDir, "skills"), targets.skills, { recursive: true });
1805
+ if (targets.agents) {
1806
+ await mkdir3(targets.agents, { recursive: true });
1807
+ await cp(resolve19(opts.tarballDir, "agents"), targets.agents, { recursive: true });
1808
+ }
1809
+ if (targets.hooksJson) {
1810
+ const srcPath = resolve19(opts.tarballDir, "hooks/hooks.json");
1811
+ const dstPath = targets.hooksJson;
1812
+ let merged;
1813
+ try {
1814
+ await access9(dstPath);
1815
+ const existing = JSON.parse(await readFile11(dstPath, "utf8"));
1816
+ const incoming = JSON.parse(await readFile11(srcPath, "utf8"));
1817
+ merged = { ...existing, ...incoming };
1818
+ } catch {
1819
+ merged = JSON.parse(await readFile11(srcPath, "utf8"));
1820
+ }
1821
+ await writeFile6(dstPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
1822
+ }
1823
+ }
1824
+ function resolveTargets(host, projectDir) {
1825
+ switch (host) {
1826
+ case "claude-code":
1827
+ return {
1828
+ skills: resolve19(projectDir, ".claude/skills/sightmap"),
1829
+ agents: resolve19(projectDir, ".claude/agents"),
1830
+ hooksJson: resolve19(projectDir, ".claude/hooks.json")
1831
+ };
1832
+ case "codex":
1833
+ return {
1834
+ skills: resolve19(projectDir, ".codex/skills/sightmap"),
1835
+ agents: resolve19(projectDir, ".codex/agents"),
1836
+ hooksJson: resolve19(projectDir, ".codex/hooks.json")
1837
+ };
1838
+ case "cursor":
1839
+ return { skills: resolve19(projectDir, ".cursor/rules/sightmap") };
1840
+ case "opencode":
1841
+ return { skills: resolve19(projectDir, ".opencode/plugins/sightmap") };
1842
+ }
1843
+ }
1844
+
1845
+ // src/cli/init/version-pin.ts
1846
+ function buildSightmapMcpServerDef(opts) {
1847
+ const args = [
1848
+ "-y",
1849
+ `@sightmap/mcp@${opts.compatibleMcpVersion}`,
1850
+ "--sightmap-dir",
1851
+ opts.sightmapDir,
1852
+ "--curate-root",
1853
+ opts.curateRoot
1854
+ ];
1855
+ if (opts.withBrowser) {
1856
+ args.push("--", "npx", "-y", "@playwright/mcp@latest");
1857
+ } else {
1858
+ args.push("--curate-only");
1859
+ }
1860
+ return {
1861
+ command: "npx",
1862
+ args,
1863
+ env: { SIGHTMAP_PLUGIN_VERSION: opts.pluginVersion }
1864
+ };
1865
+ }
1866
+
1867
+ // src/cli/init/plugin-path.ts
1868
+ var SECTIONS = {
1869
+ "claude-code": "Claude Code:\n /plugin marketplace add sightmap/plugin\n /plugin install sightmap@sightmap-marketplace",
1870
+ codex: "Codex:\n codex plugin marketplace add sightmap/plugin\n codex plugin install sightmap@sightmap-marketplace",
1871
+ cursor: "Cursor:\n /plugin add sightmap/plugin\n (in the Cursor command palette)",
1872
+ opencode: 'OpenCode:\n Add to opencode.json plugin array:\n "plugin": ["sightmap@git+https://github.com/sightmap/plugin"]\n Then restart OpenCode.'
1873
+ };
1874
+ function renderPluginInstructions(hosts) {
1875
+ const head = "Run these to finish setup:\n";
1876
+ const body = hosts.map((h) => SECTIONS[h]).join("\n\n");
1877
+ const foot = "\n\nThen restart your agent.";
1878
+ return head + "\n" + body + foot;
1879
+ }
1880
+
1881
+ // src/cli/init/framework-setup/package-manager.ts
1882
+ import { access as access10 } from "fs/promises";
1883
+ import { resolve as resolve20 } from "path";
1884
+ async function exists3(p) {
1885
+ try {
1886
+ await access10(p);
1887
+ return true;
1888
+ } catch {
1889
+ return false;
1890
+ }
1891
+ }
1892
+ async function detectPackageManager(cwd) {
1893
+ if (await exists3(resolve20(cwd, "pnpm-lock.yaml"))) return "pnpm";
1894
+ if (await exists3(resolve20(cwd, "yarn.lock"))) return "yarn";
1895
+ return "npm";
1896
+ }
1897
+
1898
+ // src/cli/init/framework-setup/install-adapter.ts
1899
+ import { spawn } from "child_process";
1900
+ var ADD_VERB = {
1901
+ pnpm: "add",
1902
+ yarn: "add",
1903
+ npm: "install"
1904
+ };
1905
+ async function installAdapter(opts) {
1906
+ await new Promise((resolveP, reject) => {
1907
+ const child = spawn(opts.packageManager, [ADD_VERB[opts.packageManager], opts.packageName], {
1908
+ cwd: opts.cwd,
1909
+ stdio: "inherit"
1910
+ });
1911
+ child.on("error", reject);
1912
+ child.on("exit", (code) => code === 0 ? resolveP() : reject(new Error(`${opts.packageManager} exited with code ${code}`)));
1913
+ });
1914
+ }
1915
+
1916
+ // src/cli/init/framework-setup/codegen.ts
1917
+ import { spawn as spawn2 } from "child_process";
1918
+ var REACT_FRAMEWORKS = ["react-vite", "react-cra", "next-app", "next-pages", "react-router-7"];
1919
+ var EXEC_VERB = {
1920
+ pnpm: ["exec"],
1921
+ yarn: ["exec"],
1922
+ npm: ["exec", "--"]
1923
+ };
1924
+ function buildCodegenCommand(opts) {
1925
+ if (!REACT_FRAMEWORKS.includes(opts.framework)) return null;
1926
+ return {
1927
+ command: opts.packageManager,
1928
+ args: [...EXEC_VERB[opts.packageManager], "sightmap-react", "gen"]
1929
+ };
1930
+ }
1931
+ async function runCodegen(cwd, cmd) {
1932
+ await new Promise((resolveP, reject) => {
1933
+ const child = spawn2(cmd.command, cmd.args, { cwd, stdio: "inherit" });
1934
+ child.on("error", reject);
1935
+ child.on("exit", (code) => code === 0 ? resolveP() : reject(new Error(`codegen exited ${code}`)));
1936
+ });
1937
+ }
1938
+
1939
+ // src/cli/init/framework-setup/provider-codemod.ts
1940
+ import * as recast from "recast";
1941
+ import * as parser from "@babel/parser";
1942
+ var babelParser = {
1943
+ parse(source) {
1944
+ return parser.parse(source, {
1945
+ sourceType: "module",
1946
+ tokens: true,
1947
+ plugins: ["jsx", "typescript"]
1948
+ });
1949
+ }
1950
+ };
1951
+ function wrapWithSightmapProvider(source) {
1952
+ if (source.includes("SightmapProvider")) {
1953
+ return { code: source, changed: false, reason: "SightmapProvider already present" };
1954
+ }
1955
+ const ast = recast.parse(source, { parser: babelParser });
1956
+ const b = recast.types.builders;
1957
+ let renderCall = null;
1958
+ let renderArgIndex = -1;
1959
+ recast.types.visit(ast, {
1960
+ visitCallExpression(path) {
1961
+ const callee = path.node.callee;
1962
+ if (callee.type === "MemberExpression" && callee.property.type === "Identifier" && callee.property.name === "render") {
1963
+ renderCall = path.node;
1964
+ renderArgIndex = 0;
1965
+ return false;
1966
+ }
1967
+ this.traverse(path);
1968
+ }
1969
+ });
1970
+ if (!renderCall) {
1971
+ return { code: source, changed: false, reason: "no .render() call found" };
1972
+ }
1973
+ const arg = renderCall.arguments[renderArgIndex];
1974
+ if (!arg) return { code: source, changed: false, reason: "render() called with no args" };
1975
+ const wrapped = b.jsxElement(
1976
+ b.jsxOpeningElement(b.jsxIdentifier("SightmapProvider"), [], false),
1977
+ b.jsxClosingElement(b.jsxIdentifier("SightmapProvider")),
1978
+ [arg]
1979
+ );
1980
+ renderCall.arguments[renderArgIndex] = wrapped;
1981
+ const importDecl = b.importDeclaration(
1982
+ [b.importSpecifier(b.identifier("SightmapProvider"), b.identifier("SightmapProvider"))],
1983
+ b.stringLiteral("@sightmap/react")
1984
+ );
1985
+ ast.program.body.unshift(importDecl);
1986
+ const output = recast.print(ast, { quote: "single" }).code;
1987
+ return { code: output, changed: true };
1988
+ }
1989
+
1990
+ // src/cli/init/existing-corpus.ts
1991
+ function formatExistingCorpusReport(input) {
1992
+ const errors = input.diagnostics.filter((d) => d.severity === "error");
1993
+ const warnings = input.diagnostics.filter((d) => d.severity === "warning");
1994
+ const lines = [];
1995
+ lines.push(`sightmap check passed (${input.viewCount} views, ${errors.length} schema errors)`);
1996
+ if (warnings.length > 0) {
1997
+ const byCode = /* @__PURE__ */ new Map();
1998
+ for (const w of warnings) byCode.set(w.code, (byCode.get(w.code) ?? 0) + 1);
1999
+ const emptyMemory = byCode.get("EMPTY_MEMORY") ?? 0;
2000
+ if (emptyMemory > 0) {
2001
+ lines.push(`${emptyMemory} views have empty memory blocks \u2014 run /sightmap audit for details`);
2002
+ }
2003
+ }
2004
+ return lines.join("\n");
2005
+ }
2006
+
2007
+ // src/cli/init/index.ts
2008
+ var REACT_FRAMEWORKS2 = ["react-vite", "react-cra", "next-app", "next-pages", "react-router-7"];
2009
+ async function runInit(opts) {
2010
+ intro2();
2011
+ const detect = {
2012
+ framework: await detectFramework(opts.cwd),
2013
+ hosts: await detectHarnesses(opts.cwd),
2014
+ sightmapAwareVendor: false,
2015
+ sightmapDir: await detectSightmapDir(opts.cwd)
2016
+ };
2017
+ detect.sightmapAwareVendor = await detectSightmapAwareVendor(opts.cwd, detect.hosts);
2018
+ showDetection(detect);
2019
+ if (detect.sightmapDir.present) {
2020
+ await runExistingCorpusFlow(opts.cwd, detect.sightmapDir.viewCount ?? 0);
2021
+ } else {
2022
+ await runFreshFrameworkSetup(opts, detect);
2023
+ }
2024
+ let installPath = opts.installPath;
2025
+ if (!installPath) {
2026
+ if (opts.yes) installPath = "plugin";
2027
+ else {
2028
+ const choice = await selectInstallPath("plugin");
2029
+ if (typeof choice === "symbol") return 1;
2030
+ installPath = choice;
2031
+ }
2032
+ }
2033
+ const hosts = await resolveHosts(opts, detect);
2034
+ if (hosts.length === 0) {
2035
+ outro2("No hosts selected \u2014 nothing to do.");
2036
+ return 0;
2037
+ }
2038
+ const withBrowser = resolveBrowserBundle(opts, detect);
2039
+ if (installPath === "plugin") {
2040
+ clack2.note(renderPluginInstructions(hosts), "Next");
2041
+ outro2("Run those, then restart your agent.");
2042
+ return 0;
2043
+ }
2044
+ const tarballDir = resolve21(tmpdir(), `sightmap-plugin-${Date.now()}`);
2045
+ await mkdir4(tarballDir, { recursive: true });
2046
+ try {
2047
+ const tar = await fetchPluginTarball({ destDir: tarballDir, version: "latest" });
2048
+ const serverDef = buildSightmapMcpServerDef({
2049
+ compatibleMcpVersion: tar.compatibleMcpVersion,
2050
+ pluginVersion: tar.pluginVersion,
2051
+ withBrowser,
2052
+ sightmapDir: ".sightmap",
2053
+ curateRoot: ".sightmap"
2054
+ });
2055
+ for (const h of hosts) {
2056
+ await writeMcpForHost(h, opts.cwd, serverDef);
2057
+ await copyPluginAssets({ tarballDir, projectDir: opts.cwd, host: h });
2058
+ }
2059
+ } finally {
2060
+ await rm(tarballDir, { recursive: true, force: true });
2061
+ }
2062
+ outro2("Done. Restart your agent to pick up the new MCP.");
2063
+ return 0;
2064
+ }
2065
+ async function runFreshFrameworkSetup(opts, detect) {
2066
+ if (!REACT_FRAMEWORKS2.includes(detect.framework)) {
2067
+ await mkdir4(resolve21(opts.cwd, ".sightmap"), { recursive: true });
2068
+ await writeFile7(
2069
+ resolve21(opts.cwd, ".sightmap/app.yaml"),
2070
+ `version: 1
2071
+ views: []
2072
+ `,
2073
+ "utf8"
2074
+ );
2075
+ return;
2076
+ }
2077
+ const pm = await detectPackageManager(opts.cwd);
2078
+ const spinner2 = clack2.spinner();
2079
+ spinner2.start(`Installing @sightmap/react via ${pm}`);
2080
+ await installAdapter({ cwd: opts.cwd, packageManager: pm, packageName: "@sightmap/react" });
2081
+ spinner2.stop("Installed @sightmap/react");
2082
+ if (!opts.noProvider) {
2083
+ const entry = await findAppEntry(opts.cwd, detect.framework);
2084
+ if (entry) {
2085
+ const source = await readFile12(entry, "utf8");
2086
+ const result = wrapWithSightmapProvider(source);
2087
+ if (result.changed) {
2088
+ if (opts.yes || await confirmCodemod(entry)) {
2089
+ await writeFile7(entry, result.code, "utf8");
2090
+ }
2091
+ } else if (result.reason) {
2092
+ clack2.log.warn(`SightmapProvider codemod skipped: ${result.reason}. Add manually to ${entry}.`);
2093
+ }
2094
+ } else {
2095
+ clack2.log.warn("Could not locate an app entry file. Add <SightmapProvider> manually.");
2096
+ }
2097
+ }
2098
+ if (!opts.noCodegen) {
2099
+ const cmd = buildCodegenCommand({ framework: detect.framework, packageManager: pm });
2100
+ if (cmd) {
2101
+ spinner2.start("Running sightmap-react gen");
2102
+ await runCodegen(opts.cwd, cmd);
2103
+ spinner2.stop("Scaffolded .sightmap/");
2104
+ }
2105
+ }
2106
+ }
2107
+ async function findAppEntry(cwd, framework) {
2108
+ const candidates = {
2109
+ "react-vite": ["src/main.tsx", "src/main.jsx", "src/index.tsx", "src/index.jsx"],
2110
+ "react-cra": ["src/index.tsx", "src/index.jsx"],
2111
+ "next-app": ["app/layout.tsx", "app/layout.jsx"],
2112
+ "next-pages": ["pages/_app.tsx", "pages/_app.jsx"],
2113
+ "react-router-7": ["app/root.tsx", "app/root.jsx", "src/root.tsx"],
2114
+ unknown: []
2115
+ };
2116
+ for (const rel of candidates[framework]) {
2117
+ const abs = resolve21(cwd, rel);
2118
+ try {
2119
+ await readFile12(abs, "utf8");
2120
+ return abs;
2121
+ } catch {
2122
+ }
2123
+ }
2124
+ return null;
2125
+ }
2126
+ async function confirmCodemod(entry) {
2127
+ const choice = await clack2.confirm({
2128
+ message: `Wrap ${entry} with <SightmapProvider>?`,
2129
+ initialValue: true
2130
+ });
2131
+ return typeof choice === "boolean" ? choice : false;
2132
+ }
2133
+ async function runExistingCorpusFlow(cwd, viewCount) {
2134
+ const spinner2 = clack2.spinner();
2135
+ spinner2.start("Auditing existing corpus...");
2136
+ await runLint({ path: ".sightmap", cwd, json: false, strict: false });
2137
+ spinner2.stop("Audit complete");
2138
+ clack2.note(formatExistingCorpusReport({ viewCount, diagnostics: [] }), `${viewCount} view${viewCount === 1 ? "" : "s"}`);
2139
+ }
2140
+ async function resolveHosts(opts, detect) {
2141
+ if (opts.hosts) {
2142
+ const valid = /* @__PURE__ */ new Set(["claude-code", "codex", "cursor", "opencode"]);
2143
+ const invalid = opts.hosts.filter((h) => !valid.has(h));
2144
+ if (invalid.length > 0) {
2145
+ throw new Error(
2146
+ `Unknown host(s): ${invalid.join(", ")}. Valid: claude-code, codex, cursor, opencode`
2147
+ );
2148
+ }
2149
+ return opts.hosts;
2150
+ }
2151
+ if (opts.yes) return detect.hosts.length > 0 ? detect.hosts : ["claude-code"];
2152
+ const defaults = detect.hosts.length > 0 ? detect.hosts : ["claude-code"];
2153
+ const choice = await selectHosts(detect.hosts, defaults);
2154
+ if (typeof choice === "symbol") return [];
2155
+ return choice;
2156
+ }
2157
+ function resolveBrowserBundle(opts, detect) {
2158
+ if (opts.withBrowser) return true;
2159
+ if (opts.curateOnly) return false;
2160
+ return !detect.sightmapAwareVendor;
2161
+ }
2162
+ async function writeMcpForHost(host, cwd, serverDef) {
2163
+ const common = { cwd, serverName: "sightmap", serverDef };
2164
+ if (host === "claude-code") return writeClaudeCodeMcp(common);
2165
+ if (host === "cursor") return writeCursorMcp(common);
2166
+ if (host === "opencode") return writeOpenCodeMcp(common);
2167
+ if (host === "codex") {
2168
+ return writeCodexMcp({
2169
+ configPath: resolve21(process.env.HOME ?? cwd, ".codex/config.toml"),
2170
+ serverName: "sightmap",
2171
+ serverDef
2172
+ });
2173
+ }
2174
+ }
2175
+
1500
2176
  // src/cli/index.ts
1501
2177
  var program = new Command();
1502
2178
  program.name("sightmap").description("CLI for the Sightmap spec \u2014 validate, lint, match, explain.").version("0.1.0");
@@ -1590,6 +2266,20 @@ program.command("fmt [path]").description("Canonicalize .sightmap/*.yaml files (
1590
2266
  process.exit(code);
1591
2267
  }
1592
2268
  );
2269
+ program.command("init").description("Initialize Sightmap in this project (detects framework + coding agent, writes config).").option("--yes", "accept all defaults (non-interactive)").option("--plugin", "force plugin install path").option("--manual", "force manual install path").option("--host <names>", "comma-separated hosts: claude-code,codex,cursor,opencode").option("--with-browser", "force bundled Playwright + browser tools").option("--curate-only", "force curation-only (skip browser tools)").option("--no-codegen", "skip sightmap-react gen").option("--no-provider", "skip <SightmapProvider> codemod").action(async (opts) => {
2270
+ const code = await runInit({
2271
+ cwd: program.opts().cwd,
2272
+ yes: opts.yes === true,
2273
+ ...opts.plugin ? { installPath: "plugin" } : {},
2274
+ ...opts.manual ? { installPath: "manual" } : {},
2275
+ ...opts.host !== void 0 ? { hosts: opts.host.split(",").map((s) => s.trim()) } : {},
2276
+ ...opts.withBrowser ? { withBrowser: true } : {},
2277
+ ...opts.curateOnly ? { curateOnly: true } : {},
2278
+ ...opts.codegen === false ? { noCodegen: true } : {},
2279
+ ...opts.provider === false ? { noProvider: true } : {}
2280
+ });
2281
+ process.exit(code);
2282
+ });
1593
2283
  program.parseAsync(process.argv).catch((err) => {
1594
2284
  process.stderr.write(`fatal: ${err instanceof Error ? err.message : String(err)}
1595
2285
  `);