@agentwares/agentguard 0.1.7 → 0.1.9

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 (48) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +223 -16
  3. package/dist/audit-2YCMHMCP.js +17 -0
  4. package/dist/audit-2YCMHMCP.js.map +1 -0
  5. package/dist/chunk-3O6KWT3E.js +345 -0
  6. package/dist/chunk-3O6KWT3E.js.map +1 -0
  7. package/dist/chunk-5WRI5ZAA.js +31 -0
  8. package/dist/chunk-5WRI5ZAA.js.map +1 -0
  9. package/dist/chunk-6S7LYVOC.js +1033 -0
  10. package/dist/chunk-6S7LYVOC.js.map +1 -0
  11. package/dist/chunk-6TUQD666.js +828 -0
  12. package/dist/chunk-6TUQD666.js.map +1 -0
  13. package/dist/chunk-BKXXTWIS.js +119 -0
  14. package/dist/chunk-BKXXTWIS.js.map +1 -0
  15. package/dist/chunk-D66U3PVB.js +529 -0
  16. package/dist/chunk-D66U3PVB.js.map +1 -0
  17. package/dist/chunk-HPYZ3JDX.js +206 -0
  18. package/dist/chunk-HPYZ3JDX.js.map +1 -0
  19. package/dist/chunk-KRKZGAJ3.js +769 -0
  20. package/dist/chunk-KRKZGAJ3.js.map +1 -0
  21. package/dist/chunk-SUXQTT3X.js +1268 -0
  22. package/dist/chunk-SUXQTT3X.js.map +1 -0
  23. package/dist/chunk-TW5KJREH.js +190 -0
  24. package/dist/chunk-TW5KJREH.js.map +1 -0
  25. package/dist/chunk-VQWQ5TOM.js +113 -0
  26. package/dist/chunk-VQWQ5TOM.js.map +1 -0
  27. package/dist/chunk-X4M7YWQA.js +270 -0
  28. package/dist/chunk-X4M7YWQA.js.map +1 -0
  29. package/dist/chunk-ZSLWPKXI.js +1144 -0
  30. package/dist/chunk-ZSLWPKXI.js.map +1 -0
  31. package/dist/cli.js +8 -4775
  32. package/dist/cli.js.map +1 -1
  33. package/dist/fixtures/crm-server.js +6 -1272
  34. package/dist/fixtures/crm-server.js.map +1 -1
  35. package/dist/fixtures/demo-agent.js +4 -115
  36. package/dist/fixtures/demo-agent.js.map +1 -1
  37. package/dist/index.d.ts +69 -10
  38. package/dist/index.js +245 -5138
  39. package/dist/index.js.map +1 -1
  40. package/dist/init-CWROGV5I.js +15 -0
  41. package/dist/init-CWROGV5I.js.map +1 -0
  42. package/dist/proxy-3Z5YEZ4R.js +154 -0
  43. package/dist/proxy-3Z5YEZ4R.js.map +1 -0
  44. package/dist/tools-3LLDG3XQ.js +15 -0
  45. package/dist/tools-3LLDG3XQ.js.map +1 -0
  46. package/llms.txt +4 -0
  47. package/package.json +9 -3
  48. package/server.json +2 -2
@@ -0,0 +1,190 @@
1
+ // src/configs.ts
2
+ import { copyFileSync, existsSync, readFileSync, writeFileSync } from "fs";
3
+ import { homedir, platform } from "os";
4
+ import { join, resolve } from "path";
5
+ function candidateConfigs(cwd = process.cwd(), home = homedir(), env = process.env) {
6
+ const desktop = platform() === "darwin" ? join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : platform() === "win32" ? join(
7
+ env.APPDATA ?? join(home, "AppData", "Roaming"),
8
+ "Claude",
9
+ "claude_desktop_config.json"
10
+ ) : join(
11
+ env.XDG_CONFIG_HOME ?? join(home, ".config"),
12
+ "Claude",
13
+ "claude_desktop_config.json"
14
+ );
15
+ return [
16
+ {
17
+ kind: "claude-code-project",
18
+ label: "Claude Code (project .mcp.json)",
19
+ path: join(cwd, ".mcp.json"),
20
+ serversKey: "mcpServers"
21
+ },
22
+ {
23
+ kind: "cursor-project",
24
+ label: "Cursor (project)",
25
+ path: join(cwd, ".cursor", "mcp.json"),
26
+ serversKey: "mcpServers"
27
+ },
28
+ {
29
+ kind: "vscode",
30
+ label: "VS Code (project)",
31
+ path: join(cwd, ".vscode", "mcp.json"),
32
+ serversKey: "servers"
33
+ },
34
+ { kind: "generic", label: "mcp.json", path: join(cwd, "mcp.json"), serversKey: "mcpServers" },
35
+ {
36
+ kind: "gemini-project",
37
+ label: "Gemini CLI (project .gemini/settings.json)",
38
+ path: join(cwd, ".gemini", "settings.json"),
39
+ serversKey: "mcpServers"
40
+ },
41
+ { kind: "claude-desktop", label: "Claude Desktop", path: desktop, serversKey: "mcpServers" },
42
+ {
43
+ kind: "cursor-user",
44
+ label: "Cursor (user)",
45
+ path: join(home, ".cursor", "mcp.json"),
46
+ serversKey: "mcpServers"
47
+ },
48
+ {
49
+ kind: "windsurf",
50
+ label: "Windsurf",
51
+ path: join(home, ".codeium", "windsurf", "mcp_config.json"),
52
+ serversKey: "mcpServers"
53
+ },
54
+ {
55
+ kind: "claude-code-user",
56
+ label: "Claude Code (user ~/.claude.json)",
57
+ path: join(home, ".claude.json"),
58
+ serversKey: "mcpServers"
59
+ },
60
+ {
61
+ kind: "gemini-user",
62
+ label: "Gemini CLI (user ~/.gemini/settings.json)",
63
+ path: join(home, ".gemini", "settings.json"),
64
+ serversKey: "mcpServers"
65
+ }
66
+ ];
67
+ }
68
+ function isGeminiConfig(file) {
69
+ return file.kind === "gemini-project" || file.kind === "gemini-user" || /[/\\]\.gemini[/\\]settings\.json$/.test(file.path);
70
+ }
71
+ function detectClientConfigs(cwd, home, env) {
72
+ return candidateConfigs(cwd, home, env).filter((c) => existsSync(c.path));
73
+ }
74
+ function describeConfigPath(path) {
75
+ const abs = resolve(path);
76
+ const known = candidateConfigs().find((c) => c.path === abs);
77
+ if (known) return known;
78
+ let serversKey = "mcpServers";
79
+ try {
80
+ const parsed = JSON.parse(readFileSync(abs, "utf8"));
81
+ if (parsed.servers && !parsed.mcpServers) serversKey = "servers";
82
+ } catch {
83
+ }
84
+ return {
85
+ kind: abs.includes(".vscode") ? "vscode" : "generic",
86
+ label: abs,
87
+ path: abs,
88
+ serversKey
89
+ };
90
+ }
91
+ function readServers(file) {
92
+ const raw = JSON.parse(readFileSync(file.path, "utf8"));
93
+ const servers = raw[file.serversKey];
94
+ if (!servers || typeof servers !== "object") return {};
95
+ return servers;
96
+ }
97
+ var PROXY_SERVER_NAME = "agentguard";
98
+ var PROXY_PACKAGE = "@agentwares/agentguard";
99
+ function isProxyEntry(entry) {
100
+ return (entry.args ?? []).some((a) => a === "agentguard" || /(^|\/)agentguard(\.js)?$/.test(a)) && (entry.args ?? []).includes("proxy");
101
+ }
102
+ function serversToUpstreams(servers) {
103
+ const upstreams = [];
104
+ const skipped = [];
105
+ for (const [rawName, entry] of Object.entries(servers)) {
106
+ if (isProxyEntry(entry)) {
107
+ skipped.push({ name: rawName, reason: "already the agentguard proxy" });
108
+ continue;
109
+ }
110
+ const name = rawName.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/^[^a-zA-Z0-9]+/, "") || "upstream";
111
+ const url = typeof entry.httpUrl === "string" ? entry.httpUrl : entry.url;
112
+ if (url) {
113
+ upstreams.push({
114
+ name,
115
+ url,
116
+ headers: entry.headers ?? {},
117
+ args: [],
118
+ env: {},
119
+ prefix: false,
120
+ timeout_ms: 6e4
121
+ });
122
+ } else if (entry.command) {
123
+ upstreams.push({
124
+ name,
125
+ command: entry.command,
126
+ args: entry.args ?? [],
127
+ env: entry.env ?? {},
128
+ cwd: entry.cwd,
129
+ headers: {},
130
+ prefix: false,
131
+ timeout_ms: 6e4
132
+ });
133
+ } else {
134
+ skipped.push({ name: rawName, reason: "no `command` or `url`" });
135
+ }
136
+ }
137
+ return { upstreams, skipped };
138
+ }
139
+ function backupPath(file) {
140
+ return `${file.path}.agentguard-backup`;
141
+ }
142
+ function proxyEntry(file, policyPath, opts = {}) {
143
+ const args = ["-y", PROXY_PACKAGE, "proxy", "--config", policyPath];
144
+ if (opts.agent) args.push("--agent", opts.agent);
145
+ const entry = { command: "npx", args };
146
+ if (file.serversKey === "servers") entry.type = "stdio";
147
+ return entry;
148
+ }
149
+ function rewriteClientConfig(file, policyPath, opts = {}) {
150
+ const raw = JSON.parse(readFileSync(file.path, "utf8"));
151
+ const servers = raw[file.serversKey] ?? {};
152
+ const replaced = Object.keys(servers).filter((n) => !isProxyEntry(servers[n]));
153
+ const backup = backupPath(file);
154
+ if (!existsSync(backup)) copyFileSync(file.path, backup);
155
+ raw[file.serversKey] = { [PROXY_SERVER_NAME]: proxyEntry(file, policyPath, opts) };
156
+ writeFileSync(file.path, JSON.stringify(raw, null, 2) + "\n");
157
+ return { backup, replaced };
158
+ }
159
+ function restoreClientConfig(file) {
160
+ const backup = backupPath(file);
161
+ if (!existsSync(backup)) return false;
162
+ copyFileSync(backup, file.path);
163
+ return true;
164
+ }
165
+ function mergeServerIntoConfig(file, name, entry) {
166
+ const raw = JSON.parse(readFileSync(file.path, "utf8"));
167
+ const servers = raw[file.serversKey] ?? {};
168
+ const replaced = name in servers;
169
+ const backup = backupPath(file);
170
+ if (!existsSync(backup)) copyFileSync(file.path, backup);
171
+ raw[file.serversKey] = { ...servers, [name]: entry };
172
+ writeFileSync(file.path, JSON.stringify(raw, null, 2) + "\n");
173
+ return { backup, replaced };
174
+ }
175
+
176
+ export {
177
+ candidateConfigs,
178
+ isGeminiConfig,
179
+ detectClientConfigs,
180
+ describeConfigPath,
181
+ readServers,
182
+ PROXY_SERVER_NAME,
183
+ PROXY_PACKAGE,
184
+ serversToUpstreams,
185
+ proxyEntry,
186
+ rewriteClientConfig,
187
+ restoreClientConfig,
188
+ mergeServerIntoConfig
189
+ };
190
+ //# sourceMappingURL=chunk-TW5KJREH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/configs.ts"],"sourcesContent":["/**\n * Find the agent's existing MCP config (Claude Code, Claude Desktop, Cursor, VS Code, Windsurf,\n * Gemini CLI, generic `mcp.json`), read its servers, and rewrite it so every server goes through\n * the proxy.\n */\nimport { copyFileSync, existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport type { Upstream } from \"@agentwares/agentguard-core\";\n\nexport type ClientKind =\n | \"claude-code-project\"\n | \"claude-code-user\"\n | \"claude-desktop\"\n | \"cursor-project\"\n | \"cursor-user\"\n | \"vscode\"\n | \"windsurf\"\n | \"gemini-project\"\n | \"gemini-user\"\n | \"generic\";\n\nexport interface ClientConfigFile {\n kind: ClientKind;\n label: string;\n path: string;\n /** `mcpServers` (most clients) or `servers` (VS Code) */\n serversKey: \"mcpServers\" | \"servers\";\n}\n\nexport interface ServerEntry {\n type?: string;\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n url?: string;\n /** Gemini CLI's name for a Streamable HTTP endpoint (its `url` means SSE) */\n httpUrl?: string;\n headers?: Record<string, string>;\n [k: string]: unknown;\n}\n\nexport function candidateConfigs(\n cwd: string = process.cwd(),\n home: string = homedir(),\n env: NodeJS.ProcessEnv = process.env,\n): ClientConfigFile[] {\n const desktop =\n platform() === \"darwin\"\n ? join(home, \"Library\", \"Application Support\", \"Claude\", \"claude_desktop_config.json\")\n : platform() === \"win32\"\n ? join(\n env.APPDATA ?? join(home, \"AppData\", \"Roaming\"),\n \"Claude\",\n \"claude_desktop_config.json\",\n )\n : join(\n env.XDG_CONFIG_HOME ?? join(home, \".config\"),\n \"Claude\",\n \"claude_desktop_config.json\",\n );\n return [\n {\n kind: \"claude-code-project\",\n label: \"Claude Code (project .mcp.json)\",\n path: join(cwd, \".mcp.json\"),\n serversKey: \"mcpServers\",\n },\n {\n kind: \"cursor-project\",\n label: \"Cursor (project)\",\n path: join(cwd, \".cursor\", \"mcp.json\"),\n serversKey: \"mcpServers\",\n },\n {\n kind: \"vscode\",\n label: \"VS Code (project)\",\n path: join(cwd, \".vscode\", \"mcp.json\"),\n serversKey: \"servers\",\n },\n { kind: \"generic\", label: \"mcp.json\", path: join(cwd, \"mcp.json\"), serversKey: \"mcpServers\" },\n {\n kind: \"gemini-project\",\n label: \"Gemini CLI (project .gemini/settings.json)\",\n path: join(cwd, \".gemini\", \"settings.json\"),\n serversKey: \"mcpServers\",\n },\n { kind: \"claude-desktop\", label: \"Claude Desktop\", path: desktop, serversKey: \"mcpServers\" },\n {\n kind: \"cursor-user\",\n label: \"Cursor (user)\",\n path: join(home, \".cursor\", \"mcp.json\"),\n serversKey: \"mcpServers\",\n },\n {\n kind: \"windsurf\",\n label: \"Windsurf\",\n path: join(home, \".codeium\", \"windsurf\", \"mcp_config.json\"),\n serversKey: \"mcpServers\",\n },\n {\n kind: \"claude-code-user\",\n label: \"Claude Code (user ~/.claude.json)\",\n path: join(home, \".claude.json\"),\n serversKey: \"mcpServers\",\n },\n {\n kind: \"gemini-user\",\n label: \"Gemini CLI (user ~/.gemini/settings.json)\",\n path: join(home, \".gemini\", \"settings.json\"),\n serversKey: \"mcpServers\",\n },\n ];\n}\n\n/** Gemini CLI reads `httpUrl` for Streamable HTTP and `url` for SSE, unlike every other client. */\nexport function isGeminiConfig(file: ClientConfigFile): boolean {\n return (\n file.kind === \"gemini-project\" ||\n file.kind === \"gemini-user\" ||\n /[/\\\\]\\.gemini[/\\\\]settings\\.json$/.test(file.path)\n );\n}\n\n/** Configs that exist on disk, project-level first. */\nexport function detectClientConfigs(\n cwd?: string,\n home?: string,\n env?: NodeJS.ProcessEnv,\n): ClientConfigFile[] {\n return candidateConfigs(cwd, home, env).filter((c) => existsSync(c.path));\n}\n\n/** Describe an explicit path the user passed with `--client`. */\nexport function describeConfigPath(path: string): ClientConfigFile {\n const abs = resolve(path);\n const known = candidateConfigs().find((c) => c.path === abs);\n if (known) return known;\n let serversKey: \"mcpServers\" | \"servers\" = \"mcpServers\";\n try {\n const parsed = JSON.parse(readFileSync(abs, \"utf8\")) as Record<string, unknown>;\n if (parsed.servers && !parsed.mcpServers) serversKey = \"servers\";\n } catch {\n // unreadable: caller reports\n }\n return {\n kind: abs.includes(\".vscode\") ? \"vscode\" : \"generic\",\n label: abs,\n path: abs,\n serversKey,\n };\n}\n\nexport function readServers(file: ClientConfigFile): Record<string, ServerEntry> {\n const raw = JSON.parse(readFileSync(file.path, \"utf8\")) as Record<string, unknown>;\n const servers = raw[file.serversKey];\n if (!servers || typeof servers !== \"object\") return {};\n return servers as Record<string, ServerEntry>;\n}\n\nexport const PROXY_SERVER_NAME = \"agentguard\";\n\n/** What `npx` is told to fetch. Must be the published package name. */\nexport const PROXY_PACKAGE = \"@agentwares/agentguard\";\n\nexport function isProxyEntry(entry: ServerEntry): boolean {\n return (\n (entry.args ?? []).some((a) => a === \"agentguard\" || /(^|\\/)agentguard(\\.js)?$/.test(a)) &&\n (entry.args ?? []).includes(\"proxy\")\n );\n}\n\n/** Client servers → policy upstreams. Names are sanitized; secrets stay where they were (in the config). */\nexport function serversToUpstreams(servers: Record<string, ServerEntry>): {\n upstreams: Upstream[];\n skipped: { name: string; reason: string }[];\n} {\n const upstreams: Upstream[] = [];\n const skipped: { name: string; reason: string }[] = [];\n for (const [rawName, entry] of Object.entries(servers)) {\n if (isProxyEntry(entry)) {\n skipped.push({ name: rawName, reason: \"already the agentguard proxy\" });\n continue;\n }\n const name =\n rawName.replace(/[^a-zA-Z0-9_-]/g, \"-\").replace(/^[^a-zA-Z0-9]+/, \"\") || \"upstream\";\n // `httpUrl` is Gemini CLI's Streamable HTTP field; agentguard reaches upstreams that way.\n const url = typeof entry.httpUrl === \"string\" ? entry.httpUrl : entry.url;\n if (url) {\n upstreams.push({\n name,\n url,\n headers: entry.headers ?? {},\n args: [],\n env: {},\n prefix: false,\n timeout_ms: 60_000,\n });\n } else if (entry.command) {\n upstreams.push({\n name,\n command: entry.command,\n args: entry.args ?? [],\n env: entry.env ?? {},\n cwd: entry.cwd,\n headers: {},\n prefix: false,\n timeout_ms: 60_000,\n });\n } else {\n skipped.push({ name: rawName, reason: \"no `command` or `url`\" });\n }\n }\n return { upstreams, skipped };\n}\n\nexport function backupPath(file: ClientConfigFile): string {\n return `${file.path}.agentguard-backup`;\n}\n\n/** The entry that replaces every server: one proxy, all upstreams behind it. */\nexport function proxyEntry(\n file: ClientConfigFile,\n policyPath: string,\n opts: { agent?: string } = {},\n): ServerEntry {\n // The published name, scope and all. `npx -y agentguard` 404s — that package does not\n // exist — so every config this wrote failed to start the proxy, and the line printed next\n // to it already said `npx @agentwares/agentguard proxy`. Worse than broken: the unscoped\n // name is unclaimed, so anyone who took it would have their code run by our onboarding.\n const args = [\"-y\", PROXY_PACKAGE, \"proxy\", \"--config\", policyPath];\n if (opts.agent) args.push(\"--agent\", opts.agent);\n const entry: ServerEntry = { command: \"npx\", args };\n if (file.serversKey === \"servers\") entry.type = \"stdio\";\n return entry;\n}\n\n/** Back up the file (once) and replace its servers with the single proxy entry. */\nexport function rewriteClientConfig(\n file: ClientConfigFile,\n policyPath: string,\n opts: { agent?: string } = {},\n): { backup: string; replaced: string[] } {\n const raw = JSON.parse(readFileSync(file.path, \"utf8\")) as Record<string, unknown>;\n const servers = (raw[file.serversKey] as Record<string, ServerEntry> | undefined) ?? {};\n const replaced = Object.keys(servers).filter((n) => !isProxyEntry(servers[n]!));\n const backup = backupPath(file);\n if (!existsSync(backup)) copyFileSync(file.path, backup);\n raw[file.serversKey] = { [PROXY_SERVER_NAME]: proxyEntry(file, policyPath, opts) };\n writeFileSync(file.path, JSON.stringify(raw, null, 2) + \"\\n\");\n return { backup, replaced };\n}\n\n/** `agentguard init --undo`: restore the backup. */\nexport function restoreClientConfig(file: ClientConfigFile): boolean {\n const backup = backupPath(file);\n if (!existsSync(backup)) return false;\n copyFileSync(backup, file.path);\n return true;\n}\n\n/**\n * Add (or replace) one named server in a client config, leaving every other server alone.\n *\n * `rewriteClientConfig` is the `init` path: it replaces the whole server list with the local\n * proxy. `connect` is the opposite shape — the hosted proxy is one more remote server sitting\n * alongside whatever the client already has — so it merges instead of replacing. Both back the\n * file up once before touching it, so `init --undo` still restores either.\n */\nexport function mergeServerIntoConfig(\n file: ClientConfigFile,\n name: string,\n entry: ServerEntry,\n): { backup: string; replaced: boolean } {\n const raw = JSON.parse(readFileSync(file.path, \"utf8\")) as Record<string, unknown>;\n const servers = (raw[file.serversKey] as Record<string, ServerEntry> | undefined) ?? {};\n const replaced = name in servers;\n const backup = backupPath(file);\n if (!existsSync(backup)) copyFileSync(file.path, backup);\n raw[file.serversKey] = { ...servers, [name]: entry };\n writeFileSync(file.path, JSON.stringify(raw, null, 2) + \"\\n\");\n return { backup, replaced };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,113 @@
1
+ import {
2
+ ProxyRuntime
3
+ } from "./chunk-D66U3PVB.js";
4
+ import {
5
+ flagBool,
6
+ loadPolicyFor
7
+ } from "./chunk-SUXQTT3X.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 agentguard's own read-only tools instead (agentguard_get_status, agentguard_get_report, agentguard_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-VQWQ5TOM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/tools.ts"],"sourcesContent":["/**\n * `agentguard 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 agentguard's own read-only tools instead (agentguard_get_status, agentguard_get_report, agentguard_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,270 @@
1
+ import {
2
+ probeUpstreams
3
+ } from "./chunk-VQWQ5TOM.js";
4
+ import {
5
+ describeConfigPath,
6
+ detectClientConfigs,
7
+ readServers,
8
+ restoreClientConfig,
9
+ rewriteClientConfig,
10
+ serversToUpstreams
11
+ } from "./chunk-TW5KJREH.js";
12
+ import {
13
+ flagBool,
14
+ flagString,
15
+ homeFrom,
16
+ policyPathFrom
17
+ } from "./chunk-SUXQTT3X.js";
18
+
19
+ // src/commands/init.ts
20
+ import { existsSync, writeFileSync } from "fs";
21
+ import { relative, resolve } from "path";
22
+ import YAML from "yaml";
23
+ function dump(value, indent = 0) {
24
+ const text = YAML.stringify(value, { indent: 2, lineWidth: 100 }).trimEnd();
25
+ const pad = " ".repeat(indent);
26
+ return text.split("\n").map((l) => pad + l).join("\n");
27
+ }
28
+ function flow(list) {
29
+ return list.length === 0 ? "[]" : `[${list.map((s) => JSON.stringify(s)).join(", ")}]`;
30
+ }
31
+ function renderStarterPolicy(opts) {
32
+ const tools = opts.tools ?? [];
33
+ const unknown = tools.filter((t) => t.classification.class === "unknown").map((t) => t.name);
34
+ const destructive = tools.filter((t) => t.classification.destructive || t.classification.verb === "delete").map((t) => t.name);
35
+ const spend = tools.filter((t) => t.classification.class === "spend").map((t) => t.name);
36
+ const date = (opts.generatedAt ?? /* @__PURE__ */ new Date()).toISOString().slice(0, 10);
37
+ const counts = tools.reduce(
38
+ (acc, t) => ({ ...acc, [t.classification.class]: (acc[t.classification.class] ?? 0) + 1 }),
39
+ {}
40
+ );
41
+ const detected = tools.length === 0 ? ["# (no upstreams probed \u2014 run `agentguard tools` after fixing the upstreams above)"] : tools.map(
42
+ (t) => `# ${t.name.padEnd(36)} ${t.classification.class.padEnd(8)} ${t.classification.verb.padEnd(8)} ${t.classification.source}: ${t.classification.reason}`
43
+ );
44
+ const probeNotes = (opts.probeErrors ?? []).map((e) => `# ! ${e}`);
45
+ 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", "."]';
46
+ return `# agentguard.yaml \u2014 generated by \`agentguard init\` on ${date}
47
+ # Every knob is explained inline. Docs: https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli#policy
48
+ version: 1
49
+
50
+ # dry-run: reads pass through; writes and spends are FAKED (plausible success, nothing executes)
51
+ # and recorded. Run your agent once, then \`agentguard report\` and \`agentguard diff\`.
52
+ # Flip to \`enforce\` when the diff looks right. Caps and the loop breaker apply in both modes.
53
+ mode: ${opts.mode ?? "dry-run"}
54
+
55
+ # The servers your agent used to talk to directly. agentguard connects to them and holds their
56
+ # credentials; your agent only talks to agentguard. Use \${ENV_VAR} for secrets.
57
+ ${upstreamsYaml}
58
+
59
+ # Detected tools (${tools.length}${tools.length ? `: ${Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(", ")}` : ""}):
60
+ ${[...detected, ...probeNotes].join("\n")}
61
+ # readOnlyHint annotations and verb heuristics classify most tools. List anything that is wrong here;
62
+ # these patterns win over annotations. \`*\` matches anything.
63
+ classify:
64
+ read: []
65
+ write: ${flow(unknown)}${unknown.length ? " # could not be classified \u2014 treated as writes; move to read: if they only read" : ""}
66
+ spend: ${flow(spend)}
67
+ unknown: write # a tool nobody can classify counts as a write (alternatives: read | block)
68
+
69
+ # Blast-radius caps. The call that would exceed a cap gets CAP_EXCEEDED with the remaining budget.
70
+ # \`writes\` counts every write or spend; \`deletes\`/\`emails\` count the subsets matched by \`counters\`.
71
+ caps:
72
+ per_run:
73
+ tool_calls: 400
74
+ writes: 50
75
+ deletes: 10
76
+ emails: 5
77
+ spend_usd: 25
78
+ per_day:
79
+ spend_usd: 200
80
+
81
+ # Dollar amounts. Declare how to read the amount from a spend tool's arguments; results reporting
82
+ # cost_usd / amount_usd are picked up automatically. Unknown spend tools cost \`default_usd\`.
83
+ spend:
84
+ tools: {}
85
+ # stripe_create_charge: { amount_arg: amount, divisor: 100, currency_arg: currency }
86
+ # openai_*: { fixed_usd: 0.01 }
87
+ default_usd: 0
88
+
89
+ # Semantic loop breaker: the same (tool, normalized arguments) ${3} times in the last 30 calls, or a
90
+ # repeating A\u2192B\u2192A\u2192B cycle, returns LOOP_DETECTED. Timestamps, ids and whitespace are ignored.
91
+ loop:
92
+ window: 30
93
+ max_repeats: 3
94
+ max_cycle_len: 4
95
+ max_read_repeats: 10
96
+
97
+ # Always fake these, even in enforce mode. Remove entries as you gain trust.
98
+ dry_run:
99
+ tools: []
100
+ synthesize: true
101
+
102
+ # Destructive actions that need a human. The agent gets APPROVAL_REQUIRED with an id; you run
103
+ # \`agentguard approve <id>\` (or click the link in Slack); the agent retries the identical call once.
104
+ approval:
105
+ tools: ${flow(destructive)}
106
+ wait_s: 0 # > 0 holds the call open this long waiting for the decision
107
+ ttl_s: 3600
108
+ # notify:
109
+ # slack: \${SLACK_WEBHOOK_URL}
110
+
111
+ # Kill switch: \`agentguard kill\` creates this file (or POST /kill on the HTTP proxy); every call
112
+ # returns KILLED until \`agentguard resume\`. AGENTGUARD_KILL=1 in the environment does the same.
113
+ kill:
114
+ file: .agentguard/KILL
115
+
116
+ # Scoped agent keys for the HTTP proxy: \`agentguard key create deployer --allow 'crm_get_*'\`.
117
+ # Only the key's hash is stored here; the agent sends the key as a Bearer token.
118
+ agents: []
119
+
120
+ # Alerts on halts (Slack incoming webhook or any HTTPS endpoint; signed with X-Agentwares-Signature).
121
+ # alerts:
122
+ # slack: \${SLACK_WEBHOOK_URL}
123
+ # on: [LOOP_DETECTED, CAP_EXCEEDED, KILLED, APPROVAL_REQUIRED]
124
+
125
+ # Hash-chained JSONL of every call; \`agentguard verify\` proves it was not edited.
126
+ audit:
127
+ path: .agentguard/audit.jsonl
128
+ redact: true
129
+ include_results: false
130
+ `;
131
+ }
132
+ function cleanUpstream(u) {
133
+ const out = { name: u.name };
134
+ if (u.url) {
135
+ out.url = u.url;
136
+ if (Object.keys(u.headers).length) out.headers = u.headers;
137
+ } else {
138
+ out.command = u.command;
139
+ if (u.args.length) out.args = u.args;
140
+ if (Object.keys(u.env).length) out.env = u.env;
141
+ if (u.cwd) out.cwd = u.cwd;
142
+ }
143
+ return out;
144
+ }
145
+ async function initCommand(args, io) {
146
+ const policyPath = policyPathFrom(args, io);
147
+ const explicit = flagString(args, "client");
148
+ const configs = explicit ? [describeConfigPath(resolve(io.cwd, explicit))] : detectClientConfigs(io.cwd, homeFrom(io), io.env);
149
+ const rel = (p) => {
150
+ const r = relative(io.cwd, p);
151
+ return r.startsWith("..") ? p : r || ".";
152
+ };
153
+ if (flagBool(args, "undo")) {
154
+ let restored = 0;
155
+ for (const c of configs) {
156
+ if (restoreClientConfig(c)) {
157
+ io.out(`restored ${rel(c.path)} from backup`);
158
+ restored += 1;
159
+ }
160
+ }
161
+ if (restored === 0)
162
+ io.err("nothing to undo (no *.agentguard-backup files next to your MCP configs)");
163
+ return 0;
164
+ }
165
+ const projectLevel = configs.filter((c) => c.path.startsWith(io.cwd + "/"));
166
+ const userLevel = configs.filter((c) => !projectLevel.includes(c));
167
+ let chosen;
168
+ if (explicit) chosen = configs;
169
+ else if (flagBool(args, "all")) chosen = configs;
170
+ else chosen = projectLevel.slice(0, 1);
171
+ if (!explicit && !flagBool(args, "all")) {
172
+ for (const c of userLevel)
173
+ io.out(
174
+ `found ${c.path} (${c.label}, user-level) \u2014 not touched; pass --client ${JSON.stringify(c.path)} to route it through agentguard`
175
+ );
176
+ }
177
+ if (!chosen.every((c) => existsSync(c.path))) {
178
+ io.err(`no MCP config at ${chosen.map((c) => c.path).join(", ")}`);
179
+ return 1;
180
+ }
181
+ const servers = {};
182
+ for (const c of chosen) {
183
+ const found = readServers(c);
184
+ io.out(
185
+ `found ${rel(c.path)} (${c.label}) \u2192 ${Object.keys(found).length} server${Object.keys(found).length === 1 ? "" : "s"}: ${Object.keys(found).join(", ") || "none"}`
186
+ );
187
+ for (const [name, entry] of Object.entries(found))
188
+ if (!(name in servers)) servers[name] = entry;
189
+ }
190
+ if (chosen.length === 0)
191
+ io.out(
192
+ "found no MCP config (Claude Code .mcp.json, Cursor, VS Code, Claude Desktop, Windsurf, Gemini CLI) \u2014 writing a policy with no upstreams"
193
+ );
194
+ const { upstreams, skipped } = serversToUpstreams(servers);
195
+ for (const s of skipped) io.err(`skipped ${s.name}: ${s.reason}`);
196
+ if (skipped.some((s) => s.reason.includes("already")) && upstreams.length === 0 && existsSync(policyPath)) {
197
+ io.out(
198
+ `already initialized: ${rel(policyPath)} exists and ${chosen.map((c) => rel(c.path)).join(", ")} already points at agentguard`
199
+ );
200
+ return 0;
201
+ }
202
+ let probed = [];
203
+ const probeErrors = [];
204
+ if (flagBool(args, "probe") !== false && upstreams.length > 0) {
205
+ io.out(`probing ${upstreams.length} upstream${upstreams.length === 1 ? "" : "s"}\u2026`);
206
+ const result = await probeUpstreams(upstreams, {
207
+ log: () => void 0,
208
+ env: io.env,
209
+ timeoutMs: 2e4
210
+ });
211
+ probed = result.tools;
212
+ for (const u of result.upstreams) {
213
+ if (u.connected) io.out(` ${u.name}: ${u.tools} tools`);
214
+ else {
215
+ io.err(
216
+ ` ${u.name}: could not connect (${u.error}) \u2014 the proxy will keep retrying at start; fix the entry in agentguard.yaml`
217
+ );
218
+ probeErrors.push(`${u.name}: ${u.error}`);
219
+ }
220
+ }
221
+ }
222
+ const mode = flagString(args, "mode") ?? "dry-run";
223
+ if (existsSync(policyPath) && !flagBool(args, "force")) {
224
+ io.out(`kept ${rel(policyPath)} (exists; pass --force to regenerate)`);
225
+ } else {
226
+ writeFileSync(policyPath, renderStarterPolicy({ upstreams, tools: probed, mode, probeErrors }));
227
+ const counts = probed.reduce(
228
+ (acc, t) => ({ ...acc, [t.classification.class]: (acc[t.classification.class] ?? 0) + 1 }),
229
+ {}
230
+ );
231
+ io.out(
232
+ `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(", ")}` : ""})`
233
+ );
234
+ }
235
+ if (flagBool(args, "rewrite") !== false) {
236
+ for (const c of chosen) {
237
+ if (resolve(c.path) === resolve(policyPath)) {
238
+ throw new Error(
239
+ `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 agentguard.yaml.`
240
+ );
241
+ }
242
+ const { backup, replaced } = rewriteClientConfig(c, policyPath, {
243
+ agent: flagString(args, "agent")
244
+ });
245
+ io.out(
246
+ `rewrote ${rel(c.path)} \u2192 ${replaced.length ? `${replaced.join(", ")} now go through` : "points at"} \`npx @agentwares/agentguard proxy\` (backup: ${rel(backup)})`
247
+ );
248
+ }
249
+ }
250
+ io.out("");
251
+ io.out("Next:");
252
+ io.out(
253
+ ` 1. restart your MCP client so it re-reads ${chosen.map((c) => rel(c.path)).join(", ") || "its config"}`
254
+ );
255
+ io.out(
256
+ ` 2. run your agent once \u2014 ${mode === "dry-run" ? "writes are faked; nothing executes upstream" : "enforce mode: caps, loop breaker and approvals are live"}`
257
+ );
258
+ io.out(" 3. agentguard report # what this run would have destroyed / spent");
259
+ io.out(" agentguard diff # the record-by-record mutation diff");
260
+ if (mode === "dry-run")
261
+ io.out(` 4. set \`mode: enforce\` in ${rel(policyPath)} when it looks right`);
262
+ io.out(" undo any time: agentguard init --undo");
263
+ return 0;
264
+ }
265
+
266
+ export {
267
+ renderStarterPolicy,
268
+ initCommand
269
+ };
270
+ //# sourceMappingURL=chunk-X4M7YWQA.js.map