@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,847 @@
1
+ import {
2
+ LEGACY_PROXY_SERVER_NAME,
3
+ PROXY_SERVER_NAME,
4
+ describeConfigPath,
5
+ detectClientConfigs,
6
+ isGeminiConfig,
7
+ mergeServerIntoConfig
8
+ } from "./chunk-OM5L7A32.js";
9
+ import {
10
+ hooksCommand
11
+ } from "./chunk-F73VDY3W.js";
12
+ import {
13
+ CLIENT_ALIASES,
14
+ hookSettingsFiles
15
+ } from "./chunk-2QDPPTPY.js";
16
+ import {
17
+ INSTALL
18
+ } from "./chunk-XPNBYSIN.js";
19
+ import {
20
+ approvalUrlFor,
21
+ createFileGuard,
22
+ defaultIo,
23
+ describeError,
24
+ flagBool,
25
+ flagList,
26
+ flagNumber,
27
+ flagString,
28
+ homeFrom,
29
+ loadPolicyFor,
30
+ parseArgs,
31
+ policyPathFrom,
32
+ readRegistration,
33
+ version
34
+ } from "./chunk-4WTFMFRY.js";
35
+
36
+ // src/commands/connect.ts
37
+ import { existsSync } from "fs";
38
+ import { relative } from "path";
39
+ import { envTwin } from "@agentwares/agentguard-core";
40
+ var DEFAULT_PROXY_URL = "https://agentwares-agentguard-proxy.vercel.app";
41
+ function fail(e) {
42
+ throw e;
43
+ }
44
+ async function connectCommand(args, io, fetchImpl = fetch) {
45
+ const key = args.positionals[0] ?? envTwin(io.env, "KEY");
46
+ if (!key) {
47
+ io.err("usage: okgate connect <key> [--write] [--client <path>] [--url <proxy-url>]");
48
+ io.err(" the key is on your dashboard; it starts with agk_");
49
+ return 2;
50
+ }
51
+ const base = (flagString(args, "url") ?? envTwin(io.env, "PROXY_URL") ?? DEFAULT_PROXY_URL).replace(/\/+$/, "");
52
+ let res;
53
+ try {
54
+ res = await fetchImpl(`${base}/connect`, {
55
+ method: "POST",
56
+ headers: { "content-type": "application/json" },
57
+ body: JSON.stringify({ key })
58
+ });
59
+ } catch (err) {
60
+ fail({
61
+ code: "PROXY_UNREACHABLE",
62
+ cause: `could not reach ${base}: ${err instanceof Error ? err.message : String(err)}`,
63
+ fix: "check the network, or pass --url if you run your own proxy",
64
+ retryable: true
65
+ });
66
+ }
67
+ const text = await res.text();
68
+ let body;
69
+ try {
70
+ body = JSON.parse(text);
71
+ } catch {
72
+ fail({
73
+ code: "PROXY_BAD_RESPONSE",
74
+ cause: `${base}/connect returned ${res.status} that was not JSON: ${text.slice(0, 200)}`,
75
+ fix: "check --url points at an okgate proxy, not at the dashboard",
76
+ retryable: false
77
+ });
78
+ }
79
+ if (!res.ok) {
80
+ const e = body;
81
+ fail({
82
+ code: e.code ?? "CONNECT_REFUSED",
83
+ cause: e.cause ?? `${base}/connect returned ${res.status}`,
84
+ fix: e.fix ?? "check the key on your dashboard; revoked keys stop working immediately",
85
+ retryable: e.retryable ?? false
86
+ });
87
+ }
88
+ const conn = body;
89
+ if (flagBool(args, "json")) {
90
+ io.out(JSON.stringify(conn, null, 2));
91
+ return 0;
92
+ }
93
+ const rel = (p) => p.startsWith(io.cwd + "/") ? relative(io.cwd, p) : p;
94
+ const entry = conn.mcpServers[PROXY_SERVER_NAME] ?? conn.mcpServers[LEGACY_PROXY_SERVER_NAME] ?? {
95
+ type: "http",
96
+ url: conn.mcpUrl,
97
+ headers: conn.headers
98
+ };
99
+ if (!flagBool(args, "write")) {
100
+ io.out(JSON.stringify({ mcpServers: { [PROXY_SERVER_NAME]: entry } }, null, 2));
101
+ } else {
102
+ const explicit = flagString(args, "client");
103
+ const configs = explicit ? [describeConfigPath(explicit)] : detectClientConfigs(io.cwd, homeFrom(io), io.env);
104
+ const projectLevel = configs.filter((c) => c.path.startsWith(io.cwd + "/"));
105
+ let chosen;
106
+ if (explicit || flagBool(args, "all")) chosen = configs;
107
+ else chosen = projectLevel.slice(0, 1);
108
+ if (chosen.length === 0) {
109
+ io.err(
110
+ "no MCP config found here (.mcp.json, .cursor/mcp.json, .vscode/mcp.json). Create one, or paste this into your client:"
111
+ );
112
+ io.out(JSON.stringify({ mcpServers: { [PROXY_SERVER_NAME]: entry } }, null, 2));
113
+ return 1;
114
+ }
115
+ for (const c of chosen) {
116
+ if (!existsSync(c.path)) {
117
+ io.err(`no MCP config at ${c.path}`);
118
+ return 1;
119
+ }
120
+ const forClient = isGeminiConfig(c) ? { httpUrl: entry.url, ...entry.headers ? { headers: entry.headers } : {} } : entry;
121
+ const { backup, replaced } = mergeServerIntoConfig(c, PROXY_SERVER_NAME, forClient, [
122
+ LEGACY_PROXY_SERVER_NAME
123
+ ]);
124
+ io.out(
125
+ `${replaced ? "updated" : "wrote "} ${rel(c.path)} (${c.label}) \u2192 ${PROXY_SERVER_NAME} = ${conn.mcpUrl}`
126
+ );
127
+ io.out(`backup ${rel(backup)}`);
128
+ }
129
+ for (const c of configs.filter((c2) => !chosen.includes(c2)))
130
+ io.out(
131
+ `found ${c.path} (${c.label}, user-level) \u2014 not touched; pass --client ${JSON.stringify(c.path)} to write it too`
132
+ );
133
+ }
134
+ const band = conn.band ? ` band=${conn.band.tier} (${conn.band.quantity} calls/mo)` : "";
135
+ io.out(`
136
+ proxy ${conn.slug} (${conn.proxyId}) mode=${conn.mode}${band}`);
137
+ if (conn.upstreams?.length) io.out(`upstreams: ${conn.upstreams.join(", ")}`);
138
+ if (conn.dashboardUrl) io.out(`dashboard: ${conn.dashboardUrl}`);
139
+ if (conn.claudeCode) io.out(`claude code: ${conn.claudeCode}`);
140
+ if (!flagBool(args, "write"))
141
+ io.out(`
142
+ run again with --write to add this to your MCP client config`);
143
+ return 0;
144
+ }
145
+
146
+ // src/commands/control.ts
147
+ import { KILL_ENV, LEGACY_KILL_ENV } from "@agentwares/agentguard-core";
148
+ async function pokeHttp(io, reg, path, body) {
149
+ if (!reg) return;
150
+ try {
151
+ const res = await fetch(`${reg.baseUrl}${path}`, {
152
+ method: "POST",
153
+ headers: { Authorization: `Bearer ${reg.token}`, "Content-Type": "application/json" },
154
+ body: body === void 0 ? void 0 : JSON.stringify(body),
155
+ signal: AbortSignal.timeout(2e3)
156
+ });
157
+ if (!res.ok)
158
+ io.err(
159
+ `running proxy at ${reg.baseUrl} answered ${res.status} (the file switch still applies)`
160
+ );
161
+ } catch {
162
+ io.err(
163
+ `no proxy answering at ${reg.baseUrl} (stale ${reg.pid}?) \u2014 the file switch still applies`
164
+ );
165
+ }
166
+ }
167
+ async function killCommand(args, io) {
168
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
169
+ const { kill } = createFileGuard(loaded, { alerts: false, env: io.env });
170
+ const reason = args.positionals.join(" ") || flagString(args, "reason") || `okgate kill by ${io.env.USER ?? "operator"}`;
171
+ kill.kill(reason);
172
+ await pokeHttp(io, readRegistration(loaded), "/kill", { reason });
173
+ io.out(
174
+ `KILLED: ${kill.path} written \u2014 every tool call through this policy now returns KILLED. Clear with: okgate resume`
175
+ );
176
+ return 0;
177
+ }
178
+ async function resumeCommand(args, io) {
179
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
180
+ const { kill } = createFileGuard(loaded, { alerts: false, env: io.env });
181
+ kill.resume();
182
+ await pokeHttp(io, readRegistration(loaded), "/resume");
183
+ io.out(`resumed: ${kill.path} removed`);
184
+ for (const name of /* @__PURE__ */ new Set([loaded.policy.kill.env, KILL_ENV, LEGACY_KILL_ENV]))
185
+ if (io.env[name] && io.env[name] !== "0" && io.env[name].toLowerCase() !== "false")
186
+ io.err(
187
+ `note: ${name} is set in this shell's environment; proxies started from it stay killed`
188
+ );
189
+ return 0;
190
+ }
191
+ async function decideCommand(decision, args, io) {
192
+ const id = args.positionals[0];
193
+ if (!id) {
194
+ io.err(
195
+ `usage: okgate ${decision === "approved" ? "approve" : "deny"} <approval-id> [--by name] [--note text]`
196
+ );
197
+ return 1;
198
+ }
199
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
200
+ const { guard } = createFileGuard(loaded, { alerts: false, env: io.env });
201
+ const record = await guard.decide(
202
+ id,
203
+ decision,
204
+ flagString(args, "by") ?? io.env.USER ?? "cli",
205
+ flagString(args, "note")
206
+ );
207
+ if (!record) {
208
+ io.err(`no approval ${id} \u2014 pending ones: okgate approvals`);
209
+ return 1;
210
+ }
211
+ io.out(
212
+ `${record.status}: ${record.tool} (${record.id}, run ${record.run_id})${record.status === "approved" ? " \u2014 the agent may now retry the identical call once" : ""}`
213
+ );
214
+ return 0;
215
+ }
216
+ async function approvalsCommand(args, io) {
217
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
218
+ const { guard } = createFileGuard(loaded, { alerts: false, env: io.env });
219
+ const list = await guard.approvals.list(flagBool(args, "all") ? void 0 : "pending");
220
+ if (flagBool(args, "json")) {
221
+ io.out(JSON.stringify(list, null, 2));
222
+ return 0;
223
+ }
224
+ if (list.length === 0) {
225
+ io.out(flagBool(args, "all") ? "no approvals recorded" : "no pending approvals");
226
+ return 0;
227
+ }
228
+ for (const r of list) {
229
+ io.out(
230
+ `${r.id} ${r.status.padEnd(9)} ${r.tool} run ${r.run_id}${r.agent ? ` agent ${r.agent}` : ""} expires ${r.expires_at}`
231
+ );
232
+ io.out(` args: ${JSON.stringify(r.args).slice(0, 300)}`);
233
+ if (r.status === "pending") io.out(` \u2192 okgate approve ${r.id} | okgate deny ${r.id}`);
234
+ }
235
+ return 0;
236
+ }
237
+
238
+ // src/commands/hook.ts
239
+ import { existsSync as existsSync3 } from "fs";
240
+ import { tmpdir } from "os";
241
+ import { basename, dirname, join, resolve } from "path";
242
+ import {
243
+ POLICY_FILES,
244
+ assessHookAction,
245
+ detectHookClient,
246
+ envTwin as envTwin2,
247
+ hookActionFromInput
248
+ } from "@agentwares/agentguard-core";
249
+
250
+ // src/hooks/transcript-file.ts
251
+ import { closeSync, existsSync as existsSync2, openSync, readSync, statSync } from "fs";
252
+ import { scanTranscript } from "@agentwares/agentguard-core";
253
+ var STEPS = [1 << 20, 8 << 20, 64 << 20];
254
+ function readTail(path, bytes, size) {
255
+ const length = Math.min(bytes, size);
256
+ const fd = openSync(path, "r");
257
+ try {
258
+ const buf = Buffer.alloc(length);
259
+ readSync(fd, buf, 0, length, size - length);
260
+ return buf.toString("utf8");
261
+ } finally {
262
+ closeSync(fd);
263
+ }
264
+ }
265
+ function humanTurnFromTranscript(path, opts = {}) {
266
+ if (!path) return { missing: "the hook input carried no transcript_path" };
267
+ if (!existsSync2(path)) return { missing: `no transcript at ${path}` };
268
+ let size;
269
+ try {
270
+ size = statSync(path).size;
271
+ } catch (err) {
272
+ return {
273
+ missing: `transcript unreadable: ${err instanceof Error ? err.message : String(err)}`
274
+ };
275
+ }
276
+ let scan;
277
+ for (const step of STEPS) {
278
+ try {
279
+ scan = scanTranscript(readTail(path, step, size), opts);
280
+ } catch (err) {
281
+ return {
282
+ missing: `transcript unreadable: ${err instanceof Error ? err.message : String(err)}`
283
+ };
284
+ }
285
+ if (scan.turn || step >= size) break;
286
+ }
287
+ if (scan?.turn) return { turn: scan.turn, scan };
288
+ return {
289
+ missing: size > STEPS[STEPS.length - 1] ? "none in the last 64 MB of the transcript" : void 0,
290
+ scan
291
+ };
292
+ }
293
+
294
+ // src/commands/hook.ts
295
+ import { loadPolicyFile } from "@agentwares/agentguard-core/node";
296
+ var RUN_PREFIX = {
297
+ "claude-code": "cc",
298
+ codex: "codex",
299
+ "gemini-cli": "gemini"
300
+ };
301
+ function findPolicyUpwards(start) {
302
+ let dir = resolve(start);
303
+ for (let i = 0; i < 64; i += 1) {
304
+ for (const name of POLICY_FILES) {
305
+ const candidate = join(dir, name);
306
+ if (existsSync3(candidate)) return candidate;
307
+ }
308
+ const parent = dirname(dir);
309
+ if (parent === dir) return void 0;
310
+ dir = parent;
311
+ }
312
+ return void 0;
313
+ }
314
+ function projectDir(io, inputCwd) {
315
+ return io.env.CLAUDE_PROJECT_DIR || io.env.GEMINI_PROJECT_DIR || inputCwd || void 0;
316
+ }
317
+ function resolveHookPolicy(args, io, inputCwd) {
318
+ const flag = flagString(args, "config");
319
+ if (flag) return { path: resolve(io.cwd, flag), found: "flag" };
320
+ const fromEnv = envTwin2(io.env, "CONFIG");
321
+ if (fromEnv) return { path: resolve(io.cwd, fromEnv), found: "env" };
322
+ const start = projectDir(io, inputCwd) ?? io.cwd;
323
+ const near = findPolicyUpwards(start);
324
+ if (near) return { path: near, found: "project" };
325
+ const fallback = flagString(args, "fallback-config");
326
+ if (fallback && existsSync3(resolve(io.cwd, fallback)))
327
+ return { path: resolve(io.cwd, fallback), found: "fallback" };
328
+ return void 0;
329
+ }
330
+ function out(io, value) {
331
+ io.out(JSON.stringify(value));
332
+ }
333
+ function warn(io, message) {
334
+ out(io, { systemMessage: message });
335
+ }
336
+ function respond(io, client, result, summary, approveCommand) {
337
+ if (result.decision === "deny" && result.error) {
338
+ const e = result.error;
339
+ const reason = JSON.stringify(e);
340
+ const id = result.approvalId ? `, approval ${result.approvalId}` : "";
341
+ const approve = e.code === "APPROVAL_REQUIRED" && result.approvalId ? ` To let this exact command run once: ${approveCommand(result.approvalId)}` : "";
342
+ const systemMessage = `okgate refused "${summary.slice(0, 120)}" (${e.code}${id}): ${e.cause}${approve}`;
343
+ if (client === "gemini-cli") out(io, { decision: "deny", reason, systemMessage });
344
+ else
345
+ out(io, {
346
+ hookSpecificOutput: {
347
+ hookEventName: "PreToolUse",
348
+ permissionDecision: "deny",
349
+ permissionDecisionReason: reason
350
+ },
351
+ systemMessage
352
+ });
353
+ return;
354
+ }
355
+ if (result.would) {
356
+ out(io, {
357
+ systemMessage: `okgate (hooks.mode: dry-run) would have stopped "${summary.slice(0, 120)}" with ${result.would.code}: ${result.would.cause} \u2014 it ran; nothing is blocked until hooks.mode is enforce`
358
+ });
359
+ }
360
+ }
361
+ async function hookCommand(args, io) {
362
+ const event = args.positionals[0];
363
+ if (event !== "pre-tool-use") {
364
+ io.err(
365
+ "usage: okgate hook pre-tool-use [--client claude|codex|gemini] [--config okgate.yaml] [--fallback-config path]\n run by a coding agent's pre-execution hook, with the hook's JSON on stdin; `okgate hooks install` sets it up"
366
+ );
367
+ return 1;
368
+ }
369
+ const flagClient = flagString(args, "client");
370
+ let client = flagClient && CLIENT_ALIASES[flagClient] || "claude-code";
371
+ try {
372
+ const raw = await io.stdin?.() ?? "";
373
+ const input = JSON.parse(raw);
374
+ if (!flagClient) client = detectHookClient(input);
375
+ const inputCwd = typeof input.cwd === "string" ? input.cwd : void 0;
376
+ const policyRef = resolveHookPolicy(args, io, inputCwd);
377
+ if (!policyRef) return 0;
378
+ const loaded = loadPolicyFile(policyRef.path, { env: io.env, allowMissingEnv: true });
379
+ const action = hookActionFromInput(client, input, inputCwd ?? io.cwd);
380
+ if (!action) return 0;
381
+ const home = homeFrom(io);
382
+ const workspace = projectDir(io, void 0) ?? (policyRef.found === "project" ? dirname(policyRef.path) : void 0) ?? action.cwd;
383
+ const tmpDirs = [
384
+ tmpdir(),
385
+ io.env.TMPDIR,
386
+ typeof input.scratchpad_dir === "string" ? input.scratchpad_dir : void 0,
387
+ "/tmp",
388
+ "/private/tmp",
389
+ "/var/folders",
390
+ "/private/var/folders"
391
+ ].filter((d) => Boolean(d));
392
+ const ctx = {
393
+ workspace: resolve(workspace),
394
+ home,
395
+ tmpDirs,
396
+ stateDir: loaded.stateDir,
397
+ // both names: creating okgate.yaml beside agentguard.yaml would replace the policy read
398
+ guardFiles: [
399
+ loaded.path,
400
+ ...POLICY_FILES.map((name) => join(dirname(loaded.path), name)),
401
+ ...hookSettingsFiles({ cwd: resolve(workspace), home })
402
+ ],
403
+ policyFile: basename(loaded.path)
404
+ };
405
+ const assessment = assessHookAction(loaded.policy, action, ctx);
406
+ const reg = readRegistration(loaded);
407
+ const { guard } = createFileGuard(loaded, {
408
+ env: io.env,
409
+ approvalUrl: approvalUrlFor(reg),
410
+ log: (line) => io.err(line)
411
+ });
412
+ const configHint = policyRef.found === "project" && dirname(policyRef.path) === resolve(workspace) ? "" : ` --config ${JSON.stringify(policyRef.path)}`;
413
+ const approveCommand = (id) => `${INSTALL} approve ${id}${configHint}`;
414
+ const result = await guard.preflight({
415
+ action,
416
+ assessment,
417
+ runId: `${RUN_PREFIX[client]}_${action.sessionId}`,
418
+ humanTurn: () => client === "claude-code" ? humanTurnFromTranscript(action.transcriptPath, {
419
+ requireOrigin: loaded.policy.hooks.irreversible.require_origin
420
+ }) : {
421
+ missing: `${client} transcripts are not a verified format for this check, so an irreversible command needs \`okgate approve\``
422
+ },
423
+ approveCommand
424
+ });
425
+ respond(io, client, result, assessment.summary, approveCommand);
426
+ return 0;
427
+ } catch (err) {
428
+ warn(
429
+ io,
430
+ `okgate hook error \u2014 this call was NOT checked: ${describeError(err).replace(/\s+/g, " ").slice(0, 400)}`
431
+ );
432
+ return 0;
433
+ }
434
+ }
435
+
436
+ // src/commands/keys.ts
437
+ import { readFileSync, writeFileSync } from "fs";
438
+ import YAML, { isMap, isSeq, YAMLSeq } from "yaml";
439
+ import { generateAgentKey, hashAgentKey } from "@agentwares/agentguard-core";
440
+ async function keyCommand(args, io) {
441
+ const [sub, name] = args.positionals;
442
+ const path = policyPathFrom(args, io);
443
+ const doc = YAML.parseDocument(readFileSync(path, "utf8"));
444
+ let agents = doc.get("agents");
445
+ if (!isSeq(agents)) {
446
+ agents = new YAMLSeq();
447
+ doc.set("agents", agents);
448
+ }
449
+ const seq = agents;
450
+ const items = seq.items.filter(isMap);
451
+ if (sub === "list" || sub === void 0) {
452
+ if (items.length === 0)
453
+ io.out("no agents in okgate.yaml \u2014 create one: okgate key create <name> --allow 'crm_get_*'");
454
+ for (const a of items) {
455
+ const allow2 = a.get("allow");
456
+ io.out(
457
+ `${String(a.get("name")).padEnd(20)} key: ${a.get("key_hash") ? "set" : "none (stdio --agent only)"} allow: ${isSeq(allow2) ? allow2.items.map(String).join(", ") : "all"}`
458
+ );
459
+ }
460
+ return 0;
461
+ }
462
+ if (!name) {
463
+ io.err(
464
+ "usage: okgate key create <agent> [--allow pattern]... [--deny pattern]... [--writes n] [--spend n] [--deletes n] [--emails n] [--tool-calls n] [--mode dry-run|enforce] [--upstream name]..."
465
+ );
466
+ return 1;
467
+ }
468
+ if (sub === "revoke") {
469
+ const idx = items.findIndex((a) => a.get("name") === name);
470
+ if (idx < 0) {
471
+ io.err(`no agent "${name}"`);
472
+ return 1;
473
+ }
474
+ seq.items.splice(seq.items.indexOf(items[idx]), 1);
475
+ writeFileSync(path, doc.toString());
476
+ io.out(`revoked ${name}: removed from okgate.yaml (running proxies pick it up on restart)`);
477
+ return 0;
478
+ }
479
+ if (sub !== "create") {
480
+ io.err(`unknown subcommand "${sub}" \u2014 use create, list or revoke`);
481
+ return 1;
482
+ }
483
+ const key = generateAgentKey();
484
+ const keyHash = await hashAgentKey(key);
485
+ const existing = items.find((a) => a.get("name") === name);
486
+ const entry = { name, key_hash: keyHash };
487
+ const allow = flagList(args, "allow");
488
+ const deny = flagList(args, "deny");
489
+ if (allow.length) entry.allow = allow;
490
+ if (deny.length) entry.deny = deny;
491
+ const upstreams = flagList(args, "upstream");
492
+ if (upstreams.length) entry.upstreams = upstreams;
493
+ const mode = flagString(args, "mode");
494
+ if (mode) entry.mode = mode;
495
+ const perRun = {};
496
+ for (const [flag, counter] of [
497
+ ["writes", "writes"],
498
+ ["spend", "spend_usd"],
499
+ ["deletes", "deletes"],
500
+ ["emails", "emails"],
501
+ ["tool-calls", "tool_calls"]
502
+ ]) {
503
+ const n = flagNumber(args, flag);
504
+ if (n !== void 0) perRun[counter] = n;
505
+ }
506
+ if (Object.keys(perRun).length) entry.caps = { per_run: perRun };
507
+ if (existing) {
508
+ for (const [k, v] of Object.entries(entry)) existing.set(k, v);
509
+ } else {
510
+ seq.add(doc.createNode(entry));
511
+ }
512
+ writeFileSync(path, doc.toString());
513
+ io.err(
514
+ `${existing ? "rotated" : "created"} agent "${name}" in ${path}${allow.length ? ` (allow: ${allow.join(", ")})` : " (allow: all tools)"}`
515
+ );
516
+ io.err(
517
+ "the key is shown once; give it to the agent as `Authorization: Bearer <key>` (HTTP) \u2014 over stdio use `okgate proxy --agent " + name + "`"
518
+ );
519
+ io.out(key);
520
+ return 0;
521
+ }
522
+
523
+ // src/commands/permission-diff.ts
524
+ import { execFileSync } from "child_process";
525
+ import {
526
+ diffPermissionFile,
527
+ renderPermissionDiffMarkdown
528
+ } from "@agentwares/agentguard-core";
529
+ var DEFAULT_PERMISSION_PATHS = [
530
+ "okgate.yaml",
531
+ "okgate.yml",
532
+ // the policy's name before the rename, still read when there is no okgate.yaml
533
+ "agentguard.yaml",
534
+ "agentguard.yml",
535
+ ".claude/settings.json",
536
+ ".claude/settings.local.json",
537
+ ".mcp.json",
538
+ "mcp.json",
539
+ ".cursor/mcp.json",
540
+ ".vscode/mcp.json"
541
+ ];
542
+ var PERMISSION_FILE = /(^|\/)((okgate|agentguard)[^/]*\.ya?ml|\.claude\/settings(\.local)?\.json|[^/]*mcp[^/]*\.json|claude_desktop_config\.json)$/;
543
+ function git(cwd, argv) {
544
+ try {
545
+ return execFileSync("git", argv, {
546
+ cwd,
547
+ encoding: "utf8",
548
+ stdio: ["ignore", "pipe", "ignore"]
549
+ });
550
+ } catch {
551
+ return void 0;
552
+ }
553
+ }
554
+ function showAt(cwd, ref, path) {
555
+ return git(cwd, ["show", `${ref}:${path}`]);
556
+ }
557
+ function changedPermissionFiles(cwd, base, head) {
558
+ const out2 = git(cwd, ["diff", "--name-only", base, head]) ?? "";
559
+ return out2.split("\n").map((l) => l.trim()).filter((l) => l && PERMISSION_FILE.test(l));
560
+ }
561
+ function resolveBase(cwd, explicit) {
562
+ if (explicit) return explicit;
563
+ for (const candidate of ["origin/main", "main", "origin/master", "master"]) {
564
+ if (git(cwd, ["rev-parse", "--verify", "--quiet", candidate]) !== void 0) return candidate;
565
+ }
566
+ return "HEAD~1";
567
+ }
568
+ function runPermissionDiff(cwd, opts) {
569
+ const base = resolveBase(cwd, opts.base);
570
+ const head = opts.head ?? "HEAD";
571
+ const files = [
572
+ .../* @__PURE__ */ new Set([
573
+ ...opts.paths ?? DEFAULT_PERMISSION_PATHS,
574
+ ...changedPermissionFiles(cwd, base, head)
575
+ ])
576
+ ];
577
+ const findings = [];
578
+ const touched = [];
579
+ for (const file of files) {
580
+ const before = showAt(cwd, base, file);
581
+ const after = showAt(cwd, head, file);
582
+ if (before === void 0 && after === void 0) continue;
583
+ if (before === after) continue;
584
+ touched.push(file);
585
+ findings.push(...diffPermissionFile(file, before, after));
586
+ }
587
+ return {
588
+ base,
589
+ head,
590
+ files: touched,
591
+ findings,
592
+ markdown: renderPermissionDiffMarkdown(findings, { title: opts.title })
593
+ };
594
+ }
595
+ async function permissionDiffCommand(args, io) {
596
+ const paths = flagList(args, "paths");
597
+ const result = runPermissionDiff(io.cwd, {
598
+ base: flagString(args, "base"),
599
+ head: flagString(args, "head"),
600
+ paths: paths.length ? paths : void 0
601
+ });
602
+ const format = flagString(args, "format") ?? (flagBool(args, "json") ? "json" : "markdown");
603
+ if (format === "json") io.out(JSON.stringify(result, null, 2));
604
+ else io.out(result.markdown);
605
+ const widen = result.findings.filter((f) => f.severity === "widen").length;
606
+ if (flagBool(args, "fail-on-widen") && widen > 0) return 1;
607
+ return 0;
608
+ }
609
+
610
+ // src/commands/report.ts
611
+ import { resolve as resolve2 } from "path";
612
+ import {
613
+ buildReport,
614
+ latestRunId,
615
+ listRuns,
616
+ renderMutationDiff,
617
+ renderReportMarkdown
618
+ } from "@agentwares/agentguard-core";
619
+ import { readAuditFile, verifyAuditFile } from "@agentwares/agentguard-core/node";
620
+ function pickRun(args, entries) {
621
+ if (flagBool(args, "all")) return void 0;
622
+ return flagString(args, "run") ?? latestRunId(entries);
623
+ }
624
+ async function reportCommand(args, io) {
625
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
626
+ const entries = readAuditFile(loaded.auditPath);
627
+ const runId = pickRun(args, entries);
628
+ const report = buildReport(entries, { runId });
629
+ if (flagBool(args, "json")) {
630
+ io.out(
631
+ JSON.stringify(
632
+ {
633
+ ...report,
634
+ mutations: report.mutations.map((m) => ({
635
+ seq: m.seq,
636
+ ts: m.ts,
637
+ tool: m.tool,
638
+ mutation: m.mutation
639
+ }))
640
+ },
641
+ null,
642
+ 2
643
+ )
644
+ );
645
+ return 0;
646
+ }
647
+ io.out(renderReportMarkdown(report));
648
+ const chain = await verifyAuditFile(loaded.auditPath);
649
+ io.out(
650
+ chain.ok ? `Audit chain: ${chain.entries} entries, verified (${loaded.auditPath}).` : `Audit chain BROKEN at entry ${chain.brokenAt}: ${chain.reason}`
651
+ );
652
+ if (runId && listRuns(entries).length > 1) io.out(`Other runs: okgate report --all`);
653
+ return 0;
654
+ }
655
+ async function diffCommand(args, io) {
656
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
657
+ const entries = readAuditFile(loaded.auditPath);
658
+ const runId = pickRun(args, entries);
659
+ io.out(renderMutationDiff(entries, { runId }).trimEnd());
660
+ return 0;
661
+ }
662
+ async function verifyCommand(args, io) {
663
+ let path = args.positionals[0];
664
+ if (!path) path = loadPolicyFor(args, io, { allowMissingEnv: true }).auditPath;
665
+ path = resolve2(io.cwd, path);
666
+ const result = await verifyAuditFile(path);
667
+ if (flagBool(args, "json")) io.out(JSON.stringify({ path, ...result }));
668
+ else if (result.ok)
669
+ io.out(
670
+ `ok: ${result.entries} entries, chain intact, head ${result.head?.slice(0, 16)}\u2026 (${path})`
671
+ );
672
+ else io.out(`BROKEN at entry ${result.brokenAt}: ${result.reason} (${path})`);
673
+ return result.ok ? 0 : 1;
674
+ }
675
+ async function statusCommand(args, io) {
676
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
677
+ const { guard } = createFileGuard(loaded, { alerts: false, env: io.env });
678
+ const entries = readAuditFile(loaded.auditPath);
679
+ const runId = flagString(args, "run") ?? latestRunId(entries) ?? "(none yet)";
680
+ const status = await guard.status(runId);
681
+ const killed = status.killed;
682
+ const reg = readRegistration(loaded);
683
+ const pending = await guard.approvals.list("pending");
684
+ if (flagBool(args, "json")) {
685
+ io.out(
686
+ JSON.stringify(
687
+ {
688
+ policy: loaded.path,
689
+ mode: loaded.policy.mode,
690
+ runId,
691
+ ...status,
692
+ killed,
693
+ pendingApprovals: pending.length,
694
+ http: reg ? { url: reg.url, pid: reg.pid } : void 0
695
+ },
696
+ null,
697
+ 2
698
+ )
699
+ );
700
+ return 0;
701
+ }
702
+ io.out(
703
+ `policy ${loaded.path} (mode: ${loaded.policy.mode}, ${loaded.policy.upstreams.length} upstreams)`
704
+ );
705
+ io.out(`kill ${killed.killed ? `ON \u2014 ${killed.reason} (okgate resume to clear)` : "off"}`);
706
+ io.out(`http ${reg ? `${reg.url} (pid ${reg.pid})` : "not running (okgate proxy --http)"}`);
707
+ io.out(`audit ${entries.length} entries in ${loaded.auditPath}`);
708
+ io.out(
709
+ `pending ${pending.length} approval${pending.length === 1 ? "" : "s"}${pending.length ? ` \u2014 okgate approvals` : ""}`
710
+ );
711
+ io.out(`run ${runId}`);
712
+ const fmt = (scope) => Object.entries(status.caps[scope]).map(([k, limit]) => `${k} ${status.usage[scope][k] ?? 0}/${limit}`).join(" ") || "no caps";
713
+ io.out(` per_run ${fmt("per_run")}`);
714
+ io.out(` per_day ${fmt("per_day")}`);
715
+ return 0;
716
+ }
717
+
718
+ // src/cli.ts
719
+ var proxyCommand = async (args, io) => (await import("./proxy-6GP63TLD.js")).proxyCommand(args, io);
720
+ var initCommand = async (args, io) => (await import("./init-MKPQU2JG.js")).initCommand(args, io);
721
+ var toolsCommand = async (args, io) => (await import("./tools-XS7PMTLT.js")).toolsCommand(args, io);
722
+ var auditCommand = async (args, io) => (await import("./audit-5LUDXZLG.js")).auditCommand(args, io);
723
+ var HELP = `okgate \u2014 MCP policy proxy for agents that touch production
724
+
725
+ ${INSTALL} init
726
+ read your MCP config, write okgate.yaml (dry-run), route every server through the proxy
727
+ okgate report [--run id|--all] what this run did / would have destroyed / spent, where it was halted
728
+ okgate diff [--run id] record-by-record mutation diff from a dry run
729
+ okgate verify [audit.jsonl] prove the hash-chained audit log was not edited
730
+ okgate connect <key> [--write] point this machine at your hosted proxy (paid tiers)
731
+
732
+ okgate hooks install [--project|--user] [--client claude|codex|gemini|all]
733
+ check a coding agent's own shell commands and file edits against okgate.yaml (dry-run first);
734
+ irreversible commands (push, merge, publish, send, rm outside the repo) only on a turn a person typed
735
+ okgate hooks uninstall | status
736
+ okgate hook pre-tool-use what the installed hook runs (reads the harness's JSON on stdin)
737
+
738
+ okgate audit [--since 7d|30d|all] [--client claude|codex|all] [--json]
739
+ which rules in CLAUDE.md / AGENTS.md / GEMINI.md / .cursor/rules your coding agents broke,
740
+ from this machine's Claude Code and Codex sessions in this repo (nothing is sent anywhere)
741
+ okgate audit --enforce [--preview]
742
+ write the checkable rules into okgate.yaml as hook rules, in dry-run, and install the hook
743
+
744
+ okgate proxy [--http --port 8788] [--agent name] [--run-id id] [--mode dry-run|enforce]
745
+ okgate tools [--json] the tools your agent will see, with class and why
746
+ okgate status [--run id] counters vs caps, kill state, pending approvals
747
+
748
+ okgate kill [reason] halt every run now (file + running HTTP proxy); okgate resume
749
+ okgate approvals [--all] pending approvals; okgate approve <id> | deny <id> [--note text]
750
+ okgate key create <agent> [--allow 'crm_get_*']... [--deny p] [--writes n] [--spend n] [--mode m]
751
+ okgate key list | revoke <agent>
752
+ okgate permission-diff [--base ref] [--head ref] [--paths a,b] [--format markdown|json] [--fail-on-widen]
753
+
754
+ Options: --config <okgate.yaml> (or OKGATE_CONFIG), --json, --help, --version
755
+ Renamed from agentguard: agentguard.yaml, .agentguard/, AGENTGUARD_* and the agentguard bin keep working.
756
+ Docs: https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli`;
757
+ async function main(argv, io = defaultIo()) {
758
+ const args = parseArgs(argv);
759
+ if (args.flags.version) {
760
+ io.out(version());
761
+ return 0;
762
+ }
763
+ if (args.command === void 0 && !args.flags.help && io.tty === false) {
764
+ try {
765
+ return await proxyCommand(args, io);
766
+ } catch (err) {
767
+ io.err(describeError(err));
768
+ return 1;
769
+ }
770
+ }
771
+ if (args.flags.help || args.command === void 0 || args.command === "help") {
772
+ io.out(HELP);
773
+ return args.command === void 0 && !args.flags.help ? 1 : 0;
774
+ }
775
+ try {
776
+ switch (args.command) {
777
+ case "init":
778
+ return await initCommand(args, io);
779
+ case "proxy":
780
+ case "serve":
781
+ return await proxyCommand(args, io);
782
+ case "report":
783
+ return await reportCommand(args, io);
784
+ case "diff":
785
+ return await diffCommand(args, io);
786
+ case "verify":
787
+ return await verifyCommand(args, io);
788
+ case "connect":
789
+ return await connectCommand(args, io);
790
+ case "status":
791
+ return await statusCommand(args, io);
792
+ case "tools":
793
+ return await toolsCommand(args, io);
794
+ case "kill":
795
+ return await killCommand(args, io);
796
+ case "resume":
797
+ return await resumeCommand(args, io);
798
+ case "approve":
799
+ return await decideCommand("approved", args, io);
800
+ case "deny":
801
+ return await decideCommand("denied", args, io);
802
+ case "approvals":
803
+ return await approvalsCommand(args, io);
804
+ case "key":
805
+ case "keys":
806
+ return await keyCommand(args, io);
807
+ case "permission-diff":
808
+ return await permissionDiffCommand(args, io);
809
+ case "hook":
810
+ return await hookCommand(args, io);
811
+ case "hooks":
812
+ return await hooksCommand(args, io);
813
+ case "audit":
814
+ return await auditCommand(args, io);
815
+ default:
816
+ io.err(`unknown command "${args.command}"
817
+ `);
818
+ io.err(HELP);
819
+ return 1;
820
+ }
821
+ } catch (err) {
822
+ io.err(describeError(err));
823
+ return 1;
824
+ }
825
+ }
826
+ var embedded = globalThis[/* @__PURE__ */ Symbol.for("okgate.cli.embedded")] === true;
827
+ var isMain = !embedded && process.argv[1] !== void 0 && /(^|[\\/])(cli\.[cm]?[jt]s|okgate|agentguard)$/.test(process.argv[1]);
828
+ if (isMain) {
829
+ main(process.argv.slice(2)).then(
830
+ (code) => {
831
+ if (code !== 0) process.exitCode = code;
832
+ },
833
+ (err) => {
834
+ process.stderr.write(`${describeError(err)}
835
+ `);
836
+ process.exitCode = 1;
837
+ }
838
+ );
839
+ }
840
+
841
+ export {
842
+ DEFAULT_PERMISSION_PATHS,
843
+ runPermissionDiff,
844
+ HELP,
845
+ main
846
+ };
847
+ //# sourceMappingURL=chunk-ZG4LGUJE.js.map