@neat.is/core 0.9.17-dev.20260916 → 0.9.17

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.cjs CHANGED
@@ -844,6 +844,17 @@ function printBanner() {
844
844
  console.log(` neat.is \xB7 v${readPackageVersion()} \xB7 Apache 2.0`);
845
845
  console.log("");
846
846
  }
847
+ function isNpxInvocation() {
848
+ if (process.env.npm_command === "exec") return true;
849
+ const execpath = process.env.npm_execpath ?? "";
850
+ if (execpath.includes("npx")) return true;
851
+ const entry2 = process.argv[1] ?? "";
852
+ if (entry2.includes("/_npx/") || entry2.includes("\\_npx\\")) return true;
853
+ return false;
854
+ }
855
+ function commandPrefix() {
856
+ return isNpxInvocation() ? "npx neat.is" : "neat";
857
+ }
847
858
 
848
859
  // src/graph.ts
849
860
  init_cjs_shims();
@@ -28848,17 +28859,17 @@ async function runOrchestrator(opts) {
28848
28859
  }
28849
28860
  const webPort = allocated?.web ?? NEAT_PORTS[2];
28850
28861
  const dashboardUrl = opts.dashboardUrl ?? `http://localhost:${webPort}`;
28851
- if (opts.noOpen || !process.stdout.isTTY) {
28852
- result.steps.browser = "skipped";
28853
- } else {
28862
+ if (opts.open === true && !opts.noOpen && process.stdout.isTTY) {
28854
28863
  result.steps.browser = openBrowser(dashboardUrl);
28864
+ } else {
28865
+ result.steps.browser = "skipped";
28855
28866
  }
28856
28867
  const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
28857
28868
  const daemonLog = daemonRunning ? import_node_path88.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
28858
- printSummary(result, graph, dashboardUrl, daemonLog);
28869
+ printSummary(result, graph, daemonLog);
28859
28870
  return result;
28860
28871
  }
28861
- function printSummary(result, graph, dashboardUrl, daemonLog) {
28872
+ function printSummary(result, graph, daemonLog) {
28862
28873
  const nodes = [];
28863
28874
  graph.forEachNode((_id, attrs) => nodes.push(attrs));
28864
28875
  const edges = [];
@@ -28873,12 +28884,21 @@ function printSummary(result, graph, dashboardUrl, daemonLog) {
28873
28884
  for (const [t, c] of [...byNode.entries()].sort()) console.log(` ${t}: ${c}`);
28874
28885
  for (const [t, c] of [...byEdge.entries()].sort()) console.log(` ${t}: ${c}`);
28875
28886
  console.log("");
28876
- console.log(`dashboard: ${dashboardUrl}`);
28887
+ const neat = commandPrefix();
28888
+ console.log("query the graph from your coding agent \u2014 NEAT runs as an MCP server, so");
28889
+ console.log("Claude Code, Cursor, and the rest read it directly:");
28890
+ console.log(` ${neat} skill --apply put NEAT in front of Claude Code`);
28891
+ console.log(` (\`${neat} --help\` lists Cursor, Codex, Gemini, and the other clients)`);
28892
+ console.log("or ask it straight from this terminal:");
28893
+ console.log(` ${neat} ask "how does checkout work?"`);
28894
+ console.log("");
28895
+ console.log(`want a visual dashboard? log in to hosted NEAT \u2014 \`${neat} login\``);
28877
28896
  const token = process.env.NEAT_AUTH_TOKEN;
28897
+ console.log("");
28878
28898
  if (typeof token === "string" && token.length > 0) {
28879
- console.log(`auth token: ${token}`);
28899
+ console.log(`auth token (needed to reach the daemon and route OTel): ${token}`);
28880
28900
  } else {
28881
- console.log("running locally \u2014 open the dashboard, no token needed");
28901
+ console.log("running locally \u2014 no auth token needed");
28882
28902
  }
28883
28903
  const failedInstalls = (result.steps.apply.packageManagerInstalls ?? []).filter(
28884
28904
  (i) => i.exitCode !== 0
@@ -30254,7 +30274,7 @@ init_cjs_shims();
30254
30274
  var import_node_http2 = require("http");
30255
30275
  var import_node_child_process4 = require("child_process");
30256
30276
  var import_node_crypto6 = require("crypto");
30257
- var DEFAULT_CP_URL = "https://api.neat.is";
30277
+ var DEFAULT_CP_URL = "https://neat-control-plane-bg5yqctn2q-nw.a.run.app";
30258
30278
  var DEFAULT_WEB_URL = "https://app.neat.is";
30259
30279
  var CALLBACK_TIMEOUT_MS = 5 * 6e4;
30260
30280
  function resolveCpUrl(env = process.env, override) {
@@ -30742,6 +30762,99 @@ async function defaultReadSecret(prompt) {
30742
30762
  });
30743
30763
  }
30744
30764
 
30765
+ // src/welcome.ts
30766
+ init_cjs_shims();
30767
+ var import_promises3 = __toESM(require("readline/promises"), 1);
30768
+ var WORDMARK = [
30769
+ "\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
30770
+ "\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D",
30771
+ "\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ",
30772
+ "\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ",
30773
+ "\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ",
30774
+ "\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D "
30775
+ ];
30776
+ var AGENT_SETUP_PROMPT = `Set up self-hosted NEAT for this project. NEAT keeps a live graph of the code
30777
+ fused with runtime OpenTelemetry data, queryable over MCP \u2014 so you can ask it
30778
+ about the system instead of grepping files.
30779
+
30780
+ 1. Extract the code graph and instrument the services for OpenTelemetry:
30781
+ npx neat.is init . --apply
30782
+ Then install the deps it added (npm install, or your language's equivalent).
30783
+ 2. Start the NEAT daemon so it watches the code and ingests live traces:
30784
+ npx neat.is watch .
30785
+ 3. Wire NEAT's MCP server into your agent so it queries the graph first:
30786
+ npx neat.is skill --apply
30787
+ (other agents: npx neat.is codex|cursor|gemini \u2026 --apply)
30788
+
30789
+ Then ask NEAT instead of reading files, e.g.
30790
+ npx neat.is ask "why is checkout failing?"`;
30791
+ function printHeader(out) {
30792
+ for (const line of WORDMARK) out(line);
30793
+ out("");
30794
+ out(` neat.is \xB7 v${readPackageVersion()}`);
30795
+ out("");
30796
+ }
30797
+ async function runWelcome(deps = {}) {
30798
+ const out = deps.out ?? ((line) => console.log(line));
30799
+ const readLine = deps.readLine ?? defaultReadLine2;
30800
+ const login = deps.login ?? (() => Promise.resolve(0));
30801
+ const orchestrator = deps.orchestrator ?? (() => Promise.resolve(0));
30802
+ const cwd = deps.cwd ?? process.cwd();
30803
+ printHeader(out);
30804
+ out("Welcome to NEAT. Let's get you a graph of this system.");
30805
+ out("");
30806
+ out(" 1) I have a NEAT account \u2014 log in");
30807
+ out(" 2) Self-hosted \u2014 set up NEAT on this machine");
30808
+ out("");
30809
+ for (; ; ) {
30810
+ const choice = (await readLine("Choose 1 or 2 (default 2): "))?.trim();
30811
+ if (choice === void 0) return runSelfHosted(out, readLine, orchestrator, cwd);
30812
+ if (choice === "1") {
30813
+ return login(["--browser"]);
30814
+ }
30815
+ if (choice === "2" || choice === "") {
30816
+ return runSelfHosted(out, readLine, orchestrator, cwd);
30817
+ }
30818
+ out(`"${choice}" isn't 1 or 2 \u2014 try again, or press Enter for self-hosted.`);
30819
+ }
30820
+ }
30821
+ async function runSelfHosted(out, readLine, orchestrator, cwd) {
30822
+ const wantsPrompt = (await readLine("Print a copy-paste setup prompt for your coding agent? [Y/n]: "))?.trim().toLowerCase();
30823
+ if (wantsPrompt !== "n" && wantsPrompt !== "no") {
30824
+ out("");
30825
+ out("\u2500\u2500\u2500 copy the prompt below into your coding agent \u2500\u2500\u2500");
30826
+ out("");
30827
+ out(AGENT_SETUP_PROMPT);
30828
+ out("");
30829
+ out("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
30830
+ out("");
30831
+ }
30832
+ out("Building your local graph now\u2026");
30833
+ out("");
30834
+ return orchestrator(cwd);
30835
+ }
30836
+ async function shouldShowWelcome(deps = {}) {
30837
+ const stdinTTY = deps.stdinIsTTY ?? Boolean(process.stdin.isTTY);
30838
+ const stdoutTTY = deps.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
30839
+ if (!stdinTTY || !stdoutTTY) return false;
30840
+ const readProfiles = deps.readProfiles ?? (() => readProfilesConfig());
30841
+ try {
30842
+ const { profiles } = await readProfiles();
30843
+ return profiles.length === 0;
30844
+ } catch {
30845
+ return false;
30846
+ }
30847
+ }
30848
+ async function defaultReadLine2(prompt) {
30849
+ if (!process.stdin.isTTY) return void 0;
30850
+ const rl = import_promises3.default.createInterface({ input: process.stdin, output: process.stdout });
30851
+ try {
30852
+ return await rl.question(prompt);
30853
+ } finally {
30854
+ rl.close();
30855
+ }
30856
+ }
30857
+
30745
30858
  // src/hooks-cli.ts
30746
30859
  init_cjs_shims();
30747
30860
  var import_node_path91 = __toESM(require("path"), 1);
@@ -32288,17 +32401,6 @@ async function runSync(opts) {
32288
32401
 
32289
32402
  // src/cli.ts
32290
32403
  var import_types110 = require("@neat.is/types");
32291
- function isNpxInvocation() {
32292
- if (process.env.npm_command === "exec") return true;
32293
- const execpath = process.env.npm_execpath ?? "";
32294
- if (execpath.includes("npx")) return true;
32295
- const entry2 = process.argv[1] ?? "";
32296
- if (entry2.includes("/_npx/") || entry2.includes("\\_npx\\")) return true;
32297
- return false;
32298
- }
32299
- function commandPrefix() {
32300
- return isNpxInvocation() ? "npx neat.is" : "neat";
32301
- }
32302
32404
  function usage5() {
32303
32405
  const neat = commandPrefix();
32304
32406
  console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
@@ -32306,8 +32408,15 @@ function usage5() {
32306
32408
  console.log(`usage: ${neat} <command> [args] [--project <name>]`);
32307
32409
  console.log("");
32308
32410
  console.log(`Run \`${neat}\` with no command from inside your project to go zero-to-graph in one step.`);
32411
+ console.log(`On your first run in a terminal, \`${neat}\` opens a short menu \u2014 log in to a hosted`);
32412
+ console.log(`NEAT, or set up self-hosted here. Run \`${neat} welcome\` to open it again anytime.`);
32413
+ console.log(`The run stays in your terminal \u2014 query the graph over MCP or the CLI. For a visual`);
32414
+ console.log(`dashboard, log in to hosted NEAT; \`--open\` launches the local preview one by choice.`);
32309
32415
  console.log("");
32310
32416
  console.log("lifecycle commands:");
32417
+ console.log(" welcome Open the first-run menu: log in to a hosted NEAT, or set up");
32418
+ console.log(" self-hosted (print an agent-setup prompt, then build the graph");
32419
+ console.log(" for the current directory).");
32311
32420
  console.log(" init <path> One-time install: discover, extract, register, plan SDK install.");
32312
32421
  console.log(" Snapshot lands in <path>/neat-out/graph.json by default");
32313
32422
  console.log(" (or <path>/neat-out/<project>.json for non-default).");
@@ -32518,6 +32627,7 @@ function parseArgs(rest) {
32518
32627
  dryRun: false,
32519
32628
  noInstall: false,
32520
32629
  noInstrument: false,
32630
+ open: false,
32521
32631
  noOpen: false,
32522
32632
  yes: false,
32523
32633
  verbose: false,
@@ -32555,6 +32665,10 @@ function parseArgs(rest) {
32555
32665
  out.noInstrument = true;
32556
32666
  continue;
32557
32667
  }
32668
+ if (arg === "--open") {
32669
+ out.open = true;
32670
+ continue;
32671
+ }
32558
32672
  if (arg === "--no-open") {
32559
32673
  out.noOpen = true;
32560
32674
  continue;
@@ -32934,6 +33048,11 @@ async function main() {
32934
33048
  }
32935
33049
  const argvParsed = parseArgs(argv);
32936
33050
  if (argvParsed.positional.length === 0) {
33051
+ if (await shouldShowWelcome()) {
33052
+ const welcomeCode = await runWelcomeFlow(argvParsed);
33053
+ if (welcomeCode !== 0) process.exit(welcomeCode);
33054
+ return;
33055
+ }
32937
33056
  const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
32938
33057
  if (orchestratorCode2 !== null && orchestratorCode2 !== 0) process.exit(orchestratorCode2);
32939
33058
  return;
@@ -32942,6 +33061,11 @@ async function main() {
32942
33061
  const parsed = { ...argvParsed, positional: argvParsed.positional.slice(1) };
32943
33062
  const { positional, apply: apply6, dryRun, noInstall } = parsed;
32944
33063
  const project = parsed.project ?? DEFAULT_PROJECT;
33064
+ if (cmd === "welcome") {
33065
+ const code = await runWelcomeFlow(parsed);
33066
+ if (code !== 0) process.exit(code);
33067
+ return;
33068
+ }
32945
33069
  if (cmd === "init") {
32946
33070
  const target = positional[0];
32947
33071
  if (!target) {
@@ -33194,11 +33318,18 @@ async function tryOrchestrator(cmd, parsed) {
33194
33318
  project: projectName,
33195
33319
  projectExplicit,
33196
33320
  noInstrument: parsed.noInstrument,
33321
+ open: parsed.open,
33197
33322
  noOpen: parsed.noOpen,
33198
33323
  yes: parsed.yes
33199
33324
  });
33200
33325
  return result.exitCode;
33201
33326
  }
33327
+ async function runWelcomeFlow(parsed) {
33328
+ return runWelcome({
33329
+ login: (loginArgv) => runLoginCommand(loginArgv),
33330
+ orchestrator: async (cwd) => await tryOrchestrator(cwd, parsed) ?? 0
33331
+ });
33332
+ }
33202
33333
  var QUERY_VERBS = /* @__PURE__ */ new Set([
33203
33334
  "root-cause",
33204
33335
  "blast-radius",