@agentwares/agentguard 0.1.7 → 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 (52) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +223 -16
  3. package/dist/audit-5LUDXZLG.js +18 -0
  4. package/dist/audit-5LUDXZLG.js.map +1 -0
  5. package/dist/chunk-2QDPPTPY.js +179 -0
  6. package/dist/chunk-2QDPPTPY.js.map +1 -0
  7. package/dist/chunk-4WTFMFRY.js +1270 -0
  8. package/dist/chunk-4WTFMFRY.js.map +1 -0
  9. package/dist/chunk-5WRI5ZAA.js +31 -0
  10. package/dist/chunk-5WRI5ZAA.js.map +1 -0
  11. package/dist/chunk-DEVXG3IB.js +113 -0
  12. package/dist/chunk-DEVXG3IB.js.map +1 -0
  13. package/dist/chunk-F73VDY3W.js +202 -0
  14. package/dist/chunk-F73VDY3W.js.map +1 -0
  15. package/dist/chunk-IEJK7N3B.js +275 -0
  16. package/dist/chunk-IEJK7N3B.js.map +1 -0
  17. package/dist/chunk-IS3DAHZI.js +1698 -0
  18. package/dist/chunk-IS3DAHZI.js.map +1 -0
  19. package/dist/chunk-JSELR4SG.js +780 -0
  20. package/dist/chunk-JSELR4SG.js.map +1 -0
  21. package/dist/chunk-OM5L7A32.js +199 -0
  22. package/dist/chunk-OM5L7A32.js.map +1 -0
  23. package/dist/chunk-SWZQMPDG.js +206 -0
  24. package/dist/chunk-SWZQMPDG.js.map +1 -0
  25. package/dist/chunk-TJXZBOHL.js +529 -0
  26. package/dist/chunk-TJXZBOHL.js.map +1 -0
  27. package/dist/chunk-XOBFANL4.js +119 -0
  28. package/dist/chunk-XOBFANL4.js.map +1 -0
  29. package/dist/chunk-XPNBYSIN.js +23 -0
  30. package/dist/chunk-XPNBYSIN.js.map +1 -0
  31. package/dist/chunk-YBVD6HTT.js +1036 -0
  32. package/dist/chunk-YBVD6HTT.js.map +1 -0
  33. package/dist/chunk-ZG4LGUJE.js +847 -0
  34. package/dist/chunk-ZG4LGUJE.js.map +1 -0
  35. package/dist/cli.js +10 -4775
  36. package/dist/cli.js.map +1 -1
  37. package/dist/fixtures/crm-server.js +6 -1272
  38. package/dist/fixtures/crm-server.js.map +1 -1
  39. package/dist/fixtures/demo-agent.js +4 -115
  40. package/dist/fixtures/demo-agent.js.map +1 -1
  41. package/dist/index.d.ts +73 -14
  42. package/dist/index.js +247 -5138
  43. package/dist/index.js.map +1 -1
  44. package/dist/init-MKPQU2JG.js +17 -0
  45. package/dist/init-MKPQU2JG.js.map +1 -0
  46. package/dist/proxy-6GP63TLD.js +146 -0
  47. package/dist/proxy-6GP63TLD.js.map +1 -0
  48. package/dist/tools-XS7PMTLT.js +15 -0
  49. package/dist/tools-XS7PMTLT.js.map +1 -0
  50. package/llms.txt +10 -4
  51. package/package.json +16 -4
  52. package/server.json +9 -9
@@ -0,0 +1,113 @@
1
+ import {
2
+ ProxyRuntime
3
+ } from "./chunk-TJXZBOHL.js";
4
+ import {
5
+ flagBool,
6
+ loadPolicyFor
7
+ } from "./chunk-4WTFMFRY.js";
8
+
9
+ // src/commands/tools.ts
10
+ import {
11
+ Guard,
12
+ classifyTool,
13
+ parsePolicy
14
+ } from "@agentwares/agentguard-core";
15
+ async function probeUpstreams(upstreams, opts = {}) {
16
+ const policy = opts.policy ?? parsePolicy({ upstreams });
17
+ const loaded = {
18
+ policy: { ...policy, upstreams },
19
+ path: "",
20
+ baseDir: process.cwd(),
21
+ stateDir: "",
22
+ auditPath: "",
23
+ killPath: ""
24
+ };
25
+ const guard = new Guard({ policy: loaded.policy, env: opts.env ?? {} });
26
+ const runtime = new ProxyRuntime({
27
+ loaded,
28
+ guard,
29
+ defaultRunId: "probe",
30
+ log: opts.log ?? (() => void 0),
31
+ connectTimeoutMs: opts.timeoutMs ?? 15e3
32
+ });
33
+ try {
34
+ await runtime.start();
35
+ return {
36
+ tools: runtime.listTools().map((t) => ({
37
+ upstream: t.upstream,
38
+ name: t.name,
39
+ tool: t.tool,
40
+ classification: classifyTool(
41
+ { name: t.name, annotations: t.tool.annotations },
42
+ loaded.policy
43
+ )
44
+ })),
45
+ upstreams: runtime.upstreamStatus()
46
+ };
47
+ } finally {
48
+ await runtime.close();
49
+ }
50
+ }
51
+ function formatToolTable(tools) {
52
+ const width = Math.min(48, Math.max(12, ...tools.map((t) => t.name.length)));
53
+ const lines = [
54
+ `${"tool".padEnd(width)} ${"class".padEnd(8)}${"verb".padEnd(9)}${"upstream".padEnd(14)}why`
55
+ ];
56
+ for (const t of tools) {
57
+ const c = t.classification;
58
+ lines.push(
59
+ `${t.name.padEnd(width)} ${c.class.padEnd(8)}${c.verb.padEnd(9)}${t.upstream.padEnd(14)}${c.source}: ${c.reason}`
60
+ );
61
+ }
62
+ return lines;
63
+ }
64
+ async function toolsCommand(args, io) {
65
+ const loaded = loadPolicyFor(args, io);
66
+ const result = await probeUpstreams(loaded.policy.upstreams, {
67
+ policy: loaded.policy,
68
+ log: io.err,
69
+ env: io.env
70
+ });
71
+ if (flagBool(args, "json")) {
72
+ io.out(
73
+ JSON.stringify(
74
+ {
75
+ upstreams: result.upstreams,
76
+ tools: result.tools.map((t) => ({
77
+ name: t.name,
78
+ upstream: t.upstream,
79
+ ...t.classification
80
+ }))
81
+ },
82
+ null,
83
+ 2
84
+ )
85
+ );
86
+ return 0;
87
+ }
88
+ for (const u of result.upstreams)
89
+ io.err(
90
+ `${u.connected ? "ok " : "down"} ${u.name}: ${u.connected ? `${u.tools} tools` : u.error}`
91
+ );
92
+ if (result.upstreams.length === 0)
93
+ io.err(
94
+ "no upstreams in the policy: an MCP client sees okgate's own read-only tools instead (okgate_get_status, okgate_get_report, okgate_verify_audit_log)"
95
+ );
96
+ for (const line of formatToolTable(result.tools)) io.out(line);
97
+ const counts = result.tools.reduce(
98
+ (acc, t) => ({ ...acc, [t.classification.class]: (acc[t.classification.class] ?? 0) + 1 }),
99
+ {}
100
+ );
101
+ io.err(
102
+ `
103
+ ${result.tools.length} tools: ${Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(", ")} \xB7 mode ${loaded.policy.mode}`
104
+ );
105
+ return result.upstreams.every((u) => u.connected) ? 0 : 2;
106
+ }
107
+
108
+ export {
109
+ probeUpstreams,
110
+ formatToolTable,
111
+ toolsCommand
112
+ };
113
+ //# sourceMappingURL=chunk-DEVXG3IB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/tools.ts"],"sourcesContent":["/**\n * `okgate tools` — connect to every upstream and print the tools the agent will see,\n * with their class and why. Also the probe `init` uses to seed the policy.\n */\nimport {\n Guard,\n classifyTool,\n parsePolicy,\n type Classification,\n type Policy,\n type Upstream,\n} from \"@agentwares/agentguard-core\";\nimport type { LoadedPolicy } from \"@agentwares/agentguard-core/node\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport { ProxyRuntime } from \"../proxy/runtime.js\";\nimport { loadPolicyFor, type Io } from \"../context.js\";\nimport type { ParsedArgs } from \"../args.js\";\nimport { flagBool } from \"../args.js\";\n\nexport interface ProbedTool {\n upstream: string;\n name: string;\n tool: Tool;\n classification: Classification;\n}\n\nexport interface ProbeResult {\n tools: ProbedTool[];\n upstreams: { name: string; connected: boolean; tools: number; error?: string }[];\n}\n\n/** Connect to the upstreams (with a policy for classification), list tools, disconnect. */\nexport async function probeUpstreams(\n upstreams: Upstream[],\n opts: {\n policy?: Policy;\n timeoutMs?: number;\n log?: (l: string) => void;\n env?: NodeJS.ProcessEnv;\n } = {},\n): Promise<ProbeResult> {\n const policy = opts.policy ?? parsePolicy({ upstreams });\n const loaded: LoadedPolicy = {\n policy: { ...policy, upstreams },\n path: \"\",\n baseDir: process.cwd(),\n stateDir: \"\",\n auditPath: \"\",\n killPath: \"\",\n };\n const guard = new Guard({ policy: loaded.policy, env: opts.env ?? {} });\n const runtime = new ProxyRuntime({\n loaded,\n guard,\n defaultRunId: \"probe\",\n log: opts.log ?? (() => undefined),\n connectTimeoutMs: opts.timeoutMs ?? 15_000,\n });\n try {\n await runtime.start();\n return {\n tools: runtime.listTools().map((t) => ({\n upstream: t.upstream,\n name: t.name,\n tool: t.tool,\n classification: classifyTool(\n { name: t.name, annotations: t.tool.annotations },\n loaded.policy,\n ),\n })),\n upstreams: runtime.upstreamStatus(),\n };\n } finally {\n await runtime.close();\n }\n}\n\nexport function formatToolTable(tools: ProbedTool[]): string[] {\n const width = Math.min(48, Math.max(12, ...tools.map((t) => t.name.length)));\n const lines = [\n `${\"tool\".padEnd(width)} ${\"class\".padEnd(8)}${\"verb\".padEnd(9)}${\"upstream\".padEnd(14)}why`,\n ];\n for (const t of tools) {\n const c = t.classification;\n lines.push(\n `${t.name.padEnd(width)} ${c.class.padEnd(8)}${c.verb.padEnd(9)}${t.upstream.padEnd(14)}${c.source}: ${c.reason}`,\n );\n }\n return lines;\n}\n\nexport async function toolsCommand(args: ParsedArgs, io: Io): Promise<number> {\n const loaded = loadPolicyFor(args, io);\n const result = await probeUpstreams(loaded.policy.upstreams, {\n policy: loaded.policy,\n log: io.err,\n env: io.env,\n });\n if (flagBool(args, \"json\")) {\n io.out(\n JSON.stringify(\n {\n upstreams: result.upstreams,\n tools: result.tools.map((t) => ({\n name: t.name,\n upstream: t.upstream,\n ...t.classification,\n })),\n },\n null,\n 2,\n ),\n );\n return 0;\n }\n for (const u of result.upstreams)\n io.err(\n `${u.connected ? \"ok \" : \"down\"} ${u.name}: ${u.connected ? `${u.tools} tools` : u.error}`,\n );\n if (result.upstreams.length === 0)\n io.err(\n \"no upstreams in the policy: an MCP client sees okgate's own read-only tools instead (okgate_get_status, okgate_get_report, okgate_verify_audit_log)\",\n );\n for (const line of formatToolTable(result.tools)) io.out(line);\n const counts = result.tools.reduce<Record<string, number>>(\n (acc, t) => ({ ...acc, [t.classification.class]: (acc[t.classification.class] ?? 0) + 1 }),\n {},\n );\n io.err(\n `\\n${result.tools.length} tools: ${Object.entries(counts)\n .map(([k, v]) => `${v} ${k}`)\n .join(\", \")} · mode ${loaded.policy.mode}`,\n );\n return result.upstreams.every((u) => u.connected) ? 0 : 2;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,202 @@
1
+ import {
2
+ CLIENT_ALIASES,
3
+ hookTarget,
4
+ installHook,
5
+ installedHandlers,
6
+ uninstallHook
7
+ } from "./chunk-2QDPPTPY.js";
8
+ import {
9
+ npxCommand
10
+ } from "./chunk-XPNBYSIN.js";
11
+ import {
12
+ flagBool,
13
+ flagList,
14
+ flagNumber,
15
+ flagString,
16
+ homeFrom,
17
+ version
18
+ } from "./chunk-4WTFMFRY.js";
19
+
20
+ // src/commands/hooks.ts
21
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
22
+ import { dirname, join, relative, resolve } from "path";
23
+ import {
24
+ LEGACY_POLICY_FILE,
25
+ LEGACY_STATE_DIR,
26
+ POLICY_FILE,
27
+ STATE_DIR
28
+ } from "@agentwares/agentguard-core";
29
+ import { findPolicyFile } from "@agentwares/agentguard-core/node";
30
+ function userPolicyPath(home) {
31
+ const next = join(home, STATE_DIR, POLICY_FILE);
32
+ const legacy = join(home, LEGACY_STATE_DIR, LEGACY_POLICY_FILE);
33
+ return !existsSync(next) && existsSync(legacy) ? legacy : next;
34
+ }
35
+ var HOOKS_POLICY_BLOCK = `
36
+ # Hook mode: your coding agent's own shell commands and file edits (Claude Code, Codex, Gemini CLI),
37
+ # checked before they run. \`okgate hooks install\` wired it; \`okgate report\` shows the decisions.
38
+ hooks:
39
+ # dry-run: every decision is logged and nothing is blocked. Read \`okgate report\`, then set
40
+ # enforce. The kill switch (\`okgate kill\`) stops guarded calls in both modes.
41
+ mode: dry-run
42
+ # Irreversible commands run only on a turn a person typed: after the session's previous
43
+ # irreversible command, recent, and naming the action ("push it", or "yes" to "shall I push?").
44
+ # Text the model writes, tool output and injected messages never count. Otherwise the agent
45
+ # gets APPROVAL_REQUIRED and an id; \`okgate approve <id>\` lets that exact command run once.
46
+ # Built in: git-push, merge, tag-delete, rm-outside-workspace, publish, send, guard-config.
47
+ irreversible:
48
+ builtins: true
49
+ window_s: 1800 # the authorizing turn is at most 30 minutes old
50
+ require_mention: true # ...and names the action
51
+ require_origin: false # true: only entries Claude Code labels as typed by a person
52
+ mentions: {} # extra words per class, e.g. { git-push: [deploy] }
53
+ rules: []
54
+ # - name: deploy
55
+ # shell: ["vercel --prod*", "fly deploy*"]
56
+ # mentions: [deploy]
57
+ shell:
58
+ deny: [] # e.g. ["terraform destroy*", "curl * | sh"]
59
+ approval: [] # e.g. ["kubectl delete *"]
60
+ paths:
61
+ deny: [] # e.g. [".env", "*.pem"]
62
+ approval: [] # e.g. ["**/migrations/**"]
63
+ `;
64
+ function renderHooksStarterPolicy(generatedAt = /* @__PURE__ */ new Date()) {
65
+ return `# okgate.yaml \u2014 generated by \`okgate hooks install\` on ${generatedAt.toISOString().slice(0, 10)}
66
+ # Docs: https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli#hook-mode-a-coding-agents-own-commands
67
+ version: 1
68
+
69
+ # MCP proxy mode (okgate init) reads upstreams; hook mode needs none.
70
+ mode: dry-run
71
+ upstreams: []
72
+ ${HOOKS_POLICY_BLOCK}
73
+ # Kill switch: \`okgate kill\` creates this file; every guarded call is refused until
74
+ # \`okgate resume\`. OKGATE_KILL=1 (or AGENTGUARD_KILL=1) in the environment does the same.
75
+ kill:
76
+ file: .okgate/KILL
77
+
78
+ # Hash-chained JSONL of every decision; \`okgate verify\` proves it was not edited.
79
+ audit:
80
+ path: .okgate/audit.jsonl
81
+ redact: true
82
+ `;
83
+ }
84
+ function quoteArg(arg) {
85
+ return /^[A-Za-z0-9_@%+=:,./-]+$/.test(arg) ? arg : JSON.stringify(arg);
86
+ }
87
+ function clientsFrom(args) {
88
+ const names = flagList(args, "client");
89
+ if (names.length === 0) return ["claude-code"];
90
+ if (names.includes("all")) return ["claude-code", "codex", "gemini-cli"];
91
+ return names.map((n) => {
92
+ const c = CLIENT_ALIASES[n.trim()];
93
+ if (!c) throw new Error(`unknown --client "${n}" (claude, codex, gemini or all)`);
94
+ return c;
95
+ });
96
+ }
97
+ function hasTopLevelHooks(yamlText) {
98
+ return /^hooks\s*:/m.test(yamlText);
99
+ }
100
+ async function hooksCommand(args, io) {
101
+ const sub = args.positionals[0] ?? "status";
102
+ const scope = flagBool(args, "user") ? "user" : "project";
103
+ const home = homeFrom(io);
104
+ const where = { cwd: io.cwd, home };
105
+ const rel = (p) => {
106
+ const r = relative(io.cwd, p);
107
+ return r.startsWith("..") ? p : r || ".";
108
+ };
109
+ const clients = clientsFrom(args);
110
+ const named = flagString(args, "config");
111
+ const policyPath = named !== void 0 ? resolve(io.cwd, named) : scope === "user" ? userPolicyPath(home) : findPolicyFile(io.cwd);
112
+ const base = flagString(args, "command") ?? npxCommand(version());
113
+ const commandFor = (client) => {
114
+ let cmd = `${base} hook pre-tool-use --client ${client === "claude-code" ? "claude" : client === "gemini-cli" ? "gemini" : "codex"}`;
115
+ if (flagString(args, "config")) cmd += ` --config ${quoteArg(policyPath)}`;
116
+ else if (scope === "user") cmd += ` --fallback-config ${quoteArg(policyPath)}`;
117
+ return cmd;
118
+ };
119
+ const timeoutS = Math.max(30, flagNumber(args, "timeout") ?? 30);
120
+ if (sub === "install") {
121
+ if (!existsSync(policyPath)) {
122
+ mkdirSync(dirname(policyPath), { recursive: true });
123
+ writeFileSync(policyPath, renderHooksStarterPolicy());
124
+ io.out(`wrote ${rel(policyPath)} (hooks.mode: dry-run)`);
125
+ } else if (!hasTopLevelHooks(readFileSync(policyPath, "utf8"))) {
126
+ appendFileSync(policyPath, HOOKS_POLICY_BLOCK);
127
+ io.out(`added a hooks: block to ${rel(policyPath)} (hooks.mode: dry-run)`);
128
+ } else io.out(`kept ${rel(policyPath)} (it already has a hooks: block)`);
129
+ for (const client of clients) {
130
+ const target = hookTarget(client, scope, where, commandFor(client), timeoutS);
131
+ const { status, backup } = installHook(target);
132
+ const verb = status === "unchanged" ? "kept " : status === "updated" ? "updated" : "added ";
133
+ io.out(
134
+ `${verb} ${target.label} ${target.event} hook in ${rel(target.settingsPath)}${backup ? ` (backup: ${rel(backup)})` : ""}`
135
+ );
136
+ if (target.note) io.out(` ${target.note}`);
137
+ }
138
+ if (scope === "project" && existsSync(join(io.cwd, ".git"))) {
139
+ const gitignore = join(io.cwd, ".gitignore");
140
+ const text = existsSync(gitignore) ? readFileSync(gitignore, "utf8") : "";
141
+ if (!/^\/?\.okgate\/?$/m.test(text)) {
142
+ appendFileSync(gitignore, `${text && !text.endsWith("\n") ? "\n" : ""}.okgate/
143
+ `);
144
+ io.out(`added .okgate/ to .gitignore (approvals, counters and the audit log stay local)`);
145
+ }
146
+ }
147
+ io.out("");
148
+ io.out("Next:");
149
+ io.out(" 1. start a new session of your coding agent (hooks are read at start)");
150
+ io.out(" 2. work as usual \u2014 hooks.mode is dry-run: decisions are logged, nothing is blocked");
151
+ io.out(" 3. okgate report # what enforce mode would have stopped, and why");
152
+ io.out(` 4. set \`hooks.mode: enforce\` in ${rel(policyPath)} when it looks right`);
153
+ io.out(" undo: okgate hooks uninstall" + (scope === "user" ? " --user" : ""));
154
+ return 0;
155
+ }
156
+ if (sub === "uninstall") {
157
+ let total = 0;
158
+ for (const client of flagString(args, "client") ? clients : ["claude-code", "codex", "gemini-cli"]) {
159
+ const target = hookTarget(client, scope, where, commandFor(client), timeoutS);
160
+ const removed = uninstallHook(target);
161
+ total += removed;
162
+ if (removed) io.out(`removed ${target.label} hook from ${rel(target.settingsPath)}`);
163
+ }
164
+ if (total === 0) io.out(`no okgate hooks in ${scope}-level settings`);
165
+ else
166
+ io.out(
167
+ `kept ${rel(policyPath)} and its state directory (${STATE_DIR}/ or ${LEGACY_STATE_DIR}/); delete them yourself if you want them gone`
168
+ );
169
+ return 0;
170
+ }
171
+ if (sub === "status") {
172
+ for (const s of ["project", "user"]) {
173
+ for (const client of ["claude-code", "codex", "gemini-cli"]) {
174
+ const target = hookTarget(client, s, where, "", timeoutS);
175
+ let handlers;
176
+ try {
177
+ handlers = installedHandlers(target);
178
+ } catch (err) {
179
+ io.out(
180
+ `${s.padEnd(7)} ${target.label.padEnd(11)} unreadable: ${err instanceof Error ? err.message : String(err)}`
181
+ );
182
+ continue;
183
+ }
184
+ io.out(
185
+ `${s.padEnd(7)} ${target.label.padEnd(11)} ${handlers.length ? `installed: ${String(handlers[0].command)}` : "\u2014"} (${rel(target.settingsPath)})`
186
+ );
187
+ }
188
+ }
189
+ return 0;
190
+ }
191
+ io.err(
192
+ "usage: okgate hooks install|uninstall|status [--project|--user] [--client claude|codex|gemini|all] [--command cmd] [--config path]"
193
+ );
194
+ return 1;
195
+ }
196
+
197
+ export {
198
+ HOOKS_POLICY_BLOCK,
199
+ renderHooksStarterPolicy,
200
+ hooksCommand
201
+ };
202
+ //# sourceMappingURL=chunk-F73VDY3W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/hooks.ts"],"sourcesContent":["/**\n * `okgate 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 `okgate hook pre-tool-use`, and makes\n * sure the policy has a `hooks:` block in dry-run. Uninstall removes only okgate's entries.\n */\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, relative, resolve } from \"node:path\";\nimport {\n LEGACY_POLICY_FILE,\n LEGACY_STATE_DIR,\n POLICY_FILE,\n STATE_DIR,\n type HookClient,\n} from \"@agentwares/agentguard-core\";\nimport { findPolicyFile } from \"@agentwares/agentguard-core/node\";\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 { npxCommand } from \"../install.js\";\nimport { version } from \"../version.js\";\n\n/** `~/.okgate/okgate.yaml`, or the `~/.agentguard/agentguard.yaml` an earlier install wrote. */\nexport function userPolicyPath(home: string): string {\n const next = join(home, STATE_DIR, POLICY_FILE);\n const legacy = join(home, LEGACY_STATE_DIR, LEGACY_POLICY_FILE);\n return !existsSync(next) && existsSync(legacy) ? legacy : next;\n}\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. \\`okgate hooks install\\` wired it; \\`okgate report\\` shows the decisions.\nhooks:\n # dry-run: every decision is logged and nothing is blocked. Read \\`okgate report\\`, then set\n # enforce. The kill switch (\\`okgate 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; \\`okgate 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 okgate.yaml yet: hooks only, no MCP upstreams. */\nexport function renderHooksStarterPolicy(generatedAt = new Date()): string {\n return `# okgate.yaml — generated by \\`okgate 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 (okgate init) reads upstreams; hook mode needs none.\nmode: dry-run\nupstreams: []\n${HOOKS_POLICY_BLOCK}\n# Kill switch: \\`okgate kill\\` creates this file; every guarded call is refused until\n# \\`okgate resume\\`. OKGATE_KILL=1 (or AGENTGUARD_KILL=1) in the environment does the same.\nkill:\n file: .okgate/KILL\n\n# Hash-chained JSONL of every decision; \\`okgate verify\\` proves it was not edited.\naudit:\n path: .okgate/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 named = flagString(args, \"config\");\n const policyPath =\n named !== undefined\n ? resolve(io.cwd, named)\n : scope === \"user\"\n ? userPolicyPath(home)\n : findPolicyFile(io.cwd);\n const base = flagString(args, \"command\") ?? npxCommand(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 (!/^\\/?\\.okgate\\/?$/m.test(text)) {\n appendFileSync(gitignore, `${text && !text.endsWith(\"\\n\") ? \"\\n\" : \"\"}.okgate/\\n`);\n io.out(`added .okgate/ to .gitignore (approvals, counters and the audit log stay local)`);\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. okgate 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: okgate 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 okgate hooks in ${scope}-level settings`);\n else\n io.out(\n `kept ${rel(policyPath)} and its state directory (${STATE_DIR}/ or ${LEGACY_STATE_DIR}/); 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: okgate 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":[]}
@@ -0,0 +1,275 @@
1
+ import {
2
+ describeConfigPath,
3
+ detectClientConfigs,
4
+ readServers,
5
+ restoreClientConfig,
6
+ rewriteClientConfig,
7
+ serversToUpstreams
8
+ } from "./chunk-OM5L7A32.js";
9
+ import {
10
+ probeUpstreams
11
+ } from "./chunk-DEVXG3IB.js";
12
+ import {
13
+ RUN
14
+ } from "./chunk-XPNBYSIN.js";
15
+ import {
16
+ flagBool,
17
+ flagString,
18
+ homeFrom,
19
+ policyPathFrom
20
+ } from "./chunk-4WTFMFRY.js";
21
+
22
+ // src/commands/init.ts
23
+ import { existsSync, writeFileSync } from "fs";
24
+ import { relative, resolve } from "path";
25
+ import YAML from "yaml";
26
+ function dump(value, indent = 0) {
27
+ const text = YAML.stringify(value, { indent: 2, lineWidth: 100 }).trimEnd();
28
+ const pad = " ".repeat(indent);
29
+ return text.split("\n").map((l) => pad + l).join("\n");
30
+ }
31
+ function flow(list) {
32
+ return list.length === 0 ? "[]" : `[${list.map((s) => JSON.stringify(s)).join(", ")}]`;
33
+ }
34
+ function renderStarterPolicy(opts) {
35
+ const tools = opts.tools ?? [];
36
+ const unknown = tools.filter((t) => t.classification.class === "unknown").map((t) => t.name);
37
+ const destructive = tools.filter((t) => t.classification.destructive || t.classification.verb === "delete").map((t) => t.name);
38
+ const spend = tools.filter((t) => t.classification.class === "spend").map((t) => t.name);
39
+ const date = (opts.generatedAt ?? /* @__PURE__ */ new Date()).toISOString().slice(0, 10);
40
+ const counts = tools.reduce(
41
+ (acc, t) => ({ ...acc, [t.classification.class]: (acc[t.classification.class] ?? 0) + 1 }),
42
+ {}
43
+ );
44
+ const detected = tools.length === 0 ? ["# (no upstreams probed \u2014 run `okgate tools` after fixing the upstreams above)"] : tools.map(
45
+ (t) => `# ${t.name.padEnd(36)} ${t.classification.class.padEnd(8)} ${t.classification.verb.padEnd(8)} ${t.classification.source}: ${t.classification.reason}`
46
+ );
47
+ const probeNotes = (opts.probeErrors ?? []).map((e) => `# ! ${e}`);
48
+ const upstreamsYaml = opts.upstreams.length ? dump({ upstreams: opts.upstreams.map(cleanUpstream) }) : 'upstreams: []\n # - name: crm\n # url: https://mcp.example.com/mcp\n # auth: ${CRM_TOKEN}\n # - name: files\n # command: npx\n # args: [-y, "@modelcontextprotocol/server-filesystem", "."]';
49
+ return `# okgate.yaml \u2014 generated by \`okgate init\` on ${date}
50
+ # Every knob is explained inline. Docs: https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli#policy
51
+ version: 1
52
+
53
+ # dry-run: reads pass through; writes and spends are FAKED (plausible success, nothing executes)
54
+ # and recorded. Run your agent once, then \`okgate report\` and \`okgate diff\`.
55
+ # Flip to \`enforce\` when the diff looks right. Caps and the loop breaker apply in both modes.
56
+ mode: ${opts.mode ?? "dry-run"}
57
+
58
+ # The servers your agent used to talk to directly. okgate connects to them and holds their
59
+ # credentials; your agent only talks to okgate. Use \${ENV_VAR} for secrets.
60
+ ${upstreamsYaml}
61
+
62
+ # Detected tools (${tools.length}${tools.length ? `: ${Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(", ")}` : ""}):
63
+ ${[...detected, ...probeNotes].join("\n")}
64
+ # readOnlyHint annotations and verb heuristics classify most tools. List anything that is wrong here;
65
+ # these patterns win over annotations. \`*\` matches anything.
66
+ classify:
67
+ read: []
68
+ write: ${flow(unknown)}${unknown.length ? " # could not be classified \u2014 treated as writes; move to read: if they only read" : ""}
69
+ spend: ${flow(spend)}
70
+ unknown: write # a tool nobody can classify counts as a write (alternatives: read | block)
71
+
72
+ # Blast-radius caps. The call that would exceed a cap gets CAP_EXCEEDED with the remaining budget.
73
+ # \`writes\` counts every write or spend; \`deletes\`/\`emails\` count the subsets matched by \`counters\`.
74
+ caps:
75
+ per_run:
76
+ tool_calls: 400
77
+ writes: 50
78
+ deletes: 10
79
+ emails: 5
80
+ spend_usd: 25
81
+ per_day:
82
+ spend_usd: 200
83
+
84
+ # Dollar amounts. Declare how to read the amount from a spend tool's arguments; results reporting
85
+ # cost_usd / amount_usd are picked up automatically. Unknown spend tools cost \`default_usd\`.
86
+ spend:
87
+ tools: {}
88
+ # stripe_create_charge: { amount_arg: amount, divisor: 100, currency_arg: currency }
89
+ # openai_*: { fixed_usd: 0.01 }
90
+ default_usd: 0
91
+
92
+ # Semantic loop breaker: the same (tool, normalized arguments) ${3} times in the last 30 calls, or a
93
+ # repeating A\u2192B\u2192A\u2192B cycle, returns LOOP_DETECTED. Timestamps, ids and whitespace are ignored.
94
+ loop:
95
+ window: 30
96
+ max_repeats: 3
97
+ max_cycle_len: 4
98
+ max_read_repeats: 10
99
+
100
+ # Always fake these, even in enforce mode. Remove entries as you gain trust.
101
+ dry_run:
102
+ tools: []
103
+ synthesize: true
104
+
105
+ # Destructive actions that need a human. The agent gets APPROVAL_REQUIRED with an id; you run
106
+ # \`okgate approve <id>\` (or click the link in Slack); the agent retries the identical call once.
107
+ approval:
108
+ tools: ${flow(destructive)}
109
+ wait_s: 0 # > 0 holds the call open this long waiting for the decision
110
+ ttl_s: 3600
111
+ # notify:
112
+ # slack: \${SLACK_WEBHOOK_URL}
113
+
114
+ # Kill switch: \`okgate kill\` creates this file (or POST /kill on the HTTP proxy); every call
115
+ # returns KILLED until \`okgate resume\`. OKGATE_KILL=1 (or AGENTGUARD_KILL=1) in the environment does the same.
116
+ kill:
117
+ file: .okgate/KILL
118
+
119
+ # Scoped agent keys for the HTTP proxy: \`okgate key create deployer --allow 'crm_get_*'\`.
120
+ # Only the key's hash is stored here; the agent sends the key as a Bearer token.
121
+ agents: []
122
+
123
+ # Alerts on halts (Slack incoming webhook or any HTTPS endpoint; signed with X-Agentwares-Signature).
124
+ # alerts:
125
+ # slack: \${SLACK_WEBHOOK_URL}
126
+ # on: [LOOP_DETECTED, CAP_EXCEEDED, KILLED, APPROVAL_REQUIRED]
127
+
128
+ # Hash-chained JSONL of every call; \`okgate verify\` proves it was not edited.
129
+ audit:
130
+ path: .okgate/audit.jsonl
131
+ redact: true
132
+ include_results: false
133
+ `;
134
+ }
135
+ function cleanUpstream(u) {
136
+ const out = { name: u.name };
137
+ if (u.url) {
138
+ out.url = u.url;
139
+ if (Object.keys(u.headers).length) out.headers = u.headers;
140
+ } else {
141
+ out.command = u.command;
142
+ if (u.args.length) out.args = u.args;
143
+ if (Object.keys(u.env).length) out.env = u.env;
144
+ if (u.cwd) out.cwd = u.cwd;
145
+ }
146
+ return out;
147
+ }
148
+ async function initCommand(args, io) {
149
+ const policyPath = policyPathFrom(args, io);
150
+ const explicit = flagString(args, "client");
151
+ const configs = explicit ? [describeConfigPath(resolve(io.cwd, explicit))] : detectClientConfigs(io.cwd, homeFrom(io), io.env);
152
+ const rel = (p) => {
153
+ const r = relative(io.cwd, p);
154
+ return r.startsWith("..") ? p : r || ".";
155
+ };
156
+ if (flagBool(args, "undo")) {
157
+ let restored = 0;
158
+ for (const c of configs) {
159
+ if (restoreClientConfig(c)) {
160
+ io.out(`restored ${rel(c.path)} from backup`);
161
+ restored += 1;
162
+ }
163
+ }
164
+ if (restored === 0)
165
+ io.err(
166
+ "nothing to undo (no *.okgate-backup or *.agentguard-backup files next to your MCP configs)"
167
+ );
168
+ return 0;
169
+ }
170
+ const projectLevel = configs.filter((c) => c.path.startsWith(io.cwd + "/"));
171
+ const userLevel = configs.filter((c) => !projectLevel.includes(c));
172
+ let chosen;
173
+ if (explicit) chosen = configs;
174
+ else if (flagBool(args, "all")) chosen = configs;
175
+ else chosen = projectLevel.slice(0, 1);
176
+ if (!explicit && !flagBool(args, "all")) {
177
+ for (const c of userLevel)
178
+ io.out(
179
+ `found ${c.path} (${c.label}, user-level) \u2014 not touched; pass --client ${JSON.stringify(c.path)} to route it through okgate`
180
+ );
181
+ }
182
+ if (!chosen.every((c) => existsSync(c.path))) {
183
+ io.err(`no MCP config at ${chosen.map((c) => c.path).join(", ")}`);
184
+ return 1;
185
+ }
186
+ const servers = {};
187
+ for (const c of chosen) {
188
+ const found = readServers(c);
189
+ io.out(
190
+ `found ${rel(c.path)} (${c.label}) \u2192 ${Object.keys(found).length} server${Object.keys(found).length === 1 ? "" : "s"}: ${Object.keys(found).join(", ") || "none"}`
191
+ );
192
+ for (const [name, entry] of Object.entries(found))
193
+ if (!(name in servers)) servers[name] = entry;
194
+ }
195
+ if (chosen.length === 0)
196
+ io.out(
197
+ "found no MCP config (Claude Code .mcp.json, Cursor, VS Code, Claude Desktop, Windsurf, Gemini CLI) \u2014 writing a policy with no upstreams"
198
+ );
199
+ const { upstreams, skipped } = serversToUpstreams(servers);
200
+ for (const s of skipped) io.err(`skipped ${s.name}: ${s.reason}`);
201
+ if (skipped.some((s) => s.reason.includes("already")) && upstreams.length === 0 && existsSync(policyPath)) {
202
+ io.out(
203
+ `already initialized: ${rel(policyPath)} exists and ${chosen.map((c) => rel(c.path)).join(", ")} already points at okgate`
204
+ );
205
+ return 0;
206
+ }
207
+ let probed = [];
208
+ const probeErrors = [];
209
+ if (flagBool(args, "probe") !== false && upstreams.length > 0) {
210
+ io.out(`probing ${upstreams.length} upstream${upstreams.length === 1 ? "" : "s"}\u2026`);
211
+ const result = await probeUpstreams(upstreams, {
212
+ log: () => void 0,
213
+ env: io.env,
214
+ timeoutMs: 2e4
215
+ });
216
+ probed = result.tools;
217
+ for (const u of result.upstreams) {
218
+ if (u.connected) io.out(` ${u.name}: ${u.tools} tools`);
219
+ else {
220
+ io.err(
221
+ ` ${u.name}: could not connect (${u.error}) \u2014 the proxy will keep retrying at start; fix the entry in okgate.yaml`
222
+ );
223
+ probeErrors.push(`${u.name}: ${u.error}`);
224
+ }
225
+ }
226
+ }
227
+ const mode = flagString(args, "mode") ?? "dry-run";
228
+ if (existsSync(policyPath) && !flagBool(args, "force")) {
229
+ io.out(`kept ${rel(policyPath)} (exists; pass --force to regenerate)`);
230
+ } else {
231
+ writeFileSync(policyPath, renderStarterPolicy({ upstreams, tools: probed, mode, probeErrors }));
232
+ const counts = probed.reduce(
233
+ (acc, t) => ({ ...acc, [t.classification.class]: (acc[t.classification.class] ?? 0) + 1 }),
234
+ {}
235
+ );
236
+ io.out(
237
+ `wrote ${rel(policyPath)} (mode: ${mode}, ${upstreams.length} upstream${upstreams.length === 1 ? "" : "s"}${probed.length ? `, ${probed.length} tools: ${Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(", ")}` : ""})`
238
+ );
239
+ }
240
+ if (flagBool(args, "rewrite") !== false) {
241
+ for (const c of chosen) {
242
+ if (resolve(c.path) === resolve(policyPath)) {
243
+ throw new Error(
244
+ `refusing: ${rel(c.path)} is both the policy file (--config) and a client config. Rewriting it would replace the policy with the proxy entry and leave the proxy pointing at itself. Use --config for the policy path and --client for the client's config, or let --config default to okgate.yaml.`
245
+ );
246
+ }
247
+ const { backup, replaced } = rewriteClientConfig(c, policyPath, {
248
+ agent: flagString(args, "agent")
249
+ });
250
+ io.out(
251
+ `rewrote ${rel(c.path)} \u2192 ${replaced.length ? `${replaced.join(", ")} now go through` : "points at"} \`${RUN} proxy\` (backup: ${rel(backup)})`
252
+ );
253
+ }
254
+ }
255
+ io.out("");
256
+ io.out("Next:");
257
+ io.out(
258
+ ` 1. restart your MCP client so it re-reads ${chosen.map((c) => rel(c.path)).join(", ") || "its config"}`
259
+ );
260
+ io.out(
261
+ ` 2. run your agent once \u2014 ${mode === "dry-run" ? "writes are faked; nothing executes upstream" : "enforce mode: caps, loop breaker and approvals are live"}`
262
+ );
263
+ io.out(" 3. okgate report # what this run would have destroyed / spent");
264
+ io.out(" okgate diff # the record-by-record mutation diff");
265
+ if (mode === "dry-run")
266
+ io.out(` 4. set \`mode: enforce\` in ${rel(policyPath)} when it looks right`);
267
+ io.out(" undo any time: okgate init --undo");
268
+ return 0;
269
+ }
270
+
271
+ export {
272
+ renderStarterPolicy,
273
+ initCommand
274
+ };
275
+ //# sourceMappingURL=chunk-IEJK7N3B.js.map