@agentwares/agentguard 0.1.9 → 0.1.10

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 (62) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/audit-5LUDXZLG.js +18 -0
  3. package/dist/chunk-2QDPPTPY.js +179 -0
  4. package/dist/chunk-2QDPPTPY.js.map +1 -0
  5. package/dist/{chunk-SUXQTT3X.js → chunk-4WTFMFRY.js} +18 -16
  6. package/dist/chunk-4WTFMFRY.js.map +1 -0
  7. package/dist/{chunk-VQWQ5TOM.js → chunk-DEVXG3IB.js} +4 -4
  8. package/dist/chunk-DEVXG3IB.js.map +1 -0
  9. package/dist/chunk-F73VDY3W.js +202 -0
  10. package/dist/chunk-F73VDY3W.js.map +1 -0
  11. package/dist/{chunk-X4M7YWQA.js → chunk-IEJK7N3B.js} +32 -27
  12. package/dist/chunk-IEJK7N3B.js.map +1 -0
  13. package/dist/{chunk-ZSLWPKXI.js → chunk-IS3DAHZI.js} +562 -8
  14. package/dist/chunk-IS3DAHZI.js.map +1 -0
  15. package/dist/{chunk-KRKZGAJ3.js → chunk-JSELR4SG.js} +66 -55
  16. package/dist/chunk-JSELR4SG.js.map +1 -0
  17. package/dist/{chunk-TW5KJREH.js → chunk-OM5L7A32.js} +20 -11
  18. package/dist/chunk-OM5L7A32.js.map +1 -0
  19. package/dist/{chunk-HPYZ3JDX.js → chunk-SWZQMPDG.js} +3 -3
  20. package/dist/{chunk-HPYZ3JDX.js.map → chunk-SWZQMPDG.js.map} +1 -1
  21. package/dist/{chunk-D66U3PVB.js → chunk-TJXZBOHL.js} +6 -6
  22. package/dist/chunk-TJXZBOHL.js.map +1 -0
  23. package/dist/{chunk-BKXXTWIS.js → chunk-XOBFANL4.js} +5 -5
  24. package/dist/chunk-XOBFANL4.js.map +1 -0
  25. package/dist/chunk-XPNBYSIN.js +23 -0
  26. package/dist/chunk-XPNBYSIN.js.map +1 -0
  27. package/dist/{chunk-6S7LYVOC.js → chunk-YBVD6HTT.js} +27 -24
  28. package/dist/chunk-YBVD6HTT.js.map +1 -0
  29. package/dist/{chunk-6TUQD666.js → chunk-ZG4LGUJE.js} +100 -81
  30. package/dist/chunk-ZG4LGUJE.js.map +1 -0
  31. package/dist/cli.js +6 -4
  32. package/dist/fixtures/crm-server.js +2 -2
  33. package/dist/fixtures/demo-agent.js +1 -1
  34. package/dist/index.d.ts +5 -5
  35. package/dist/index.js +23 -21
  36. package/dist/index.js.map +1 -1
  37. package/dist/init-MKPQU2JG.js +17 -0
  38. package/dist/{proxy-3Z5YEZ4R.js → proxy-6GP63TLD.js} +21 -29
  39. package/dist/proxy-6GP63TLD.js.map +1 -0
  40. package/dist/tools-XS7PMTLT.js +15 -0
  41. package/llms.txt +8 -6
  42. package/package.json +10 -4
  43. package/server.json +9 -9
  44. package/dist/audit-2YCMHMCP.js +0 -17
  45. package/dist/chunk-3O6KWT3E.js +0 -345
  46. package/dist/chunk-3O6KWT3E.js.map +0 -1
  47. package/dist/chunk-6S7LYVOC.js.map +0 -1
  48. package/dist/chunk-6TUQD666.js.map +0 -1
  49. package/dist/chunk-BKXXTWIS.js.map +0 -1
  50. package/dist/chunk-D66U3PVB.js.map +0 -1
  51. package/dist/chunk-KRKZGAJ3.js.map +0 -1
  52. package/dist/chunk-SUXQTT3X.js.map +0 -1
  53. package/dist/chunk-TW5KJREH.js.map +0 -1
  54. package/dist/chunk-VQWQ5TOM.js.map +0 -1
  55. package/dist/chunk-X4M7YWQA.js.map +0 -1
  56. package/dist/chunk-ZSLWPKXI.js.map +0 -1
  57. package/dist/init-CWROGV5I.js +0 -15
  58. package/dist/proxy-3Z5YEZ4R.js.map +0 -1
  59. package/dist/tools-3LLDG3XQ.js +0 -15
  60. /package/dist/{audit-2YCMHMCP.js.map → audit-5LUDXZLG.js.map} +0 -0
  61. /package/dist/{init-CWROGV5I.js.map → init-MKPQU2JG.js.map} +0 -0
  62. /package/dist/{tools-3LLDG3XQ.js.map → tools-XS7PMTLT.js.map} +0 -0
package/llms.txt CHANGED
@@ -1,14 +1,16 @@
1
- # agentguard
1
+ # okgate
2
2
 
3
- > MCP policy proxy for AI agents: hard spend limits, destructive-action gating with approvals, kill switch, scoped agent keys, dry-run writes with mutation diffs, semantic loop breaker, blast-radius caps, hash-chained audit log. CLI `npx @agentwares/agentguard`; SDK `@agentwares/agentguard-sdk`. No LLM calls, no phone-home, no account.
3
+ > Nothing irreversible without an OK. okgate (formerly agentguard) is an MCP policy proxy and coding-agent hook: hard spend limits, destructive-action gating with approvals, kill switch, scoped agent keys, dry-run writes with mutation diffs, semantic loop breaker, blast-radius caps, hash-chained audit log. CLI `npx -p @agentwares/agentguard okgate`; SDK `@agentwares/agentguard-sdk`. No LLM calls, no phone-home, no account.
4
4
 
5
- Set up: `npx -y @agentwares/agentguard init` in the project directory writes agentguard.yaml (dry-run) and routes the project's MCP servers through the proxy. The same command ships as a Claude Code plugin (`/plugin marketplace add agentwares/agentguard`, then `/plugin install agentguard@agentwares`) and a Gemini CLI extension (`gemini extensions install https://github.com/agentwares/agentguard`).
5
+ Set up: `npx -y -p @agentwares/agentguard okgate init` in the project directory writes okgate.yaml (dry-run) and routes the project's MCP servers through the proxy. The same command ships as a Claude Code plugin (`/plugin marketplace add agentwares/agentguard`, then `/plugin install agentguard@agentwares`) and a Gemini CLI extension (`gemini extensions install https://github.com/agentwares/agentguard`).
6
6
 
7
- Started with no agentguard.yaml (an editor install button, a registry install, the repo's Dockerfile), the proxy fronts no servers and serves three read-only tools of its own: `agentguard_get_status` (what it guards, caps used and remaining, kill switch, pending approvals), `agentguard_get_report` (what a run did, or would have done in dry-run) and `agentguard_verify_audit_log` (the hash chain). With upstreams configured, the agent sees only the upstreams' tools.
7
+ Started with no okgate.yaml (an editor install button, a registry install, the repo's Dockerfile), the proxy fronts no servers and serves three read-only tools of its own: `okgate_get_status` (what it guards, caps used and remaining, kill switch, pending approvals), `okgate_get_report` (what a run did, or would have done in dry-run) and `okgate_verify_audit_log` (the hash chain). The agentguard_* names they had before the rename still work until 2027-01-15. With upstreams configured, the agent sees only the upstreams' tools.
8
8
 
9
- Hook mode: `npx -y @agentwares/agentguard hooks install` (add `--client codex`, `--client gemini` or `--client all`) writes a pre-execution hook (Claude Code PreToolUse, Codex PreToolUse, Gemini CLI BeforeTool) that checks the coding agent's own shell commands, file edits and MCP calls against the same agentguard.yaml. Irreversible commands (git push, merges, tag deletion, rm outside the workspace, package publish, sending mail or messages, edits to the guard itself) run only on a recent turn a person typed that names the action; in Claude Code that is read from the session transcript, and text the model writes never counts. Otherwise the agent gets APPROVAL_REQUIRED with an id, and `agentguard approve <id>` lets the identical command run once. Dry-run (log only) until `hooks.mode: enforce`.
9
+ Renamed from agentguard on 8 Oct 2026: agentguard.yaml, an existing .agentguard/ directory, AGENTGUARD_* variables, the `agentguard` bin and the agentguard_* tool names keep working; npm serves the `okgate` bin from @agentwares/agentguard until @agentwares/okgate is published.
10
10
 
11
- Audit: `npx -y @agentwares/agentguard audit [--since 7d|30d|all] [--json]` reads the repository's CLAUDE.md, AGENTS.md, GEMINI.md and .cursor/rules (and `audit.rules` in agentguard.yaml) and this machine's Claude Code and Codex sessions in it, and reports per rule how many sessions broke it and when. Rules are recognised by a pattern library with no LLM (never push to main, never force-push, never --no-verify, run X before pushing, use pnpm not npm, do not edit or commit a path, no new dependencies without asking, never rm outside the repo, never publish, never deploy, never run a named command), or stated with an inline `<!-- agentguard: <check> -->`. It runs locally and sends nothing; `--json` carries counts, rule ids and dates only. `--enforce` writes the checkable rules into agentguard.yaml as hook-mode rules in dry-run and installs the hook. The plugin's `/agentguard:audit` has your own agent judge the rules no script can check.
11
+ Hook mode: `npx -y -p @agentwares/agentguard okgate hooks install` (add `--client codex`, `--client gemini` or `--client all`) writes a pre-execution hook (Claude Code PreToolUse, Codex PreToolUse, Gemini CLI BeforeTool) that checks the coding agent's own shell commands, file edits and MCP calls against the same okgate.yaml. Irreversible commands (git push, merges, tag deletion, rm outside the workspace, package publish, sending mail or messages, edits to the guard itself) run only on a recent turn a person typed that names the action; in Claude Code that is read from the session transcript, and text the model writes never counts. Otherwise the agent gets APPROVAL_REQUIRED with an id, and `npx -p @agentwares/agentguard okgate approve <id>` lets the identical command run once. Dry-run (log only) until `hooks.mode: enforce`.
12
+
13
+ Audit: `npx -y -p @agentwares/agentguard okgate audit [--since 7d|30d|all] [--json]` reads the repository's CLAUDE.md, AGENTS.md, GEMINI.md and .cursor/rules (and `audit.rules` in okgate.yaml) and this machine's Claude Code and Codex sessions in it, and reports per rule how many sessions broke it and when. Rules are recognised by a pattern library with no LLM (never push to main, never force-push, never --no-verify, run X before pushing, use pnpm not npm, do not edit or commit a path, no new dependencies without asking, never rm outside the repo, never publish, never deploy, never run a named command), or stated with an inline `<!-- okgate: <check> -->` (`<!-- agentguard: … -->` still works). It runs locally and sends nothing; `--json` carries counts, rule ids and dates only. `--enforce` writes the checkable rules into okgate.yaml as hook-mode rules in dry-run and installs the hook. The plugin's `/agentguard:audit` has your own agent judge the rules no script can check.
12
14
 
13
15
  ## Links
14
16
 
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@agentwares/agentguard",
3
- "version": "0.1.9",
4
- "description": "MCP policy proxy for AI agents: hard spend limits, destructive-action gating with approvals, a kill switch, scoped credentials, dry-run writes with mutation diffs, loop breaker, blast-radius caps and a hash-chained audit log. Hook mode applies the same policy to Claude Code, Codex and Gemini CLI shell commands and file edits, and runs irreversible commands only on a turn a person typed. Audit mode reports which CLAUDE.md / AGENTS.md rules your coding agents broke, from your own sessions, locally. No LLM calls, no phone-home, no account.",
3
+ "version": "0.1.10",
4
+ "description": "okgate (formerly agentguard): nothing irreversible without an OK. MCP policy proxy for AI agents: hard spend limits, destructive-action gating with approvals, a kill switch, scoped credentials, dry-run writes with mutation diffs, loop breaker, blast-radius caps and a hash-chained audit log. Hook mode applies the same policy to Claude Code, Codex and Gemini CLI shell commands and file edits, and runs irreversible commands only on a turn a person typed. Audit mode reports which CLAUDE.md / AGENTS.md rules your coding agents broke, from your own sessions, locally. No LLM calls, no phone-home, no account. Runs as `okgate`; the `agentguard` bin, agentguard.yaml, .agentguard/ and AGENTGUARD_* keep working.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
8
+ "okgate": "./dist/cli.js",
8
9
  "agentguard": "./dist/cli.js"
9
10
  },
10
11
  "main": "./dist/index.js",
@@ -13,6 +14,9 @@
13
14
  ".": {
14
15
  "types": "./dist/index.d.ts",
15
16
  "import": "./dist/index.js"
17
+ },
18
+ "./cli": {
19
+ "import": "./dist/cli.js"
16
20
  }
17
21
  },
18
22
  "files": [
@@ -25,7 +29,7 @@
25
29
  "publishConfig": {
26
30
  "access": "public"
27
31
  },
28
- "mcpName": "io.github.agentwares/agentguard",
32
+ "mcpName": "io.github.agentwares/okgate",
29
33
  "repository": {
30
34
  "type": "git",
31
35
  "url": "git+https://github.com/agentwares/agentguard.git",
@@ -33,6 +37,8 @@
33
37
  },
34
38
  "homepage": "https://github.com/agentwares/agentguard#readme",
35
39
  "keywords": [
40
+ "okgate",
41
+ "agentguard",
36
42
  "mcp",
37
43
  "mcp-proxy",
38
44
  "model-context-protocol",
@@ -68,7 +74,7 @@
68
74
  "conformance": "node scripts/conformance.mjs"
69
75
  },
70
76
  "dependencies": {
71
- "@agentwares/agentguard-core": "^0.1.5",
77
+ "@agentwares/agentguard-core": "^0.1.6",
72
78
  "@modelcontextprotocol/sdk": "^1.30.0",
73
79
  "yaml": "^2.9.0",
74
80
  "zod": "^4.5.4"
package/server.json CHANGED
@@ -1,35 +1,35 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
- "name": "io.github.agentwares/agentguard",
4
- "description": "MCP policy proxy: spend caps, approvals for destructive tools, kill switch, dry-run, audit log.",
5
- "version": "0.1.9",
3
+ "name": "io.github.agentwares/okgate",
4
+ "description": "Nothing irreversible without an OK: MCP policy proxy with spend caps, approvals, kill switch.",
5
+ "version": "0.1.10",
6
6
  "repository": {
7
7
  "url": "https://github.com/agentwares/agentguard",
8
8
  "source": "github",
9
9
  "subfolder": "apps/agentguard-cli"
10
10
  },
11
- "title": "agentguard",
11
+ "title": "okgate",
12
12
  "websiteUrl": "https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli",
13
13
  "packages": [
14
14
  {
15
15
  "registryType": "npm",
16
16
  "registryBaseUrl": "https://registry.npmjs.org",
17
17
  "identifier": "@agentwares/agentguard",
18
- "version": "0.1.9",
18
+ "version": "0.1.10",
19
19
  "runtimeHint": "npx",
20
20
  "transport": {
21
21
  "type": "stdio"
22
22
  },
23
23
  "environmentVariables": [
24
24
  {
25
- "name": "AGENTGUARD_CONFIG",
26
- "description": "Path to agentguard.yaml (default ./agentguard.yaml). If unset and that file does not exist, agentguard serves only its own read-only tools: agentguard_get_status, agentguard_get_report, agentguard_verify_audit_log",
25
+ "name": "OKGATE_CONFIG",
26
+ "description": "Path to okgate.yaml (default ./okgate.yaml, else ./agentguard.yaml; AGENTGUARD_CONFIG also works). With neither file, okgate serves only its own read-only tools: okgate_get_status, okgate_get_report, okgate_verify_audit_log",
27
27
  "isRequired": false,
28
28
  "format": "filepath"
29
29
  },
30
30
  {
31
- "name": "AGENTGUARD_KILL",
32
- "description": "Set to 1 to halt every tool call (kill switch)",
31
+ "name": "OKGATE_KILL",
32
+ "description": "Set to 1 to halt every tool call (kill switch); AGENTGUARD_KILL=1 does the same",
33
33
  "isRequired": false
34
34
  }
35
35
  ]
@@ -1,17 +0,0 @@
1
- import {
2
- TEAM_HISTORY_LINE,
3
- TEAM_HISTORY_URL,
4
- auditCommand,
5
- parseSince
6
- } from "./chunk-6S7LYVOC.js";
7
- import "./chunk-3O6KWT3E.js";
8
- import "./chunk-TW5KJREH.js";
9
- import "./chunk-SUXQTT3X.js";
10
- import "./chunk-5WRI5ZAA.js";
11
- export {
12
- TEAM_HISTORY_LINE,
13
- TEAM_HISTORY_URL,
14
- auditCommand,
15
- parseSince
16
- };
17
- //# sourceMappingURL=audit-2YCMHMCP.js.map
@@ -1,345 +0,0 @@
1
- import {
2
- PROXY_PACKAGE
3
- } from "./chunk-TW5KJREH.js";
4
- import {
5
- flagBool,
6
- flagList,
7
- flagNumber,
8
- flagString,
9
- homeFrom,
10
- version
11
- } from "./chunk-SUXQTT3X.js";
12
-
13
- // src/hooks/clients.ts
14
- import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
15
- import { dirname, join } from "path";
16
- var MATCHERS = {
17
- "claude-code": "^(Bash|PowerShell|Write|Edit|MultiEdit|NotebookEdit)$|^mcp__",
18
- codex: "^(Bash|apply_patch)$|^mcp__",
19
- "gemini-cli": "^(run_shell_command|write_file|replace)$|^mcp_"
20
- };
21
- var CLIENT_ALIASES = {
22
- claude: "claude-code",
23
- "claude-code": "claude-code",
24
- codex: "codex",
25
- gemini: "gemini-cli",
26
- "gemini-cli": "gemini-cli"
27
- };
28
- function hookTarget(client, scope, where, command, timeoutS = 30) {
29
- const root = scope === "project" ? where.cwd : where.home;
30
- if (client === "claude-code")
31
- return {
32
- client,
33
- label: "Claude Code",
34
- scope,
35
- settingsPath: join(root, ".claude", "settings.json"),
36
- event: "PreToolUse",
37
- matcher: MATCHERS[client],
38
- handler: { type: "command", command, timeout: timeoutS, statusMessage: "agentguard" }
39
- };
40
- if (client === "codex")
41
- return {
42
- client,
43
- label: "Codex CLI",
44
- scope,
45
- settingsPath: join(root, ".codex", "hooks.json"),
46
- event: "PreToolUse",
47
- matcher: MATCHERS[client],
48
- handler: { type: "command", command, timeout: timeoutS, statusMessage: "agentguard" },
49
- note: "Codex runs a new hook only after you trust it: open /hooks in Codex and trust the agentguard entry."
50
- };
51
- return {
52
- client,
53
- label: "Gemini CLI",
54
- scope,
55
- settingsPath: join(root, ".gemini", "settings.json"),
56
- event: "BeforeTool",
57
- matcher: MATCHERS[client],
58
- handler: {
59
- name: "agentguard",
60
- type: "command",
61
- command,
62
- timeout: timeoutS * 1e3,
63
- description: "agentguard: one rulebook for shell commands and file edits"
64
- },
65
- note: scope === "project" ? "Gemini CLI fingerprints project hooks: confirm the agentguard hook the first time it asks." : void 0
66
- };
67
- }
68
- function isAgentguardHookCommand(command) {
69
- return typeof command === "string" && /agentguard/.test(command) && /\bhook\s+pre-tool-use\b/.test(command);
70
- }
71
- function readSettings(path) {
72
- if (!existsSync(path)) return {};
73
- const text = readFileSync(path, "utf8");
74
- if (!text.trim()) return {};
75
- let parsed;
76
- try {
77
- parsed = JSON.parse(text);
78
- } catch (err) {
79
- throw new Error(
80
- `${path} is not valid JSON (${err instanceof Error ? err.message : String(err)}); fix it or move it aside \u2014 agentguard will not overwrite it`,
81
- { cause: err }
82
- );
83
- }
84
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
85
- throw new Error(`${path} is not a JSON object; agentguard will not overwrite it`);
86
- return parsed;
87
- }
88
- function groupsOf(settings, event) {
89
- const hooks = settings.hooks;
90
- if (!hooks || typeof hooks !== "object") return [];
91
- const groups = hooks[event];
92
- return Array.isArray(groups) ? groups : [];
93
- }
94
- function installedHandlers(target) {
95
- const settings = readSettings(target.settingsPath);
96
- return groupsOf(settings, target.event).flatMap(
97
- (g) => (g.hooks ?? []).filter((h) => isAgentguardHookCommand(h.command))
98
- );
99
- }
100
- function installHook(target) {
101
- const existed = existsSync(target.settingsPath);
102
- const settings = readSettings(target.settingsPath);
103
- const hooks = settings.hooks && typeof settings.hooks === "object" ? settings.hooks : {};
104
- const groups = groupsOf(settings, target.event);
105
- const isOurs = (h) => isAgentguardHookCommand(h.command);
106
- const ours = groups.flatMap((g) => (g.hooks ?? []).filter(isOurs));
107
- const exact = groups.some(
108
- (g) => g.matcher === target.matcher && g.hooks?.length === 1 && JSON.stringify(g.hooks[0]) === JSON.stringify(target.handler)
109
- );
110
- if (exact && ours.length === 1) return { status: "unchanged" };
111
- const kept = [];
112
- for (const group of groups) {
113
- if (!(group.hooks ?? []).some(isOurs)) {
114
- kept.push(group);
115
- continue;
116
- }
117
- const others = (group.hooks ?? []).filter((h) => !isOurs(h));
118
- if (others.length > 0) kept.push({ ...group, hooks: others });
119
- }
120
- kept.push({ matcher: target.matcher, hooks: [target.handler] });
121
- const status = ours.length > 0 ? "updated" : "added";
122
- hooks[target.event] = kept;
123
- settings.hooks = hooks;
124
- let backup;
125
- if (existed) {
126
- backup = `${target.settingsPath}.agentguard-backup`;
127
- if (!existsSync(backup)) copyFileSync(target.settingsPath, backup);
128
- }
129
- mkdirSync(dirname(target.settingsPath), { recursive: true });
130
- writeFileSync(target.settingsPath, JSON.stringify(settings, null, 2) + "\n");
131
- return { status, backup };
132
- }
133
- function uninstallHook(target) {
134
- if (!existsSync(target.settingsPath)) return 0;
135
- const settings = readSettings(target.settingsPath);
136
- const groups = groupsOf(settings, target.event);
137
- let removed = 0;
138
- const kept = [];
139
- for (const group of groups) {
140
- const others = (group.hooks ?? []).filter((h) => !isAgentguardHookCommand(h.command));
141
- removed += (group.hooks ?? []).length - others.length;
142
- if (others.length > 0) kept.push({ ...group, hooks: others });
143
- }
144
- if (removed === 0) return 0;
145
- const hooks = settings.hooks;
146
- if (kept.length > 0) hooks[target.event] = kept;
147
- else delete hooks[target.event];
148
- if (Object.keys(hooks).length === 0) delete settings.hooks;
149
- writeFileSync(target.settingsPath, JSON.stringify(settings, null, 2) + "\n");
150
- return removed;
151
- }
152
- function hookSettingsFiles(where) {
153
- const out = [];
154
- for (const root of [where.cwd, where.home]) {
155
- out.push(
156
- join(root, ".claude", "settings.json"),
157
- join(root, ".claude", "settings.local.json"),
158
- join(root, ".codex", "hooks.json"),
159
- join(root, ".codex", "config.toml"),
160
- join(root, ".gemini", "settings.json")
161
- );
162
- }
163
- out.push(join(where.home, ".agentguard", "agentguard.yaml"));
164
- return out;
165
- }
166
-
167
- // src/commands/hooks.ts
168
- import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
169
- import { dirname as dirname2, join as join2, relative, resolve } from "path";
170
- var HOOKS_POLICY_BLOCK = `
171
- # Hook mode: your coding agent's own shell commands and file edits (Claude Code, Codex, Gemini CLI),
172
- # checked before they run. \`agentguard hooks install\` wired it; \`agentguard report\` shows the decisions.
173
- hooks:
174
- # dry-run: every decision is logged and nothing is blocked. Read \`agentguard report\`, then set
175
- # enforce. The kill switch (\`agentguard kill\`) stops guarded calls in both modes.
176
- mode: dry-run
177
- # Irreversible commands run only on a turn a person typed: after the session's previous
178
- # irreversible command, recent, and naming the action ("push it", or "yes" to "shall I push?").
179
- # Text the model writes, tool output and injected messages never count. Otherwise the agent
180
- # gets APPROVAL_REQUIRED and an id; \`agentguard approve <id>\` lets that exact command run once.
181
- # Built in: git-push, merge, tag-delete, rm-outside-workspace, publish, send, guard-config.
182
- irreversible:
183
- builtins: true
184
- window_s: 1800 # the authorizing turn is at most 30 minutes old
185
- require_mention: true # ...and names the action
186
- require_origin: false # true: only entries Claude Code labels as typed by a person
187
- mentions: {} # extra words per class, e.g. { git-push: [deploy] }
188
- rules: []
189
- # - name: deploy
190
- # shell: ["vercel --prod*", "fly deploy*"]
191
- # mentions: [deploy]
192
- shell:
193
- deny: [] # e.g. ["terraform destroy*", "curl * | sh"]
194
- approval: [] # e.g. ["kubectl delete *"]
195
- paths:
196
- deny: [] # e.g. [".env", "*.pem"]
197
- approval: [] # e.g. ["**/migrations/**"]
198
- `;
199
- function renderHooksStarterPolicy(generatedAt = /* @__PURE__ */ new Date()) {
200
- return `# agentguard.yaml \u2014 generated by \`agentguard hooks install\` on ${generatedAt.toISOString().slice(0, 10)}
201
- # Docs: https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli#hook-mode-a-coding-agents-own-commands
202
- version: 1
203
-
204
- # MCP proxy mode (agentguard init) reads upstreams; hook mode needs none.
205
- mode: dry-run
206
- upstreams: []
207
- ${HOOKS_POLICY_BLOCK}
208
- # Kill switch: \`agentguard kill\` creates this file; every guarded call is refused until
209
- # \`agentguard resume\`. AGENTGUARD_KILL=1 in the environment does the same.
210
- kill:
211
- file: .agentguard/KILL
212
-
213
- # Hash-chained JSONL of every decision; \`agentguard verify\` proves it was not edited.
214
- audit:
215
- path: .agentguard/audit.jsonl
216
- redact: true
217
- `;
218
- }
219
- function quoteArg(arg) {
220
- return /^[A-Za-z0-9_@%+=:,./-]+$/.test(arg) ? arg : JSON.stringify(arg);
221
- }
222
- function clientsFrom(args) {
223
- const names = flagList(args, "client");
224
- if (names.length === 0) return ["claude-code"];
225
- if (names.includes("all")) return ["claude-code", "codex", "gemini-cli"];
226
- return names.map((n) => {
227
- const c = CLIENT_ALIASES[n.trim()];
228
- if (!c) throw new Error(`unknown --client "${n}" (claude, codex, gemini or all)`);
229
- return c;
230
- });
231
- }
232
- function hasTopLevelHooks(yamlText) {
233
- return /^hooks\s*:/m.test(yamlText);
234
- }
235
- async function hooksCommand(args, io) {
236
- const sub = args.positionals[0] ?? "status";
237
- const scope = flagBool(args, "user") ? "user" : "project";
238
- const home = homeFrom(io);
239
- const where = { cwd: io.cwd, home };
240
- const rel = (p) => {
241
- const r = relative(io.cwd, p);
242
- return r.startsWith("..") ? p : r || ".";
243
- };
244
- const clients = clientsFrom(args);
245
- const policyPath = resolve(
246
- io.cwd,
247
- flagString(args, "config") ?? (scope === "user" ? join2(home, ".agentguard", "agentguard.yaml") : "agentguard.yaml")
248
- );
249
- const base = flagString(args, "command") ?? `npx -y ${PROXY_PACKAGE}@${version()}`;
250
- const commandFor = (client) => {
251
- let cmd = `${base} hook pre-tool-use --client ${client === "claude-code" ? "claude" : client === "gemini-cli" ? "gemini" : "codex"}`;
252
- if (flagString(args, "config")) cmd += ` --config ${quoteArg(policyPath)}`;
253
- else if (scope === "user") cmd += ` --fallback-config ${quoteArg(policyPath)}`;
254
- return cmd;
255
- };
256
- const timeoutS = Math.max(30, flagNumber(args, "timeout") ?? 30);
257
- if (sub === "install") {
258
- if (!existsSync2(policyPath)) {
259
- mkdirSync2(dirname2(policyPath), { recursive: true });
260
- writeFileSync2(policyPath, renderHooksStarterPolicy());
261
- io.out(`wrote ${rel(policyPath)} (hooks.mode: dry-run)`);
262
- } else if (!hasTopLevelHooks(readFileSync2(policyPath, "utf8"))) {
263
- appendFileSync(policyPath, HOOKS_POLICY_BLOCK);
264
- io.out(`added a hooks: block to ${rel(policyPath)} (hooks.mode: dry-run)`);
265
- } else io.out(`kept ${rel(policyPath)} (it already has a hooks: block)`);
266
- for (const client of clients) {
267
- const target = hookTarget(client, scope, where, commandFor(client), timeoutS);
268
- const { status, backup } = installHook(target);
269
- const verb = status === "unchanged" ? "kept " : status === "updated" ? "updated" : "added ";
270
- io.out(
271
- `${verb} ${target.label} ${target.event} hook in ${rel(target.settingsPath)}${backup ? ` (backup: ${rel(backup)})` : ""}`
272
- );
273
- if (target.note) io.out(` ${target.note}`);
274
- }
275
- if (scope === "project" && existsSync2(join2(io.cwd, ".git"))) {
276
- const gitignore = join2(io.cwd, ".gitignore");
277
- const text = existsSync2(gitignore) ? readFileSync2(gitignore, "utf8") : "";
278
- if (!/^\/?\.agentguard\/?$/m.test(text)) {
279
- appendFileSync(gitignore, `${text && !text.endsWith("\n") ? "\n" : ""}.agentguard/
280
- `);
281
- io.out(
282
- `added .agentguard/ to .gitignore (approvals, counters and the audit log stay local)`
283
- );
284
- }
285
- }
286
- io.out("");
287
- io.out("Next:");
288
- io.out(" 1. start a new session of your coding agent (hooks are read at start)");
289
- io.out(" 2. work as usual \u2014 hooks.mode is dry-run: decisions are logged, nothing is blocked");
290
- io.out(" 3. agentguard report # what enforce mode would have stopped, and why");
291
- io.out(` 4. set \`hooks.mode: enforce\` in ${rel(policyPath)} when it looks right`);
292
- io.out(" undo: agentguard hooks uninstall" + (scope === "user" ? " --user" : ""));
293
- return 0;
294
- }
295
- if (sub === "uninstall") {
296
- let total = 0;
297
- for (const client of flagString(args, "client") ? clients : ["claude-code", "codex", "gemini-cli"]) {
298
- const target = hookTarget(client, scope, where, commandFor(client), timeoutS);
299
- const removed = uninstallHook(target);
300
- total += removed;
301
- if (removed) io.out(`removed ${target.label} hook from ${rel(target.settingsPath)}`);
302
- }
303
- if (total === 0) io.out(`no agentguard hooks in ${scope}-level settings`);
304
- else
305
- io.out(
306
- `kept ${rel(policyPath)} and .agentguard/ (delete them yourself if you want them gone)`
307
- );
308
- return 0;
309
- }
310
- if (sub === "status") {
311
- for (const s of ["project", "user"]) {
312
- for (const client of ["claude-code", "codex", "gemini-cli"]) {
313
- const target = hookTarget(client, s, where, "", timeoutS);
314
- let handlers;
315
- try {
316
- handlers = installedHandlers(target);
317
- } catch (err) {
318
- io.out(
319
- `${s.padEnd(7)} ${target.label.padEnd(11)} unreadable: ${err instanceof Error ? err.message : String(err)}`
320
- );
321
- continue;
322
- }
323
- io.out(
324
- `${s.padEnd(7)} ${target.label.padEnd(11)} ${handlers.length ? `installed: ${String(handlers[0].command)}` : "\u2014"} (${rel(target.settingsPath)})`
325
- );
326
- }
327
- }
328
- return 0;
329
- }
330
- io.err(
331
- "usage: agentguard hooks install|uninstall|status [--project|--user] [--client claude|codex|gemini|all] [--command cmd] [--config path]"
332
- );
333
- return 1;
334
- }
335
-
336
- export {
337
- CLIENT_ALIASES,
338
- hookTarget,
339
- installedHandlers,
340
- hookSettingsFiles,
341
- HOOKS_POLICY_BLOCK,
342
- renderHooksStarterPolicy,
343
- hooksCommand
344
- };
345
- //# sourceMappingURL=chunk-3O6KWT3E.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/clients.ts","../src/commands/hooks.ts"],"sourcesContent":["/**\n * Where each coding agent reads its pre-execution hooks, and the entry agentguard writes there.\n * Verified 7 October 2026 against:\n *\n * - Claude Code: https://code.claude.com/docs/en/hooks — `hooks.PreToolUse[]` in\n * `.claude/settings.json` (project) or `~/.claude/settings.json` (user); a matcher with\n * characters beyond letters, digits, `_`, `-`, `,` and `|` is an unanchored regex; `timeout`\n * in seconds.\n * - Codex CLI: https://developers.openai.com/codex/hooks — `hooks.PreToolUse[]` in\n * `.codex/hooks.json` or `~/.codex/hooks.json`; the matcher is a regex over the tool name\n * (`Bash`, `apply_patch`, `mcp__…`); `timeout` in seconds; a new hook runs only after the user\n * trusts it in `/hooks`.\n * - Gemini CLI: https://github.com/google-gemini/gemini-cli/blob/main/docs/hooks/reference.md —\n * `hooks.BeforeTool[]` in `.gemini/settings.json` or `~/.gemini/settings.json`; regex matcher\n * over `run_shell_command`, `write_file`, `replace`, `mcp_…`; `timeout` in milliseconds.\n */\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport type { HookClient } from \"@agentwares/agentguard-core\";\n\nexport type InstallScope = \"project\" | \"user\";\n\nexport interface HookTarget {\n client: HookClient;\n label: string;\n scope: InstallScope;\n settingsPath: string;\n event: \"PreToolUse\" | \"BeforeTool\";\n matcher: string;\n handler: Record<string, unknown>;\n /** what the user has to do in the client before the hook runs */\n note?: string;\n}\n\n/** Tools worth a check: the shell, file writes and MCP tools. Reads are left alone. */\nexport const MATCHERS: Record<HookClient, string> = {\n \"claude-code\": \"^(Bash|PowerShell|Write|Edit|MultiEdit|NotebookEdit)$|^mcp__\",\n codex: \"^(Bash|apply_patch)$|^mcp__\",\n \"gemini-cli\": \"^(run_shell_command|write_file|replace)$|^mcp_\",\n};\n\nexport const CLIENT_ALIASES: Record<string, HookClient> = {\n claude: \"claude-code\",\n \"claude-code\": \"claude-code\",\n codex: \"codex\",\n gemini: \"gemini-cli\",\n \"gemini-cli\": \"gemini-cli\",\n};\n\nexport function hookTarget(\n client: HookClient,\n scope: InstallScope,\n where: { cwd: string; home: string },\n command: string,\n timeoutS = 30,\n): HookTarget {\n const root = scope === \"project\" ? where.cwd : where.home;\n if (client === \"claude-code\")\n return {\n client,\n label: \"Claude Code\",\n scope,\n settingsPath: join(root, \".claude\", \"settings.json\"),\n event: \"PreToolUse\",\n matcher: MATCHERS[client],\n handler: { type: \"command\", command, timeout: timeoutS, statusMessage: \"agentguard\" },\n };\n if (client === \"codex\")\n return {\n client,\n label: \"Codex CLI\",\n scope,\n settingsPath: join(root, \".codex\", \"hooks.json\"),\n event: \"PreToolUse\",\n matcher: MATCHERS[client],\n handler: { type: \"command\", command, timeout: timeoutS, statusMessage: \"agentguard\" },\n note: \"Codex runs a new hook only after you trust it: open /hooks in Codex and trust the agentguard entry.\",\n };\n return {\n client,\n label: \"Gemini CLI\",\n scope,\n settingsPath: join(root, \".gemini\", \"settings.json\"),\n event: \"BeforeTool\",\n matcher: MATCHERS[client],\n handler: {\n name: \"agentguard\",\n type: \"command\",\n command,\n timeout: timeoutS * 1000,\n description: \"agentguard: one rulebook for shell commands and file edits\",\n },\n note:\n scope === \"project\"\n ? \"Gemini CLI fingerprints project hooks: confirm the agentguard hook the first time it asks.\"\n : undefined,\n };\n}\n\n/** Is this hook command one agentguard wrote? */\nexport function isAgentguardHookCommand(command: unknown): boolean {\n return (\n typeof command === \"string\" &&\n /agentguard/.test(command) &&\n /\\bhook\\s+pre-tool-use\\b/.test(command)\n );\n}\n\ntype Json = Record<string, unknown>;\ninterface MatcherGroup {\n matcher?: string;\n hooks?: Json[];\n [k: string]: unknown;\n}\n\nfunction readSettings(path: string): Json {\n if (!existsSync(path)) return {};\n const text = readFileSync(path, \"utf8\");\n if (!text.trim()) return {};\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n throw new Error(\n `${path} is not valid JSON (${err instanceof Error ? err.message : String(err)}); fix it or move it aside — agentguard will not overwrite it`,\n { cause: err },\n );\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed))\n throw new Error(`${path} is not a JSON object; agentguard will not overwrite it`);\n return parsed as Json;\n}\n\nfunction groupsOf(settings: Json, event: string): MatcherGroup[] {\n const hooks = settings.hooks;\n if (!hooks || typeof hooks !== \"object\") return [];\n const groups = (hooks as Json)[event];\n return Array.isArray(groups) ? (groups as MatcherGroup[]) : [];\n}\n\n/** The agentguard handlers currently in a settings file. */\nexport function installedHandlers(target: HookTarget): Json[] {\n const settings = readSettings(target.settingsPath);\n return groupsOf(settings, target.event).flatMap((g) =>\n (g.hooks ?? []).filter((h) => isAgentguardHookCommand(h.command)),\n );\n}\n\n/**\n * Write (or update in place) the agentguard entry, keeping every other setting and hook. The\n * first change to an existing file leaves a `*.agentguard-backup` next to it.\n */\nexport function installHook(target: HookTarget): {\n status: \"added\" | \"updated\" | \"unchanged\";\n backup?: string;\n} {\n const existed = existsSync(target.settingsPath);\n const settings = readSettings(target.settingsPath);\n const hooks = (\n settings.hooks && typeof settings.hooks === \"object\" ? settings.hooks : {}\n ) as Json;\n const groups = groupsOf(settings, target.event);\n const isOurs = (h: Json): boolean => isAgentguardHookCommand(h.command);\n const ours = groups.flatMap((g) => (g.hooks ?? []).filter(isOurs));\n const exact = groups.some(\n (g) =>\n g.matcher === target.matcher &&\n g.hooks?.length === 1 &&\n JSON.stringify(g.hooks[0]) === JSON.stringify(target.handler),\n );\n if (exact && ours.length === 1) return { status: \"unchanged\" };\n const kept: MatcherGroup[] = [];\n for (const group of groups) {\n if (!(group.hooks ?? []).some(isOurs)) {\n kept.push(group);\n continue;\n }\n const others = (group.hooks ?? []).filter((h) => !isOurs(h));\n if (others.length > 0) kept.push({ ...group, hooks: others });\n }\n kept.push({ matcher: target.matcher, hooks: [target.handler] });\n const status = ours.length > 0 ? \"updated\" : \"added\";\n hooks[target.event] = kept;\n settings.hooks = hooks;\n let backup: string | undefined;\n if (existed) {\n backup = `${target.settingsPath}.agentguard-backup`;\n if (!existsSync(backup)) copyFileSync(target.settingsPath, backup);\n }\n mkdirSync(dirname(target.settingsPath), { recursive: true });\n writeFileSync(target.settingsPath, JSON.stringify(settings, null, 2) + \"\\n\");\n return { status, backup };\n}\n\n/** Remove agentguard's entries; other hooks and settings stay. Returns how many were removed. */\nexport function uninstallHook(target: HookTarget): number {\n if (!existsSync(target.settingsPath)) return 0;\n const settings = readSettings(target.settingsPath);\n const groups = groupsOf(settings, target.event);\n let removed = 0;\n const kept: MatcherGroup[] = [];\n for (const group of groups) {\n const others = (group.hooks ?? []).filter((h) => !isAgentguardHookCommand(h.command));\n removed += (group.hooks ?? []).length - others.length;\n if (others.length > 0) kept.push({ ...group, hooks: others });\n }\n if (removed === 0) return 0;\n const hooks = settings.hooks as Json;\n if (kept.length > 0) hooks[target.event] = kept;\n else delete hooks[target.event];\n if (Object.keys(hooks).length === 0) delete settings.hooks;\n writeFileSync(target.settingsPath, JSON.stringify(settings, null, 2) + \"\\n\");\n return removed;\n}\n\n/** Every settings file that can hold a hook, project and user level: edits to them are guard-config. */\nexport function hookSettingsFiles(where: { cwd: string; home: string }): string[] {\n const out: string[] = [];\n for (const root of [where.cwd, where.home]) {\n out.push(\n join(root, \".claude\", \"settings.json\"),\n join(root, \".claude\", \"settings.local.json\"),\n join(root, \".codex\", \"hooks.json\"),\n join(root, \".codex\", \"config.toml\"),\n join(root, \".gemini\", \"settings.json\"),\n );\n }\n out.push(join(where.home, \".agentguard\", \"agentguard.yaml\"));\n return out;\n}\n","/**\n * `agentguard hooks install|uninstall|status [--project|--user] [--client claude|codex|gemini]`\n *\n * Install writes the coding agent's pre-execution hook entry (Claude Code `PreToolUse`, Codex\n * `PreToolUse`, Gemini CLI `BeforeTool`) pointing at `agentguard hook pre-tool-use`, and makes\n * sure the policy has a `hooks:` block in dry-run. Uninstall removes only agentguard's entries.\n */\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, relative, resolve } from \"node:path\";\nimport type { HookClient } from \"@agentwares/agentguard-core\";\nimport { flagBool, flagList, flagNumber, flagString, type ParsedArgs } from \"../args.js\";\nimport { homeFrom, type Io } from \"../context.js\";\nimport {\n CLIENT_ALIASES,\n hookTarget,\n installHook,\n installedHandlers,\n uninstallHook,\n type InstallScope,\n} from \"../hooks/clients.js\";\nimport { PROXY_PACKAGE } from \"../configs.js\";\nimport { version } from \"../version.js\";\n\n/** The commented `hooks:` block: the file people read, so every knob explains itself. */\nexport const HOOKS_POLICY_BLOCK = `\n# Hook mode: your coding agent's own shell commands and file edits (Claude Code, Codex, Gemini CLI),\n# checked before they run. \\`agentguard hooks install\\` wired it; \\`agentguard report\\` shows the decisions.\nhooks:\n # dry-run: every decision is logged and nothing is blocked. Read \\`agentguard report\\`, then set\n # enforce. The kill switch (\\`agentguard kill\\`) stops guarded calls in both modes.\n mode: dry-run\n # Irreversible commands run only on a turn a person typed: after the session's previous\n # irreversible command, recent, and naming the action (\"push it\", or \"yes\" to \"shall I push?\").\n # Text the model writes, tool output and injected messages never count. Otherwise the agent\n # gets APPROVAL_REQUIRED and an id; \\`agentguard approve <id>\\` lets that exact command run once.\n # Built in: git-push, merge, tag-delete, rm-outside-workspace, publish, send, guard-config.\n irreversible:\n builtins: true\n window_s: 1800 # the authorizing turn is at most 30 minutes old\n require_mention: true # ...and names the action\n require_origin: false # true: only entries Claude Code labels as typed by a person\n mentions: {} # extra words per class, e.g. { git-push: [deploy] }\n rules: []\n # - name: deploy\n # shell: [\"vercel --prod*\", \"fly deploy*\"]\n # mentions: [deploy]\n shell:\n deny: [] # e.g. [\"terraform destroy*\", \"curl * | sh\"]\n approval: [] # e.g. [\"kubectl delete *\"]\n paths:\n deny: [] # e.g. [\".env\", \"*.pem\"]\n approval: [] # e.g. [\"**/migrations/**\"]\n`;\n\n/** A policy for a project that has no agentguard.yaml yet: hooks only, no MCP upstreams. */\nexport function renderHooksStarterPolicy(generatedAt = new Date()): string {\n return `# agentguard.yaml — generated by \\`agentguard hooks install\\` on ${generatedAt.toISOString().slice(0, 10)}\n# Docs: https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli#hook-mode-a-coding-agents-own-commands\nversion: 1\n\n# MCP proxy mode (agentguard init) reads upstreams; hook mode needs none.\nmode: dry-run\nupstreams: []\n${HOOKS_POLICY_BLOCK}\n# Kill switch: \\`agentguard kill\\` creates this file; every guarded call is refused until\n# \\`agentguard resume\\`. AGENTGUARD_KILL=1 in the environment does the same.\nkill:\n file: .agentguard/KILL\n\n# Hash-chained JSONL of every decision; \\`agentguard verify\\` proves it was not edited.\naudit:\n path: .agentguard/audit.jsonl\n redact: true\n`;\n}\n\nfunction quoteArg(arg: string): string {\n return /^[A-Za-z0-9_@%+=:,./-]+$/.test(arg) ? arg : JSON.stringify(arg);\n}\n\nfunction clientsFrom(args: ParsedArgs): HookClient[] {\n const names = flagList(args, \"client\");\n if (names.length === 0) return [\"claude-code\"];\n if (names.includes(\"all\")) return [\"claude-code\", \"codex\", \"gemini-cli\"];\n return names.map((n) => {\n const c = CLIENT_ALIASES[n.trim()];\n if (!c) throw new Error(`unknown --client \"${n}\" (claude, codex, gemini or all)`);\n return c;\n });\n}\n\nfunction hasTopLevelHooks(yamlText: string): boolean {\n return /^hooks\\s*:/m.test(yamlText);\n}\n\nexport async function hooksCommand(args: ParsedArgs, io: Io): Promise<number> {\n const sub = args.positionals[0] ?? \"status\";\n const scope: InstallScope = flagBool(args, \"user\") ? \"user\" : \"project\";\n const home = homeFrom(io);\n const where = { cwd: io.cwd, home };\n const rel = (p: string): string => {\n const r = relative(io.cwd, p);\n return r.startsWith(\"..\") ? p : r || \".\";\n };\n const clients = clientsFrom(args);\n const policyPath = resolve(\n io.cwd,\n flagString(args, \"config\") ??\n (scope === \"user\" ? join(home, \".agentguard\", \"agentguard.yaml\") : \"agentguard.yaml\"),\n );\n const base = flagString(args, \"command\") ?? `npx -y ${PROXY_PACKAGE}@${version()}`;\n const commandFor = (client: HookClient): string => {\n let cmd = `${base} hook pre-tool-use --client ${client === \"claude-code\" ? \"claude\" : client === \"gemini-cli\" ? \"gemini\" : \"codex\"}`;\n if (flagString(args, \"config\")) cmd += ` --config ${quoteArg(policyPath)}`;\n else if (scope === \"user\") cmd += ` --fallback-config ${quoteArg(policyPath)}`;\n return cmd;\n };\n const timeoutS = Math.max(30, flagNumber(args, \"timeout\") ?? 30);\n\n if (sub === \"install\") {\n // the policy first: a hook with no policy to read guards nothing\n if (!existsSync(policyPath)) {\n mkdirSync(dirname(policyPath), { recursive: true });\n writeFileSync(policyPath, renderHooksStarterPolicy());\n io.out(`wrote ${rel(policyPath)} (hooks.mode: dry-run)`);\n } else if (!hasTopLevelHooks(readFileSync(policyPath, \"utf8\"))) {\n appendFileSync(policyPath, HOOKS_POLICY_BLOCK);\n io.out(`added a hooks: block to ${rel(policyPath)} (hooks.mode: dry-run)`);\n } else io.out(`kept ${rel(policyPath)} (it already has a hooks: block)`);\n\n for (const client of clients) {\n const target = hookTarget(client, scope, where, commandFor(client), timeoutS);\n const { status, backup } = installHook(target);\n const verb =\n status === \"unchanged\" ? \"kept \" : status === \"updated\" ? \"updated\" : \"added \";\n io.out(\n `${verb} ${target.label} ${target.event} hook in ${rel(target.settingsPath)}${backup ? ` (backup: ${rel(backup)})` : \"\"}`,\n );\n if (target.note) io.out(` ${target.note}`);\n }\n\n if (scope === \"project\" && existsSync(join(io.cwd, \".git\"))) {\n const gitignore = join(io.cwd, \".gitignore\");\n const text = existsSync(gitignore) ? readFileSync(gitignore, \"utf8\") : \"\";\n if (!/^\\/?\\.agentguard\\/?$/m.test(text)) {\n appendFileSync(gitignore, `${text && !text.endsWith(\"\\n\") ? \"\\n\" : \"\"}.agentguard/\\n`);\n io.out(\n `added .agentguard/ to .gitignore (approvals, counters and the audit log stay local)`,\n );\n }\n }\n\n io.out(\"\");\n io.out(\"Next:\");\n io.out(\" 1. start a new session of your coding agent (hooks are read at start)\");\n io.out(\" 2. work as usual — hooks.mode is dry-run: decisions are logged, nothing is blocked\");\n io.out(\" 3. agentguard report # what enforce mode would have stopped, and why\");\n io.out(` 4. set \\`hooks.mode: enforce\\` in ${rel(policyPath)} when it looks right`);\n io.out(\" undo: agentguard hooks uninstall\" + (scope === \"user\" ? \" --user\" : \"\"));\n return 0;\n }\n\n if (sub === \"uninstall\") {\n let total = 0;\n for (const client of flagString(args, \"client\")\n ? clients\n : ([\"claude-code\", \"codex\", \"gemini-cli\"] as HookClient[])) {\n const target = hookTarget(client, scope, where, commandFor(client), timeoutS);\n const removed = uninstallHook(target);\n total += removed;\n if (removed) io.out(`removed ${target.label} hook from ${rel(target.settingsPath)}`);\n }\n if (total === 0) io.out(`no agentguard hooks in ${scope}-level settings`);\n else\n io.out(\n `kept ${rel(policyPath)} and .agentguard/ (delete them yourself if you want them gone)`,\n );\n return 0;\n }\n\n if (sub === \"status\") {\n for (const s of [\"project\", \"user\"] as InstallScope[]) {\n for (const client of [\"claude-code\", \"codex\", \"gemini-cli\"] as HookClient[]) {\n const target = hookTarget(client, s, where, \"\", timeoutS);\n let handlers: Record<string, unknown>[];\n try {\n handlers = installedHandlers(target);\n } catch (err) {\n io.out(\n `${s.padEnd(7)} ${target.label.padEnd(11)} unreadable: ${err instanceof Error ? err.message : String(err)}`,\n );\n continue;\n }\n io.out(\n `${s.padEnd(7)} ${target.label.padEnd(11)} ${handlers.length ? `installed: ${String(handlers[0]!.command)}` : \"—\"} (${rel(target.settingsPath)})`,\n );\n }\n }\n return 0;\n }\n\n io.err(\n \"usage: agentguard hooks install|uninstall|status [--project|--user] [--client claude|codex|gemini|all] [--command cmd] [--config path]\",\n );\n return 1;\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readFileSync","writeFileSync","dirname","join","join","existsSync","mkdirSync","dirname","writeFileSync","readFileSync"]}