agentinel 1.0.3 → 1.0.5
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 +44 -15
- package/dist/asen.js +217 -127
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -42,11 +42,17 @@ Every other tool in this space guards your terminal. **Agentinel guards your age
|
|
|
42
42
|
|
|
43
43
|
## 🚀 Installation
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
For the best experience across all your projects and to use the short `asen` alias, install Agentinel globally:
|
|
46
46
|
|
|
47
|
+
```sh
|
|
48
|
+
npm install -g agentinel
|
|
49
|
+
asen init
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Alternatively, you can install it as a dev-dependency per-project:
|
|
47
53
|
```sh
|
|
48
54
|
npm install --save-dev agentinel
|
|
49
|
-
npx
|
|
55
|
+
npx agentinel init
|
|
50
56
|
```
|
|
51
57
|
*(No account, no server, no complex configuration.)*
|
|
52
58
|
|
|
@@ -83,7 +89,9 @@ Agentinel provides an opt-in **PATH shim**. By default, running `npx asen init`
|
|
|
83
89
|
npx asen init
|
|
84
90
|
```
|
|
85
91
|
|
|
86
|
-
This puts a tiny, fail-open wrapper script earlier in your `PATH`. When you type `npm install
|
|
92
|
+
This puts a tiny, fail-open wrapper script earlier in your `PATH`. When you type `npm install <pkg>`, the shim checks the package first. If it's safe, the real `npm` command runs instantly.
|
|
93
|
+
|
|
94
|
+
As a bonus, if you just run a plain `npm install` with no arguments, the shim instantly checks your unstaged `package.json` for any newly added dependencies, ensuring that packages you pasted in are scanned before they resolve!
|
|
87
95
|
|
|
88
96
|
**Terminal Example:**
|
|
89
97
|
```bash
|
|
@@ -151,22 +159,43 @@ Agentinel hard-blocks the install and returns an error payload to the agent. The
|
|
|
151
159
|
|
|
152
160
|
## 🧰 Command Reference
|
|
153
161
|
|
|
154
|
-
|
|
162
|
+
You can run Agentinel using `npx agentinel <command>`.
|
|
163
|
+
If you have installed `agentinel` globally (`npm install -g agentinel`) or locally in your project, you can use the shorter alias: `npx asen <command>` (or just `asen <command>` if global).
|
|
164
|
+
|
|
165
|
+
Here are all the commands:
|
|
155
166
|
|
|
156
167
|
### `npx asen init [--no-shim]`
|
|
157
168
|
Wires up agent hooks and git hooks in the current repo, and installs the global PATH shim for human terminal protection.
|
|
158
169
|
```bash
|
|
159
170
|
$ npx asen init
|
|
160
|
-
wrote .agentinel.json
|
|
161
|
-
registered the Claude Code PreToolUse hook in .claude/settings.json
|
|
162
|
-
installed the git pre-commit hook in .git/hooks
|
|
163
|
-
wrote shims for npm, npx, pnpm, yarn, bun in /Users/user/.agentinel/bin
|
|
164
|
-
added the shims to PATH in /Users/user/.zshrc
|
|
165
|
-
Mode is warn, so a risky package typed at the terminal will be reported, not blocked.
|
|
166
|
-
Open a new terminal, or run \`asen unshim\` to undo this.
|
|
167
171
|
|
|
168
|
-
agentinel
|
|
169
|
-
|
|
172
|
+
╭─ agentinel ──────────────────────────────╮
|
|
173
|
+
│ agentinel setup complete │
|
|
174
|
+
│ │
|
|
175
|
+
│ New npm packages will be checked before │
|
|
176
|
+
│ they land. │
|
|
177
|
+
│ │
|
|
178
|
+
│ ✔ wrote .agentinel.json │
|
|
179
|
+
│ ✔ registered the Claude Code PreToolUse │
|
|
180
|
+
│ hook in .claude/settings.json │
|
|
181
|
+
│ ✔ installed the git pre-commit hook in │
|
|
182
|
+
│ .git/hooks │
|
|
183
|
+
│ ✔ wrote shims for npm, npx, pnpm, yarn, │
|
|
184
|
+
│ bun in /Users/user/.agentinel/bin │
|
|
185
|
+
│ ✔ added the shims to PATH in │
|
|
186
|
+
│ /Users/user/.zshrc │
|
|
187
|
+
│ ✔ Open a new terminal, or run `asen │
|
|
188
|
+
│ unshim` to undo this. │
|
|
189
|
+
│ │
|
|
190
|
+
│ Default mode is strict. Set "mode": │
|
|
191
|
+
│ "warn" in .agentinel.json to only warn │
|
|
192
|
+
│ instead. │
|
|
193
|
+
╰──────────────────────────────────────────╯
|
|
194
|
+
|
|
195
|
+
Performance Tip:
|
|
196
|
+
The hook runs on every command, and resolving through npx each time is slow.
|
|
197
|
+
For faster hooks, add it to the repo and run init again:
|
|
198
|
+
npm install --save-dev agentinel && npx asen init
|
|
170
199
|
```
|
|
171
200
|
|
|
172
201
|
When used with `--no-shim`, it wires up hooks but skips installing the global PATH shim.
|
|
@@ -177,11 +206,11 @@ registered the Claude Code PreToolUse hook in .claude/settings.json
|
|
|
177
206
|
installed the git pre-commit hook in .git/hooks
|
|
178
207
|
|
|
179
208
|
agentinel is set up. New npm packages will be checked before they land.
|
|
180
|
-
Default mode is
|
|
209
|
+
Default mode is strict. Set "mode": "warn" in .agentinel.json to only warn instead.
|
|
181
210
|
```
|
|
182
211
|
|
|
183
212
|
### `npx asen check [pkg...]`
|
|
184
|
-
Scans the
|
|
213
|
+
Scans the unstaged (or newly added) dependencies in your working tree, including the lockfile. Exits non-zero if flagged. (The Git pre-commit hook uses a strictly staged version of this check).
|
|
185
214
|
```bash
|
|
186
215
|
$ npx asen check
|
|
187
216
|
checked 142 package(s), nothing suspicious
|
package/dist/asen.js
CHANGED
|
@@ -27,27 +27,68 @@ function readFlag(args, flag) {
|
|
|
27
27
|
|
|
28
28
|
// src/checks/package-guard/staged-deps.ts
|
|
29
29
|
import { execFileSync } from "child_process";
|
|
30
|
+
import { readFileSync, lstatSync } from "fs";
|
|
31
|
+
import { join, resolve, sep } from "path";
|
|
30
32
|
var DEP_FIELDS = ["dependencies", "devDependencies", "optionalDependencies"];
|
|
31
33
|
function newStagedDependencies(repoRoot) {
|
|
32
34
|
const names = [];
|
|
33
|
-
for (const path of
|
|
35
|
+
for (const path of changedManifestPaths(repoRoot, [
|
|
36
|
+
"diff",
|
|
37
|
+
"--cached",
|
|
38
|
+
"--name-only",
|
|
39
|
+
"-z",
|
|
40
|
+
"--diff-filter=ACMR"
|
|
41
|
+
]) ?? []) {
|
|
34
42
|
const staged = gitJson(repoRoot, `:${path}`);
|
|
35
|
-
if (!staged)
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
43
|
+
if (!staged) continue;
|
|
38
44
|
const before = collectNames(gitJson(repoRoot, `HEAD:${path}`) ?? {});
|
|
39
45
|
for (const name of collectNames(staged)) {
|
|
40
|
-
if (!before.has(name) && !names.includes(name))
|
|
41
|
-
|
|
46
|
+
if (!before.has(name) && !names.includes(name)) names.push(name);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return names;
|
|
50
|
+
}
|
|
51
|
+
function newWorkingTreeDependencies(repoRoot) {
|
|
52
|
+
const names = [];
|
|
53
|
+
let paths = changedManifestPaths(repoRoot, [
|
|
54
|
+
"diff",
|
|
55
|
+
"HEAD",
|
|
56
|
+
"--name-only",
|
|
57
|
+
"-z",
|
|
58
|
+
"--diff-filter=ACMR"
|
|
59
|
+
]);
|
|
60
|
+
if (paths === null) {
|
|
61
|
+
paths = changedManifestPaths(repoRoot, ["ls-files", "-z", "-c", "-o", "--exclude-standard"]) ?? [];
|
|
62
|
+
} else {
|
|
63
|
+
const untracked = changedManifestPaths(repoRoot, ["ls-files", "-z", "-o", "--exclude-standard"]) ?? [];
|
|
64
|
+
paths = Array.from(/* @__PURE__ */ new Set([...paths, ...untracked]));
|
|
65
|
+
}
|
|
66
|
+
for (const path of paths) {
|
|
67
|
+
let onDisk = null;
|
|
68
|
+
try {
|
|
69
|
+
const fullPath = resolve(join(repoRoot, path));
|
|
70
|
+
if (!fullPath.startsWith(resolve(repoRoot) + sep) && fullPath !== resolve(repoRoot)) {
|
|
71
|
+
continue;
|
|
42
72
|
}
|
|
73
|
+
if (lstatSync(fullPath).isSymbolicLink()) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
onDisk = JSON.parse(readFileSync(fullPath, "utf8"));
|
|
77
|
+
} catch {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (!onDisk) continue;
|
|
81
|
+
const before = collectNames(gitJson(repoRoot, `HEAD:${path}`) ?? {});
|
|
82
|
+
for (const name of collectNames(onDisk)) {
|
|
83
|
+
if (!before.has(name) && !names.includes(name)) names.push(name);
|
|
43
84
|
}
|
|
44
85
|
}
|
|
45
86
|
return names;
|
|
46
87
|
}
|
|
47
|
-
function
|
|
48
|
-
const output = git(repoRoot,
|
|
88
|
+
function changedManifestPaths(repoRoot, gitArgs) {
|
|
89
|
+
const output = git(repoRoot, gitArgs);
|
|
49
90
|
if (output === null) {
|
|
50
|
-
return
|
|
91
|
+
return null;
|
|
51
92
|
}
|
|
52
93
|
return output.split("\0").filter((path) => path === "package.json" || path.endsWith("/package.json")).filter((path) => !isInsideDependencies(path));
|
|
53
94
|
}
|
|
@@ -96,14 +137,14 @@ function git(cwd, args) {
|
|
|
96
137
|
}
|
|
97
138
|
|
|
98
139
|
// src/config/load.ts
|
|
99
|
-
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
100
|
-
import { join } from "path";
|
|
140
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync } from "fs";
|
|
141
|
+
import { join as join2 } from "path";
|
|
101
142
|
|
|
102
143
|
// src/config/schema.ts
|
|
103
144
|
var CONFIG_FILENAME = ".agentinel.json";
|
|
104
145
|
function defaultConfig() {
|
|
105
146
|
return {
|
|
106
|
-
mode: "
|
|
147
|
+
mode: "strict",
|
|
107
148
|
allow: [
|
|
108
149
|
{
|
|
109
150
|
name: "asen",
|
|
@@ -129,7 +170,7 @@ function parseConfig(raw, warn2 = () => {
|
|
|
129
170
|
config.mode = source.mode;
|
|
130
171
|
} else if (source.mode !== void 0) {
|
|
131
172
|
warn2(
|
|
132
|
-
`unknown mode ${JSON.stringify(source.mode)} in ${CONFIG_FILENAME}, falling back to "
|
|
173
|
+
`unknown mode ${JSON.stringify(source.mode)} in ${CONFIG_FILENAME}, falling back to "strict". Valid modes are "warn" and "strict".`
|
|
133
174
|
);
|
|
134
175
|
}
|
|
135
176
|
if (Array.isArray(source.allow)) {
|
|
@@ -155,7 +196,7 @@ function isAllowlisted(config, name) {
|
|
|
155
196
|
var ConfigError = class extends Error {
|
|
156
197
|
};
|
|
157
198
|
function configPath(repoRoot) {
|
|
158
|
-
return
|
|
199
|
+
return join2(repoRoot, CONFIG_FILENAME);
|
|
159
200
|
}
|
|
160
201
|
function loadConfig(repoRoot) {
|
|
161
202
|
const path = configPath(repoRoot);
|
|
@@ -164,7 +205,7 @@ function loadConfig(repoRoot) {
|
|
|
164
205
|
}
|
|
165
206
|
let text;
|
|
166
207
|
try {
|
|
167
|
-
text =
|
|
208
|
+
text = readFileSync2(path, "utf8");
|
|
168
209
|
} catch (error) {
|
|
169
210
|
throw new ConfigError(`could not read ${CONFIG_FILENAME}: ${describe(error)}`);
|
|
170
211
|
}
|
|
@@ -304,8 +345,8 @@ function describeFailure(error) {
|
|
|
304
345
|
}
|
|
305
346
|
|
|
306
347
|
// src/checks/package-guard/malware.ts
|
|
307
|
-
import { existsSync as existsSync2, readFileSync as
|
|
308
|
-
import { dirname, join as
|
|
348
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
349
|
+
import { dirname, join as join3 } from "path";
|
|
309
350
|
import { fileURLToPath } from "url";
|
|
310
351
|
import { gunzipSync } from "zlib";
|
|
311
352
|
var cache = null;
|
|
@@ -331,7 +372,7 @@ function load() {
|
|
|
331
372
|
return {};
|
|
332
373
|
}
|
|
333
374
|
try {
|
|
334
|
-
const raw = gunzipSync(
|
|
375
|
+
const raw = gunzipSync(readFileSync3(path)).toString("utf8");
|
|
335
376
|
const parsed = JSON.parse(raw);
|
|
336
377
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
337
378
|
} catch {
|
|
@@ -341,9 +382,9 @@ function load() {
|
|
|
341
382
|
function listPath() {
|
|
342
383
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
343
384
|
const candidates = [
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
385
|
+
join3(here, "..", "data", "malware-names.json.gz"),
|
|
386
|
+
join3(here, "..", "..", "data", "malware-names.json.gz"),
|
|
387
|
+
join3(here, "..", "..", "..", "data", "malware-names.json.gz")
|
|
347
388
|
];
|
|
348
389
|
return candidates.find((path) => existsSync2(path)) ?? null;
|
|
349
390
|
}
|
|
@@ -735,15 +776,18 @@ function scanForKnownMalware(tree, config) {
|
|
|
735
776
|
|
|
736
777
|
// src/checks/package-guard/lockfile.ts
|
|
737
778
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
738
|
-
import { existsSync as existsSync3, readFileSync as
|
|
739
|
-
import { join as
|
|
779
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
780
|
+
import { join as join4 } from "path";
|
|
740
781
|
function packagesInLockfile(repoRoot) {
|
|
741
|
-
const path =
|
|
782
|
+
const path = join4(repoRoot, "package-lock.json");
|
|
742
783
|
if (!existsSync3(path)) {
|
|
743
784
|
return [];
|
|
744
785
|
}
|
|
745
786
|
return packagesInLockText(readFileOrNull(path));
|
|
746
787
|
}
|
|
788
|
+
function workingTreeLockfilePackages(repoRoot) {
|
|
789
|
+
return packagesInLockfile(repoRoot);
|
|
790
|
+
}
|
|
747
791
|
function stagedLockfilePackages(repoRoot) {
|
|
748
792
|
let text = null;
|
|
749
793
|
try {
|
|
@@ -778,7 +822,7 @@ function packagesInLockText(text) {
|
|
|
778
822
|
}
|
|
779
823
|
function readFileOrNull(path) {
|
|
780
824
|
try {
|
|
781
|
-
return
|
|
825
|
+
return readFileSync4(path, "utf8");
|
|
782
826
|
} catch {
|
|
783
827
|
return null;
|
|
784
828
|
}
|
|
@@ -818,7 +862,10 @@ function collectFromDependencies(lock, found) {
|
|
|
818
862
|
var RESET = "\x1B[0m";
|
|
819
863
|
var YELLOW = "\x1B[33m";
|
|
820
864
|
var RED = "\x1B[31m";
|
|
865
|
+
var GREEN = "\x1B[32m";
|
|
866
|
+
var CYAN = "\x1B[36m";
|
|
821
867
|
var DIM = "\x1B[2m";
|
|
868
|
+
var BOLD = "\x1B[1m";
|
|
822
869
|
var WIDTH = 46;
|
|
823
870
|
var TITLE = "agentinel";
|
|
824
871
|
function supportsColor(stream = process.stdout) {
|
|
@@ -908,6 +955,39 @@ function draw(b, paint) {
|
|
|
908
955
|
return b.action ? `${box}
|
|
909
956
|
${paint(DIM, b.action)}` : box;
|
|
910
957
|
}
|
|
958
|
+
function drawSuccessBox(heading, body, stream = process.stdout) {
|
|
959
|
+
const inner = WIDTH - 4;
|
|
960
|
+
const text = inner - 1;
|
|
961
|
+
const hasColor = supportsColor(stream);
|
|
962
|
+
const color = hasColor ? GREEN : "";
|
|
963
|
+
const paint = (code, str) => hasColor ? `${code}${str}${RESET}` : str;
|
|
964
|
+
const titlePart = hasColor ? `${BOLD}${TITLE}${RESET}${color}` : TITLE;
|
|
965
|
+
const top = paint(color, `\u256D\u2500 ${titlePart} ${"\u2500".repeat(WIDTH - TITLE.length - 5)}\u256E`);
|
|
966
|
+
const bottom = paint(color, `\u2570${"\u2500".repeat(WIDTH - 2)}\u256F`);
|
|
967
|
+
const rows = [];
|
|
968
|
+
const push = (line, styled) => {
|
|
969
|
+
const edge = paint(color, "\u2502");
|
|
970
|
+
const plainLine = (styled ?? line).replace(/\x1b\[[0-9;]*m/g, "");
|
|
971
|
+
const pad = " ".repeat(Math.max(0, inner - plainLine.length));
|
|
972
|
+
rows.push(`${edge} ${styled ?? line}${pad}${edge}`);
|
|
973
|
+
};
|
|
974
|
+
push(heading, paint(BOLD, heading));
|
|
975
|
+
push("");
|
|
976
|
+
for (const line of body) {
|
|
977
|
+
if (line === "") {
|
|
978
|
+
push("");
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
if (line.includes("\x1B")) {
|
|
982
|
+
push(line, line);
|
|
983
|
+
} else {
|
|
984
|
+
for (const wrapped of wrap(line, text)) {
|
|
985
|
+
push(wrapped);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
return [top, ...rows, bottom].join("\n");
|
|
990
|
+
}
|
|
911
991
|
function wrap(line, width) {
|
|
912
992
|
const lines = [];
|
|
913
993
|
let current = "";
|
|
@@ -981,8 +1061,8 @@ async function runCheck(names) {
|
|
|
981
1061
|
named = names;
|
|
982
1062
|
tree = [];
|
|
983
1063
|
} else {
|
|
984
|
-
named =
|
|
985
|
-
tree =
|
|
1064
|
+
named = newWorkingTreeDependencies(repoRoot);
|
|
1065
|
+
tree = workingTreeLockfilePackages(repoRoot).filter((entry) => !named.includes(entry.name));
|
|
986
1066
|
}
|
|
987
1067
|
if (named.length === 0 && tree.length === 0) {
|
|
988
1068
|
console.log("no new packages to check");
|
|
@@ -1020,9 +1100,9 @@ async function runCheck(names) {
|
|
|
1020
1100
|
|
|
1021
1101
|
// src/commands/init.ts
|
|
1022
1102
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
1023
|
-
import { chmodSync as chmodSync2, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as
|
|
1103
|
+
import { chmodSync as chmodSync2, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
1024
1104
|
import { homedir as homedir2 } from "os";
|
|
1025
|
-
import { isAbsolute, join as
|
|
1105
|
+
import { isAbsolute, join as join6 } from "path";
|
|
1026
1106
|
|
|
1027
1107
|
// src/platform.ts
|
|
1028
1108
|
function isWindows() {
|
|
@@ -1033,9 +1113,9 @@ function npmCommand() {
|
|
|
1033
1113
|
}
|
|
1034
1114
|
|
|
1035
1115
|
// src/commands/shim.ts
|
|
1036
|
-
import { chmodSync, existsSync as existsSync4, mkdirSync, readFileSync as
|
|
1116
|
+
import { chmodSync, existsSync as existsSync4, mkdirSync, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1037
1117
|
import { homedir } from "os";
|
|
1038
|
-
import { join as
|
|
1118
|
+
import { join as join5 } from "path";
|
|
1039
1119
|
var CLIENTS = ["npm", "npx", "pnpm", "yarn", "bun"];
|
|
1040
1120
|
var BLOCK_EXIT_CODE = 2;
|
|
1041
1121
|
var RC_MARKER = "# agentinel";
|
|
@@ -1043,7 +1123,7 @@ function currentTarget() {
|
|
|
1043
1123
|
return { home: homedir(), shell: process.env.SHELL ?? "", platform: process.platform };
|
|
1044
1124
|
}
|
|
1045
1125
|
function shimDirectory(home) {
|
|
1046
|
-
return
|
|
1126
|
+
return join5(home, ".agentinel", "bin");
|
|
1047
1127
|
}
|
|
1048
1128
|
function onWindows(target) {
|
|
1049
1129
|
return target.platform === "win32";
|
|
@@ -1053,33 +1133,29 @@ function shimFileName(client, target) {
|
|
|
1053
1133
|
}
|
|
1054
1134
|
function startupFilePath(target) {
|
|
1055
1135
|
if (onWindows(target)) {
|
|
1056
|
-
return
|
|
1136
|
+
return join5(target.home, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1");
|
|
1057
1137
|
}
|
|
1058
1138
|
if (target.shell.includes("zsh")) {
|
|
1059
|
-
return
|
|
1139
|
+
return join5(target.home, ".zshrc");
|
|
1060
1140
|
}
|
|
1061
1141
|
if (target.shell.includes("bash")) {
|
|
1062
|
-
return
|
|
1142
|
+
return join5(target.home, ".bashrc");
|
|
1063
1143
|
}
|
|
1064
|
-
return
|
|
1144
|
+
return join5(target.home, ".profile");
|
|
1065
1145
|
}
|
|
1066
|
-
function installShim(repoRoot, target = currentTarget()) {
|
|
1146
|
+
function installShim(repoRoot, target = currentTarget(), messages = []) {
|
|
1067
1147
|
const dir = shimDirectory(target.home);
|
|
1068
1148
|
mkdirSync(dir, { recursive: true });
|
|
1069
1149
|
for (const client of CLIENTS) {
|
|
1070
|
-
const path =
|
|
1150
|
+
const path = join5(dir, shimFileName(client, target));
|
|
1071
1151
|
writeFileSync2(path, shimScript(client, target), "utf8");
|
|
1072
1152
|
if (!onWindows(target)) {
|
|
1073
1153
|
chmodSync(path, 493);
|
|
1074
1154
|
}
|
|
1075
1155
|
}
|
|
1076
|
-
|
|
1077
|
-
addPathLine(target);
|
|
1078
|
-
|
|
1079
|
-
console.log(
|
|
1080
|
-
mode === "strict" ? "Mode is strict, so a risky package typed at the terminal will be blocked." : "Mode is warn, so a risky package typed at the terminal will be reported, not blocked."
|
|
1081
|
-
);
|
|
1082
|
-
console.log("Open a new terminal, or run `asen unshim` to undo this.");
|
|
1156
|
+
messages.push(`wrote shims for ${CLIENTS.join(", ")} in ${dir}`);
|
|
1157
|
+
addPathLine(target, messages);
|
|
1158
|
+
messages.push("Open a new terminal, or run `asen unshim` to undo this.");
|
|
1083
1159
|
return 0;
|
|
1084
1160
|
}
|
|
1085
1161
|
function removeShim(target = currentTarget()) {
|
|
@@ -1097,24 +1173,24 @@ function pathLine(target) {
|
|
|
1097
1173
|
return `export PATH="$HOME/.agentinel/bin:$PATH" ${RC_MARKER}
|
|
1098
1174
|
`;
|
|
1099
1175
|
}
|
|
1100
|
-
function addPathLine(target) {
|
|
1176
|
+
function addPathLine(target, messages) {
|
|
1101
1177
|
const path = startupFilePath(target);
|
|
1102
|
-
const existing = existsSync4(path) ?
|
|
1178
|
+
const existing = existsSync4(path) ? readFileSync5(path, "utf8") : "";
|
|
1103
1179
|
if (existing.includes(RC_MARKER)) {
|
|
1104
|
-
|
|
1180
|
+
messages.push(`${path} already puts the shims on PATH, left alone`);
|
|
1105
1181
|
return;
|
|
1106
1182
|
}
|
|
1107
|
-
mkdirSync(
|
|
1183
|
+
mkdirSync(join5(path, ".."), { recursive: true });
|
|
1108
1184
|
const separator = existing === "" || existing.endsWith("\n") ? "" : "\n";
|
|
1109
1185
|
writeFileSync2(path, `${existing}${separator}${pathLine(target)}`, "utf8");
|
|
1110
|
-
|
|
1186
|
+
messages.push(`added the shims to PATH in ${path}`);
|
|
1111
1187
|
}
|
|
1112
1188
|
function removePathLine(target) {
|
|
1113
1189
|
const path = startupFilePath(target);
|
|
1114
1190
|
if (!existsSync4(path)) {
|
|
1115
1191
|
return;
|
|
1116
1192
|
}
|
|
1117
|
-
const lines =
|
|
1193
|
+
const lines = readFileSync5(path, "utf8").split("\n");
|
|
1118
1194
|
const kept = lines.filter((line) => !line.includes(RC_MARKER));
|
|
1119
1195
|
if (kept.length === lines.length) {
|
|
1120
1196
|
return;
|
|
@@ -1224,12 +1300,15 @@ async function runCheckCommand(command) {
|
|
|
1224
1300
|
if (!command) {
|
|
1225
1301
|
return 0;
|
|
1226
1302
|
}
|
|
1227
|
-
const { installs, executes } = parseCommand(command);
|
|
1303
|
+
const { installs, executes, lockfile } = parseCommand(command);
|
|
1304
|
+
const repoRoot = repoRootOrCwd();
|
|
1228
1305
|
const names = [.../* @__PURE__ */ new Set([...installs, ...executes])];
|
|
1229
1306
|
if (names.length === 0) {
|
|
1230
|
-
return 0;
|
|
1307
|
+
if (!lockfile) return 0;
|
|
1308
|
+
const workingTree = newWorkingTreeDependencies(repoRoot);
|
|
1309
|
+
if (workingTree.length === 0) return 0;
|
|
1310
|
+
names.push(...workingTree);
|
|
1231
1311
|
}
|
|
1232
|
-
const repoRoot = repoRootOrCwd();
|
|
1233
1312
|
const config = loadConfig(repoRoot);
|
|
1234
1313
|
const verdicts = await checkPackages(names, config);
|
|
1235
1314
|
for (const verdict of verdicts) {
|
|
@@ -1253,24 +1332,35 @@ var HOOK_MARKER = "agentinel";
|
|
|
1253
1332
|
var HOOK_SUBCOMMAND = "hook claude-code";
|
|
1254
1333
|
function runInit(options = {}) {
|
|
1255
1334
|
const repoRoot = repoRootOrCwd();
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1335
|
+
const messages = [];
|
|
1336
|
+
writeConfig(repoRoot, messages);
|
|
1337
|
+
wireClaudeCodeHook(repoRoot, claudeCodeCommand(repoRoot), messages);
|
|
1338
|
+
wireOtherAgents(repoRoot, agentHookCommand(repoRoot), messages);
|
|
1339
|
+
wirePreCommitHook(repoRoot, gitHookCommand(repoRoot), messages);
|
|
1260
1340
|
if (options.shim) {
|
|
1261
|
-
installShim(repoRoot, options.shimTarget);
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1341
|
+
installShim(repoRoot, options.shimTarget, messages);
|
|
1342
|
+
}
|
|
1343
|
+
const successBox = drawSuccessBox("agentinel setup complete", [
|
|
1344
|
+
"New npm packages will be checked before they land.",
|
|
1345
|
+
"",
|
|
1346
|
+
...messages.map((m) => `\u2714 ${m}`),
|
|
1347
|
+
"",
|
|
1348
|
+
`Default mode is ${GREEN}strict${RESET}.`,
|
|
1349
|
+
'Set "mode": "warn" in .agentinel.json to only warn instead.'
|
|
1350
|
+
]);
|
|
1351
|
+
console.log(`
|
|
1352
|
+
${successBox}`);
|
|
1265
1353
|
if (!hasLocalInstall(repoRoot)) {
|
|
1266
|
-
console.log(
|
|
1267
|
-
|
|
1268
|
-
console.log(
|
|
1354
|
+
console.log(`
|
|
1355
|
+
${CYAN}Performance Tip:${RESET}`);
|
|
1356
|
+
console.log(`The hook runs on every command, and resolving through npx each time is slow.`);
|
|
1357
|
+
console.log(`For faster hooks, add it to the repo and run init again:`);
|
|
1358
|
+
console.log(` ${DIM}npm install --save-dev agentinel && npx asen init${RESET}`);
|
|
1269
1359
|
}
|
|
1270
1360
|
return 0;
|
|
1271
1361
|
}
|
|
1272
1362
|
function hasLocalInstall(repoRoot) {
|
|
1273
|
-
return existsSync5(
|
|
1363
|
+
return existsSync5(join6(repoRoot, "node_modules", ".bin", "asen"));
|
|
1274
1364
|
}
|
|
1275
1365
|
function claudeCodeCommand(repoRoot) {
|
|
1276
1366
|
if (isWindows()) {
|
|
@@ -1281,34 +1371,34 @@ function claudeCodeCommand(repoRoot) {
|
|
|
1281
1371
|
function gitHookCommand(repoRoot) {
|
|
1282
1372
|
return hasLocalInstall(repoRoot) ? "./node_modules/.bin/asen" : "npx agentinel";
|
|
1283
1373
|
}
|
|
1284
|
-
function writeConfig(repoRoot) {
|
|
1374
|
+
function writeConfig(repoRoot, messages) {
|
|
1285
1375
|
const path = configPath(repoRoot);
|
|
1286
1376
|
if (existsSync5(path)) {
|
|
1287
|
-
|
|
1377
|
+
messages.push(".agentinel.json already exists, left alone");
|
|
1288
1378
|
return;
|
|
1289
1379
|
}
|
|
1290
1380
|
saveConfig(repoRoot, defaultConfig());
|
|
1291
|
-
|
|
1381
|
+
messages.push("wrote .agentinel.json");
|
|
1292
1382
|
}
|
|
1293
|
-
function wireClaudeCodeHook(repoRoot, command) {
|
|
1294
|
-
const dir =
|
|
1295
|
-
const path =
|
|
1383
|
+
function wireClaudeCodeHook(repoRoot, command, messages) {
|
|
1384
|
+
const dir = join6(repoRoot, ".claude");
|
|
1385
|
+
const path = join6(dir, "settings.json");
|
|
1296
1386
|
let settings = {};
|
|
1297
1387
|
if (existsSync5(path)) {
|
|
1298
1388
|
try {
|
|
1299
|
-
const parsed = JSON.parse(
|
|
1389
|
+
const parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
1300
1390
|
if (typeof parsed === "object" && parsed !== null) {
|
|
1301
1391
|
settings = parsed;
|
|
1302
1392
|
}
|
|
1303
1393
|
} catch {
|
|
1304
|
-
|
|
1394
|
+
messages.push(".claude/settings.json is not valid JSON, skipping the Claude Code hook");
|
|
1305
1395
|
return;
|
|
1306
1396
|
}
|
|
1307
1397
|
}
|
|
1308
1398
|
const hooks = asRecord2(settings.hooks) ?? {};
|
|
1309
1399
|
const preToolUse = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
|
|
1310
1400
|
if (alreadyRegistered(preToolUse)) {
|
|
1311
|
-
|
|
1401
|
+
messages.push("Claude Code hook already registered, left alone");
|
|
1312
1402
|
return;
|
|
1313
1403
|
}
|
|
1314
1404
|
preToolUse.push({
|
|
@@ -1319,7 +1409,7 @@ function wireClaudeCodeHook(repoRoot, command) {
|
|
|
1319
1409
|
settings.hooks = hooks;
|
|
1320
1410
|
mkdirSync2(dir, { recursive: true });
|
|
1321
1411
|
writeFileSync3(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
1322
|
-
|
|
1412
|
+
messages.push("registered the Claude Code PreToolUse hook in .claude/settings.json");
|
|
1323
1413
|
}
|
|
1324
1414
|
function agentHookCommand(repoRoot) {
|
|
1325
1415
|
if (isWindows()) {
|
|
@@ -1327,31 +1417,31 @@ function agentHookCommand(repoRoot) {
|
|
|
1327
1417
|
}
|
|
1328
1418
|
return hasLocalInstall(repoRoot) ? '"$(git rev-parse --show-toplevel)"/node_modules/.bin/asen' : "npx agentinel";
|
|
1329
1419
|
}
|
|
1330
|
-
function wireOtherAgents(repoRoot, command) {
|
|
1420
|
+
function wireOtherAgents(repoRoot, command, messages) {
|
|
1331
1421
|
if (uses(repoRoot, ".codex")) {
|
|
1332
|
-
wireCodexHook(repoRoot, command);
|
|
1422
|
+
wireCodexHook(repoRoot, command, messages);
|
|
1333
1423
|
}
|
|
1334
|
-
if (uses(repoRoot, ".copilot") || existsSync5(
|
|
1335
|
-
wireCopilotHook(repoRoot, command);
|
|
1424
|
+
if (uses(repoRoot, ".copilot") || existsSync5(join6(repoRoot, ".github", "hooks"))) {
|
|
1425
|
+
wireCopilotHook(repoRoot, command, messages);
|
|
1336
1426
|
}
|
|
1337
1427
|
if (uses(repoRoot, ".gemini")) {
|
|
1338
|
-
wireGeminiHook(repoRoot, command);
|
|
1428
|
+
wireGeminiHook(repoRoot, command, messages);
|
|
1339
1429
|
}
|
|
1340
1430
|
}
|
|
1341
1431
|
function uses(repoRoot, dir) {
|
|
1342
|
-
return existsSync5(
|
|
1432
|
+
return existsSync5(join6(repoRoot, dir)) || existsSync5(join6(homedir2(), dir));
|
|
1343
1433
|
}
|
|
1344
|
-
function wireCodexHook(repoRoot, command) {
|
|
1345
|
-
const path =
|
|
1434
|
+
function wireCodexHook(repoRoot, command, messages) {
|
|
1435
|
+
const path = join6(repoRoot, ".codex", "hooks.json");
|
|
1346
1436
|
const file = readJson(path);
|
|
1347
1437
|
if (file === null) {
|
|
1348
|
-
|
|
1438
|
+
messages.push(".codex/hooks.json is not valid JSON, skipping the Codex hook");
|
|
1349
1439
|
return;
|
|
1350
1440
|
}
|
|
1351
1441
|
const hooks = asRecord2(file.hooks) ?? {};
|
|
1352
1442
|
const preToolUse = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
|
|
1353
1443
|
if (registers(preToolUse, "codex")) {
|
|
1354
|
-
|
|
1444
|
+
messages.push("Codex hook already registered, left alone");
|
|
1355
1445
|
return;
|
|
1356
1446
|
}
|
|
1357
1447
|
preToolUse.push({
|
|
@@ -1361,19 +1451,19 @@ function wireCodexHook(repoRoot, command) {
|
|
|
1361
1451
|
hooks.PreToolUse = preToolUse;
|
|
1362
1452
|
file.hooks = hooks;
|
|
1363
1453
|
writeJson(path, file);
|
|
1364
|
-
|
|
1454
|
+
messages.push("registered the Codex PreToolUse hook in .codex/hooks.json");
|
|
1365
1455
|
}
|
|
1366
|
-
function wireCopilotHook(repoRoot, command) {
|
|
1367
|
-
const path =
|
|
1456
|
+
function wireCopilotHook(repoRoot, command, messages) {
|
|
1457
|
+
const path = join6(repoRoot, ".github", "hooks", "agentinel.json");
|
|
1368
1458
|
const file = readJson(path);
|
|
1369
1459
|
if (file === null) {
|
|
1370
|
-
|
|
1460
|
+
messages.push(".github/hooks/agentinel.json is not valid JSON, skipping the Copilot hook");
|
|
1371
1461
|
return;
|
|
1372
1462
|
}
|
|
1373
1463
|
const hooks = asRecord2(file.hooks) ?? {};
|
|
1374
1464
|
const preToolUse = Array.isArray(hooks.preToolUse) ? hooks.preToolUse : [];
|
|
1375
1465
|
if (registers(preToolUse, "copilot")) {
|
|
1376
|
-
|
|
1466
|
+
messages.push("Copilot hook already registered, left alone");
|
|
1377
1467
|
return;
|
|
1378
1468
|
}
|
|
1379
1469
|
preToolUse.push({ type: "command", matcher: "bash", bash: `${command} hook copilot` });
|
|
@@ -1381,19 +1471,19 @@ function wireCopilotHook(repoRoot, command) {
|
|
|
1381
1471
|
file.version = 1;
|
|
1382
1472
|
file.hooks = hooks;
|
|
1383
1473
|
writeJson(path, file);
|
|
1384
|
-
|
|
1474
|
+
messages.push("registered the Copilot preToolUse hook in .github/hooks/agentinel.json");
|
|
1385
1475
|
}
|
|
1386
|
-
function wireGeminiHook(repoRoot, command) {
|
|
1387
|
-
const path =
|
|
1476
|
+
function wireGeminiHook(repoRoot, command, messages) {
|
|
1477
|
+
const path = join6(repoRoot, ".gemini", "settings.json");
|
|
1388
1478
|
const file = readJson(path);
|
|
1389
1479
|
if (file === null) {
|
|
1390
|
-
|
|
1480
|
+
messages.push(".gemini/settings.json is not valid JSON, skipping the Gemini hook");
|
|
1391
1481
|
return;
|
|
1392
1482
|
}
|
|
1393
1483
|
const hooks = asRecord2(file.hooks) ?? {};
|
|
1394
1484
|
const beforeTool = Array.isArray(hooks.BeforeTool) ? hooks.BeforeTool : [];
|
|
1395
1485
|
if (registers(beforeTool, "gemini")) {
|
|
1396
|
-
|
|
1486
|
+
messages.push("Gemini hook already registered, left alone");
|
|
1397
1487
|
return;
|
|
1398
1488
|
}
|
|
1399
1489
|
beforeTool.push({
|
|
@@ -1403,20 +1493,20 @@ function wireGeminiHook(repoRoot, command) {
|
|
|
1403
1493
|
hooks.BeforeTool = beforeTool;
|
|
1404
1494
|
file.hooks = hooks;
|
|
1405
1495
|
writeJson(path, file);
|
|
1406
|
-
|
|
1496
|
+
messages.push("registered the Gemini BeforeTool hook in .gemini/settings.json");
|
|
1407
1497
|
}
|
|
1408
1498
|
function readJson(path) {
|
|
1409
1499
|
if (!existsSync5(path)) {
|
|
1410
1500
|
return {};
|
|
1411
1501
|
}
|
|
1412
1502
|
try {
|
|
1413
|
-
return asRecord2(JSON.parse(
|
|
1503
|
+
return asRecord2(JSON.parse(readFileSync6(path, "utf8"))) ?? {};
|
|
1414
1504
|
} catch {
|
|
1415
1505
|
return null;
|
|
1416
1506
|
}
|
|
1417
1507
|
}
|
|
1418
1508
|
function writeJson(path, value) {
|
|
1419
|
-
mkdirSync2(
|
|
1509
|
+
mkdirSync2(join6(path, ".."), { recursive: true });
|
|
1420
1510
|
writeFileSync3(path, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
1421
1511
|
}
|
|
1422
1512
|
function registers(entries, kind) {
|
|
@@ -1436,40 +1526,40 @@ function git2(repoRoot, args) {
|
|
|
1436
1526
|
function hooksDirectory(repoRoot) {
|
|
1437
1527
|
const configured = git2(repoRoot, ["config", "--get", "core.hooksPath"]);
|
|
1438
1528
|
if (configured) {
|
|
1439
|
-
return isAbsolute(configured) ? configured :
|
|
1529
|
+
return isAbsolute(configured) ? configured : join6(repoRoot, configured);
|
|
1440
1530
|
}
|
|
1441
1531
|
const path = git2(repoRoot, ["rev-parse", "--git-path", "hooks"]);
|
|
1442
1532
|
if (path) {
|
|
1443
|
-
return isAbsolute(path) ? path :
|
|
1533
|
+
return isAbsolute(path) ? path : join6(repoRoot, path);
|
|
1444
1534
|
}
|
|
1445
|
-
return
|
|
1535
|
+
return join6(repoRoot, ".git", "hooks");
|
|
1446
1536
|
}
|
|
1447
|
-
function wirePreCommitHook(repoRoot, command) {
|
|
1448
|
-
if (!existsSync5(
|
|
1449
|
-
|
|
1537
|
+
function wirePreCommitHook(repoRoot, command, messages) {
|
|
1538
|
+
if (!existsSync5(join6(repoRoot, ".git"))) {
|
|
1539
|
+
messages.push("not a git repo, skipping the pre-commit hook");
|
|
1450
1540
|
return;
|
|
1451
1541
|
}
|
|
1452
1542
|
const dir = hooksDirectory(repoRoot);
|
|
1453
|
-
const path =
|
|
1543
|
+
const path = join6(dir, "pre-commit");
|
|
1454
1544
|
const script = `#!/bin/sh
|
|
1455
1545
|
# ${HOOK_MARKER}: check newly added npm packages before they get committed
|
|
1456
1546
|
${command} hook pre-commit
|
|
1457
1547
|
`;
|
|
1458
1548
|
if (existsSync5(path)) {
|
|
1459
|
-
const existing =
|
|
1549
|
+
const existing = readFileSync6(path, "utf8");
|
|
1460
1550
|
if (existing.includes(HOOK_MARKER)) {
|
|
1461
|
-
|
|
1551
|
+
messages.push("pre-commit hook already installed, left alone");
|
|
1462
1552
|
return;
|
|
1463
1553
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1554
|
+
messages.push("a pre-commit hook already exists, not overwriting it");
|
|
1555
|
+
messages.push(`add this line to ${path}:
|
|
1466
1556
|
${command} hook pre-commit`);
|
|
1467
1557
|
return;
|
|
1468
1558
|
}
|
|
1469
1559
|
mkdirSync2(dir, { recursive: true });
|
|
1470
1560
|
writeFileSync3(path, script, "utf8");
|
|
1471
1561
|
chmodSync2(path, 493);
|
|
1472
|
-
|
|
1562
|
+
messages.push(`installed the git pre-commit hook in ${dir}`);
|
|
1473
1563
|
}
|
|
1474
1564
|
function alreadyRegistered(preToolUse) {
|
|
1475
1565
|
return JSON.stringify(preToolUse).includes(`${HOOK_SUBCOMMAND}`);
|
|
@@ -1497,8 +1587,8 @@ function runMode(targetMode) {
|
|
|
1497
1587
|
}
|
|
1498
1588
|
|
|
1499
1589
|
// src/commands/uninstall.ts
|
|
1500
|
-
import { existsSync as existsSync6, readFileSync as
|
|
1501
|
-
import { join as
|
|
1590
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1591
|
+
import { join as join7 } from "path";
|
|
1502
1592
|
var HOOK_MARKER2 = "agentinel";
|
|
1503
1593
|
var HOOK_SUBCOMMAND2 = "hook claude-code";
|
|
1504
1594
|
function runUninstall() {
|
|
@@ -1513,7 +1603,7 @@ function runUninstall() {
|
|
|
1513
1603
|
return 0;
|
|
1514
1604
|
}
|
|
1515
1605
|
function unwireClaudeCodeHook(repoRoot) {
|
|
1516
|
-
const path =
|
|
1606
|
+
const path = join7(repoRoot, ".claude", "settings.json");
|
|
1517
1607
|
if (!existsSync6(path)) return;
|
|
1518
1608
|
const file = readJson2(path);
|
|
1519
1609
|
if (file === null) return;
|
|
@@ -1531,7 +1621,7 @@ function unwireClaudeCodeHook(repoRoot) {
|
|
|
1531
1621
|
console.log("removed Claude Code hook");
|
|
1532
1622
|
}
|
|
1533
1623
|
function unwireCodexHook(repoRoot) {
|
|
1534
|
-
const path =
|
|
1624
|
+
const path = join7(repoRoot, ".codex", "hooks.json");
|
|
1535
1625
|
if (!existsSync6(path)) return;
|
|
1536
1626
|
const file = readJson2(path);
|
|
1537
1627
|
if (file === null) return;
|
|
@@ -1543,7 +1633,7 @@ function unwireCodexHook(repoRoot) {
|
|
|
1543
1633
|
console.log("removed Codex hook");
|
|
1544
1634
|
}
|
|
1545
1635
|
function unwireCopilotHook(repoRoot) {
|
|
1546
|
-
const path =
|
|
1636
|
+
const path = join7(repoRoot, ".github", "hooks", "agentinel.json");
|
|
1547
1637
|
if (!existsSync6(path)) return;
|
|
1548
1638
|
const file = readJson2(path);
|
|
1549
1639
|
if (file === null) return;
|
|
@@ -1555,7 +1645,7 @@ function unwireCopilotHook(repoRoot) {
|
|
|
1555
1645
|
console.log("removed Copilot hook");
|
|
1556
1646
|
}
|
|
1557
1647
|
function unwireGeminiHook(repoRoot) {
|
|
1558
|
-
const path =
|
|
1648
|
+
const path = join7(repoRoot, ".gemini", "settings.json");
|
|
1559
1649
|
if (!existsSync6(path)) return;
|
|
1560
1650
|
const file = readJson2(path);
|
|
1561
1651
|
if (file === null) return;
|
|
@@ -1567,17 +1657,17 @@ function unwireGeminiHook(repoRoot) {
|
|
|
1567
1657
|
console.log("removed Gemini hook");
|
|
1568
1658
|
}
|
|
1569
1659
|
function unwirePreCommitHook(repoRoot) {
|
|
1570
|
-
if (!existsSync6(
|
|
1571
|
-
const path =
|
|
1660
|
+
if (!existsSync6(join7(repoRoot, ".git"))) return;
|
|
1661
|
+
const path = join7(hooksDirectory(repoRoot), "pre-commit");
|
|
1572
1662
|
if (!existsSync6(path)) return;
|
|
1573
|
-
const existing =
|
|
1663
|
+
const existing = readFileSync7(path, "utf8");
|
|
1574
1664
|
if (!existing.includes(HOOK_MARKER2)) return;
|
|
1575
1665
|
rmSync2(path);
|
|
1576
1666
|
console.log("removed git pre-commit hook");
|
|
1577
1667
|
}
|
|
1578
1668
|
function readJson2(path) {
|
|
1579
1669
|
try {
|
|
1580
|
-
return asRecord3(JSON.parse(
|
|
1670
|
+
return asRecord3(JSON.parse(readFileSync7(path, "utf8"))) ?? {};
|
|
1581
1671
|
} catch {
|
|
1582
1672
|
return null;
|
|
1583
1673
|
}
|
|
@@ -1591,7 +1681,7 @@ function asRecord3(value) {
|
|
|
1591
1681
|
|
|
1592
1682
|
// src/hooks/claude-code.ts
|
|
1593
1683
|
import { existsSync as existsSync7 } from "fs";
|
|
1594
|
-
import { join as
|
|
1684
|
+
import { join as join8 } from "path";
|
|
1595
1685
|
|
|
1596
1686
|
// src/checks/package-guard/resolve.ts
|
|
1597
1687
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
@@ -1675,7 +1765,7 @@ function candidatesFor(command, repoRoot) {
|
|
|
1675
1765
|
}
|
|
1676
1766
|
function isLocalTool(repoRoot, name) {
|
|
1677
1767
|
const binary = name.includes("/") ? name.split("/").pop() : name;
|
|
1678
|
-
return existsSync7(
|
|
1768
|
+
return existsSync7(join8(repoRoot, "node_modules", ".bin", binary));
|
|
1679
1769
|
}
|
|
1680
1770
|
function warn(verdicts) {
|
|
1681
1771
|
const summary = plainSummary(verdicts);
|
|
@@ -1889,14 +1979,14 @@ async function runPreCommitHook() {
|
|
|
1889
1979
|
}
|
|
1890
1980
|
|
|
1891
1981
|
// bin/asen.ts
|
|
1892
|
-
import { readFileSync as
|
|
1893
|
-
import { dirname as dirname2, join as
|
|
1982
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
1983
|
+
import { dirname as dirname2, join as join9 } from "path";
|
|
1894
1984
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1895
1985
|
function getVersion() {
|
|
1896
1986
|
try {
|
|
1897
1987
|
const file = fileURLToPath2(import.meta.url);
|
|
1898
|
-
const pkgPath =
|
|
1899
|
-
const pkg = JSON.parse(
|
|
1988
|
+
const pkgPath = join9(dirname2(file), "..", "package.json");
|
|
1989
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
1900
1990
|
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
1901
1991
|
} catch {
|
|
1902
1992
|
return "unknown";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentinel",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Catches AI hallucinated npm packages before an agent installs them",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Aman Janwani",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
},
|
|
15
15
|
"homepage": "https://github.com/aman-janwani/agentinel#readme",
|
|
16
16
|
"bin": {
|
|
17
|
-
"asen": "./dist/asen.js"
|
|
17
|
+
"asen": "./dist/asen.js",
|
|
18
|
+
"agentinel": "./dist/asen.js"
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"dist",
|