@sightmap/sightmap 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1499,9 +1499,9 @@ async function processFile(absPath, relPath, opts, fileResults, diagnostics) {
1499
1499
 
1500
1500
  // src/cli/init/index.ts
1501
1501
  import { resolve as resolve22 } from "path";
1502
- import { mkdir as mkdir4, readFile as readFile13, rm, writeFile as writeFile7 } from "fs/promises";
1502
+ import { mkdir as mkdir4, readFile as readFile14, rm, writeFile as writeFile7 } from "fs/promises";
1503
1503
  import { tmpdir } from "os";
1504
- import * as clack2 from "@clack/prompts";
1504
+ import * as clack3 from "@clack/prompts";
1505
1505
 
1506
1506
  // src/cli/init/detect/framework.ts
1507
1507
  import { readFile as readFile4, access } from "fs/promises";
@@ -2232,9 +2232,11 @@ function resolveTargets(host, projectDir) {
2232
2232
  }
2233
2233
 
2234
2234
  // src/cli/init/version-pin.ts
2235
+ var DEFAULT_LAUNCHER = { pm: "npm", command: "npx", runArgs: ["-y"] };
2235
2236
  function buildSightmapMcpServerDef(opts) {
2237
+ const launcher = opts.launcher ?? DEFAULT_LAUNCHER;
2236
2238
  const args = [
2237
- "-y",
2239
+ ...launcher.runArgs,
2238
2240
  `@sightmap/mcp@${opts.compatibleMcpVersion}`,
2239
2241
  "--sightmap-dir",
2240
2242
  opts.sightmapDir,
@@ -2242,19 +2244,150 @@ function buildSightmapMcpServerDef(opts) {
2242
2244
  opts.curateRoot
2243
2245
  ];
2244
2246
  if (opts.withBrowser) {
2245
- args.push("--", "npx", "-y", "@playwright/mcp@latest");
2247
+ args.push("--", launcher.command, ...launcher.runArgs, "@playwright/mcp@latest");
2246
2248
  } else {
2247
2249
  args.push("--curate-only");
2248
2250
  }
2249
2251
  return {
2250
- command: "npx",
2252
+ command: launcher.command,
2251
2253
  args,
2252
2254
  env: { SIGHTMAP_PLUGIN_VERSION: opts.pluginVersion }
2253
2255
  };
2254
2256
  }
2255
2257
 
2258
+ // src/cli/init/smoke-mcp.ts
2259
+ import { spawn } from "child_process";
2260
+ import * as clack2 from "@clack/prompts";
2261
+ import pc2 from "picocolors";
2262
+ var DEFAULT_TIMEOUT_MS = 15e3;
2263
+ var INITIALIZE_PAYLOAD = {
2264
+ jsonrpc: "2.0",
2265
+ id: 1,
2266
+ method: "initialize",
2267
+ params: {
2268
+ protocolVersion: "2024-11-05",
2269
+ capabilities: {},
2270
+ clientInfo: { name: "sightmap-init-smoke", version: "1.0.0" }
2271
+ }
2272
+ };
2273
+ async function runSmokeTest(opts) {
2274
+ const startedAt = Date.now();
2275
+ const timeout = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
2276
+ return new Promise((resolveResult) => {
2277
+ let settled = false;
2278
+ const settle = (r) => {
2279
+ if (settled) return;
2280
+ settled = true;
2281
+ try {
2282
+ child.kill("SIGTERM");
2283
+ } catch {
2284
+ }
2285
+ resolveResult(r);
2286
+ };
2287
+ const child = spawn(opts.serverDef.command, opts.serverDef.args, {
2288
+ cwd: opts.cwd,
2289
+ env: { ...process.env, ...opts.serverDef.env },
2290
+ stdio: ["pipe", "pipe", "pipe"]
2291
+ });
2292
+ let stdoutBuf = "";
2293
+ let stderrBuf = "";
2294
+ child.stdout?.on("data", (chunk) => {
2295
+ stdoutBuf += chunk.toString("utf8");
2296
+ let nl;
2297
+ while ((nl = stdoutBuf.indexOf("\n")) !== -1) {
2298
+ const line = stdoutBuf.slice(0, nl).trim();
2299
+ stdoutBuf = stdoutBuf.slice(nl + 1);
2300
+ if (!line) continue;
2301
+ try {
2302
+ const msg = JSON.parse(line);
2303
+ if (msg.id === 1 && msg.result) {
2304
+ settle({ ok: true, latencyMs: Date.now() - startedAt });
2305
+ return;
2306
+ }
2307
+ if (msg.id === 1 && msg.error) {
2308
+ settle({
2309
+ ok: false,
2310
+ reason: `Server returned JSON-RPC error: ${JSON.stringify(msg.error)}`,
2311
+ latencyMs: Date.now() - startedAt
2312
+ });
2313
+ return;
2314
+ }
2315
+ } catch {
2316
+ }
2317
+ }
2318
+ });
2319
+ child.stderr?.on("data", (chunk) => {
2320
+ stderrBuf += chunk.toString("utf8");
2321
+ });
2322
+ child.on("error", (err) => {
2323
+ settle({
2324
+ ok: false,
2325
+ reason: `Failed to spawn: ${err.message}`,
2326
+ latencyMs: Date.now() - startedAt,
2327
+ ...stderrBuf ? { stderr: stderrBuf } : {}
2328
+ });
2329
+ });
2330
+ child.on("exit", (code, signal) => {
2331
+ if (settled) return;
2332
+ settle({
2333
+ ok: false,
2334
+ reason: stdoutBuf.length === 0 && stderrBuf.length === 0 ? `Spawn produced no output and exited (code=${code ?? "?"}, signal=${signal ?? "?"}). This is the silent-stdin bug \u2014 try a different package manager launcher.` : `Process exited before initialize response (code=${code ?? "?"}, signal=${signal ?? "?"})`,
2335
+ latencyMs: Date.now() - startedAt,
2336
+ ...stderrBuf ? { stderr: stderrBuf } : {}
2337
+ });
2338
+ });
2339
+ setTimeout(() => {
2340
+ settle({
2341
+ ok: false,
2342
+ reason: `Timed out after ${timeout}ms waiting for initialize response`,
2343
+ latencyMs: Date.now() - startedAt,
2344
+ ...stderrBuf ? { stderr: stderrBuf } : {}
2345
+ });
2346
+ }, timeout);
2347
+ try {
2348
+ child.stdin?.write(JSON.stringify(INITIALIZE_PAYLOAD) + "\n");
2349
+ child.stdin?.end();
2350
+ } catch (e) {
2351
+ settle({
2352
+ ok: false,
2353
+ reason: `Failed to write initialize payload: ${e.message}`,
2354
+ latencyMs: Date.now() - startedAt
2355
+ });
2356
+ }
2357
+ });
2358
+ }
2359
+ async function smokeTestMcp(opts) {
2360
+ const spinner3 = clack2.spinner();
2361
+ spinner3.start("Verifying MCP can launch");
2362
+ const result = await runSmokeTest(opts);
2363
+ if (result.ok) {
2364
+ spinner3.stop(`MCP ready (initialized in ${result.latencyMs}ms)`);
2365
+ return;
2366
+ }
2367
+ spinner3.stop("MCP smoke test failed");
2368
+ const cmd = `${opts.serverDef.command} ${opts.serverDef.args.join(" ")}`;
2369
+ const lines = [
2370
+ pc2.bold("Could not start the MCP server."),
2371
+ "",
2372
+ pc2.cyan("Reason:") + " " + result.reason,
2373
+ pc2.cyan("Command:") + " " + pc2.white(cmd)
2374
+ ];
2375
+ if (result.stderr) {
2376
+ lines.push("", pc2.cyan("stderr:"));
2377
+ for (const ln of result.stderr.trim().split("\n").slice(0, 6)) {
2378
+ lines.push(" " + pc2.dim(ln));
2379
+ }
2380
+ }
2381
+ lines.push(
2382
+ "",
2383
+ pc2.dim("The config was written. You can retry by restarting your agent."),
2384
+ pc2.dim("Re-run `npx @sightmap/sightmap init --no-smoke` to skip this check next time.")
2385
+ );
2386
+ clack2.log.warn(lines.join("\n"));
2387
+ }
2388
+
2256
2389
  // src/cli/init/framework-setup/package-manager.ts
2257
- import { access as access10 } from "fs/promises";
2390
+ import { access as access10, readFile as readFile13 } from "fs/promises";
2258
2391
  import { dirname as dirname4, resolve as resolve21 } from "path";
2259
2392
  async function exists4(p) {
2260
2393
  try {
@@ -2265,6 +2398,9 @@ async function exists4(p) {
2265
2398
  }
2266
2399
  }
2267
2400
  var SIGNALS = [
2401
+ // Bun first — bun.lock can coexist with package-lock.json during migration.
2402
+ { file: "bun.lock", pm: "bun" },
2403
+ { file: "bun.lockb", pm: "bun" },
2268
2404
  { file: "pnpm-lock.yaml", pm: "pnpm" },
2269
2405
  { file: "pnpm-workspace.yaml", pm: "pnpm" },
2270
2406
  { file: "yarn.lock", pm: "yarn" }
@@ -2280,17 +2416,56 @@ async function detectPackageManager(cwd) {
2280
2416
  dir = parent;
2281
2417
  }
2282
2418
  }
2419
+ async function detectLauncher(cwd) {
2420
+ const pm = await detectPackageManager(cwd);
2421
+ if (pm === "bun") return { pm, command: "bunx", runArgs: [] };
2422
+ if (pm === "pnpm") return { pm, command: "pnpm", runArgs: ["dlx"] };
2423
+ if (pm === "yarn") {
2424
+ if (await isYarnBerry(cwd)) {
2425
+ return { pm, command: "yarn", runArgs: ["dlx"] };
2426
+ }
2427
+ return { pm: "npm", command: "npx", runArgs: ["-y"] };
2428
+ }
2429
+ return { pm: "npm", command: "npx", runArgs: ["-y"] };
2430
+ }
2431
+ async function isYarnBerry(cwd) {
2432
+ let dir = resolve21(cwd);
2433
+ while (true) {
2434
+ if (await exists4(resolve21(dir, ".yarnrc.yml"))) return true;
2435
+ const parent = dirname4(dir);
2436
+ if (parent === dir) break;
2437
+ dir = parent;
2438
+ }
2439
+ dir = resolve21(cwd);
2440
+ while (true) {
2441
+ const pkg = resolve21(dir, "package.json");
2442
+ if (await exists4(pkg)) {
2443
+ try {
2444
+ const parsed = JSON.parse(await readFile13(pkg, "utf8"));
2445
+ const pmField = parsed.packageManager ?? "";
2446
+ const match2 = /^yarn@(\d+)\./.exec(pmField);
2447
+ if (match2 && Number(match2[1]) >= 2) return true;
2448
+ } catch {
2449
+ }
2450
+ }
2451
+ const parent = dirname4(dir);
2452
+ if (parent === dir) break;
2453
+ dir = parent;
2454
+ }
2455
+ return false;
2456
+ }
2283
2457
 
2284
2458
  // src/cli/init/framework-setup/install-adapter.ts
2285
- import { spawn } from "child_process";
2459
+ import { spawn as spawn2 } from "child_process";
2286
2460
  var ADD_VERB = {
2287
2461
  pnpm: "add",
2288
2462
  yarn: "add",
2289
- npm: "install"
2463
+ npm: "install",
2464
+ bun: "add"
2290
2465
  };
2291
2466
  async function installAdapter(opts) {
2292
2467
  await new Promise((resolveP, reject) => {
2293
- const child = spawn(opts.packageManager, [ADD_VERB[opts.packageManager], opts.packageName], {
2468
+ const child = spawn2(opts.packageManager, [ADD_VERB[opts.packageManager], opts.packageName], {
2294
2469
  cwd: opts.cwd,
2295
2470
  stdio: "inherit"
2296
2471
  });
@@ -2300,12 +2475,13 @@ async function installAdapter(opts) {
2300
2475
  }
2301
2476
 
2302
2477
  // src/cli/init/framework-setup/codegen.ts
2303
- import { spawn as spawn2 } from "child_process";
2478
+ import { spawn as spawn3 } from "child_process";
2304
2479
  var REACT_FRAMEWORKS = ["react-vite", "react-cra", "next-app", "next-pages", "react-router-7"];
2305
2480
  var EXEC_VERB = {
2306
2481
  pnpm: ["exec"],
2307
2482
  yarn: ["exec"],
2308
- npm: ["exec", "--"]
2483
+ npm: ["exec", "--"],
2484
+ bun: ["x"]
2309
2485
  };
2310
2486
  function buildCodegenCommand(opts) {
2311
2487
  if (!REACT_FRAMEWORKS.includes(opts.framework)) return null;
@@ -2316,7 +2492,7 @@ function buildCodegenCommand(opts) {
2316
2492
  }
2317
2493
  async function runCodegen(cwd, cmd) {
2318
2494
  await new Promise((resolveP, reject) => {
2319
- const child = spawn2(cmd.command, cmd.args, { cwd, stdio: "inherit" });
2495
+ const child = spawn3(cmd.command, cmd.args, { cwd, stdio: "inherit" });
2320
2496
  child.on("error", reject);
2321
2497
  child.on("exit", (code) => code === 0 ? resolveP() : reject(new Error(`codegen exited ${code}`)));
2322
2498
  });
@@ -2473,7 +2649,7 @@ async function runInit(opts) {
2473
2649
  if (typeof chosen === "symbol") return 1;
2474
2650
  if (chosen) {
2475
2651
  opts = { ...opts, cwd: chosen.dir };
2476
- clack2.log.info(`Targeting workspace: ${chosen.relPath}`);
2652
+ clack3.log.info(`Targeting workspace: ${chosen.relPath}`);
2477
2653
  detect = {
2478
2654
  framework: await detectFramework(opts.cwd),
2479
2655
  hosts: detect.hosts,
@@ -2511,17 +2687,22 @@ async function runInit(opts) {
2511
2687
  await mkdir4(tarballDir, { recursive: true });
2512
2688
  try {
2513
2689
  const tar = await fetchPluginTarball({ destDir: tarballDir, version: "latest" });
2690
+ const launcher = await detectLauncher(opts.cwd);
2514
2691
  const serverDef = buildSightmapMcpServerDef({
2515
2692
  compatibleMcpVersion: tar.compatibleMcpVersion,
2516
2693
  pluginVersion: tar.pluginVersion,
2517
2694
  withBrowser,
2518
2695
  sightmapDir: ".sightmap",
2519
- curateRoot: ".sightmap"
2696
+ curateRoot: ".sightmap",
2697
+ launcher
2520
2698
  });
2521
2699
  for (const h of hosts) {
2522
2700
  await writeMcpForHost(h, opts.cwd, serverDef);
2523
2701
  await copyPluginAssets({ tarballDir, projectDir: opts.cwd, host: h });
2524
2702
  }
2703
+ if (!opts.noSmoke) {
2704
+ await smokeTestMcp({ serverDef, cwd: opts.cwd });
2705
+ }
2525
2706
  } finally {
2526
2707
  await rm(tarballDir, { recursive: true, force: true });
2527
2708
  }
@@ -2541,19 +2722,19 @@ views: []
2541
2722
  return;
2542
2723
  }
2543
2724
  const pm = await detectPackageManager(opts.cwd);
2544
- const spinner2 = clack2.spinner();
2545
- spinner2.start(`Installing @sightmap/react via ${pm}`);
2725
+ const spinner3 = clack3.spinner();
2726
+ spinner3.start(`Installing @sightmap/react via ${pm}`);
2546
2727
  await installAdapter({ cwd: opts.cwd, packageManager: pm, packageName: "@sightmap/react" });
2547
- spinner2.stop("Installed @sightmap/react");
2728
+ spinner3.stop("Installed @sightmap/react");
2548
2729
  if (!opts.noProvider) {
2549
2730
  await runProviderStep(opts, detect);
2550
2731
  }
2551
2732
  if (!opts.noCodegen) {
2552
2733
  const cmd = buildCodegenCommand({ framework: detect.framework, packageManager: pm });
2553
2734
  if (cmd) {
2554
- spinner2.start("Running sightmap-react gen");
2735
+ spinner3.start("Running sightmap-react gen");
2555
2736
  await runCodegen(opts.cwd, cmd);
2556
- spinner2.stop("Scaffolded .sightmap/");
2737
+ spinner3.stop("Scaffolded .sightmap/");
2557
2738
  }
2558
2739
  }
2559
2740
  }
@@ -2568,7 +2749,7 @@ async function runProviderStep(opts, detect) {
2568
2749
  showProviderSnippet(snippet, "We couldn't locate the entry file \u2014 drop these lines into your app's root.");
2569
2750
  return;
2570
2751
  }
2571
- const source = await readFile13(entry, "utf8");
2752
+ const source = await readFile14(entry, "utf8");
2572
2753
  const result = wrapWithSightmapProvider(source, { framework: detect.framework });
2573
2754
  if (!result.changed) {
2574
2755
  const snippet = renderProviderSnippet({
@@ -2613,7 +2794,7 @@ async function findAppEntry(cwd, framework) {
2613
2794
  for (const rel of candidates[framework]) {
2614
2795
  const abs = resolve22(cwd, rel);
2615
2796
  try {
2616
- await readFile13(abs, "utf8");
2797
+ await readFile14(abs, "utf8");
2617
2798
  return abs;
2618
2799
  } catch {
2619
2800
  }
@@ -2621,13 +2802,13 @@ async function findAppEntry(cwd, framework) {
2621
2802
  return null;
2622
2803
  }
2623
2804
  async function runExistingCorpusFlow(cwd, viewCount) {
2624
- const spinner2 = clack2.spinner();
2625
- spinner2.start("Auditing existing corpus...");
2805
+ const spinner3 = clack3.spinner();
2806
+ spinner3.start("Auditing existing corpus...");
2626
2807
  await runLint({ path: ".sightmap", cwd, json: false, strict: false });
2627
- spinner2.stop("Audit complete");
2808
+ spinner3.stop("Audit complete");
2628
2809
  const header = `${viewCount} view${viewCount === 1 ? "" : "s"}`;
2629
2810
  const body = formatExistingCorpusReport({ viewCount, diagnostics: [] });
2630
- clack2.log.message(`${header}
2811
+ clack3.log.message(`${header}
2631
2812
 
2632
2813
  ${body}`);
2633
2814
  }
@@ -2660,7 +2841,7 @@ async function maybePickWorkspace(opts) {
2660
2841
  if (withFramework.length === 0) return null;
2661
2842
  if (opts.yes) {
2662
2843
  if (withFramework.length === 1) return withFramework[0];
2663
- clack2.log.warn(
2844
+ clack3.log.warn(
2664
2845
  `Found ${withFramework.length} workspaces with a framework. Re-run from one of them, or without -y to pick interactively.`
2665
2846
  );
2666
2847
  return null;
@@ -2774,7 +2955,7 @@ program.command("fmt [path]").description("Canonicalize .sightmap/*.yaml files (
2774
2955
  process.exit(code);
2775
2956
  }
2776
2957
  );
2777
- 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) => {
2958
+ 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").option("--no-smoke", "skip the MCP launch smoke test (CI / headless use)").action(async (opts) => {
2778
2959
  const code = await runInit({
2779
2960
  cwd: program.opts().cwd,
2780
2961
  yes: opts.yes === true,
@@ -2784,7 +2965,8 @@ program.command("init").description("Initialize Sightmap in this project (detect
2784
2965
  ...opts.withBrowser ? { withBrowser: true } : {},
2785
2966
  ...opts.curateOnly ? { curateOnly: true } : {},
2786
2967
  ...opts.codegen === false ? { noCodegen: true } : {},
2787
- ...opts.provider === false ? { noProvider: true } : {}
2968
+ ...opts.provider === false ? { noProvider: true } : {},
2969
+ ...opts.smoke === false ? { noSmoke: true } : {}
2788
2970
  });
2789
2971
  process.exit(code);
2790
2972
  });