@jeeva1398/eventa 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -27,7 +27,7 @@ Eventa parses the stack trace or `tsc` output, reads the source lines it points
27
27
  Reviews `git diff --staged`, falling back to unstaged changes. Use `--base main` to review everything since `main`. Each file gets static checks first (missing `await`, SQL/command injection, `eval`, sync fs, empty `catch`, disabled TLS, hard-coded secrets), then the model confirms or dismisses them and reports `- [severity] line N: problem → fix`.
28
28
 
29
29
  ### `eventa deps`
30
- Runs `npm audit` and `npm outdated` and scans your imports. It prints the vulnerabilities with their **exact fix commands**, outdated packages (flagging major bumps), unused dependencies and packages that are imported but not declared. Then it adds AI advice on upgrade risk. Use `--no-ai` for the report only.
30
+ Runs `npm audit` and `npm outdated` and scans your imports. It prints the vulnerabilities with their **exact fix commands**, outdated packages (flagging major bumps), unused dependencies and packages that are imported but not declared. Then it prints an **action plan** built only from facts: the fix commands, verified breaking-change notes for about 25 popular packages (others say "read the changelog" instead of guessing), and cleanup commands. It is instant and works without the model. Add `--ai` for optional extra commentary from the model.
31
31
 
32
32
  ### More
33
33
  - `eventa ask <question>`: any Node.js question
@@ -46,6 +46,24 @@ Global flags: `-p, --provider auto|ollama|local`, `-m, --model <name>`, `--host
46
46
  | `contextSize` | `8192` | |
47
47
  | `temperature` | `0.2` | |
48
48
 
49
+ ## Privacy & security
50
+
51
+ Your code is only sent to the model running on your machine: the built-in runtime, or Ollama at `ollamaHost`. If you point that at a remote server, code goes there. Eventa makes these network calls:
52
+
53
+ | When | Where | What is sent |
54
+ |---|---|---|
55
+ | First local use | npm registry | Downloads the runtime from a **pinned lockfile** with integrity hashes and **install scripts disabled** |
56
+ | First local use | huggingface.co | Downloads the model from a pinned commit and verifies its **SHA-256** |
57
+ | `eventa deps` | npm registry | Runs `npm audit` / `npm outdated`, which send your dependency names and versions (exactly as running them yourself would) |
58
+
59
+ Other safeguards:
60
+ - `explain` only reads source files inside the current project, never arbitrary paths from a pasted stack trace.
61
+ - Model output is printed as text with terminal control sequences stripped, and is never executed.
62
+ - `--run` executes only the command you type.
63
+ - The npm package is published with provenance from GitHub Actions.
64
+
65
+ Found a security problem? Please open a private advisory: https://github.com/Jeeva1398/eventa/security/advisories/new
66
+
49
67
  ## Limitations
50
68
  This is a 1.5B model. It is fast and private, but it can be wrong, so treat its answers as a strong hint, not a verdict. The facts Eventa computes itself (stack frames, audit fixes, unused/missing packages, static checks) are deterministic.
51
69
 
package/dist/index.js CHANGED
@@ -92,10 +92,10 @@ var modelPath = (spec) => spec.url ? join2(modelsDir(), spec.file) : spec.file;
92
92
  var isDownloaded = (spec) => existsSync(modelPath(spec));
93
93
 
94
94
  // src/model/runtime.ts
95
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
95
+ import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync } from "fs";
96
96
  import { createRequire } from "module";
97
97
  import { join as join3 } from "path";
98
- import { pathToFileURL } from "url";
98
+ import { fileURLToPath, pathToFileURL } from "url";
99
99
 
100
100
  // src/context/exec.ts
101
101
  import { spawn } from "child_process";
@@ -135,15 +135,32 @@ async function installRuntime(log2 = () => {
135
135
  if (Number(process.versions.node.split(".")[0]) < 20) {
136
136
  throw new Error("The built-in runtime needs Node.js 20 or newer. Upgrade Node, or use Ollama.");
137
137
  }
138
+ if (!existsSync2(join3(LOCK_DIR, "package-lock.json"))) {
139
+ throw new Error("The built-in runtime is installed by the npm package. Use `npx @jeeva1398/eventa`, or run Ollama with this binary.");
140
+ }
138
141
  const dir = runtimeDir();
142
+ const platform = platformPackage();
143
+ rmSync(join3(dir, "node_modules"), { recursive: true, force: true });
139
144
  mkdirSync2(dir, { recursive: true });
140
- const manifest = join3(dir, "package.json");
141
- if (!existsSync2(manifest)) writeFileSync2(manifest, JSON.stringify({ name: "eventa-runtime", private: true }, null, 2));
145
+ for (const f of ["package.json", "package-lock.json"]) copyFileSync(join3(LOCK_DIR, f), join3(dir, f));
146
+ const lock = JSON.parse(readFileSync2(join3(dir, "package-lock.json"), "utf8"));
147
+ const expected = lock.packages[`node_modules/${platform}`]?.integrity;
148
+ if (!expected) throw new Error(`No pinned runtime binary for ${platform}.`);
142
149
  log2(`Installing the local runtime (node-llama-cpp ${LLAMA_VERSION}, about 80 MB, one time)\u2026`);
143
- const r = await exec("npm", ["install", `node-llama-cpp@${LLAMA_VERSION}`, `${platformPackage()}@${LLAMA_VERSION}`, "--omit=optional", "--no-audit", "--no-fund", "--loglevel=error"], { cwd: dir });
144
- if (r.code !== 0) throw new Error(`Runtime install failed:
145
- ${r.stderr.trim()}`);
150
+ const npm = (args) => exec("npm", [...args, "--ignore-scripts", "--no-audit", "--no-fund", "--loglevel=error"], { cwd: dir });
151
+ const ci = await npm(["ci", "--omit=optional"]);
152
+ if (ci.code !== 0) throw new Error(`Runtime install failed:
153
+ ${ci.stderr.trim()}`);
154
+ const bin = await npm(["install", "--no-save", "--omit=optional", `${platform}@${LLAMA_VERSION}`]);
155
+ if (bin.code !== 0) throw new Error(`Runtime install failed:
156
+ ${bin.stderr.trim()}`);
157
+ const installed = JSON.parse(readFileSync2(join3(dir, "node_modules", ".package-lock.json"), "utf8")).packages[`node_modules/${platform}`]?.integrity;
158
+ if (installed !== expected) {
159
+ rmSync(join3(dir, "node_modules"), { recursive: true, force: true });
160
+ throw new Error(`Integrity check failed for ${platform}: expected ${expected}, got ${installed ?? "nothing"}.`);
161
+ }
146
162
  }
163
+ var LOCK_DIR = fileURLToPath(new URL("../runtime/", import.meta.url));
147
164
  var cached;
148
165
  function loadRuntime() {
149
166
  cached ??= (async () => {
@@ -429,6 +446,7 @@ function formatLine(line, state) {
429
446
  if (heading) return chalk2.bold.underline(heading[2]);
430
447
  return line.replace(/\*\*([^*]+)\*\*/g, (_, t) => chalk2.bold(t)).replace(/`([^`]+)`/g, (_, t) => chalk2.yellow(t)).replace(/^(\s*)[-*]\s+/, (_, s) => `${s}\u2022 `);
431
448
  }
449
+ var stripControl = (s) => s.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, "");
432
450
  async function renderStream(tokens, opts = {}) {
433
451
  const out = opts.out ?? process.stdout;
434
452
  const pretty = !opts.raw && Boolean(out.isTTY);
@@ -437,8 +455,9 @@ async function renderStream(tokens, opts = {}) {
437
455
  let full = "";
438
456
  let pending = "";
439
457
  try {
440
- for await (const token of tokens) {
458
+ for await (const raw of tokens) {
441
459
  spinner?.stop();
460
+ const token = stripControl(raw);
442
461
  full += token;
443
462
  if (!pretty) {
444
463
  out.write(token);
@@ -517,7 +536,7 @@ import chalk3 from "chalk";
517
536
  import ora2 from "ora";
518
537
 
519
538
  // src/context/deps.ts
520
- import { readdirSync, readFileSync as readFileSync2 } from "fs";
539
+ import { readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
521
540
  import { builtinModules } from "module";
522
541
  import { extname, join as join4 } from "path";
523
542
  var SEVERITY_ORDER = ["critical", "high", "moderate", "low", "info"];
@@ -527,7 +546,7 @@ var IMPORT_RE = /(?:import|export)\s[^'"]*?from\s*['"]([^'"]+)['"]|import\s*\(\s
527
546
  var BUILTINS = new Set(builtinModules);
528
547
  function readPackageJson(cwd) {
529
548
  try {
530
- return JSON.parse(readFileSync2(join4(cwd, "package.json"), "utf8"));
549
+ return JSON.parse(readFileSync3(join4(cwd, "package.json"), "utf8"));
531
550
  } catch {
532
551
  throw new Error(`No readable package.json in ${cwd}`);
533
552
  }
@@ -559,6 +578,11 @@ function fixPlan(vulns) {
559
578
  }
560
579
  return [...groups].map(([command, fixes]) => ({ command, fixes }));
561
580
  }
581
+ function breaking(current, latest) {
582
+ const [cMajor, cMinor] = current.split(".");
583
+ const [lMajor, lMinor] = latest.split(".");
584
+ return cMajor !== lMajor || cMajor === "0" && cMinor !== lMinor;
585
+ }
562
586
  function parseOutdated(text) {
563
587
  const json = parseJson(text) ?? {};
564
588
  return Object.entries(json).map(([name, v]) => ({
@@ -566,7 +590,7 @@ function parseOutdated(text) {
566
590
  current: v.current,
567
591
  wanted: v.wanted,
568
592
  latest: v.latest,
569
- major: Boolean(v.current && v.latest && v.current.split(".")[0] !== v.latest.split(".")[0])
593
+ major: Boolean(v.current && v.latest && breaking(v.current, v.latest))
570
594
  }));
571
595
  }
572
596
  function packageName(specifier) {
@@ -590,9 +614,11 @@ function scanImports(cwd, maxFiles = 3e3) {
590
614
  if (count >= maxFiles) return;
591
615
  if (e.isDirectory()) {
592
616
  if (!SKIP_DIRS.has(e.name) && !e.name.startsWith(".")) walk(join4(dir, e.name));
593
- } else if (SOURCE_EXT.has(extname(e.name)) || /\.config\.[cm]?[jt]s$/.test(e.name)) {
617
+ } else if ((SOURCE_EXT.has(extname(e.name)) || /\.config\.[cm]?[jt]s$/.test(e.name)) && !/\.min\.[cm]?js$/.test(e.name)) {
618
+ const path = join4(dir, e.name);
619
+ if (statSync2(path).size > 1e6) continue;
594
620
  count++;
595
- const text = readFileSync2(join4(dir, e.name), "utf8");
621
+ const text = readFileSync3(path, "utf8");
596
622
  for (const m of text.matchAll(IMPORT_RE)) {
597
623
  const name = packageName(m[1] ?? m[2] ?? m[3] ?? m[4]);
598
624
  if (name) found.add(name);
@@ -606,7 +632,7 @@ function scanImports(cwd, maxFiles = 3e3) {
606
632
  function binNames(cwd, dep) {
607
633
  let text;
608
634
  try {
609
- text = readFileSync2(join4(cwd, "node_modules", dep, "package.json"), "utf8");
635
+ text = readFileSync3(join4(cwd, "node_modules", dep, "package.json"), "utf8");
610
636
  } catch {
611
637
  return [];
612
638
  }
@@ -644,21 +670,99 @@ async function collectDeps(cwd) {
644
670
  };
645
671
  }
646
672
 
673
+ // src/prompts/upgrades.ts
674
+ var UPGRADE_NOTES = {
675
+ "express@5": "Express 5 uses path-to-regexp v8: wildcard routes must be named (`/*splat`) and optional params use braces; `req.param()` and `app.del()` were removed; rejected promises in async handlers now go to error middleware. Run your route tests.",
676
+ "body-parser@2": "body-parser 2 (used by Express 5) drops deprecated options and changes `urlencoded` `extended` default to false; check form parsing.",
677
+ "chalk@5": "chalk 5 is ESM-only; `require('chalk')` fails in CommonJS. Stay on 4 for CJS or migrate the project to ESM.",
678
+ "node-fetch@3": "node-fetch 3 is ESM-only. On Node 18+ prefer the built-in global `fetch` and remove the dependency.",
679
+ uuid: "Deep imports such as `require('uuid/v4')` were removed in v8; use `const { v4 } = require('uuid')`.",
680
+ "mongoose@7": "Mongoose 7 removed callback support from model methods (use async/await) and changed the `strictQuery` default.",
681
+ "mongoose@8": "Mongoose 7 removed callback support from model methods (use async/await) and changed the `strictQuery` default; Mongoose 8 needs MongoDB 4.4+.",
682
+ "jest@28": "Jest 28 no longer bundles jsdom (install `jest-environment-jsdom`) and renamed `testURL` to `testEnvironmentOptions.url`.",
683
+ "jest@29": "Jest 28 no longer bundles jsdom (install `jest-environment-jsdom`) and renamed `testURL` to `testEnvironmentOptions.url`; Jest 29 changed the default snapshot format.",
684
+ "eslint@9": "ESLint 9 uses flat config (`eslint.config.js`) by default and ignores `.eslintrc`; many plugins need updated versions.",
685
+ "typescript@5": "TypeScript 5 deprecates flags such as `importsNotUsedAsValues` and implements standard decorators unless `experimentalDecorators` is set; expect some new type errors.",
686
+ "axios@1": "axios 1.x adds an `exports` map, so deep imports like `axios/lib/...` break, and some TypeScript types were renamed.",
687
+ helmet: "Newer helmet enables a strict Content-Security-Policy and cross-origin headers by default, which can block inline scripts and third-party assets; test your pages.",
688
+ inquirer: "inquirer 9+ is ESM-only and v10+ moved to the new `@inquirer/prompts` API.",
689
+ got: "got 12+ is ESM-only and changed several option names; consider native `fetch` for simple calls.",
690
+ "webpack@5": "webpack 5 removed automatic Node.js polyfills (`Buffer`, `process`, `crypto`) for browser bundles; add `resolve.fallback` where needed. It also fixes ERR_OSSL_EVP_UNSUPPORTED on Node 17+.",
691
+ dotenv: "Low risk: dotenv 16 keeps the same `config()` API and adds multiline values.",
692
+ "@nestjs/core@11": "Nest 11 requires Node 20+ and uses Express 5 by default, so wildcard routes change syntax (`*` must be named, e.g. `*splat`). Upgrade all `@nestjs/*` packages together.",
693
+ "@nestjs/common@11": "Nest 11 requires Node 20+ and uses Express 5 by default, so wildcard routes change syntax (`*` must be named, e.g. `*splat`). Upgrade all `@nestjs/*` packages together.",
694
+ "@prisma/client": "Prisma 5 removed `rejectOnNotFound` (use `findUniqueOrThrow`); Prisma 6 needs Node 18.18+ and TypeScript 5.1+, and `Bytes` fields become `Uint8Array`. Upgrade `prisma` and `@prisma/client` to the same version and re-run `prisma generate`.",
695
+ prisma: "Upgrade `prisma` and `@prisma/client` to the same version and re-run `prisma generate`; Prisma 5 removed `rejectOnNotFound`, Prisma 6 needs Node 18.18+ and TypeScript 5.1+.",
696
+ "class-validator@0": "Low risk for most apps: 0.14 fixes a security issue where unknown values skipped validation; `forbidUnknownValues` now defaults to true, so plain objects without decorators are rejected.",
697
+ "redis@4": "node-redis 4 is promise-based: call `await client.connect()` before use, and commands are camelCase (`hSet`, not `hset`) with no callbacks.",
698
+ glob: "glob 9+ has a promise API only (no callbacks) and a named export: `import { glob } from 'glob'`.",
699
+ rimraf: "rimraf 4+ has a promise API only and a named export: `import { rimraf } from 'rimraf'`."
700
+ };
701
+ var major = (version) => version?.split(".")[0];
702
+ function upgradeNote(name, latest) {
703
+ return UPGRADE_NOTES[`${name}@${major(latest)}`] ?? UPGRADE_NOTES[name];
704
+ }
705
+
706
+ // src/context/depsPlan.ts
707
+ var SEVERITY_RANK = ["critical", "high", "moderate", "low", "info"];
708
+ function buildPlan(r) {
709
+ const sections = [];
710
+ const byName = new Map(r.vulnerabilities.map((v) => [v.name, v]));
711
+ if (r.vulnerabilities.length) {
712
+ const items = fixPlan(r.vulnerabilities).map(({ command, fixes }) => {
713
+ const vulns = fixes.map((n) => byName.get(n));
714
+ const worst = vulns.map((v) => v.severity).sort((a, b) => SEVERITY_RANK.indexOf(a) - SEVERITY_RANK.indexOf(b))[0];
715
+ const titles = vulns.map((v) => `${v.name}: ${v.title ?? "advisory"}`).join("; ");
716
+ if (command === "no fix available") return `- **No fix available** (${worst}): ${titles}. Replace the package or check whether the vulnerable code is reachable.`;
717
+ const major2 = command.endsWith("(major)");
718
+ return `- \`${command.replace(" (major)", "")}\`${major2 ? " **(major version, see notes below)**" : ""} (${worst}): ${titles}.`;
719
+ });
720
+ sections.push(`**1. Security**
721
+ ${items.join("\n")}`);
722
+ }
723
+ const majors = r.outdated.filter((o) => o.major);
724
+ if (majors.length) {
725
+ const items = majors.map((o) => {
726
+ const note = upgradeNote(o.name, o.latest);
727
+ return `- **${o.name} ${o.current ?? "?"} \u2192 ${o.latest}**: ${note ?? "no verified notes. Read its changelog / release notes before upgrading."}`;
728
+ });
729
+ sections.push(`**2. Major upgrades**
730
+ ${items.join("\n")}`);
731
+ }
732
+ const cleanup = [];
733
+ if (r.unused.length) cleanup.push(`- \`npm uninstall ${r.unused.join(" ")}\`: not imported anywhere (double-check config files, scripts and dynamic requires first).`);
734
+ if (r.missing.length) cleanup.push(`- \`npm install ${r.missing.join(" ")}\`: imported in code but missing from package.json.`);
735
+ const minors = r.outdated.filter((o) => !o.major);
736
+ if (minors.length) cleanup.push(`- \`npm update\`: safe minor/patch updates for ${minors.map((m) => m.name).join(", ")}.`);
737
+ if (cleanup.length) sections.push(`**3. Cleanup**
738
+ ${cleanup.join("\n")}`);
739
+ return sections.join("\n\n");
740
+ }
741
+
647
742
  // src/prompts/deps.ts
648
743
  function depsPrompt(r, nodeVersion) {
649
- const lines = [`Node ${nodeVersion}`];
744
+ const lines2 = [`Node ${nodeVersion}`];
650
745
  if (r.vulnerabilities.length) {
651
- lines.push("Vulnerabilities:", ...r.vulnerabilities.slice(0, 20).map((v) => `- ${v.name} [${v.severity}] ${v.title ?? ""}`));
652
- lines.push("Fix commands (already computed and correct):", ...fixPlan(r.vulnerabilities).map((f) => `- ${f.command} \u2192 fixes ${f.fixes.join(", ")}`));
746
+ const title = new Map(r.vulnerabilities.map((v) => [v.name, v.title]));
747
+ lines2.push("Vulnerabilities:", ...r.vulnerabilities.slice(0, 20).map((v) => `- ${v.name} [${v.severity}] ${v.title ?? ""}`));
748
+ lines2.push(
749
+ "Fix commands (already computed and correct):",
750
+ ...fixPlan(r.vulnerabilities).map((f) => `- ${f.command} \u2192 fixes ${f.fixes.map((n) => title.get(n) ? `${n} (${title.get(n)})` : n).join(", ")}`)
751
+ );
653
752
  }
654
753
  if (r.outdated.length) {
655
- lines.push("Outdated:", ...r.outdated.slice(0, 25).map((o) => `- ${o.name} ${o.current ?? "not installed"} \u2192 latest ${o.latest}${o.major ? " (MAJOR)" : ""}`));
754
+ lines2.push("Outdated:", ...r.outdated.slice(0, 25).map((o) => `- ${o.name} ${o.current ?? "not installed"} \u2192 latest ${o.latest}${o.major ? " (MAJOR)" : ""}`));
755
+ const notes = r.outdated.filter((o) => o.major).map((o) => [o, upgradeNote(o.name, o.latest)]);
756
+ const known = notes.filter(([, n]) => n);
757
+ if (known.length) lines2.push("Verified upgrade notes:", ...known.map(([o, n]) => `- ${o.name} \u2192 ${o.latest}: ${n}`));
758
+ const unknown = notes.filter(([, n]) => !n).map(([o]) => o.name);
759
+ if (unknown.length) lines2.push(`No verified notes for: ${unknown.join(", ")}`);
656
760
  }
657
- if (r.unused.length) lines.push(`Possibly unused dependencies: ${r.unused.join(", ")}`);
658
- if (r.missing.length) lines.push(`Imported but not declared in package.json: ${r.missing.join(", ")}`);
761
+ if (r.unused.length) lines2.push(`Possibly unused dependencies: ${r.unused.join(", ")}`);
762
+ if (r.missing.length) lines2.push(`Imported but not declared in package.json: ${r.missing.join(", ")}`);
659
763
  return {
660
- instruction: "You are given facts about a Node.js project's dependencies. Write a short, prioritized action plan:\n1. Security: repeat each fix command exactly as given and say in one line why it matters. Never change versions.\n2. Major upgrades (marked MAJOR only): for each, the main breaking-change risk you know of and what to check.\n3. Cleanup: unused packages to remove and missing packages to add.\nDo not repeat the raw lists. Do not invent versions or advisories. Skip any section with nothing to do.",
661
- input: lines.join("\n")
764
+ instruction: "You are given facts about a Node.js project's dependencies. Write a short, prioritized action plan:\n1. Security: repeat each fix command exactly as given and say in one line why it matters, based only on the advisory titles. Never change versions.\n2. Major upgrades (marked MAJOR only): restate the verified upgrade note for each. For packages with no verified notes, say to read their changelog before upgrading; do not guess API changes.\n3. Cleanup: unused packages to remove and missing packages to add.\nDo not repeat the raw lists. Do not invent versions, advisories or APIs. Skip any section with nothing to do.",
765
+ input: lines2.join("\n")
662
766
  };
663
767
  }
664
768
 
@@ -702,9 +806,10 @@ async function depsCommand(config2, opts) {
702
806
  const spinner = !opts.json && process.stderr.isTTY ? ora2({ text: "Running npm audit and npm outdated\u2026", stream: process.stderr }).start() : null;
703
807
  const report = await collectDeps(cwd).finally(() => spinner?.stop());
704
808
  const wantAi = opts.ai && !isHealthy(report);
809
+ const plan = isHealthy(report) ? "" : buildPlan(report);
705
810
  if (opts.json) {
706
811
  const analysis = wantAi ? await generate(config2, depsPrompt(report, process.versions.node), { json: true }) : void 0;
707
- printJson({ ...report, analysis });
812
+ printJson({ ...report, plan, analysis });
708
813
  return;
709
814
  }
710
815
  printReport(report);
@@ -712,11 +817,18 @@ async function depsCommand(config2, opts) {
712
817
  console.log(chalk3.green("\u2714 Dependencies look healthy."));
713
818
  return;
714
819
  }
820
+ console.log(chalk3.bold.underline("Action plan"));
821
+ await renderStream(lines(plan), { raw: opts.raw });
715
822
  if (wantAi) {
716
- console.log(chalk3.bold.underline("Advice"));
823
+ console.log(chalk3.bold.underline("\nAI notes"));
717
824
  await generate(config2, depsPrompt(report, process.versions.node), opts);
825
+ } else {
826
+ console.log(chalk3.dim("\nRun with --ai for extra AI commentary."));
718
827
  }
719
828
  }
829
+ async function* lines(text) {
830
+ yield text;
831
+ }
720
832
 
721
833
  // src/commands/doctor.ts
722
834
  import chalk4 from "chalk";
@@ -735,8 +847,8 @@ async function probe(name, check) {
735
847
  }
736
848
  }
737
849
  async function doctorCommand(config2) {
738
- const major = Number(process.versions.node.split(".")[0]);
739
- if (major >= 20) ok(`Node.js ${process.versions.node}`);
850
+ const major2 = Number(process.versions.node.split(".")[0]);
851
+ if (major2 >= 20) ok(`Node.js ${process.versions.node}`);
740
852
  else fail(`Node.js ${process.versions.node}`, "Eventa needs Node.js 20 or newer");
741
853
  const ollamaOk = await probe(`Ollama (${config2.model})`, () => new OllamaProvider(config2.model, config2.ollamaHost).check());
742
854
  const localOk = await probe(`Built-in runtime (${config2.localModel})`, () => new LocalProvider(config2.localModel).check());
@@ -744,11 +856,11 @@ async function doctorCommand(config2) {
744
856
  console.log(chalk4.dim(`
745
857
  Provider setting: ${config2.provider} \u2192 using ${active.name} (${active.model})`));
746
858
  if (!ollamaOk && !localOk) console.log(chalk4.dim("Neither is ready yet. Eventa installs the built-in runtime and model on first use, or run `eventa model pull` now."));
747
- return major >= 20 && (ollamaOk || localOk);
859
+ return major2 >= 20 && (ollamaOk || localOk);
748
860
  }
749
861
 
750
862
  // src/commands/model.ts
751
- import { rmSync } from "fs";
863
+ import { rmSync as rmSync2 } from "fs";
752
864
  import chalk5 from "chalk";
753
865
  function modelList() {
754
866
  const { localModel } = loadConfig();
@@ -774,20 +886,20 @@ function modelUse(id) {
774
886
  function modelRemove(id) {
775
887
  const spec = resolveModel(id);
776
888
  if (!spec.url) throw new Error("Refusing to delete a local file you provided yourself.");
777
- rmSync(modelPath(spec), { force: true });
778
- rmSync(`${modelPath(spec)}.part`, { force: true });
889
+ rmSync2(modelPath(spec), { force: true });
890
+ rmSync2(`${modelPath(spec)}.part`, { force: true });
779
891
  console.log(`Removed ${spec.id}`);
780
892
  }
781
893
 
782
894
  // src/commands/explain.ts
783
- import { readFileSync as readFileSync4 } from "fs";
895
+ import { readFileSync as readFileSync5 } from "fs";
784
896
  import { relative as relative2 } from "path";
785
897
  import chalk6 from "chalk";
786
898
 
787
899
  // src/context/stackParser.ts
788
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
900
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
789
901
  import { isAbsolute, relative, resolve as resolve2 } from "path";
790
- import { fileURLToPath } from "url";
902
+ import { fileURLToPath as fileURLToPath2 } from "url";
791
903
  var FRAME_RE = /^\s*at\s+(?:async\s+(?=[^(]*$))?(?:(.+?)\s+\()?((?:file:\/\/|node:|[A-Za-z]:[\\/]|[\\/.]).+?):(\d+)(?::(\d+))?\)?\s*$/;
792
904
  var HEADER_RE = /^((?:file:\/\/\/?)?(?:[A-Za-z]:)?[^\s:]+\.(?:[cm]?[jt]sx?)):(\d+)$/;
793
905
  var MESSAGE_RE = /^\s*(?:Uncaught\s+)?((?:[A-Z][A-Za-z]*)?Error(?:\s\[[A-Z0-9_]+\])?:\s.*)$/;
@@ -802,7 +914,7 @@ var PATTERN_CODES = [
802
914
  function toPath(file) {
803
915
  if (file.startsWith("file://")) {
804
916
  try {
805
- return fileURLToPath(file);
917
+ return fileURLToPath2(file);
806
918
  } catch {
807
919
  return file;
808
920
  }
@@ -855,17 +967,21 @@ function parseStack(raw) {
855
967
  result.code = PATTERN_CODES.find(([re]) => re.test(text))?.[1] ?? CODE_RE.exec(text)?.[1];
856
968
  return result;
857
969
  }
970
+ function insideProject(file, cwd) {
971
+ const rel = relative(resolve2(cwd), resolve2(file));
972
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel) && !/(^|[\\/])node_modules([\\/]|$)/.test(rel);
973
+ }
858
974
  function userFrames(parsed, cwd, limit = 3) {
859
- return parsed.frames.filter((f) => !f.internal && !f.dependency).map((f) => ({ ...f, file: isAbsolute(f.file) ? f.file : resolve2(cwd, f.file) })).filter((f) => existsSync4(f.file)).slice(0, limit);
975
+ return parsed.frames.filter((f) => !f.internal && !f.dependency).map((f) => ({ ...f, file: isAbsolute(f.file) ? f.file : resolve2(cwd, f.file) })).filter((f) => insideProject(f.file, cwd) && existsSync4(f.file)).slice(0, limit);
860
976
  }
861
977
  function readSnippet(file, marked, radius = 15, cwd = process.cwd()) {
862
978
  const targets = (Array.isArray(marked) ? marked : [marked]).slice().sort((a, b) => a - b);
863
- const lines = readFileSync3(file, "utf8").split(/\r?\n/);
979
+ const lines2 = readFileSync4(file, "utf8").split(/\r?\n/);
864
980
  const start = Math.max(1, targets[0] - radius);
865
- const end = Math.min(lines.length, targets[targets.length - 1] + radius);
981
+ const end = Math.min(lines2.length, targets[targets.length - 1] + radius);
866
982
  const body = [];
867
983
  for (let n = start; n <= end; n++) {
868
- body.push(targets.includes(n) ? `${lines[n - 1]} // <-- line ${n}, in the stack trace` : lines[n - 1]);
984
+ body.push(targets.includes(n) ? `${lines2[n - 1]} // <-- line ${n}, in the stack trace` : lines2[n - 1]);
869
985
  }
870
986
  return `// ${relative(cwd, file) || file} (lines ${start}-${end})
871
987
  ${body.join("\n")}`;
@@ -873,12 +989,12 @@ ${body.join("\n")}`;
873
989
  function snippetsFor(frames, radius, cwd) {
874
990
  const byFile = /* @__PURE__ */ new Map();
875
991
  for (const f of frames) {
876
- const lines = byFile.get(f.file) ?? [];
877
- const last = lines[lines.length - 1];
878
- if (last === void 0 || Math.abs(f.line - last) <= radius * 2) lines.push(f.line);
879
- byFile.set(f.file, lines);
992
+ const lines2 = byFile.get(f.file) ?? [];
993
+ const last = lines2[lines2.length - 1];
994
+ if (last === void 0 || Math.abs(f.line - last) <= radius * 2) lines2.push(f.line);
995
+ byFile.set(f.file, lines2);
880
996
  }
881
- return [...byFile].map(([file, lines]) => readSnippet(file, lines, radius, cwd));
997
+ return [...byFile].map(([file, lines2]) => readSnippet(file, lines2, radius, cwd));
882
998
  }
883
999
 
884
1000
  // src/context/tokens.ts
@@ -987,7 +1103,7 @@ ${r.stdout}`.trim();
987
1103
  if (r.code === 0 && !parseStack(output).message) return null;
988
1104
  return output;
989
1105
  }
990
- if (opts.file) return readFileSync4(opts.file, "utf8");
1106
+ if (opts.file) return readFileSync5(opts.file, "utf8");
991
1107
  if (words.length) return words.join(" ");
992
1108
  return readStdin();
993
1109
  }
@@ -1197,7 +1313,7 @@ async function reviewCommand(config2, opts) {
1197
1313
 
1198
1314
  // src/index.ts
1199
1315
  var program = new Command();
1200
- program.name("eventa").description("Offline AI assistant for Node.js developers").version("0.1.0").option("-m, --model <name>", "model to use (overrides config)").option("--host <url>", "Ollama host URL").option("-p, --provider <name>", "auto | ollama | local").option("--raw", "print plain output without terminal formatting");
1316
+ program.name("eventa").description("Offline AI assistant for Node.js developers").version("0.1.2").option("-m, --model <name>", "model to use (overrides config)").option("--host <url>", "Ollama host URL").option("-p, --provider <name>", "auto | ollama | local").option("--raw", "print plain output without terminal formatting");
1201
1317
  var configFrom = (cmd) => {
1202
1318
  const g = cmd.optsWithGlobals();
1203
1319
  if (g.provider && !["auto", "ollama", "local"].includes(g.provider)) throw new Error("--provider must be auto, ollama or local");
@@ -1215,7 +1331,7 @@ program.command("review").description("review your staged git diff (falls back t
1215
1331
  const { config: config2, raw } = configFrom(cmd);
1216
1332
  await reviewCommand(config2, { ...opts, raw });
1217
1333
  });
1218
- program.command("deps").description("audit dependencies: vulnerabilities, outdated, unused and missing packages").option("--no-ai", "only print the report, skip AI advice").option("--json", "print JSON output").action(async (opts, cmd) => {
1334
+ program.command("deps").description("audit dependencies: vulnerabilities, outdated, unused and missing packages").option("--ai", "also ask the model for extra commentary (the action plan itself is always deterministic)").option("--json", "print JSON output").action(async (opts, cmd) => {
1219
1335
  const { config: config2, raw } = configFrom(cmd);
1220
1336
  await depsCommand(config2, { ...opts, raw });
1221
1337
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeeva1398/eventa",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Offline AI assistant for Node.js developers: explain errors, review diffs, audit dependencies.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "files": [
10
10
  "dist",
11
+ "runtime",
11
12
  "README.md",
12
13
  "LICENSE"
13
14
  ],