agentinel 0.0.10 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +63 -23
  2. package/dist/asen.js +154 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,6 +4,7 @@
4
4
  [![npm version](https://img.shields.io/npm/v/agentinel.svg?style=flat-square)](https://www.npmjs.com/package/agentinel)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](LICENSE)
6
6
  [![Node.js CI](https://img.shields.io/github/actions/workflow/status/aman-janwani/agentinel/publish.yml?style=flat-square)](https://github.com/aman-janwani/agentinel/actions)
7
+ [![Tested On](https://img.shields.io/badge/Tested_On-macOS_%7C_Linux_%7C_Windows-success?style=flat-square)](#)
7
8
  </div>
8
9
 
9
10
  <br />
@@ -34,6 +35,7 @@ Every other tool in this space guards your terminal. **Agentinel guards your age
34
35
  - **Known Malware:** Bundles a local OSV database of 216,000+ confirmed malicious packages.
35
36
  - **Zero False Positives on Popular Packages:** Tested against the top 100 npm packages.
36
37
  - **Heuristic Scanning:** Flags npm takedowns, packages under 30 days old with < 1k downloads (slopsquatting), publisher drift, and non-existent hallucinated names.
38
+ - **Cross-Platform Compatibility:** Fully tested and natively supported across macOS, Linux, and Windows.
37
39
  - **Fails Open:** Designed so that if it crashes or can't reach the registry, it fails open. It will never permanently wedge your terminal or block your work.
38
40
 
39
41
  ---
@@ -75,10 +77,10 @@ The AI reads this, realizes the package is fake or malicious, and intelligently
75
77
 
76
78
  What about installs that never go through an agent? (e.g., You typing `npm install` manually).
77
79
 
78
- Agentinel provides an opt-in **PATH shim**.
80
+ Agentinel provides an opt-in **PATH shim**. By default, running `npx asen init` installs this shim automatically.
79
81
 
80
82
  ```sh
81
- npx asen init --shim
83
+ npx asen init
82
84
  ```
83
85
 
84
86
  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.
@@ -119,17 +121,31 @@ How do we stack up against traditional commercial security scanners?
119
121
 
120
122
  ---
121
123
 
122
- ## ⚙️ Configuration (Warn vs. Block)
124
+ ## ⚙️ Modes: warn vs strict
125
+
126
+ Agentinel supports two operating modes, controlled by the `mode` field. You can switch between them using `npx asen mode <warn|strict>`.
127
+
128
+ ### warn (default)
129
+ Agentinel surfaces a warning in the agent output but does not block the install. The agent decides whether to proceed.
130
+
131
+ `.agentinel.json`
132
+ ```json
133
+ {
134
+ "mode": "warn"
135
+ }
136
+ ```
137
+ *Best for teams migrating to Agentinel gradually or using agents in read-heavy workflows.*
123
138
 
124
- By default, Agentinel runs in **Warn Mode**. It alerts you (and the agent) but gets out of the way.
125
- To strictly block bad packages, change your `.agentinel.json` file to strict mode:
139
+ ### strict
140
+ Agentinel hard-blocks the install and returns an error payload to the agent. The install never reaches npm.
126
141
 
142
+ `.agentinel.json`
127
143
  ```json
128
144
  {
129
- "mode": "strict",
130
- "allowlist": {}
145
+ "mode": "strict"
131
146
  }
132
147
  ```
148
+ *Recommended for production repos, CI pipelines, and any project with autonomous agentic access.*
133
149
 
134
150
  ---
135
151
 
@@ -137,31 +153,40 @@ To strictly block bad packages, change your `.agentinel.json` file to strict mod
137
153
 
138
154
  Here are all the commands you can run via `npx asen <command>`:
139
155
 
140
- ### `npx asen init`
141
- Wires up agent hooks and git hooks in the current repo.
156
+ ### `npx asen init [--no-shim]`
157
+ Wires up agent hooks and git hooks in the current repo, and installs the global PATH shim for human terminal protection.
142
158
  ```bash
143
159
  $ npx asen init
144
- Wired up pre-commit git hook.
145
- Wired up Claude Code PreToolUse hook.
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
+
168
+ agentinel is set up. New npm packages will be checked before they land.
169
+ Default mode is warn. Set "mode": "strict" in .agentinel.json to block instead.
146
170
  ```
147
171
 
148
- ### `npx asen init --shim`
149
- Wires up hooks AND installs the global PATH shim for human terminal protection.
172
+ When used with `--no-shim`, it wires up hooks but skips installing the global PATH shim.
150
173
  ```bash
151
- $ npx asen init --shim
152
- Shim installed. Native terminal npm commands are now guarded.
153
- Wired up pre-commit git hook.
174
+ $ npx asen init --no-shim
175
+ wrote .agentinel.json
176
+ registered the Claude Code PreToolUse hook in .claude/settings.json
177
+ installed the git pre-commit hook in .git/hooks
178
+
179
+ agentinel is set up. New npm packages will be checked before they land.
180
+ Default mode is warn. Set "mode": "strict" in .agentinel.json to block instead.
154
181
  ```
155
182
 
156
- ### `npx asen check`
183
+ ### `npx asen check [pkg...]`
157
184
  Scans the currently staged dependencies in your lockfile. Exits non-zero if flagged (great for CI/CD pipelines).
158
185
  ```bash
159
186
  $ npx asen check
160
- Scanning staged dependencies...
161
- ✅ All 142 staged packages passed security checks.
187
+ checked 142 package(s), nothing suspicious
162
188
  ```
163
189
 
164
- ### `npx asen check <package-name>`
165
190
  Scans a specific package instantly without installing it.
166
191
  ```bash
167
192
  $ npx asen check react-router-v7-fake
@@ -169,18 +194,33 @@ $ npx asen check react-router-v7-fake
169
194
  This matches the profile of a slopsquatting or malicious package.
170
195
  ```
171
196
 
172
- ### `npx asen allow <package-name> --reason "why"`
197
+ ### `npx asen allow <pkg> --reason "..."`
173
198
  Adds a package to the allowlist in `.agentinel.json` with a required reason. This provides an audited trail for your team.
174
199
  ```bash
175
200
  $ npx asen allow my-internal-pkg --reason "Internal company package not on public npm"
176
- Added my-internal-pkg to .agentinel.json allowlist.
201
+ allowlisted my-internal-pkg in .agentinel.json
202
+ ```
203
+
204
+ ### `npx asen mode <warn|strict>`
205
+ Switches Agentinel's operating mode in the `.agentinel.json` file.
206
+ ```bash
207
+ $ npx asen mode strict
208
+ set mode to strict in .agentinel.json
209
+ ```
210
+
211
+ ### `npx asen uninstall`
212
+ Completely removes all Agentinel hooks from your repository config files (`.claude`, `.gemini`, `.github`, etc.) and removes global shims.
213
+ ```bash
214
+ $ npx asen uninstall
215
+ agentinel has been completely uninstalled from this repository.
177
216
  ```
178
217
 
179
218
  ### `npx asen unshim`
180
219
  Removes the global PATH shim.
181
220
  ```bash
182
221
  $ npx asen unshim
183
- ✅ Shim removed. Normal npm path restored.
222
+ removed /Users/user/.agentinel/bin
223
+ removed the PATH line from /Users/user/.zshrc
184
224
  ```
185
225
 
186
226
  ---
package/dist/asen.js CHANGED
@@ -102,7 +102,21 @@ import { join } from "path";
102
102
  // src/config/schema.ts
103
103
  var CONFIG_FILENAME = ".agentinel.json";
104
104
  function defaultConfig() {
105
- return { mode: "warn", allow: [] };
105
+ return {
106
+ mode: "warn",
107
+ allow: [
108
+ {
109
+ name: "asen",
110
+ reason: "Agentinel CLI tool",
111
+ date: (/* @__PURE__ */ new Date()).toISOString().substring(0, 10)
112
+ },
113
+ {
114
+ name: "agentinel",
115
+ reason: "Agentinel package",
116
+ date: (/* @__PURE__ */ new Date()).toISOString().substring(0, 10)
117
+ }
118
+ ]
119
+ };
106
120
  }
107
121
  function parseConfig(raw, warn2 = () => {
108
122
  }) {
@@ -1464,9 +1478,120 @@ function asRecord2(value) {
1464
1478
  return typeof value === "object" && value !== null ? value : null;
1465
1479
  }
1466
1480
 
1467
- // src/hooks/claude-code.ts
1468
- import { existsSync as existsSync6 } from "fs";
1481
+ // src/commands/mode.ts
1482
+ function runMode(targetMode) {
1483
+ if (targetMode !== "warn" && targetMode !== "strict") {
1484
+ console.error("usage: asen mode <warn|strict>");
1485
+ return 1;
1486
+ }
1487
+ const repoRoot = repoRootOrCwd();
1488
+ const config = loadConfig(repoRoot);
1489
+ if (config.mode === targetMode) {
1490
+ console.log(`mode is already ${targetMode}`);
1491
+ return 0;
1492
+ }
1493
+ config.mode = targetMode;
1494
+ saveConfig(repoRoot, config);
1495
+ console.log(`set mode to ${targetMode} in .agentinel.json`);
1496
+ return 0;
1497
+ }
1498
+
1499
+ // src/commands/uninstall.ts
1500
+ import { existsSync as existsSync6, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
1469
1501
  import { join as join6 } from "path";
1502
+ var HOOK_MARKER2 = "agentinel";
1503
+ var HOOK_SUBCOMMAND2 = "hook claude-code";
1504
+ function runUninstall() {
1505
+ const repoRoot = repoRootOrCwd();
1506
+ unwireClaudeCodeHook(repoRoot);
1507
+ unwireCodexHook(repoRoot);
1508
+ unwireCopilotHook(repoRoot);
1509
+ unwireGeminiHook(repoRoot);
1510
+ unwirePreCommitHook(repoRoot);
1511
+ removeShim();
1512
+ console.log("agentinel has been completely uninstalled from this repository.");
1513
+ return 0;
1514
+ }
1515
+ function unwireClaudeCodeHook(repoRoot) {
1516
+ const path = join6(repoRoot, ".claude", "settings.json");
1517
+ if (!existsSync6(path)) return;
1518
+ const file = readJson2(path);
1519
+ if (file === null) return;
1520
+ const hooks = asRecord3(file.hooks);
1521
+ if (!hooks || !Array.isArray(hooks.PreToolUse)) return;
1522
+ const preToolUse = hooks.PreToolUse;
1523
+ hooks.PreToolUse = preToolUse.filter((hook) => !JSON.stringify(hook).includes(HOOK_SUBCOMMAND2));
1524
+ if (hooks.PreToolUse.length === 0) {
1525
+ delete hooks.PreToolUse;
1526
+ }
1527
+ if (Object.keys(hooks).length === 0) {
1528
+ delete file.hooks;
1529
+ }
1530
+ writeJson2(path, file);
1531
+ console.log("removed Claude Code hook");
1532
+ }
1533
+ function unwireCodexHook(repoRoot) {
1534
+ const path = join6(repoRoot, ".codex", "hooks.json");
1535
+ if (!existsSync6(path)) return;
1536
+ const file = readJson2(path);
1537
+ if (file === null) return;
1538
+ const hooks = asRecord3(file.hooks);
1539
+ if (!hooks || !Array.isArray(hooks.PreToolUse)) return;
1540
+ const preToolUse = hooks.PreToolUse;
1541
+ hooks.PreToolUse = preToolUse.filter((hook) => !JSON.stringify(hook).includes("hook codex"));
1542
+ writeJson2(path, file);
1543
+ console.log("removed Codex hook");
1544
+ }
1545
+ function unwireCopilotHook(repoRoot) {
1546
+ const path = join6(repoRoot, ".github", "hooks", "agentinel.json");
1547
+ if (!existsSync6(path)) return;
1548
+ const file = readJson2(path);
1549
+ if (file === null) return;
1550
+ const hooks = asRecord3(file.hooks);
1551
+ if (!hooks || !Array.isArray(hooks.preToolUse)) return;
1552
+ const preToolUse = hooks.preToolUse;
1553
+ hooks.preToolUse = preToolUse.filter((hook) => !JSON.stringify(hook).includes("hook copilot"));
1554
+ writeJson2(path, file);
1555
+ console.log("removed Copilot hook");
1556
+ }
1557
+ function unwireGeminiHook(repoRoot) {
1558
+ const path = join6(repoRoot, ".gemini", "settings.json");
1559
+ if (!existsSync6(path)) return;
1560
+ const file = readJson2(path);
1561
+ if (file === null) return;
1562
+ const hooks = asRecord3(file.hooks);
1563
+ if (!hooks || !Array.isArray(hooks.BeforeTool)) return;
1564
+ const beforeTool = hooks.BeforeTool;
1565
+ hooks.BeforeTool = beforeTool.filter((hook) => !JSON.stringify(hook).includes("hook gemini"));
1566
+ writeJson2(path, file);
1567
+ console.log("removed Gemini hook");
1568
+ }
1569
+ function unwirePreCommitHook(repoRoot) {
1570
+ if (!existsSync6(join6(repoRoot, ".git"))) return;
1571
+ const path = join6(hooksDirectory(repoRoot), "pre-commit");
1572
+ if (!existsSync6(path)) return;
1573
+ const existing = readFileSync6(path, "utf8");
1574
+ if (!existing.includes(HOOK_MARKER2)) return;
1575
+ rmSync2(path);
1576
+ console.log("removed git pre-commit hook");
1577
+ }
1578
+ function readJson2(path) {
1579
+ try {
1580
+ return asRecord3(JSON.parse(readFileSync6(path, "utf8"))) ?? {};
1581
+ } catch {
1582
+ return null;
1583
+ }
1584
+ }
1585
+ function writeJson2(path, value) {
1586
+ writeFileSync4(path, JSON.stringify(value, null, 2) + "\n", "utf8");
1587
+ }
1588
+ function asRecord3(value) {
1589
+ return typeof value === "object" && value !== null ? value : null;
1590
+ }
1591
+
1592
+ // src/hooks/claude-code.ts
1593
+ import { existsSync as existsSync7 } from "fs";
1594
+ import { join as join7 } from "path";
1470
1595
 
1471
1596
  // src/checks/package-guard/resolve.ts
1472
1597
  import { execFileSync as execFileSync4 } from "child_process";
@@ -1550,7 +1675,7 @@ function candidatesFor(command, repoRoot) {
1550
1675
  }
1551
1676
  function isLocalTool(repoRoot, name) {
1552
1677
  const binary = name.includes("/") ? name.split("/").pop() : name;
1553
- return existsSync6(join6(repoRoot, "node_modules", ".bin", binary));
1678
+ return existsSync7(join7(repoRoot, "node_modules", ".bin", binary));
1554
1679
  }
1555
1680
  function warn(verdicts) {
1556
1681
  const summary = plainSummary(verdicts);
@@ -1764,22 +1889,41 @@ async function runPreCommitHook() {
1764
1889
  }
1765
1890
 
1766
1891
  // bin/asen.ts
1892
+ import { readFileSync as readFileSync7 } from "fs";
1893
+ import { dirname as dirname2, join as join8 } from "path";
1894
+ import { fileURLToPath as fileURLToPath2 } from "url";
1895
+ function getVersion() {
1896
+ try {
1897
+ const file = fileURLToPath2(import.meta.url);
1898
+ const pkgPath = join8(dirname2(file), "..", "package.json");
1899
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
1900
+ return typeof pkg.version === "string" ? pkg.version : "unknown";
1901
+ } catch {
1902
+ return "unknown";
1903
+ }
1904
+ }
1767
1905
  var USAGE = `asen, a guard for AI coding agent workflows
1768
1906
 
1769
- asen init [--shim] set up the hooks and config in this repo
1907
+ asen init [--no-shim] set up the hooks and config in this repo
1770
1908
  asen check [pkg...] check staged dependencies, or specific packages
1771
1909
  asen allow <pkg> --reason "..." allowlist a flagged package, with a logged reason
1910
+ asen mode <warn|strict> switch Agentinel's operating mode
1911
+ asen uninstall remove all agent hooks from this repository
1772
1912
  asen unshim remove the PATH shims that --shim installed
1773
1913
  `;
1774
1914
  async function main(argv2) {
1775
1915
  const [command, ...rest] = argv2;
1776
1916
  switch (command) {
1777
1917
  case "init":
1778
- return runInit({ shim: rest.includes("--shim") });
1918
+ return runInit({ shim: !rest.includes("--no-shim") });
1779
1919
  case "check":
1780
1920
  return runCheck(positionals(rest));
1781
1921
  case "allow":
1782
1922
  return runAllow(positionals(rest)[0], readFlag(rest, "--reason"));
1923
+ case "mode":
1924
+ return runMode(positionals(rest)[0]);
1925
+ case "uninstall":
1926
+ return runUninstall();
1783
1927
  case "unshim":
1784
1928
  return removeShim();
1785
1929
  // Not documented in the usage text on purpose. These are what the installed hooks call.
@@ -1788,6 +1932,10 @@ async function main(argv2) {
1788
1932
  // What the PATH shims call, with the whole command line as one argument.
1789
1933
  case "check-command":
1790
1934
  return runCheckCommand(rest[0]);
1935
+ case "--version":
1936
+ case "-v":
1937
+ console.log(`agentinel v${getVersion()}`);
1938
+ return 0;
1791
1939
  case "--help":
1792
1940
  case "-h":
1793
1941
  case void 0:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentinel",
3
- "version": "0.0.10",
3
+ "version": "1.0.2",
4
4
  "description": "Catches AI hallucinated npm packages before an agent installs them",
5
5
  "license": "MIT",
6
6
  "author": "Aman Janwani",