@jeeva1398/eventa 0.1.1 → 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 +18 -0
- package/dist/index.js +49 -24
- package/package.json +2 -1
- package/runtime/package-lock.json +1797 -0
- package/runtime/package.json +7 -0
package/README.md
CHANGED
|
@@ -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,
|
|
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
|
|
141
|
-
|
|
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
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
}
|
|
@@ -595,9 +614,11 @@ function scanImports(cwd, maxFiles = 3e3) {
|
|
|
595
614
|
if (count >= maxFiles) return;
|
|
596
615
|
if (e.isDirectory()) {
|
|
597
616
|
if (!SKIP_DIRS.has(e.name) && !e.name.startsWith(".")) walk(join4(dir, e.name));
|
|
598
|
-
} 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;
|
|
599
620
|
count++;
|
|
600
|
-
const text =
|
|
621
|
+
const text = readFileSync3(path, "utf8");
|
|
601
622
|
for (const m of text.matchAll(IMPORT_RE)) {
|
|
602
623
|
const name = packageName(m[1] ?? m[2] ?? m[3] ?? m[4]);
|
|
603
624
|
if (name) found.add(name);
|
|
@@ -611,7 +632,7 @@ function scanImports(cwd, maxFiles = 3e3) {
|
|
|
611
632
|
function binNames(cwd, dep) {
|
|
612
633
|
let text;
|
|
613
634
|
try {
|
|
614
|
-
text =
|
|
635
|
+
text = readFileSync3(join4(cwd, "node_modules", dep, "package.json"), "utf8");
|
|
615
636
|
} catch {
|
|
616
637
|
return [];
|
|
617
638
|
}
|
|
@@ -839,7 +860,7 @@ Provider setting: ${config2.provider} \u2192 using ${active.name} (${active.mode
|
|
|
839
860
|
}
|
|
840
861
|
|
|
841
862
|
// src/commands/model.ts
|
|
842
|
-
import { rmSync } from "fs";
|
|
863
|
+
import { rmSync as rmSync2 } from "fs";
|
|
843
864
|
import chalk5 from "chalk";
|
|
844
865
|
function modelList() {
|
|
845
866
|
const { localModel } = loadConfig();
|
|
@@ -865,20 +886,20 @@ function modelUse(id) {
|
|
|
865
886
|
function modelRemove(id) {
|
|
866
887
|
const spec = resolveModel(id);
|
|
867
888
|
if (!spec.url) throw new Error("Refusing to delete a local file you provided yourself.");
|
|
868
|
-
|
|
869
|
-
|
|
889
|
+
rmSync2(modelPath(spec), { force: true });
|
|
890
|
+
rmSync2(`${modelPath(spec)}.part`, { force: true });
|
|
870
891
|
console.log(`Removed ${spec.id}`);
|
|
871
892
|
}
|
|
872
893
|
|
|
873
894
|
// src/commands/explain.ts
|
|
874
|
-
import { readFileSync as
|
|
895
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
875
896
|
import { relative as relative2 } from "path";
|
|
876
897
|
import chalk6 from "chalk";
|
|
877
898
|
|
|
878
899
|
// src/context/stackParser.ts
|
|
879
|
-
import { existsSync as existsSync4, readFileSync as
|
|
900
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
880
901
|
import { isAbsolute, relative, resolve as resolve2 } from "path";
|
|
881
|
-
import { fileURLToPath } from "url";
|
|
902
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
882
903
|
var FRAME_RE = /^\s*at\s+(?:async\s+(?=[^(]*$))?(?:(.+?)\s+\()?((?:file:\/\/|node:|[A-Za-z]:[\\/]|[\\/.]).+?):(\d+)(?::(\d+))?\)?\s*$/;
|
|
883
904
|
var HEADER_RE = /^((?:file:\/\/\/?)?(?:[A-Za-z]:)?[^\s:]+\.(?:[cm]?[jt]sx?)):(\d+)$/;
|
|
884
905
|
var MESSAGE_RE = /^\s*(?:Uncaught\s+)?((?:[A-Z][A-Za-z]*)?Error(?:\s\[[A-Z0-9_]+\])?:\s.*)$/;
|
|
@@ -893,7 +914,7 @@ var PATTERN_CODES = [
|
|
|
893
914
|
function toPath(file) {
|
|
894
915
|
if (file.startsWith("file://")) {
|
|
895
916
|
try {
|
|
896
|
-
return
|
|
917
|
+
return fileURLToPath2(file);
|
|
897
918
|
} catch {
|
|
898
919
|
return file;
|
|
899
920
|
}
|
|
@@ -946,12 +967,16 @@ function parseStack(raw) {
|
|
|
946
967
|
result.code = PATTERN_CODES.find(([re]) => re.test(text))?.[1] ?? CODE_RE.exec(text)?.[1];
|
|
947
968
|
return result;
|
|
948
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
|
+
}
|
|
949
974
|
function userFrames(parsed, cwd, limit = 3) {
|
|
950
|
-
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);
|
|
951
976
|
}
|
|
952
977
|
function readSnippet(file, marked, radius = 15, cwd = process.cwd()) {
|
|
953
978
|
const targets = (Array.isArray(marked) ? marked : [marked]).slice().sort((a, b) => a - b);
|
|
954
|
-
const lines2 =
|
|
979
|
+
const lines2 = readFileSync4(file, "utf8").split(/\r?\n/);
|
|
955
980
|
const start = Math.max(1, targets[0] - radius);
|
|
956
981
|
const end = Math.min(lines2.length, targets[targets.length - 1] + radius);
|
|
957
982
|
const body = [];
|
|
@@ -1078,7 +1103,7 @@ ${r.stdout}`.trim();
|
|
|
1078
1103
|
if (r.code === 0 && !parseStack(output).message) return null;
|
|
1079
1104
|
return output;
|
|
1080
1105
|
}
|
|
1081
|
-
if (opts.file) return
|
|
1106
|
+
if (opts.file) return readFileSync5(opts.file, "utf8");
|
|
1082
1107
|
if (words.length) return words.join(" ");
|
|
1083
1108
|
return readStdin();
|
|
1084
1109
|
}
|
|
@@ -1288,7 +1313,7 @@ async function reviewCommand(config2, opts) {
|
|
|
1288
1313
|
|
|
1289
1314
|
// src/index.ts
|
|
1290
1315
|
var program = new Command();
|
|
1291
|
-
program.name("eventa").description("Offline AI assistant for Node.js developers").version("0.1.
|
|
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");
|
|
1292
1317
|
var configFrom = (cmd) => {
|
|
1293
1318
|
const g = cmd.optsWithGlobals();
|
|
1294
1319
|
if (g.provider && !["auto", "ollama", "local"].includes(g.provider)) throw new Error("--provider must be auto, ollama or local");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeeva1398/eventa",
|
|
3
|
-
"version": "0.1.
|
|
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
|
],
|