agentinel 1.0.3 → 1.0.4
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 +17 -8
- package/dist/asen.js +120 -74
- 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
|
|
|
@@ -151,7 +157,10 @@ Agentinel hard-blocks the install and returns an error payload to the agent. The
|
|
|
151
157
|
|
|
152
158
|
## 🧰 Command Reference
|
|
153
159
|
|
|
154
|
-
|
|
160
|
+
You can run Agentinel using `npx agentinel <command>`.
|
|
161
|
+
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).
|
|
162
|
+
|
|
163
|
+
Here are all the commands:
|
|
155
164
|
|
|
156
165
|
### `npx asen init [--no-shim]`
|
|
157
166
|
Wires up agent hooks and git hooks in the current repo, and installs the global PATH shim for human terminal protection.
|
|
@@ -162,11 +171,11 @@ registered the Claude Code PreToolUse hook in .claude/settings.json
|
|
|
162
171
|
installed the git pre-commit hook in .git/hooks
|
|
163
172
|
wrote shims for npm, npx, pnpm, yarn, bun in /Users/user/.agentinel/bin
|
|
164
173
|
added the shims to PATH in /Users/user/.zshrc
|
|
165
|
-
Mode is
|
|
166
|
-
Open a new terminal, or run
|
|
174
|
+
Mode is strict, so a risky package typed at the terminal will be blocked.
|
|
175
|
+
Open a new terminal, or run `asen unshim` to undo this.
|
|
167
176
|
|
|
168
177
|
agentinel is set up. New npm packages will be checked before they land.
|
|
169
|
-
Default mode is
|
|
178
|
+
Default mode is strict. Set "mode": "warn" in .agentinel.json to only warn instead.
|
|
170
179
|
```
|
|
171
180
|
|
|
172
181
|
When used with `--no-shim`, it wires up hooks but skips installing the global PATH shim.
|
|
@@ -177,11 +186,11 @@ registered the Claude Code PreToolUse hook in .claude/settings.json
|
|
|
177
186
|
installed the git pre-commit hook in .git/hooks
|
|
178
187
|
|
|
179
188
|
agentinel is set up. New npm packages will be checked before they land.
|
|
180
|
-
Default mode is
|
|
189
|
+
Default mode is strict. Set "mode": "warn" in .agentinel.json to only warn instead.
|
|
181
190
|
```
|
|
182
191
|
|
|
183
192
|
### `npx asen check [pkg...]`
|
|
184
|
-
Scans the
|
|
193
|
+
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
194
|
```bash
|
|
186
195
|
$ npx asen check
|
|
187
196
|
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
|
}
|
|
@@ -981,8 +1025,8 @@ async function runCheck(names) {
|
|
|
981
1025
|
named = names;
|
|
982
1026
|
tree = [];
|
|
983
1027
|
} else {
|
|
984
|
-
named =
|
|
985
|
-
tree =
|
|
1028
|
+
named = newWorkingTreeDependencies(repoRoot);
|
|
1029
|
+
tree = workingTreeLockfilePackages(repoRoot).filter((entry) => !named.includes(entry.name));
|
|
986
1030
|
}
|
|
987
1031
|
if (named.length === 0 && tree.length === 0) {
|
|
988
1032
|
console.log("no new packages to check");
|
|
@@ -1020,9 +1064,9 @@ async function runCheck(names) {
|
|
|
1020
1064
|
|
|
1021
1065
|
// src/commands/init.ts
|
|
1022
1066
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
1023
|
-
import { chmodSync as chmodSync2, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as
|
|
1067
|
+
import { chmodSync as chmodSync2, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
1024
1068
|
import { homedir as homedir2 } from "os";
|
|
1025
|
-
import { isAbsolute, join as
|
|
1069
|
+
import { isAbsolute, join as join6 } from "path";
|
|
1026
1070
|
|
|
1027
1071
|
// src/platform.ts
|
|
1028
1072
|
function isWindows() {
|
|
@@ -1033,9 +1077,9 @@ function npmCommand() {
|
|
|
1033
1077
|
}
|
|
1034
1078
|
|
|
1035
1079
|
// src/commands/shim.ts
|
|
1036
|
-
import { chmodSync, existsSync as existsSync4, mkdirSync, readFileSync as
|
|
1080
|
+
import { chmodSync, existsSync as existsSync4, mkdirSync, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1037
1081
|
import { homedir } from "os";
|
|
1038
|
-
import { join as
|
|
1082
|
+
import { join as join5 } from "path";
|
|
1039
1083
|
var CLIENTS = ["npm", "npx", "pnpm", "yarn", "bun"];
|
|
1040
1084
|
var BLOCK_EXIT_CODE = 2;
|
|
1041
1085
|
var RC_MARKER = "# agentinel";
|
|
@@ -1043,7 +1087,7 @@ function currentTarget() {
|
|
|
1043
1087
|
return { home: homedir(), shell: process.env.SHELL ?? "", platform: process.platform };
|
|
1044
1088
|
}
|
|
1045
1089
|
function shimDirectory(home) {
|
|
1046
|
-
return
|
|
1090
|
+
return join5(home, ".agentinel", "bin");
|
|
1047
1091
|
}
|
|
1048
1092
|
function onWindows(target) {
|
|
1049
1093
|
return target.platform === "win32";
|
|
@@ -1053,21 +1097,21 @@ function shimFileName(client, target) {
|
|
|
1053
1097
|
}
|
|
1054
1098
|
function startupFilePath(target) {
|
|
1055
1099
|
if (onWindows(target)) {
|
|
1056
|
-
return
|
|
1100
|
+
return join5(target.home, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1");
|
|
1057
1101
|
}
|
|
1058
1102
|
if (target.shell.includes("zsh")) {
|
|
1059
|
-
return
|
|
1103
|
+
return join5(target.home, ".zshrc");
|
|
1060
1104
|
}
|
|
1061
1105
|
if (target.shell.includes("bash")) {
|
|
1062
|
-
return
|
|
1106
|
+
return join5(target.home, ".bashrc");
|
|
1063
1107
|
}
|
|
1064
|
-
return
|
|
1108
|
+
return join5(target.home, ".profile");
|
|
1065
1109
|
}
|
|
1066
1110
|
function installShim(repoRoot, target = currentTarget()) {
|
|
1067
1111
|
const dir = shimDirectory(target.home);
|
|
1068
1112
|
mkdirSync(dir, { recursive: true });
|
|
1069
1113
|
for (const client of CLIENTS) {
|
|
1070
|
-
const path =
|
|
1114
|
+
const path = join5(dir, shimFileName(client, target));
|
|
1071
1115
|
writeFileSync2(path, shimScript(client, target), "utf8");
|
|
1072
1116
|
if (!onWindows(target)) {
|
|
1073
1117
|
chmodSync(path, 493);
|
|
@@ -1099,12 +1143,12 @@ function pathLine(target) {
|
|
|
1099
1143
|
}
|
|
1100
1144
|
function addPathLine(target) {
|
|
1101
1145
|
const path = startupFilePath(target);
|
|
1102
|
-
const existing = existsSync4(path) ?
|
|
1146
|
+
const existing = existsSync4(path) ? readFileSync5(path, "utf8") : "";
|
|
1103
1147
|
if (existing.includes(RC_MARKER)) {
|
|
1104
1148
|
console.log(`${path} already puts the shims on PATH, left alone`);
|
|
1105
1149
|
return;
|
|
1106
1150
|
}
|
|
1107
|
-
mkdirSync(
|
|
1151
|
+
mkdirSync(join5(path, ".."), { recursive: true });
|
|
1108
1152
|
const separator = existing === "" || existing.endsWith("\n") ? "" : "\n";
|
|
1109
1153
|
writeFileSync2(path, `${existing}${separator}${pathLine(target)}`, "utf8");
|
|
1110
1154
|
console.log(`added the shims to PATH in ${path}`);
|
|
@@ -1114,7 +1158,7 @@ function removePathLine(target) {
|
|
|
1114
1158
|
if (!existsSync4(path)) {
|
|
1115
1159
|
return;
|
|
1116
1160
|
}
|
|
1117
|
-
const lines =
|
|
1161
|
+
const lines = readFileSync5(path, "utf8").split("\n");
|
|
1118
1162
|
const kept = lines.filter((line) => !line.includes(RC_MARKER));
|
|
1119
1163
|
if (kept.length === lines.length) {
|
|
1120
1164
|
return;
|
|
@@ -1261,7 +1305,9 @@ function runInit(options = {}) {
|
|
|
1261
1305
|
installShim(repoRoot, options.shimTarget);
|
|
1262
1306
|
}
|
|
1263
1307
|
console.log("\nagentinel is set up. New npm packages will be checked before they land.");
|
|
1264
|
-
console.log(
|
|
1308
|
+
console.log(
|
|
1309
|
+
'Default mode is strict. Set "mode": "warn" in .agentinel.json to only warn instead.'
|
|
1310
|
+
);
|
|
1265
1311
|
if (!hasLocalInstall(repoRoot)) {
|
|
1266
1312
|
console.log("\nThe hook runs on every command, and going through npx each time is slow.");
|
|
1267
1313
|
console.log("For faster hooks, add it to the repo and run init again:");
|
|
@@ -1270,7 +1316,7 @@ function runInit(options = {}) {
|
|
|
1270
1316
|
return 0;
|
|
1271
1317
|
}
|
|
1272
1318
|
function hasLocalInstall(repoRoot) {
|
|
1273
|
-
return existsSync5(
|
|
1319
|
+
return existsSync5(join6(repoRoot, "node_modules", ".bin", "asen"));
|
|
1274
1320
|
}
|
|
1275
1321
|
function claudeCodeCommand(repoRoot) {
|
|
1276
1322
|
if (isWindows()) {
|
|
@@ -1291,12 +1337,12 @@ function writeConfig(repoRoot) {
|
|
|
1291
1337
|
console.log("wrote .agentinel.json");
|
|
1292
1338
|
}
|
|
1293
1339
|
function wireClaudeCodeHook(repoRoot, command) {
|
|
1294
|
-
const dir =
|
|
1295
|
-
const path =
|
|
1340
|
+
const dir = join6(repoRoot, ".claude");
|
|
1341
|
+
const path = join6(dir, "settings.json");
|
|
1296
1342
|
let settings = {};
|
|
1297
1343
|
if (existsSync5(path)) {
|
|
1298
1344
|
try {
|
|
1299
|
-
const parsed = JSON.parse(
|
|
1345
|
+
const parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
1300
1346
|
if (typeof parsed === "object" && parsed !== null) {
|
|
1301
1347
|
settings = parsed;
|
|
1302
1348
|
}
|
|
@@ -1331,7 +1377,7 @@ function wireOtherAgents(repoRoot, command) {
|
|
|
1331
1377
|
if (uses(repoRoot, ".codex")) {
|
|
1332
1378
|
wireCodexHook(repoRoot, command);
|
|
1333
1379
|
}
|
|
1334
|
-
if (uses(repoRoot, ".copilot") || existsSync5(
|
|
1380
|
+
if (uses(repoRoot, ".copilot") || existsSync5(join6(repoRoot, ".github", "hooks"))) {
|
|
1335
1381
|
wireCopilotHook(repoRoot, command);
|
|
1336
1382
|
}
|
|
1337
1383
|
if (uses(repoRoot, ".gemini")) {
|
|
@@ -1339,10 +1385,10 @@ function wireOtherAgents(repoRoot, command) {
|
|
|
1339
1385
|
}
|
|
1340
1386
|
}
|
|
1341
1387
|
function uses(repoRoot, dir) {
|
|
1342
|
-
return existsSync5(
|
|
1388
|
+
return existsSync5(join6(repoRoot, dir)) || existsSync5(join6(homedir2(), dir));
|
|
1343
1389
|
}
|
|
1344
1390
|
function wireCodexHook(repoRoot, command) {
|
|
1345
|
-
const path =
|
|
1391
|
+
const path = join6(repoRoot, ".codex", "hooks.json");
|
|
1346
1392
|
const file = readJson(path);
|
|
1347
1393
|
if (file === null) {
|
|
1348
1394
|
console.log(".codex/hooks.json is not valid JSON, skipping the Codex hook");
|
|
@@ -1364,7 +1410,7 @@ function wireCodexHook(repoRoot, command) {
|
|
|
1364
1410
|
console.log("registered the Codex PreToolUse hook in .codex/hooks.json");
|
|
1365
1411
|
}
|
|
1366
1412
|
function wireCopilotHook(repoRoot, command) {
|
|
1367
|
-
const path =
|
|
1413
|
+
const path = join6(repoRoot, ".github", "hooks", "agentinel.json");
|
|
1368
1414
|
const file = readJson(path);
|
|
1369
1415
|
if (file === null) {
|
|
1370
1416
|
console.log(".github/hooks/agentinel.json is not valid JSON, skipping the Copilot hook");
|
|
@@ -1384,7 +1430,7 @@ function wireCopilotHook(repoRoot, command) {
|
|
|
1384
1430
|
console.log("registered the Copilot preToolUse hook in .github/hooks/agentinel.json");
|
|
1385
1431
|
}
|
|
1386
1432
|
function wireGeminiHook(repoRoot, command) {
|
|
1387
|
-
const path =
|
|
1433
|
+
const path = join6(repoRoot, ".gemini", "settings.json");
|
|
1388
1434
|
const file = readJson(path);
|
|
1389
1435
|
if (file === null) {
|
|
1390
1436
|
console.log(".gemini/settings.json is not valid JSON, skipping the Gemini hook");
|
|
@@ -1410,13 +1456,13 @@ function readJson(path) {
|
|
|
1410
1456
|
return {};
|
|
1411
1457
|
}
|
|
1412
1458
|
try {
|
|
1413
|
-
return asRecord2(JSON.parse(
|
|
1459
|
+
return asRecord2(JSON.parse(readFileSync6(path, "utf8"))) ?? {};
|
|
1414
1460
|
} catch {
|
|
1415
1461
|
return null;
|
|
1416
1462
|
}
|
|
1417
1463
|
}
|
|
1418
1464
|
function writeJson(path, value) {
|
|
1419
|
-
mkdirSync2(
|
|
1465
|
+
mkdirSync2(join6(path, ".."), { recursive: true });
|
|
1420
1466
|
writeFileSync3(path, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
1421
1467
|
}
|
|
1422
1468
|
function registers(entries, kind) {
|
|
@@ -1436,27 +1482,27 @@ function git2(repoRoot, args) {
|
|
|
1436
1482
|
function hooksDirectory(repoRoot) {
|
|
1437
1483
|
const configured = git2(repoRoot, ["config", "--get", "core.hooksPath"]);
|
|
1438
1484
|
if (configured) {
|
|
1439
|
-
return isAbsolute(configured) ? configured :
|
|
1485
|
+
return isAbsolute(configured) ? configured : join6(repoRoot, configured);
|
|
1440
1486
|
}
|
|
1441
1487
|
const path = git2(repoRoot, ["rev-parse", "--git-path", "hooks"]);
|
|
1442
1488
|
if (path) {
|
|
1443
|
-
return isAbsolute(path) ? path :
|
|
1489
|
+
return isAbsolute(path) ? path : join6(repoRoot, path);
|
|
1444
1490
|
}
|
|
1445
|
-
return
|
|
1491
|
+
return join6(repoRoot, ".git", "hooks");
|
|
1446
1492
|
}
|
|
1447
1493
|
function wirePreCommitHook(repoRoot, command) {
|
|
1448
|
-
if (!existsSync5(
|
|
1494
|
+
if (!existsSync5(join6(repoRoot, ".git"))) {
|
|
1449
1495
|
console.log("not a git repo, skipping the pre-commit hook");
|
|
1450
1496
|
return;
|
|
1451
1497
|
}
|
|
1452
1498
|
const dir = hooksDirectory(repoRoot);
|
|
1453
|
-
const path =
|
|
1499
|
+
const path = join6(dir, "pre-commit");
|
|
1454
1500
|
const script = `#!/bin/sh
|
|
1455
1501
|
# ${HOOK_MARKER}: check newly added npm packages before they get committed
|
|
1456
1502
|
${command} hook pre-commit
|
|
1457
1503
|
`;
|
|
1458
1504
|
if (existsSync5(path)) {
|
|
1459
|
-
const existing =
|
|
1505
|
+
const existing = readFileSync6(path, "utf8");
|
|
1460
1506
|
if (existing.includes(HOOK_MARKER)) {
|
|
1461
1507
|
console.log("pre-commit hook already installed, left alone");
|
|
1462
1508
|
return;
|
|
@@ -1497,8 +1543,8 @@ function runMode(targetMode) {
|
|
|
1497
1543
|
}
|
|
1498
1544
|
|
|
1499
1545
|
// src/commands/uninstall.ts
|
|
1500
|
-
import { existsSync as existsSync6, readFileSync as
|
|
1501
|
-
import { join as
|
|
1546
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1547
|
+
import { join as join7 } from "path";
|
|
1502
1548
|
var HOOK_MARKER2 = "agentinel";
|
|
1503
1549
|
var HOOK_SUBCOMMAND2 = "hook claude-code";
|
|
1504
1550
|
function runUninstall() {
|
|
@@ -1513,7 +1559,7 @@ function runUninstall() {
|
|
|
1513
1559
|
return 0;
|
|
1514
1560
|
}
|
|
1515
1561
|
function unwireClaudeCodeHook(repoRoot) {
|
|
1516
|
-
const path =
|
|
1562
|
+
const path = join7(repoRoot, ".claude", "settings.json");
|
|
1517
1563
|
if (!existsSync6(path)) return;
|
|
1518
1564
|
const file = readJson2(path);
|
|
1519
1565
|
if (file === null) return;
|
|
@@ -1531,7 +1577,7 @@ function unwireClaudeCodeHook(repoRoot) {
|
|
|
1531
1577
|
console.log("removed Claude Code hook");
|
|
1532
1578
|
}
|
|
1533
1579
|
function unwireCodexHook(repoRoot) {
|
|
1534
|
-
const path =
|
|
1580
|
+
const path = join7(repoRoot, ".codex", "hooks.json");
|
|
1535
1581
|
if (!existsSync6(path)) return;
|
|
1536
1582
|
const file = readJson2(path);
|
|
1537
1583
|
if (file === null) return;
|
|
@@ -1543,7 +1589,7 @@ function unwireCodexHook(repoRoot) {
|
|
|
1543
1589
|
console.log("removed Codex hook");
|
|
1544
1590
|
}
|
|
1545
1591
|
function unwireCopilotHook(repoRoot) {
|
|
1546
|
-
const path =
|
|
1592
|
+
const path = join7(repoRoot, ".github", "hooks", "agentinel.json");
|
|
1547
1593
|
if (!existsSync6(path)) return;
|
|
1548
1594
|
const file = readJson2(path);
|
|
1549
1595
|
if (file === null) return;
|
|
@@ -1555,7 +1601,7 @@ function unwireCopilotHook(repoRoot) {
|
|
|
1555
1601
|
console.log("removed Copilot hook");
|
|
1556
1602
|
}
|
|
1557
1603
|
function unwireGeminiHook(repoRoot) {
|
|
1558
|
-
const path =
|
|
1604
|
+
const path = join7(repoRoot, ".gemini", "settings.json");
|
|
1559
1605
|
if (!existsSync6(path)) return;
|
|
1560
1606
|
const file = readJson2(path);
|
|
1561
1607
|
if (file === null) return;
|
|
@@ -1567,17 +1613,17 @@ function unwireGeminiHook(repoRoot) {
|
|
|
1567
1613
|
console.log("removed Gemini hook");
|
|
1568
1614
|
}
|
|
1569
1615
|
function unwirePreCommitHook(repoRoot) {
|
|
1570
|
-
if (!existsSync6(
|
|
1571
|
-
const path =
|
|
1616
|
+
if (!existsSync6(join7(repoRoot, ".git"))) return;
|
|
1617
|
+
const path = join7(hooksDirectory(repoRoot), "pre-commit");
|
|
1572
1618
|
if (!existsSync6(path)) return;
|
|
1573
|
-
const existing =
|
|
1619
|
+
const existing = readFileSync7(path, "utf8");
|
|
1574
1620
|
if (!existing.includes(HOOK_MARKER2)) return;
|
|
1575
1621
|
rmSync2(path);
|
|
1576
1622
|
console.log("removed git pre-commit hook");
|
|
1577
1623
|
}
|
|
1578
1624
|
function readJson2(path) {
|
|
1579
1625
|
try {
|
|
1580
|
-
return asRecord3(JSON.parse(
|
|
1626
|
+
return asRecord3(JSON.parse(readFileSync7(path, "utf8"))) ?? {};
|
|
1581
1627
|
} catch {
|
|
1582
1628
|
return null;
|
|
1583
1629
|
}
|
|
@@ -1591,7 +1637,7 @@ function asRecord3(value) {
|
|
|
1591
1637
|
|
|
1592
1638
|
// src/hooks/claude-code.ts
|
|
1593
1639
|
import { existsSync as existsSync7 } from "fs";
|
|
1594
|
-
import { join as
|
|
1640
|
+
import { join as join8 } from "path";
|
|
1595
1641
|
|
|
1596
1642
|
// src/checks/package-guard/resolve.ts
|
|
1597
1643
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
@@ -1675,7 +1721,7 @@ function candidatesFor(command, repoRoot) {
|
|
|
1675
1721
|
}
|
|
1676
1722
|
function isLocalTool(repoRoot, name) {
|
|
1677
1723
|
const binary = name.includes("/") ? name.split("/").pop() : name;
|
|
1678
|
-
return existsSync7(
|
|
1724
|
+
return existsSync7(join8(repoRoot, "node_modules", ".bin", binary));
|
|
1679
1725
|
}
|
|
1680
1726
|
function warn(verdicts) {
|
|
1681
1727
|
const summary = plainSummary(verdicts);
|
|
@@ -1889,14 +1935,14 @@ async function runPreCommitHook() {
|
|
|
1889
1935
|
}
|
|
1890
1936
|
|
|
1891
1937
|
// bin/asen.ts
|
|
1892
|
-
import { readFileSync as
|
|
1893
|
-
import { dirname as dirname2, join as
|
|
1938
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
1939
|
+
import { dirname as dirname2, join as join9 } from "path";
|
|
1894
1940
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1895
1941
|
function getVersion() {
|
|
1896
1942
|
try {
|
|
1897
1943
|
const file = fileURLToPath2(import.meta.url);
|
|
1898
|
-
const pkgPath =
|
|
1899
|
-
const pkg = JSON.parse(
|
|
1944
|
+
const pkgPath = join9(dirname2(file), "..", "package.json");
|
|
1945
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
1900
1946
|
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
1901
1947
|
} catch {
|
|
1902
1948
|
return "unknown";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentinel",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
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",
|