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

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.d.cts CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  declare function readPackageVersion(): string;
3
3
  declare function printBanner(): void;
4
+ declare function isNpxInvocation(): boolean;
5
+ declare function commandPrefix(): string;
4
6
 
5
7
  interface HttpClient {
6
8
  get<T>(path: string): Promise<T>;
@@ -21,8 +23,6 @@ interface InitResult {
21
23
  exitCode: number;
22
24
  writtenFiles: string[];
23
25
  }
24
- declare function isNpxInvocation(): boolean;
25
- declare function commandPrefix(): string;
26
26
  declare function usage(): void;
27
27
  interface ParsedArgs {
28
28
  project: string | null;
@@ -31,6 +31,7 @@ interface ParsedArgs {
31
31
  dryRun: boolean;
32
32
  noInstall: boolean;
33
33
  noInstrument: boolean;
34
+ open: boolean;
34
35
  noOpen: boolean;
35
36
  yes: boolean;
36
37
  verbose: boolean;
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  declare function readPackageVersion(): string;
3
3
  declare function printBanner(): void;
4
+ declare function isNpxInvocation(): boolean;
5
+ declare function commandPrefix(): string;
4
6
 
5
7
  interface HttpClient {
6
8
  get<T>(path: string): Promise<T>;
@@ -21,8 +23,6 @@ interface InitResult {
21
23
  exitCode: number;
22
24
  writtenFiles: string[];
23
25
  }
24
- declare function isNpxInvocation(): boolean;
25
- declare function commandPrefix(): string;
26
26
  declare function usage(): void;
27
27
  interface ParsedArgs {
28
28
  project: string | null;
@@ -31,6 +31,7 @@ interface ParsedArgs {
31
31
  dryRun: boolean;
32
32
  noInstall: boolean;
33
33
  noInstrument: boolean;
34
+ open: boolean;
34
35
  noOpen: boolean;
35
36
  yes: boolean;
36
37
  verbose: boolean;
package/dist/cli.js CHANGED
@@ -129,6 +129,17 @@ function printBanner() {
129
129
  console.log(` neat.is \xB7 v${readPackageVersion()} \xB7 Apache 2.0`);
130
130
  console.log("");
131
131
  }
132
+ function isNpxInvocation() {
133
+ if (process.env.npm_command === "exec") return true;
134
+ const execpath = process.env.npm_execpath ?? "";
135
+ if (execpath.includes("npx")) return true;
136
+ const entry2 = process.argv[1] ?? "";
137
+ if (entry2.includes("/_npx/") || entry2.includes("\\_npx\\")) return true;
138
+ return false;
139
+ }
140
+ function commandPrefix() {
141
+ return isNpxInvocation() ? "npx neat.is" : "neat";
142
+ }
132
143
 
133
144
  // src/gitignore.ts
134
145
  import { promises as fs } from "fs";
@@ -4379,17 +4390,17 @@ async function runOrchestrator(opts) {
4379
4390
  }
4380
4391
  const webPort = allocated?.web ?? NEAT_PORTS[2];
4381
4392
  const dashboardUrl = opts.dashboardUrl ?? `http://localhost:${webPort}`;
4382
- if (opts.noOpen || !process.stdout.isTTY) {
4383
- result.steps.browser = "skipped";
4384
- } else {
4393
+ if (opts.open === true && !opts.noOpen && process.stdout.isTTY) {
4385
4394
  result.steps.browser = openBrowser(dashboardUrl);
4395
+ } else {
4396
+ result.steps.browser = "skipped";
4386
4397
  }
4387
4398
  const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
4388
4399
  const daemonLog = daemonRunning ? path10.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
4389
- printSummary(result, graph, dashboardUrl, daemonLog);
4400
+ printSummary(result, graph, daemonLog);
4390
4401
  return result;
4391
4402
  }
4392
- function printSummary(result, graph, dashboardUrl, daemonLog) {
4403
+ function printSummary(result, graph, daemonLog) {
4393
4404
  const nodes = [];
4394
4405
  graph.forEachNode((_id, attrs) => nodes.push(attrs));
4395
4406
  const edges = [];
@@ -4404,12 +4415,21 @@ function printSummary(result, graph, dashboardUrl, daemonLog) {
4404
4415
  for (const [t, c] of [...byNode.entries()].sort()) console.log(` ${t}: ${c}`);
4405
4416
  for (const [t, c] of [...byEdge.entries()].sort()) console.log(` ${t}: ${c}`);
4406
4417
  console.log("");
4407
- console.log(`dashboard: ${dashboardUrl}`);
4418
+ const neat = commandPrefix();
4419
+ console.log("query the graph from your coding agent \u2014 NEAT runs as an MCP server, so");
4420
+ console.log("Claude Code, Cursor, and the rest read it directly:");
4421
+ console.log(` ${neat} skill --apply put NEAT in front of Claude Code`);
4422
+ console.log(` (\`${neat} --help\` lists Cursor, Codex, Gemini, and the other clients)`);
4423
+ console.log("or ask it straight from this terminal:");
4424
+ console.log(` ${neat} ask "how does checkout work?"`);
4425
+ console.log("");
4426
+ console.log(`want a visual dashboard? log in to hosted NEAT \u2014 \`${neat} login\``);
4408
4427
  const token = process.env.NEAT_AUTH_TOKEN;
4428
+ console.log("");
4409
4429
  if (typeof token === "string" && token.length > 0) {
4410
- console.log(`auth token: ${token}`);
4430
+ console.log(`auth token (needed to reach the daemon and route OTel): ${token}`);
4411
4431
  } else {
4412
- console.log("running locally \u2014 open the dashboard, no token needed");
4432
+ console.log("running locally \u2014 no auth token needed");
4413
4433
  }
4414
4434
  const failedInstalls = (result.steps.apply.packageManagerInstalls ?? []).filter(
4415
4435
  (i) => i.exitCode !== 0
@@ -5779,7 +5799,7 @@ async function clearActiveProfile(home = neatHome()) {
5779
5799
  import { createServer } from "http";
5780
5800
  import { spawn as spawn3 } from "child_process";
5781
5801
  import { randomBytes as randomBytes2 } from "crypto";
5782
- var DEFAULT_CP_URL = "https://api.neat.is";
5802
+ var DEFAULT_CP_URL = "https://neat-control-plane-bg5yqctn2q-nw.a.run.app";
5783
5803
  var DEFAULT_WEB_URL = "https://app.neat.is";
5784
5804
  var CALLBACK_TIMEOUT_MS = 5 * 6e4;
5785
5805
  function resolveCpUrl(env = process.env, override) {
@@ -6267,6 +6287,98 @@ async function defaultReadSecret(prompt) {
6267
6287
  });
6268
6288
  }
6269
6289
 
6290
+ // src/welcome.ts
6291
+ import readline4 from "readline/promises";
6292
+ var WORDMARK = [
6293
+ "\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",
6294
+ "\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",
6295
+ "\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 ",
6296
+ "\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 ",
6297
+ "\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 ",
6298
+ "\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 "
6299
+ ];
6300
+ var AGENT_SETUP_PROMPT = `Set up self-hosted NEAT for this project. NEAT keeps a live graph of the code
6301
+ fused with runtime OpenTelemetry data, queryable over MCP \u2014 so you can ask it
6302
+ about the system instead of grepping files.
6303
+
6304
+ 1. Extract the code graph and instrument the services for OpenTelemetry:
6305
+ npx neat.is init . --apply
6306
+ Then install the deps it added (npm install, or your language's equivalent).
6307
+ 2. Start the NEAT daemon so it watches the code and ingests live traces:
6308
+ npx neat.is watch .
6309
+ 3. Wire NEAT's MCP server into your agent so it queries the graph first:
6310
+ npx neat.is skill --apply
6311
+ (other agents: npx neat.is codex|cursor|gemini \u2026 --apply)
6312
+
6313
+ Then ask NEAT instead of reading files, e.g.
6314
+ npx neat.is ask "why is checkout failing?"`;
6315
+ function printHeader(out) {
6316
+ for (const line of WORDMARK) out(line);
6317
+ out("");
6318
+ out(` neat.is \xB7 v${readPackageVersion()}`);
6319
+ out("");
6320
+ }
6321
+ async function runWelcome(deps = {}) {
6322
+ const out = deps.out ?? ((line) => console.log(line));
6323
+ const readLine = deps.readLine ?? defaultReadLine2;
6324
+ const login = deps.login ?? (() => Promise.resolve(0));
6325
+ const orchestrator = deps.orchestrator ?? (() => Promise.resolve(0));
6326
+ const cwd = deps.cwd ?? process.cwd();
6327
+ printHeader(out);
6328
+ out("Welcome to NEAT. Let's get you a graph of this system.");
6329
+ out("");
6330
+ out(" 1) I have a NEAT account \u2014 log in");
6331
+ out(" 2) Self-hosted \u2014 set up NEAT on this machine");
6332
+ out("");
6333
+ for (; ; ) {
6334
+ const choice = (await readLine("Choose 1 or 2 (default 2): "))?.trim();
6335
+ if (choice === void 0) return runSelfHosted(out, readLine, orchestrator, cwd);
6336
+ if (choice === "1") {
6337
+ return login(["--browser"]);
6338
+ }
6339
+ if (choice === "2" || choice === "") {
6340
+ return runSelfHosted(out, readLine, orchestrator, cwd);
6341
+ }
6342
+ out(`"${choice}" isn't 1 or 2 \u2014 try again, or press Enter for self-hosted.`);
6343
+ }
6344
+ }
6345
+ async function runSelfHosted(out, readLine, orchestrator, cwd) {
6346
+ const wantsPrompt = (await readLine("Print a copy-paste setup prompt for your coding agent? [Y/n]: "))?.trim().toLowerCase();
6347
+ if (wantsPrompt !== "n" && wantsPrompt !== "no") {
6348
+ out("");
6349
+ out("\u2500\u2500\u2500 copy the prompt below into your coding agent \u2500\u2500\u2500");
6350
+ out("");
6351
+ out(AGENT_SETUP_PROMPT);
6352
+ out("");
6353
+ 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");
6354
+ out("");
6355
+ }
6356
+ out("Building your local graph now\u2026");
6357
+ out("");
6358
+ return orchestrator(cwd);
6359
+ }
6360
+ async function shouldShowWelcome(deps = {}) {
6361
+ const stdinTTY = deps.stdinIsTTY ?? Boolean(process.stdin.isTTY);
6362
+ const stdoutTTY = deps.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
6363
+ if (!stdinTTY || !stdoutTTY) return false;
6364
+ const readProfiles = deps.readProfiles ?? (() => readProfilesConfig());
6365
+ try {
6366
+ const { profiles } = await readProfiles();
6367
+ return profiles.length === 0;
6368
+ } catch {
6369
+ return false;
6370
+ }
6371
+ }
6372
+ async function defaultReadLine2(prompt) {
6373
+ if (!process.stdin.isTTY) return void 0;
6374
+ const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
6375
+ try {
6376
+ return await rl.question(prompt);
6377
+ } finally {
6378
+ rl.close();
6379
+ }
6380
+ }
6381
+
6270
6382
  // src/hooks-cli.ts
6271
6383
  import path13 from "path";
6272
6384
  import os2 from "os";
@@ -7807,17 +7919,6 @@ async function runSync(opts) {
7807
7919
 
7808
7920
  // src/cli.ts
7809
7921
  import { DivergenceTypeSchema } from "@neat.is/types";
7810
- function isNpxInvocation() {
7811
- if (process.env.npm_command === "exec") return true;
7812
- const execpath = process.env.npm_execpath ?? "";
7813
- if (execpath.includes("npx")) return true;
7814
- const entry2 = process.argv[1] ?? "";
7815
- if (entry2.includes("/_npx/") || entry2.includes("\\_npx\\")) return true;
7816
- return false;
7817
- }
7818
- function commandPrefix() {
7819
- return isNpxInvocation() ? "npx neat.is" : "neat";
7820
- }
7821
7922
  function usage5() {
7822
7923
  const neat = commandPrefix();
7823
7924
  console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
@@ -7825,8 +7926,15 @@ function usage5() {
7825
7926
  console.log(`usage: ${neat} <command> [args] [--project <name>]`);
7826
7927
  console.log("");
7827
7928
  console.log(`Run \`${neat}\` with no command from inside your project to go zero-to-graph in one step.`);
7929
+ console.log(`On your first run in a terminal, \`${neat}\` opens a short menu \u2014 log in to a hosted`);
7930
+ console.log(`NEAT, or set up self-hosted here. Run \`${neat} welcome\` to open it again anytime.`);
7931
+ console.log(`The run stays in your terminal \u2014 query the graph over MCP or the CLI. For a visual`);
7932
+ console.log(`dashboard, log in to hosted NEAT; \`--open\` launches the local preview one by choice.`);
7828
7933
  console.log("");
7829
7934
  console.log("lifecycle commands:");
7935
+ console.log(" welcome Open the first-run menu: log in to a hosted NEAT, or set up");
7936
+ console.log(" self-hosted (print an agent-setup prompt, then build the graph");
7937
+ console.log(" for the current directory).");
7830
7938
  console.log(" init <path> One-time install: discover, extract, register, plan SDK install.");
7831
7939
  console.log(" Snapshot lands in <path>/neat-out/graph.json by default");
7832
7940
  console.log(" (or <path>/neat-out/<project>.json for non-default).");
@@ -8037,6 +8145,7 @@ function parseArgs(rest) {
8037
8145
  dryRun: false,
8038
8146
  noInstall: false,
8039
8147
  noInstrument: false,
8148
+ open: false,
8040
8149
  noOpen: false,
8041
8150
  yes: false,
8042
8151
  verbose: false,
@@ -8074,6 +8183,10 @@ function parseArgs(rest) {
8074
8183
  out.noInstrument = true;
8075
8184
  continue;
8076
8185
  }
8186
+ if (arg === "--open") {
8187
+ out.open = true;
8188
+ continue;
8189
+ }
8077
8190
  if (arg === "--no-open") {
8078
8191
  out.noOpen = true;
8079
8192
  continue;
@@ -8453,6 +8566,11 @@ async function main() {
8453
8566
  }
8454
8567
  const argvParsed = parseArgs(argv);
8455
8568
  if (argvParsed.positional.length === 0) {
8569
+ if (await shouldShowWelcome()) {
8570
+ const welcomeCode = await runWelcomeFlow(argvParsed);
8571
+ if (welcomeCode !== 0) process.exit(welcomeCode);
8572
+ return;
8573
+ }
8456
8574
  const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
8457
8575
  if (orchestratorCode2 !== null && orchestratorCode2 !== 0) process.exit(orchestratorCode2);
8458
8576
  return;
@@ -8461,6 +8579,11 @@ async function main() {
8461
8579
  const parsed = { ...argvParsed, positional: argvParsed.positional.slice(1) };
8462
8580
  const { positional, apply: apply6, dryRun, noInstall } = parsed;
8463
8581
  const project = parsed.project ?? DEFAULT_PROJECT;
8582
+ if (cmd === "welcome") {
8583
+ const code = await runWelcomeFlow(parsed);
8584
+ if (code !== 0) process.exit(code);
8585
+ return;
8586
+ }
8464
8587
  if (cmd === "init") {
8465
8588
  const target = positional[0];
8466
8589
  if (!target) {
@@ -8713,11 +8836,18 @@ async function tryOrchestrator(cmd, parsed) {
8713
8836
  project: projectName,
8714
8837
  projectExplicit,
8715
8838
  noInstrument: parsed.noInstrument,
8839
+ open: parsed.open,
8716
8840
  noOpen: parsed.noOpen,
8717
8841
  yes: parsed.yes
8718
8842
  });
8719
8843
  return result.exitCode;
8720
8844
  }
8845
+ async function runWelcomeFlow(parsed) {
8846
+ return runWelcome({
8847
+ login: (loginArgv) => runLoginCommand(loginArgv),
8848
+ orchestrator: async (cwd) => await tryOrchestrator(cwd, parsed) ?? 0
8849
+ });
8850
+ }
8721
8851
  var QUERY_VERBS = /* @__PURE__ */ new Set([
8722
8852
  "root-cause",
8723
8853
  "blast-radius",