agentinel 1.0.4 → 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.
Files changed (3) hide show
  1. package/README.md +30 -10
  2. package/dist/asen.js +100 -56
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -89,7 +89,9 @@ Agentinel provides an opt-in **PATH shim**. By default, running `npx asen init`
89
89
  npx asen init
90
90
  ```
91
91
 
92
- This puts a tiny, fail-open wrapper script earlier in your `PATH`. When you type `npm install`, the shim checks the package first. If it's safe, the real `npm` command runs instantly.
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!
93
95
 
94
96
  **Terminal Example:**
95
97
  ```bash
@@ -166,16 +168,34 @@ Here are all the commands:
166
168
  Wires up agent hooks and git hooks in the current repo, and installs the global PATH shim for human terminal protection.
167
169
  ```bash
168
170
  $ npx asen init
169
- wrote .agentinel.json
170
- registered the Claude Code PreToolUse hook in .claude/settings.json
171
- installed the git pre-commit hook in .git/hooks
172
- wrote shims for npm, npx, pnpm, yarn, bun in /Users/user/.agentinel/bin
173
- added the shims to PATH in /Users/user/.zshrc
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.
176
171
 
177
- agentinel is set up. New npm packages will be checked before they land.
178
- Default mode is strict. Set "mode": "warn" in .agentinel.json to only warn instead.
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
179
199
  ```
180
200
 
181
201
  When used with `--no-shim`, it wires up hooks but skips installing the global PATH shim.
package/dist/asen.js CHANGED
@@ -862,7 +862,10 @@ function collectFromDependencies(lock, found) {
862
862
  var RESET = "\x1B[0m";
863
863
  var YELLOW = "\x1B[33m";
864
864
  var RED = "\x1B[31m";
865
+ var GREEN = "\x1B[32m";
866
+ var CYAN = "\x1B[36m";
865
867
  var DIM = "\x1B[2m";
868
+ var BOLD = "\x1B[1m";
866
869
  var WIDTH = 46;
867
870
  var TITLE = "agentinel";
868
871
  function supportsColor(stream = process.stdout) {
@@ -952,6 +955,39 @@ function draw(b, paint) {
952
955
  return b.action ? `${box}
953
956
  ${paint(DIM, b.action)}` : box;
954
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
+ }
955
991
  function wrap(line, width) {
956
992
  const lines = [];
957
993
  let current = "";
@@ -1107,7 +1143,7 @@ function startupFilePath(target) {
1107
1143
  }
1108
1144
  return join5(target.home, ".profile");
1109
1145
  }
1110
- function installShim(repoRoot, target = currentTarget()) {
1146
+ function installShim(repoRoot, target = currentTarget(), messages = []) {
1111
1147
  const dir = shimDirectory(target.home);
1112
1148
  mkdirSync(dir, { recursive: true });
1113
1149
  for (const client of CLIENTS) {
@@ -1117,13 +1153,9 @@ function installShim(repoRoot, target = currentTarget()) {
1117
1153
  chmodSync(path, 493);
1118
1154
  }
1119
1155
  }
1120
- console.log(`wrote shims for ${CLIENTS.join(", ")} in ${dir}`);
1121
- addPathLine(target);
1122
- const mode = loadConfig(repoRoot).mode;
1123
- console.log(
1124
- 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."
1125
- );
1126
- 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.");
1127
1159
  return 0;
1128
1160
  }
1129
1161
  function removeShim(target = currentTarget()) {
@@ -1141,17 +1173,17 @@ function pathLine(target) {
1141
1173
  return `export PATH="$HOME/.agentinel/bin:$PATH" ${RC_MARKER}
1142
1174
  `;
1143
1175
  }
1144
- function addPathLine(target) {
1176
+ function addPathLine(target, messages) {
1145
1177
  const path = startupFilePath(target);
1146
1178
  const existing = existsSync4(path) ? readFileSync5(path, "utf8") : "";
1147
1179
  if (existing.includes(RC_MARKER)) {
1148
- console.log(`${path} already puts the shims on PATH, left alone`);
1180
+ messages.push(`${path} already puts the shims on PATH, left alone`);
1149
1181
  return;
1150
1182
  }
1151
1183
  mkdirSync(join5(path, ".."), { recursive: true });
1152
1184
  const separator = existing === "" || existing.endsWith("\n") ? "" : "\n";
1153
1185
  writeFileSync2(path, `${existing}${separator}${pathLine(target)}`, "utf8");
1154
- console.log(`added the shims to PATH in ${path}`);
1186
+ messages.push(`added the shims to PATH in ${path}`);
1155
1187
  }
1156
1188
  function removePathLine(target) {
1157
1189
  const path = startupFilePath(target);
@@ -1268,12 +1300,15 @@ async function runCheckCommand(command) {
1268
1300
  if (!command) {
1269
1301
  return 0;
1270
1302
  }
1271
- const { installs, executes } = parseCommand(command);
1303
+ const { installs, executes, lockfile } = parseCommand(command);
1304
+ const repoRoot = repoRootOrCwd();
1272
1305
  const names = [.../* @__PURE__ */ new Set([...installs, ...executes])];
1273
1306
  if (names.length === 0) {
1274
- return 0;
1307
+ if (!lockfile) return 0;
1308
+ const workingTree = newWorkingTreeDependencies(repoRoot);
1309
+ if (workingTree.length === 0) return 0;
1310
+ names.push(...workingTree);
1275
1311
  }
1276
- const repoRoot = repoRootOrCwd();
1277
1312
  const config = loadConfig(repoRoot);
1278
1313
  const verdicts = await checkPackages(names, config);
1279
1314
  for (const verdict of verdicts) {
@@ -1297,21 +1332,30 @@ var HOOK_MARKER = "agentinel";
1297
1332
  var HOOK_SUBCOMMAND = "hook claude-code";
1298
1333
  function runInit(options = {}) {
1299
1334
  const repoRoot = repoRootOrCwd();
1300
- writeConfig(repoRoot);
1301
- wireClaudeCodeHook(repoRoot, claudeCodeCommand(repoRoot));
1302
- wireOtherAgents(repoRoot, agentHookCommand(repoRoot));
1303
- wirePreCommitHook(repoRoot, gitHookCommand(repoRoot));
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);
1304
1340
  if (options.shim) {
1305
- installShim(repoRoot, options.shimTarget);
1306
- }
1307
- console.log("\nagentinel is set up. New npm packages will be checked before they land.");
1308
- console.log(
1309
- 'Default mode is strict. Set "mode": "warn" in .agentinel.json to only warn instead.'
1310
- );
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}`);
1311
1353
  if (!hasLocalInstall(repoRoot)) {
1312
- console.log("\nThe hook runs on every command, and going through npx each time is slow.");
1313
- console.log("For faster hooks, add it to the repo and run init again:");
1314
- console.log(" npm install --save-dev agentinel && npx asen init");
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}`);
1315
1359
  }
1316
1360
  return 0;
1317
1361
  }
@@ -1327,16 +1371,16 @@ function claudeCodeCommand(repoRoot) {
1327
1371
  function gitHookCommand(repoRoot) {
1328
1372
  return hasLocalInstall(repoRoot) ? "./node_modules/.bin/asen" : "npx agentinel";
1329
1373
  }
1330
- function writeConfig(repoRoot) {
1374
+ function writeConfig(repoRoot, messages) {
1331
1375
  const path = configPath(repoRoot);
1332
1376
  if (existsSync5(path)) {
1333
- console.log(".agentinel.json already exists, left alone");
1377
+ messages.push(".agentinel.json already exists, left alone");
1334
1378
  return;
1335
1379
  }
1336
1380
  saveConfig(repoRoot, defaultConfig());
1337
- console.log("wrote .agentinel.json");
1381
+ messages.push("wrote .agentinel.json");
1338
1382
  }
1339
- function wireClaudeCodeHook(repoRoot, command) {
1383
+ function wireClaudeCodeHook(repoRoot, command, messages) {
1340
1384
  const dir = join6(repoRoot, ".claude");
1341
1385
  const path = join6(dir, "settings.json");
1342
1386
  let settings = {};
@@ -1347,14 +1391,14 @@ function wireClaudeCodeHook(repoRoot, command) {
1347
1391
  settings = parsed;
1348
1392
  }
1349
1393
  } catch {
1350
- console.log(".claude/settings.json is not valid JSON, skipping the Claude Code hook");
1394
+ messages.push(".claude/settings.json is not valid JSON, skipping the Claude Code hook");
1351
1395
  return;
1352
1396
  }
1353
1397
  }
1354
1398
  const hooks = asRecord2(settings.hooks) ?? {};
1355
1399
  const preToolUse = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
1356
1400
  if (alreadyRegistered(preToolUse)) {
1357
- console.log("Claude Code hook already registered, left alone");
1401
+ messages.push("Claude Code hook already registered, left alone");
1358
1402
  return;
1359
1403
  }
1360
1404
  preToolUse.push({
@@ -1365,7 +1409,7 @@ function wireClaudeCodeHook(repoRoot, command) {
1365
1409
  settings.hooks = hooks;
1366
1410
  mkdirSync2(dir, { recursive: true });
1367
1411
  writeFileSync3(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
1368
- console.log("registered the Claude Code PreToolUse hook in .claude/settings.json");
1412
+ messages.push("registered the Claude Code PreToolUse hook in .claude/settings.json");
1369
1413
  }
1370
1414
  function agentHookCommand(repoRoot) {
1371
1415
  if (isWindows()) {
@@ -1373,31 +1417,31 @@ function agentHookCommand(repoRoot) {
1373
1417
  }
1374
1418
  return hasLocalInstall(repoRoot) ? '"$(git rev-parse --show-toplevel)"/node_modules/.bin/asen' : "npx agentinel";
1375
1419
  }
1376
- function wireOtherAgents(repoRoot, command) {
1420
+ function wireOtherAgents(repoRoot, command, messages) {
1377
1421
  if (uses(repoRoot, ".codex")) {
1378
- wireCodexHook(repoRoot, command);
1422
+ wireCodexHook(repoRoot, command, messages);
1379
1423
  }
1380
1424
  if (uses(repoRoot, ".copilot") || existsSync5(join6(repoRoot, ".github", "hooks"))) {
1381
- wireCopilotHook(repoRoot, command);
1425
+ wireCopilotHook(repoRoot, command, messages);
1382
1426
  }
1383
1427
  if (uses(repoRoot, ".gemini")) {
1384
- wireGeminiHook(repoRoot, command);
1428
+ wireGeminiHook(repoRoot, command, messages);
1385
1429
  }
1386
1430
  }
1387
1431
  function uses(repoRoot, dir) {
1388
1432
  return existsSync5(join6(repoRoot, dir)) || existsSync5(join6(homedir2(), dir));
1389
1433
  }
1390
- function wireCodexHook(repoRoot, command) {
1434
+ function wireCodexHook(repoRoot, command, messages) {
1391
1435
  const path = join6(repoRoot, ".codex", "hooks.json");
1392
1436
  const file = readJson(path);
1393
1437
  if (file === null) {
1394
- console.log(".codex/hooks.json is not valid JSON, skipping the Codex hook");
1438
+ messages.push(".codex/hooks.json is not valid JSON, skipping the Codex hook");
1395
1439
  return;
1396
1440
  }
1397
1441
  const hooks = asRecord2(file.hooks) ?? {};
1398
1442
  const preToolUse = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
1399
1443
  if (registers(preToolUse, "codex")) {
1400
- console.log("Codex hook already registered, left alone");
1444
+ messages.push("Codex hook already registered, left alone");
1401
1445
  return;
1402
1446
  }
1403
1447
  preToolUse.push({
@@ -1407,19 +1451,19 @@ function wireCodexHook(repoRoot, command) {
1407
1451
  hooks.PreToolUse = preToolUse;
1408
1452
  file.hooks = hooks;
1409
1453
  writeJson(path, file);
1410
- console.log("registered the Codex PreToolUse hook in .codex/hooks.json");
1454
+ messages.push("registered the Codex PreToolUse hook in .codex/hooks.json");
1411
1455
  }
1412
- function wireCopilotHook(repoRoot, command) {
1456
+ function wireCopilotHook(repoRoot, command, messages) {
1413
1457
  const path = join6(repoRoot, ".github", "hooks", "agentinel.json");
1414
1458
  const file = readJson(path);
1415
1459
  if (file === null) {
1416
- console.log(".github/hooks/agentinel.json is not valid JSON, skipping the Copilot hook");
1460
+ messages.push(".github/hooks/agentinel.json is not valid JSON, skipping the Copilot hook");
1417
1461
  return;
1418
1462
  }
1419
1463
  const hooks = asRecord2(file.hooks) ?? {};
1420
1464
  const preToolUse = Array.isArray(hooks.preToolUse) ? hooks.preToolUse : [];
1421
1465
  if (registers(preToolUse, "copilot")) {
1422
- console.log("Copilot hook already registered, left alone");
1466
+ messages.push("Copilot hook already registered, left alone");
1423
1467
  return;
1424
1468
  }
1425
1469
  preToolUse.push({ type: "command", matcher: "bash", bash: `${command} hook copilot` });
@@ -1427,19 +1471,19 @@ function wireCopilotHook(repoRoot, command) {
1427
1471
  file.version = 1;
1428
1472
  file.hooks = hooks;
1429
1473
  writeJson(path, file);
1430
- console.log("registered the Copilot preToolUse hook in .github/hooks/agentinel.json");
1474
+ messages.push("registered the Copilot preToolUse hook in .github/hooks/agentinel.json");
1431
1475
  }
1432
- function wireGeminiHook(repoRoot, command) {
1476
+ function wireGeminiHook(repoRoot, command, messages) {
1433
1477
  const path = join6(repoRoot, ".gemini", "settings.json");
1434
1478
  const file = readJson(path);
1435
1479
  if (file === null) {
1436
- console.log(".gemini/settings.json is not valid JSON, skipping the Gemini hook");
1480
+ messages.push(".gemini/settings.json is not valid JSON, skipping the Gemini hook");
1437
1481
  return;
1438
1482
  }
1439
1483
  const hooks = asRecord2(file.hooks) ?? {};
1440
1484
  const beforeTool = Array.isArray(hooks.BeforeTool) ? hooks.BeforeTool : [];
1441
1485
  if (registers(beforeTool, "gemini")) {
1442
- console.log("Gemini hook already registered, left alone");
1486
+ messages.push("Gemini hook already registered, left alone");
1443
1487
  return;
1444
1488
  }
1445
1489
  beforeTool.push({
@@ -1449,7 +1493,7 @@ function wireGeminiHook(repoRoot, command) {
1449
1493
  hooks.BeforeTool = beforeTool;
1450
1494
  file.hooks = hooks;
1451
1495
  writeJson(path, file);
1452
- console.log("registered the Gemini BeforeTool hook in .gemini/settings.json");
1496
+ messages.push("registered the Gemini BeforeTool hook in .gemini/settings.json");
1453
1497
  }
1454
1498
  function readJson(path) {
1455
1499
  if (!existsSync5(path)) {
@@ -1490,9 +1534,9 @@ function hooksDirectory(repoRoot) {
1490
1534
  }
1491
1535
  return join6(repoRoot, ".git", "hooks");
1492
1536
  }
1493
- function wirePreCommitHook(repoRoot, command) {
1537
+ function wirePreCommitHook(repoRoot, command, messages) {
1494
1538
  if (!existsSync5(join6(repoRoot, ".git"))) {
1495
- console.log("not a git repo, skipping the pre-commit hook");
1539
+ messages.push("not a git repo, skipping the pre-commit hook");
1496
1540
  return;
1497
1541
  }
1498
1542
  const dir = hooksDirectory(repoRoot);
@@ -1504,18 +1548,18 @@ ${command} hook pre-commit
1504
1548
  if (existsSync5(path)) {
1505
1549
  const existing = readFileSync6(path, "utf8");
1506
1550
  if (existing.includes(HOOK_MARKER)) {
1507
- console.log("pre-commit hook already installed, left alone");
1551
+ messages.push("pre-commit hook already installed, left alone");
1508
1552
  return;
1509
1553
  }
1510
- console.log("a pre-commit hook already exists, not overwriting it");
1511
- console.log(`add this line to ${path}:
1554
+ messages.push("a pre-commit hook already exists, not overwriting it");
1555
+ messages.push(`add this line to ${path}:
1512
1556
  ${command} hook pre-commit`);
1513
1557
  return;
1514
1558
  }
1515
1559
  mkdirSync2(dir, { recursive: true });
1516
1560
  writeFileSync3(path, script, "utf8");
1517
1561
  chmodSync2(path, 493);
1518
- console.log(`installed the git pre-commit hook in ${dir}`);
1562
+ messages.push(`installed the git pre-commit hook in ${dir}`);
1519
1563
  }
1520
1564
  function alreadyRegistered(preToolUse) {
1521
1565
  return JSON.stringify(preToolUse).includes(`${HOOK_SUBCOMMAND}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentinel",
3
- "version": "1.0.4",
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",