@unerr-ai/unerr 0.2.8 → 0.2.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unerr-ai/unerr",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "mcpName": "io.github.unerr-ai/unerr",
5
5
  "description": "Your AI agent has read your codebase but still can't safely change it. unerr is a local guardrail that hands the agent the call graph and your rules at the moment it edits.",
6
6
  "repository": {
@@ -30,14 +30,9 @@
30
30
  "typecheck": "tsc --noEmit",
31
31
  "test:local-mode": "vitest run src/__tests__/local-mode-offline.test.ts src/__tests__/network-boundary.test.ts",
32
32
  "check:tool-budget": "tsx scripts/check-tool-budget.ts",
33
- "gen:docs": "tsx scripts/gen-docs-reference.ts",
34
- "postinstall": "node scripts/postinstall.mjs || true"
33
+ "gen:docs": "tsx scripts/gen-docs-reference.ts"
35
34
  },
36
35
  "dependencies": {
37
- "@ai-sdk/anthropic": "^3.0.73",
38
- "@ai-sdk/google": "^3.0.67",
39
- "@ai-sdk/openai": "^3.0.57",
40
- "@anthropic-ai/sdk": "^0.82.0",
41
36
  "@clack/prompts": "^1.2.0",
42
37
  "@hono/node-server": "^2.0.1",
43
38
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -45,11 +40,7 @@
45
40
  "@sourcegraph/scip-python": "^0.6.6",
46
41
  "@sourcegraph/scip-typescript": "^0.4.0",
47
42
  "@tanstack/react-query": "^5.90.12",
48
- "ai": "^6.0.172",
49
43
  "better-sqlite3": "^12.10.0",
50
- "class-variance-authority": "^0.7.1",
51
- "cli-progress": "^3.12.0",
52
- "clsx": "^2.1.1",
53
44
  "commander": "^12.1.0",
54
45
  "consola": "^3.4.2",
55
46
  "cozo-node": "^0.7.6",
@@ -57,25 +48,21 @@
57
48
  "graphology": "^0.25.4",
58
49
  "graphology-communities-louvain": "^2.0.1",
59
50
  "graphology-layout-forceatlas2": "^0.10.1",
60
- "hono": "^4.12.16",
51
+ "hono": "^4.12.21",
61
52
  "ignore": "^6.0.0",
62
53
  "ink": "^6.8.0",
63
54
  "ink-text-input": "^6.0.0",
64
- "lucide-react": "^0.511.0",
65
55
  "msgpackr": "^1.11.0",
66
56
  "ora": "^8.2.0",
67
57
  "picocolors": "^1.1.1",
68
- "prompts": "^2.4.2",
69
58
  "react": "^19.2.5",
70
59
  "react-day-picker": "^10.0.0",
71
60
  "react-dom": "^19.2.5",
72
61
  "simple-git": "^3.36.0",
73
- "tailwind-merge": "^3.5.0",
74
62
  "tailwindcss": "^4.2.4",
75
63
  "tinyexec": "^1.1.2",
76
64
  "tinypool": "^2.1.0",
77
65
  "tree-sitter-wasms": "^0.1.13",
78
- "vis-data": "^8.0.3",
79
66
  "vis-network": "^10.0.2",
80
67
  "web-tree-sitter": "^0.24.3",
81
68
  "xstate": "^5.31.0",
@@ -90,10 +77,8 @@
90
77
  "@biomejs/biome": "^1.9.0",
91
78
  "@tailwindcss/vite": "^4.2.4",
92
79
  "@types/better-sqlite3": "^7.6.13",
93
- "@types/cli-progress": "^3.11.0",
94
80
  "@types/jsdom": "^21.1.7",
95
81
  "@types/node": "^20.0.0",
96
- "@types/prompts": "^2.4.9",
97
82
  "@types/react": "^19.2.14",
98
83
  "@types/react-dom": "^19.2.3",
99
84
  "@types/turndown": "^5.0.5",
@@ -121,8 +106,7 @@
121
106
  "dist/ui/fonts/**",
122
107
  "!dist/ui/assets/**",
123
108
  "!dist/ui/screenshots/**",
124
- "!dist/ui/prototype-sandbox/**",
125
- "scripts/postinstall.mjs"
109
+ "!dist/ui/prototype-sandbox/**"
126
110
  ],
127
111
  "license": "Elastic-2.0",
128
112
  "pnpm": {
@@ -132,7 +116,11 @@
132
116
  "cozo-node",
133
117
  "esbuild",
134
118
  "msgpackr-extract"
135
- ]
119
+ ],
120
+ "overrides": {
121
+ "tar@<7.5.11": "^7.5.11",
122
+ "ws@<8.20.1": "^8.20.1"
123
+ }
136
124
  },
137
125
  "keywords": [
138
126
  "mcp",
@@ -1,312 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * postinstall script for @unerr-ai/unerr
5
- *
6
- * Detects whether the npm global bin directory is on the user's PATH.
7
- * If not, offers to automatically fix it (append to shell RC) with user consent,
8
- * or prints manual instructions as a fallback.
9
- *
10
- * Skipped silently in CI, non-global installs, and non-TTY environments.
11
- */
12
-
13
- import {
14
- appendFileSync,
15
- createReadStream,
16
- existsSync,
17
- openSync,
18
- readFileSync,
19
- } from "node:fs";
20
- import { createInterface } from "node:readline";
21
- import { homedir } from "node:os";
22
- import { join } from "node:path";
23
- import { execSync } from "node:child_process";
24
-
25
- // ── Gate checks ──────────────────────────────────────────────
26
-
27
- const ci =
28
- process.env.CI ||
29
- process.env.CONTINUOUS_INTEGRATION ||
30
- process.env.GITHUB_ACTIONS;
31
- if (ci) process.exit(0);
32
-
33
- const npmGlobal = process.env.npm_config_global;
34
- if (npmGlobal !== undefined && npmGlobal !== "true") process.exit(0);
35
-
36
- if (
37
- npmGlobal === undefined &&
38
- existsSync(join(process.cwd(), "tsconfig.json"))
39
- )
40
- process.exit(0);
41
-
42
- // ── Resolve npm global bin ───────────────────────────────────
43
-
44
- let globalBin = "";
45
- try {
46
- globalBin = execSync("npm prefix -g", {
47
- encoding: "utf-8",
48
- timeout: 5000,
49
- }).trim();
50
- globalBin = join(globalBin, "bin");
51
- } catch {
52
- process.exit(0);
53
- }
54
- if (!globalBin) process.exit(0);
55
-
56
- // ANSI codes (use plain text if stderr is not a TTY)
57
- const hasTTY = !!process.stderr.isTTY;
58
- const W = hasTTY ? "\x1b[33m" : "";
59
- const G = hasTTY ? "\x1b[32m" : "";
60
- const B = hasTTY ? "\x1b[1m" : "";
61
- const D = hasTTY ? "\x1b[2m" : "";
62
- const R = hasTTY ? "\x1b[0m" : "";
63
- const C = hasTTY ? "\x1b[36m" : "";
64
-
65
- const stderr = (msg) => process.stderr.write(msg);
66
-
67
- // ── Check PATH ───────────────────────────────────────────────
68
-
69
- const pathSep = process.platform === "win32" ? ";" : ":";
70
- const pathDirs = (process.env.PATH || "").split(pathSep);
71
- const normalizedGlobalBin = globalBin.replace(/\/+$/, "");
72
- const isOnPath = pathDirs.some(
73
- (d) => d.replace(/\/+$/, "") === normalizedGlobalBin
74
- );
75
-
76
- if (isOnPath) {
77
- stderr(
78
- `\n ${G}✓${R} ${B}unerr${R} is installed and ready to use.\n` +
79
- ` ${D}Run ${C}unerr${D} in any project to start.${R}\n\n`
80
- );
81
- process.exit(0);
82
- }
83
-
84
- // For the interactive warning/prompt, we need a TTY
85
- if (!hasTTY) process.exit(0);
86
-
87
- // ── Detect shell & version manager ──────────────────────────
88
-
89
- const shell = (process.env.SHELL || "").split("/").pop() || "unknown";
90
- const hasNvm = !!process.env.NVM_DIR;
91
- const hasFnm = !!process.env.FNM_MULTISHELL_PATH;
92
- const hasVolta = !!process.env.VOLTA_HOME;
93
- const home = homedir();
94
-
95
- // ── Shell RC helpers ─────────────────────────────────────────
96
-
97
- function getRcPath() {
98
- if (shell === "zsh") return join(home, ".zshrc");
99
- if (shell === "fish") return join(home, ".config", "fish", "config.fish");
100
- return join(home, ".bashrc");
101
- }
102
-
103
- function getRcDisplayName() {
104
- return getRcPath().replace(home, "~");
105
- }
106
-
107
- /**
108
- * Build the lines to append to the shell RC file.
109
- * Returns { lines: string[], description: string, canAutoFix: boolean }
110
- */
111
- function getFixPayload() {
112
- if (hasNvm) {
113
- if (shell === "fish") {
114
- return {
115
- lines: [
116
- "# nvm — install nvm.fish: https://github.com/jorgebucaran/nvm.fish",
117
- ],
118
- description: "nvm init for fish (manual — requires nvm.fish plugin)",
119
- canAutoFix: false,
120
- };
121
- }
122
- const nvmDir = process.env.NVM_DIR || "$HOME/.nvm";
123
- return {
124
- lines: [
125
- "",
126
- "# nvm — load Node version manager (added by unerr postinstall)",
127
- `export NVM_DIR="${nvmDir}"`,
128
- `[ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"`,
129
- ],
130
- description: "nvm init block",
131
- canAutoFix: true,
132
- };
133
- }
134
-
135
- if (hasFnm) {
136
- const initLine = shell === "fish" ? "fnm env | source" : 'eval "$(fnm env)"';
137
- return {
138
- lines: [
139
- "",
140
- "# fnm — fast Node manager (added by unerr postinstall)",
141
- initLine,
142
- ],
143
- description: "fnm env init",
144
- canAutoFix: true,
145
- };
146
- }
147
-
148
- if (hasVolta) {
149
- return {
150
- lines: [
151
- "",
152
- "# volta — JavaScript tool manager (added by unerr postinstall)",
153
- 'export VOLTA_HOME="$HOME/.volta"',
154
- 'export PATH="$VOLTA_HOME/bin:$PATH"',
155
- ],
156
- description: "volta PATH setup",
157
- canAutoFix: true,
158
- };
159
- }
160
-
161
- // Generic: direct PATH export — use $HOME instead of absolute home path for portability
162
- const portableBin = normalizedGlobalBin.startsWith(home)
163
- ? normalizedGlobalBin.replace(home, "$HOME")
164
- : normalizedGlobalBin;
165
- const exportLine =
166
- shell === "fish"
167
- ? `set -gx PATH ${portableBin} $PATH`
168
- : `export PATH="${portableBin}:$PATH"`;
169
-
170
- return {
171
- lines: ["", "# npm global bin (added by unerr postinstall)", exportLine],
172
- description: `PATH export for ${normalizedGlobalBin}`,
173
- canAutoFix: true,
174
- };
175
- }
176
-
177
- function isAlreadyInRc(rcPath, fix) {
178
- if (!existsSync(rcPath)) return false;
179
- const content = readFileSync(rcPath, "utf-8");
180
- const meaningful = fix.lines.filter(
181
- (l) => l.trim() && !l.trim().startsWith("#")
182
- );
183
- return meaningful.some((line) => content.includes(line.trim()));
184
- }
185
-
186
- // ── Interactive prompt ───────────────────────────────────────
187
-
188
- /**
189
- * Prompt for Y/n. Works even when npm pipes stdin by falling back to /dev/tty.
190
- * Returns true (yes), false (no), or null (could not prompt).
191
- */
192
- async function askYesNo(question) {
193
- // Approach 1: stdin is a TTY (direct terminal execution)
194
- if (process.stdin.isTTY) {
195
- return promptFromStream(process.stdin, question);
196
- }
197
-
198
- // Approach 2: /dev/tty fallback (macOS/Linux — works when npm pipes stdin)
199
- if (process.platform !== "win32" && existsSync("/dev/tty")) {
200
- try {
201
- const fd = openSync("/dev/tty", "r");
202
- const ttyStream = createReadStream("", { fd });
203
- const result = await promptFromStream(ttyStream, question);
204
- ttyStream.destroy();
205
- return result;
206
- } catch {
207
- return null;
208
- }
209
- }
210
-
211
- // No interactive input available
212
- return null;
213
- }
214
-
215
- function promptFromStream(input, question) {
216
- return new Promise((resolve) => {
217
- const rl = createInterface({ input, output: process.stderr });
218
-
219
- const timer = setTimeout(() => {
220
- rl.close();
221
- stderr(`\n ${D}(timed out — no changes made)${R}\n`);
222
- resolve(false);
223
- }, 30_000);
224
-
225
- rl.question(question, (answer) => {
226
- clearTimeout(timer);
227
- rl.close();
228
- const a = answer.trim().toLowerCase();
229
- resolve(a === "" || a === "y" || a === "yes");
230
- });
231
- });
232
- }
233
-
234
- // ── Apply fix ────────────────────────────────────────────────
235
-
236
- function applyFix(rcPath, rcName, fix) {
237
- try {
238
- appendFileSync(rcPath, fix.lines.join("\n") + "\n", "utf-8");
239
- stderr(`\n ${G}✓${R} Updated ${C}${rcName}${R}\n`);
240
- stderr(
241
- ` ${G}✓${R} Run ${C}source ${rcName}${R} or open a new terminal.\n\n`
242
- );
243
- } catch (err) {
244
- stderr(`\n ${W}⚠ Could not write to ${rcName}: ${err.message}${R}\n`);
245
- printManualSteps(fix, rcName);
246
- }
247
- }
248
-
249
- function printManualSteps(fix, rcName) {
250
- stderr(`\n ${B}Add the following to ${rcName}:${R}\n\n`);
251
- for (const line of fix.lines) {
252
- if (line.trim()) stderr(` ${C}${line}${R}\n`);
253
- }
254
- stderr(
255
- `\n Then reload: ${C}source ${rcName}${R} or open a new terminal.\n\n`
256
- );
257
- }
258
-
259
- // ── Main ─────────────────────────────────────────────────────
260
-
261
- async function main() {
262
- const fix = getFixPayload();
263
- const rcPath = getRcPath();
264
- const rcName = getRcDisplayName();
265
-
266
- stderr(
267
- `\n${W}⚠ unerr installed, but may not be available in new terminal sessions.${R}\n\n`
268
- );
269
- stderr(` The npm global bin directory is ${B}not on your PATH${R}:\n`);
270
- stderr(` ${C}${normalizedGlobalBin}${R}\n\n`);
271
-
272
- // Already in RC but PATH still broken → likely shell config issue
273
- if (isAlreadyInRc(rcPath, fix)) {
274
- stderr(
275
- ` ${D}The required lines already exist in ${rcName} but PATH still doesn't include the bin dir.${R}\n`
276
- );
277
- stderr(
278
- ` ${D}This may mean ${rcName} isn't being sourced by your terminal.${R}\n`
279
- );
280
- stderr(
281
- ` ${D}Check your terminal app settings, or try: ${C}source ${rcName}${R}\n\n`
282
- );
283
- return;
284
- }
285
-
286
- // Can't auto-fix → print manual instructions only
287
- if (!fix.canAutoFix) {
288
- printManualSteps(fix, rcName);
289
- return;
290
- }
291
-
292
- // Show what we'd add
293
- const preview = fix.lines.filter((l) => l.trim()).join("\n ");
294
- stderr(` ${B}Fix:${R} Append ${fix.description} to ${C}${rcName}${R}\n\n`);
295
- stderr(` ${D}${preview}${R}\n\n`);
296
-
297
- // Ask for consent
298
- const answer = await askYesNo(` Add to ${rcName} now? ${D}[Y/n]${R} `);
299
-
300
- if (answer === true) {
301
- applyFix(rcPath, rcName, fix);
302
- } else if (answer === false) {
303
- stderr(
304
- `\n ${D}No changes made. To fix manually, add the lines above to ${rcName}${R}\n\n`
305
- );
306
- } else {
307
- // null → couldn't prompt, print manual instructions
308
- printManualSteps(fix, rcName);
309
- }
310
- }
311
-
312
- main().catch(() => process.exit(0));