@agentwares/agentguard 0.1.2 → 0.1.4

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.
package/dist/cli.js CHANGED
@@ -805,9 +805,6 @@ var require_dist = __commonJS({
805
805
  }
806
806
  });
807
807
 
808
- // src/cli.ts
809
- import { readFileSync as readFileSync4 } from "fs";
810
-
811
808
  // src/args.ts
812
809
  var VALUE_FLAGS = /* @__PURE__ */ new Set([
813
810
  "config",
@@ -914,6 +911,19 @@ function flagNumber(args, name) {
914
911
  return Number.isFinite(n) ? n : void 0;
915
912
  }
916
913
 
914
+ // src/version.ts
915
+ import { readFileSync } from "fs";
916
+ var cached;
917
+ function version() {
918
+ if (cached !== void 0) return cached;
919
+ try {
920
+ cached = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
921
+ } catch {
922
+ cached = "0.0.0";
923
+ }
924
+ return cached;
925
+ }
926
+
917
927
  // src/context.ts
918
928
  import { existsSync as existsSync2 } from "fs";
919
929
  import { resolve } from "path";
@@ -925,9 +935,9 @@ import {
925
935
  FileStateStore,
926
936
  loadPolicyFile
927
937
  } from "@agentwares/agentguard-core/node";
928
- import { existsSync as exists, readFileSync } from "fs";
938
+ import { existsSync as exists, readFileSync as readFileSync2 } from "fs";
929
939
 
930
- // ../../packages/mcp-kit/dist/chunk-AO7AV7MI.js
940
+ // ../../packages/mcp-kit/dist/chunk-7LRFDQNC.js
931
941
  import { completable } from "@modelcontextprotocol/sdk/server/completable.js";
932
942
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
933
943
  import {
@@ -939,6 +949,19 @@ import {
939
949
  import { z } from "zod";
940
950
 
941
951
  // ../../packages/x402/dist/index.js
952
+ var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
953
+ var USDC_DECIMALS = 6;
954
+ var USDC_EIP712_DOMAIN = { name: "USD Coin", version: "2" };
955
+ function toAtomicUnits(amountUsd, decimals = USDC_DECIMALS) {
956
+ const trimmed = amountUsd.trim();
957
+ if (!/^\d+(\.\d+)?$/.test(trimmed)) throw new Error(`not a decimal amount: ${amountUsd}`);
958
+ const [whole = "0", fraction = ""] = trimmed.split(".");
959
+ if (fraction.length > decimals) {
960
+ throw new Error(`${amountUsd} has more than ${decimals} decimal places`);
961
+ }
962
+ return `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
963
+ }
964
+ var DEFAULT_X402_NETWORK = "eip155:8453";
942
965
  var X402_HEADER = "X-Payment-Requirements";
943
966
  var X402_REQUIRED_HEADER = "PAYMENT-REQUIRED";
944
967
  function toAmount(priceUsd) {
@@ -948,42 +971,69 @@ function toAmount(priceUsd) {
948
971
  }
949
972
  function buildPaymentRequired(opts) {
950
973
  const amountUsd = toAmount(opts.priceUsd);
951
- const accepts = [
952
- {
974
+ const creditsPayTo = opts.creditsPayTo ?? opts.selfServe?.keysUrl;
975
+ const accepts = [];
976
+ if (opts.payTo) {
977
+ accepts.push({
953
978
  rail: "x402",
979
+ scheme: "exact",
954
980
  amountUsd,
981
+ amount: toAtomicUnits(amountUsd, opts.assetDecimals),
955
982
  resource: opts.resource,
956
983
  description: opts.description,
957
- network: "eip155:8453",
958
- asset: "USDC",
984
+ mimeType: "application/json",
985
+ network: opts.network ?? DEFAULT_X402_NETWORK,
986
+ asset: opts.asset ?? USDC_BASE,
987
+ extra: opts.assetDomain ?? USDC_EIP712_DOMAIN,
959
988
  payTo: opts.payTo,
960
- maxTimeoutSeconds: 300
961
- },
962
- {
963
- rail: "mpp",
989
+ maxTimeoutSeconds: 300,
990
+ ...opts.bazaar ? { bazaar: opts.bazaar } : {}
991
+ });
992
+ }
993
+ if (creditsPayTo) {
994
+ accepts.push({
995
+ rail: "prepaid_credits",
964
996
  amountUsd,
965
997
  resource: opts.resource,
966
998
  description: opts.description,
967
999
  currency: "usd",
968
- payTo: opts.payTo,
969
- maxTimeoutSeconds: 300
970
- }
971
- ];
1000
+ payTo: creditsPayTo,
1001
+ maxTimeoutSeconds: 300,
1002
+ // funding a balance ends at a hosted Stripe Checkout page, which an agent cannot fill in
1003
+ humanRequired: true
1004
+ });
1005
+ }
972
1006
  const live = opts.live === true;
1007
+ const selfServe = opts.selfServe;
1008
+ const x402Rail = accepts.find((a) => a.rail === "x402" && a.payTo);
1009
+ const usdcFix = x402Rail ? `Pay in USDC with no account and no human: take the \`accepts\` entry with \`rail: "x402"\`, sign an EIP-3009 transferWithAuthorization for ${x402Rail.amountUsd} USDC to ${x402Rail.payTo} on ${x402Rail.network}, and retry with it base64 in the X-PAYMENT header. The facilitator submits the transfer, so you need USDC and no gas. ` : "";
1010
+ const sampleFix = opts.sampleMode ? "Free first: retry with sample=true \u2014 no key, no payment. " : "";
1011
+ const cardFix = selfServe ? `Or pay by card: POST ${selfServe.keysUrl} (no auth, no signup) returns a key and a Stripe Checkout URL. Funding that key needs a person at a browser; if you are running unattended, surface the URL to your operator rather than retrying, or use the USDC rail above. Once funded, send Authorization: Bearer <key>, plus Idempotency-Key so a repeated call is charged once.` : "";
1012
+ const liveFix = usdcFix || cardFix ? `${sampleFix}${usdcFix}${cardFix}`.trim() : "Retry with a PAYMENT header satisfying one of `accepts`, or send an API key with credit balance.";
973
1013
  const body = {
974
1014
  code: "PAYMENT_REQUIRED",
975
1015
  cause: live ? `This call costs $${amountUsd}. No valid payment or credit was presented.` : `This call costs $${amountUsd}. Pay-per-call (x402 / Stripe MPP) is coming soon; use an API key with prepaid credits.`,
976
- fix: live ? "Retry with a PAYMENT header satisfying one of `accepts`, or send an API key with credit balance." : `Create an API key${opts.signupUrl ? ` at ${opts.signupUrl}` : ""} and retry with Authorization: Bearer <key>. Try the free sample mode first.`,
1016
+ fix: live ? liveFix : `Create an API key${opts.signupUrl ? ` at ${opts.signupUrl}` : ""} and retry with Authorization: Bearer <key>. Try the free sample mode first.`,
977
1017
  retryable: true,
978
1018
  priceUsd: opts.priceUsd,
979
1019
  accepts,
980
1020
  comingSoon: !live,
981
- alternatives: ["api_key_credits", "sample_mode"]
1021
+ alternatives: [
1022
+ ...creditsPayTo ? ["api_key_credits"] : [],
1023
+ ...opts.sampleMode ? ["sample_mode"] : []
1024
+ ],
1025
+ ...selfServe ? { selfServe } : {},
1026
+ ...opts.bazaar ? { extensions: { bazaar: opts.bazaar } } : {}
982
1027
  };
983
- const encoded = JSON.stringify({ x402Version: 2, accepts, resource: opts.resource });
1028
+ const encoded = JSON.stringify({
1029
+ x402Version: 2,
1030
+ accepts,
1031
+ resource: opts.resource,
1032
+ ...opts.bazaar ? { extensions: { bazaar: opts.bazaar } } : {}
1033
+ });
984
1034
  const headers = {
985
1035
  "Content-Type": "application/json",
986
- "WWW-Authenticate": `Payment realm="agentwares", methods="x402 mpp", price="${amountUsd} USD"`,
1036
+ "WWW-Authenticate": `Payment realm="agentwares", methods="x402 prepaid_credits", price="${amountUsd} USD"`,
987
1037
  [X402_HEADER]: encoded,
988
1038
  [X402_REQUIRED_HEADER]: btoa(encoded),
989
1039
  "Cache-Control": "no-store"
@@ -1001,7 +1051,7 @@ var StubVerifier = class {
1001
1051
  }
1002
1052
  };
1003
1053
 
1004
- // ../../packages/mcp-kit/dist/chunk-AO7AV7MI.js
1054
+ // ../../packages/mcp-kit/dist/chunk-7LRFDQNC.js
1005
1055
  import { z as z2 } from "zod";
1006
1056
  import { z as z3 } from "zod";
1007
1057
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -1183,13 +1233,13 @@ function defineTool(opts) {
1183
1233
  annotations,
1184
1234
  handler,
1185
1235
  async invoke(rawInput, ctx) {
1186
- const fail = (err) => {
1236
+ const fail2 = (err) => {
1187
1237
  const result = errorResult(err, { requestId: ctx.requestId });
1188
1238
  if (output) delete result.structuredContent;
1189
1239
  return result;
1190
1240
  };
1191
1241
  const parsed = input.safeParse(rawInput ?? {});
1192
- if (!parsed.success) return fail(invalidInputError(parsed.error.issues));
1242
+ if (!parsed.success) return fail2(invalidInputError(parsed.error.issues));
1193
1243
  try {
1194
1244
  const value = await handler(parsed.data, ctx);
1195
1245
  if (output && !isCallToolResult(value)) {
@@ -1207,7 +1257,7 @@ function defineTool(opts) {
1207
1257
  }
1208
1258
  return toCallToolResult(value);
1209
1259
  } catch (err) {
1210
- return fail(err);
1260
+ return fail2(err);
1211
1261
  }
1212
1262
  }
1213
1263
  };
@@ -1448,6 +1498,7 @@ function withPayment(tool, opts) {
1448
1498
  );
1449
1499
  }
1450
1500
  const verifier = opts.verifier ?? new StubVerifier();
1501
+ const liveRail = opts.verifier !== void 0;
1451
1502
  const input = tool.input.extend({
1452
1503
  [sampleField]: z2.boolean().default(false).describe(
1453
1504
  "Set true to return an example response at no charge (sample mode). Default false."
@@ -1472,23 +1523,46 @@ function withPayment(tool, opts) {
1472
1523
  if (auth.ok) return tool.handler(record, { ...ctx, userId: auth.userId });
1473
1524
  reason = auth.reason;
1474
1525
  }
1526
+ const resource = ctx.resourceUrl ?? tool.name;
1475
1527
  const required = buildPaymentRequired({
1476
- resource: tool.name,
1528
+ resource,
1477
1529
  priceUsd: opts.priceUsd,
1478
1530
  live: opts.live,
1479
1531
  description: opts.description,
1480
1532
  payTo: opts.payTo,
1481
- signupUrl: opts.signupUrl
1533
+ creditsPayTo: opts.creditsPayTo,
1534
+ signupUrl: opts.signupUrl,
1535
+ selfServe: opts.selfServe,
1536
+ // withPayment always adds a free `sample` flag to the tool's input schema
1537
+ sampleMode: true,
1538
+ ...opts.bazaar ? { bazaar: opts.bazaar } : {}
1482
1539
  });
1483
- const requirement = required.body.accepts[0];
1484
- if (requirement) {
1485
- const verified = await verifier.verify(
1486
- new Request(`https://mcp.local/${tool.name}`, { headers: ctx.headers }),
1487
- requirement
1488
- );
1489
- if (verified.ok) return tool.handler(record, ctx);
1490
- reason = reason ?? verified.reason;
1540
+ const advertised = required.body.accepts.find((a) => a.rail === "prepaid_credits") ?? required.body.accepts[0];
1541
+ const requirement = advertised ?? {
1542
+ rail: "prepaid_credits",
1543
+ amountUsd: String(required.body.priceUsd),
1544
+ resource,
1545
+ ...opts.description ? { description: opts.description } : {}
1546
+ };
1547
+ const verified = await verifier.verify(
1548
+ new Request(`https://mcp.local/${tool.name}`, { headers: ctx.headers }),
1549
+ requirement
1550
+ );
1551
+ if (verified.ok) {
1552
+ let result;
1553
+ try {
1554
+ result = await tool.handler(record, ctx);
1555
+ } catch (error) {
1556
+ await opts.onPaidFailure?.(ctx, verified, error);
1557
+ throw error;
1558
+ }
1559
+ const settled = await verifier.settle?.(verified);
1560
+ if (settled && !settled.ok) {
1561
+ opts.onSettlementFailure?.(ctx, verified, settled.reason);
1562
+ }
1563
+ return result;
1491
1564
  }
1565
+ reason = liveRail ? verified.reason ?? reason : reason ?? verified.reason;
1492
1566
  throw paymentRequiredError(required.body, reason);
1493
1567
  }
1494
1568
  });
@@ -1881,7 +1955,7 @@ import {
1881
1955
  keyFromHeaders,
1882
1956
  resolveAgent
1883
1957
  } from "@agentwares/agentguard-core";
1884
- var PROXY_VERSION = "0.1.0";
1958
+ var PROXY_VERSION = version();
1885
1959
  function headerValue(extra, name) {
1886
1960
  const raw = extra.requestInfo?.headers?.[name] ?? extra.requestInfo?.headers?.[name.toLowerCase()];
1887
1961
  if (Array.isArray(raw)) return raw[0];
@@ -2355,8 +2429,8 @@ var MAX_HEADERS_SIZE = 2 * 1024 * 1024;
2355
2429
 
2356
2430
  // ../../node_modules/.pnpm/resend@6.25.0/node_modules/resend/dist/index.mjs
2357
2431
  var import_standardwebhooks = __toESM(require_dist(), 1);
2358
- var version = "6.25.0";
2359
- var defaultUserAgent = `resend-node:${version}`;
2432
+ var version2 = "6.25.0";
2433
+ var defaultUserAgent = `resend-node:${version2}`;
2360
2434
 
2361
2435
  // ../../packages/notify/dist/index.js
2362
2436
  var palette = {
@@ -2600,7 +2674,7 @@ function readRegistration(loaded) {
2600
2674
  const file = registrationPath(loaded.stateDir);
2601
2675
  if (!exists(file)) return void 0;
2602
2676
  try {
2603
- const reg = JSON.parse(readFileSync(file, "utf8"));
2677
+ const reg = JSON.parse(readFileSync2(file, "utf8"));
2604
2678
  return reg;
2605
2679
  } catch {
2606
2680
  return void 0;
@@ -2615,104 +2689,12 @@ function describeError(err) {
2615
2689
  return err instanceof Error ? err.message : String(err);
2616
2690
  }
2617
2691
 
2618
- // src/commands/control.ts
2619
- async function pokeHttp(io, reg, path, body) {
2620
- if (!reg) return;
2621
- try {
2622
- const res = await fetch(`${reg.baseUrl}${path}`, {
2623
- method: "POST",
2624
- headers: { Authorization: `Bearer ${reg.token}`, "Content-Type": "application/json" },
2625
- body: body === void 0 ? void 0 : JSON.stringify(body),
2626
- signal: AbortSignal.timeout(2e3)
2627
- });
2628
- if (!res.ok)
2629
- io.err(
2630
- `running proxy at ${reg.baseUrl} answered ${res.status} (the file switch still applies)`
2631
- );
2632
- } catch {
2633
- io.err(
2634
- `no proxy answering at ${reg.baseUrl} (stale ${reg.pid}?) \u2014 the file switch still applies`
2635
- );
2636
- }
2637
- }
2638
- async function killCommand(args, io) {
2639
- const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
2640
- const { kill } = createFileGuard(loaded, { alerts: false, env: io.env });
2641
- const reason = args.positionals.join(" ") || flagString(args, "reason") || `agentguard kill by ${io.env.USER ?? "operator"}`;
2642
- kill.kill(reason);
2643
- await pokeHttp(io, readRegistration(loaded), "/kill", { reason });
2644
- io.out(
2645
- `KILLED: ${kill.path} written \u2014 every tool call through this policy now returns KILLED. Clear with: agentguard resume`
2646
- );
2647
- return 0;
2648
- }
2649
- async function resumeCommand(args, io) {
2650
- const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
2651
- const { kill } = createFileGuard(loaded, { alerts: false, env: io.env });
2652
- kill.resume();
2653
- await pokeHttp(io, readRegistration(loaded), "/resume");
2654
- io.out(`resumed: ${kill.path} removed`);
2655
- if (io.env[loaded.policy.kill.env])
2656
- io.err(
2657
- `note: ${loaded.policy.kill.env} is set in this shell's environment; proxies started from it stay killed`
2658
- );
2659
- return 0;
2660
- }
2661
- async function decideCommand(decision, args, io) {
2662
- const id = args.positionals[0];
2663
- if (!id) {
2664
- io.err(
2665
- `usage: agentguard ${decision === "approved" ? "approve" : "deny"} <approval-id> [--by name] [--note text]`
2666
- );
2667
- return 1;
2668
- }
2669
- const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
2670
- const { guard } = createFileGuard(loaded, { alerts: false, env: io.env });
2671
- const record = await guard.decide(
2672
- id,
2673
- decision,
2674
- flagString(args, "by") ?? io.env.USER ?? "cli",
2675
- flagString(args, "note")
2676
- );
2677
- if (!record) {
2678
- io.err(`no approval ${id} \u2014 pending ones: agentguard approvals`);
2679
- return 1;
2680
- }
2681
- io.out(
2682
- `${record.status}: ${record.tool} (${record.id}, run ${record.run_id})${record.status === "approved" ? " \u2014 the agent may now retry the identical call once" : ""}`
2683
- );
2684
- return 0;
2685
- }
2686
- async function approvalsCommand(args, io) {
2687
- const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
2688
- const { guard } = createFileGuard(loaded, { alerts: false, env: io.env });
2689
- const list = await guard.approvals.list(flagBool(args, "all") ? void 0 : "pending");
2690
- if (flagBool(args, "json")) {
2691
- io.out(JSON.stringify(list, null, 2));
2692
- return 0;
2693
- }
2694
- if (list.length === 0) {
2695
- io.out(flagBool(args, "all") ? "no approvals recorded" : "no pending approvals");
2696
- return 0;
2697
- }
2698
- for (const r of list) {
2699
- io.out(
2700
- `${r.id} ${r.status.padEnd(9)} ${r.tool} run ${r.run_id}${r.agent ? ` agent ${r.agent}` : ""} expires ${r.expires_at}`
2701
- );
2702
- io.out(` args: ${JSON.stringify(r.args).slice(0, 300)}`);
2703
- if (r.status === "pending")
2704
- io.out(` \u2192 agentguard approve ${r.id} | agentguard deny ${r.id}`);
2705
- }
2706
- return 0;
2707
- }
2708
-
2709
- // src/commands/init.ts
2710
- import { existsSync as existsSync4, writeFileSync as writeFileSync3 } from "fs";
2711
- import { relative, resolve as resolve3 } from "path";
2712
- import YAML from "yaml";
2692
+ // src/commands/connect.ts
2693
+ import { existsSync as existsSync4 } from "fs";
2694
+ import { relative } from "path";
2713
2695
 
2714
2696
  // src/configs.ts
2715
- import { copyFileSync, existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2697
+ import { copyFileSync, existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
2716
2698
  import { homedir as homedir2, platform } from "os";
2717
2699
  import { join as join2, resolve as resolve2 } from "path";
2718
2700
  function candidateConfigs(cwd = process.cwd(), home = homedir2(), env = process.env) {
@@ -2775,7 +2757,7 @@ function describeConfigPath(path) {
2775
2757
  if (known) return known;
2776
2758
  let serversKey = "mcpServers";
2777
2759
  try {
2778
- const parsed = JSON.parse(readFileSync2(abs, "utf8"));
2760
+ const parsed = JSON.parse(readFileSync3(abs, "utf8"));
2779
2761
  if (parsed.servers && !parsed.mcpServers) serversKey = "servers";
2780
2762
  } catch {
2781
2763
  }
@@ -2787,12 +2769,13 @@ function describeConfigPath(path) {
2787
2769
  };
2788
2770
  }
2789
2771
  function readServers(file) {
2790
- const raw = JSON.parse(readFileSync2(file.path, "utf8"));
2772
+ const raw = JSON.parse(readFileSync3(file.path, "utf8"));
2791
2773
  const servers = raw[file.serversKey];
2792
2774
  if (!servers || typeof servers !== "object") return {};
2793
2775
  return servers;
2794
2776
  }
2795
2777
  var PROXY_SERVER_NAME = "agentguard";
2778
+ var PROXY_PACKAGE = "@agentwares/agentguard";
2796
2779
  function isProxyEntry(entry) {
2797
2780
  return (entry.args ?? []).some((a) => a === "agentguard" || /(^|\/)agentguard(\.js)?$/.test(a)) && (entry.args ?? []).includes("proxy");
2798
2781
  }
@@ -2836,14 +2819,14 @@ function backupPath(file) {
2836
2819
  return `${file.path}.agentguard-backup`;
2837
2820
  }
2838
2821
  function proxyEntry(file, policyPath, opts = {}) {
2839
- const args = ["-y", "agentguard", "proxy", "--config", policyPath];
2822
+ const args = ["-y", PROXY_PACKAGE, "proxy", "--config", policyPath];
2840
2823
  if (opts.agent) args.push("--agent", opts.agent);
2841
2824
  const entry = { command: "npx", args };
2842
2825
  if (file.serversKey === "servers") entry.type = "stdio";
2843
2826
  return entry;
2844
2827
  }
2845
2828
  function rewriteClientConfig(file, policyPath, opts = {}) {
2846
- const raw = JSON.parse(readFileSync2(file.path, "utf8"));
2829
+ const raw = JSON.parse(readFileSync3(file.path, "utf8"));
2847
2830
  const servers = raw[file.serversKey] ?? {};
2848
2831
  const replaced = Object.keys(servers).filter((n) => !isProxyEntry(servers[n]));
2849
2832
  const backup = backupPath(file);
@@ -2858,6 +2841,216 @@ function restoreClientConfig(file) {
2858
2841
  copyFileSync(backup, file.path);
2859
2842
  return true;
2860
2843
  }
2844
+ function mergeServerIntoConfig(file, name, entry) {
2845
+ const raw = JSON.parse(readFileSync3(file.path, "utf8"));
2846
+ const servers = raw[file.serversKey] ?? {};
2847
+ const replaced = name in servers;
2848
+ const backup = backupPath(file);
2849
+ if (!existsSync3(backup)) copyFileSync(file.path, backup);
2850
+ raw[file.serversKey] = { ...servers, [name]: entry };
2851
+ writeFileSync2(file.path, JSON.stringify(raw, null, 2) + "\n");
2852
+ return { backup, replaced };
2853
+ }
2854
+
2855
+ // src/commands/connect.ts
2856
+ var DEFAULT_PROXY_URL = "https://agentwares-agentguard-proxy.vercel.app";
2857
+ function fail(e) {
2858
+ throw e;
2859
+ }
2860
+ async function connectCommand(args, io, fetchImpl = fetch) {
2861
+ const key = args.positionals[0] ?? io.env.AGENTGUARD_KEY;
2862
+ if (!key) {
2863
+ io.err("usage: agentguard connect <key> [--write] [--client <path>] [--url <proxy-url>]");
2864
+ io.err(" the key is on your dashboard; it starts with agk_");
2865
+ return 2;
2866
+ }
2867
+ const base = (flagString(args, "url") ?? io.env.AGENTGUARD_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\/+$/, "");
2868
+ let res;
2869
+ try {
2870
+ res = await fetchImpl(`${base}/connect`, {
2871
+ method: "POST",
2872
+ headers: { "content-type": "application/json" },
2873
+ body: JSON.stringify({ key })
2874
+ });
2875
+ } catch (err) {
2876
+ fail({
2877
+ code: "PROXY_UNREACHABLE",
2878
+ cause: `could not reach ${base}: ${err instanceof Error ? err.message : String(err)}`,
2879
+ fix: "check the network, or pass --url if you run your own proxy",
2880
+ retryable: true
2881
+ });
2882
+ }
2883
+ const text = await res.text();
2884
+ let body;
2885
+ try {
2886
+ body = JSON.parse(text);
2887
+ } catch {
2888
+ fail({
2889
+ code: "PROXY_BAD_RESPONSE",
2890
+ cause: `${base}/connect returned ${res.status} that was not JSON: ${text.slice(0, 200)}`,
2891
+ fix: "check --url points at an agentguard proxy, not at the dashboard",
2892
+ retryable: false
2893
+ });
2894
+ }
2895
+ if (!res.ok) {
2896
+ const e = body;
2897
+ fail({
2898
+ code: e.code ?? "CONNECT_REFUSED",
2899
+ cause: e.cause ?? `${base}/connect returned ${res.status}`,
2900
+ fix: e.fix ?? "check the key on your dashboard; revoked keys stop working immediately",
2901
+ retryable: e.retryable ?? false
2902
+ });
2903
+ }
2904
+ const conn = body;
2905
+ if (flagBool(args, "json")) {
2906
+ io.out(JSON.stringify(conn, null, 2));
2907
+ return 0;
2908
+ }
2909
+ const rel = (p) => p.startsWith(io.cwd + "/") ? relative(io.cwd, p) : p;
2910
+ const entry = conn.mcpServers[PROXY_SERVER_NAME] ?? {
2911
+ type: "http",
2912
+ url: conn.mcpUrl,
2913
+ headers: conn.headers
2914
+ };
2915
+ if (!flagBool(args, "write")) {
2916
+ io.out(JSON.stringify({ mcpServers: { [PROXY_SERVER_NAME]: entry } }, null, 2));
2917
+ } else {
2918
+ const explicit = flagString(args, "client");
2919
+ const configs = explicit ? [describeConfigPath(explicit)] : detectClientConfigs(io.cwd, homeFrom(io), io.env);
2920
+ const projectLevel = configs.filter((c) => c.path.startsWith(io.cwd + "/"));
2921
+ let chosen;
2922
+ if (explicit || flagBool(args, "all")) chosen = configs;
2923
+ else chosen = projectLevel.slice(0, 1);
2924
+ if (chosen.length === 0) {
2925
+ io.err(
2926
+ "no MCP config found here (.mcp.json, .cursor/mcp.json, .vscode/mcp.json). Create one, or paste this into your client:"
2927
+ );
2928
+ io.out(JSON.stringify({ mcpServers: { [PROXY_SERVER_NAME]: entry } }, null, 2));
2929
+ return 1;
2930
+ }
2931
+ for (const c of chosen) {
2932
+ if (!existsSync4(c.path)) {
2933
+ io.err(`no MCP config at ${c.path}`);
2934
+ return 1;
2935
+ }
2936
+ const { backup, replaced } = mergeServerIntoConfig(c, PROXY_SERVER_NAME, entry);
2937
+ io.out(
2938
+ `${replaced ? "updated" : "wrote "} ${rel(c.path)} (${c.label}) \u2192 ${PROXY_SERVER_NAME} = ${conn.mcpUrl}`
2939
+ );
2940
+ io.out(`backup ${rel(backup)}`);
2941
+ }
2942
+ for (const c of configs.filter((c2) => !chosen.includes(c2)))
2943
+ io.out(
2944
+ `found ${c.path} (${c.label}, user-level) \u2014 not touched; pass --client ${JSON.stringify(c.path)} to write it too`
2945
+ );
2946
+ }
2947
+ const band = conn.band ? ` band=${conn.band.tier} (${conn.band.quantity} calls/mo)` : "";
2948
+ io.out(`
2949
+ proxy ${conn.slug} (${conn.proxyId}) mode=${conn.mode}${band}`);
2950
+ if (conn.upstreams?.length) io.out(`upstreams: ${conn.upstreams.join(", ")}`);
2951
+ if (conn.dashboardUrl) io.out(`dashboard: ${conn.dashboardUrl}`);
2952
+ if (conn.claudeCode) io.out(`claude code: ${conn.claudeCode}`);
2953
+ if (!flagBool(args, "write"))
2954
+ io.out(`
2955
+ run again with --write to add this to your MCP client config`);
2956
+ return 0;
2957
+ }
2958
+
2959
+ // src/commands/control.ts
2960
+ async function pokeHttp(io, reg, path, body) {
2961
+ if (!reg) return;
2962
+ try {
2963
+ const res = await fetch(`${reg.baseUrl}${path}`, {
2964
+ method: "POST",
2965
+ headers: { Authorization: `Bearer ${reg.token}`, "Content-Type": "application/json" },
2966
+ body: body === void 0 ? void 0 : JSON.stringify(body),
2967
+ signal: AbortSignal.timeout(2e3)
2968
+ });
2969
+ if (!res.ok)
2970
+ io.err(
2971
+ `running proxy at ${reg.baseUrl} answered ${res.status} (the file switch still applies)`
2972
+ );
2973
+ } catch {
2974
+ io.err(
2975
+ `no proxy answering at ${reg.baseUrl} (stale ${reg.pid}?) \u2014 the file switch still applies`
2976
+ );
2977
+ }
2978
+ }
2979
+ async function killCommand(args, io) {
2980
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
2981
+ const { kill } = createFileGuard(loaded, { alerts: false, env: io.env });
2982
+ const reason = args.positionals.join(" ") || flagString(args, "reason") || `agentguard kill by ${io.env.USER ?? "operator"}`;
2983
+ kill.kill(reason);
2984
+ await pokeHttp(io, readRegistration(loaded), "/kill", { reason });
2985
+ io.out(
2986
+ `KILLED: ${kill.path} written \u2014 every tool call through this policy now returns KILLED. Clear with: agentguard resume`
2987
+ );
2988
+ return 0;
2989
+ }
2990
+ async function resumeCommand(args, io) {
2991
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
2992
+ const { kill } = createFileGuard(loaded, { alerts: false, env: io.env });
2993
+ kill.resume();
2994
+ await pokeHttp(io, readRegistration(loaded), "/resume");
2995
+ io.out(`resumed: ${kill.path} removed`);
2996
+ if (io.env[loaded.policy.kill.env])
2997
+ io.err(
2998
+ `note: ${loaded.policy.kill.env} is set in this shell's environment; proxies started from it stay killed`
2999
+ );
3000
+ return 0;
3001
+ }
3002
+ async function decideCommand(decision, args, io) {
3003
+ const id = args.positionals[0];
3004
+ if (!id) {
3005
+ io.err(
3006
+ `usage: agentguard ${decision === "approved" ? "approve" : "deny"} <approval-id> [--by name] [--note text]`
3007
+ );
3008
+ return 1;
3009
+ }
3010
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
3011
+ const { guard } = createFileGuard(loaded, { alerts: false, env: io.env });
3012
+ const record = await guard.decide(
3013
+ id,
3014
+ decision,
3015
+ flagString(args, "by") ?? io.env.USER ?? "cli",
3016
+ flagString(args, "note")
3017
+ );
3018
+ if (!record) {
3019
+ io.err(`no approval ${id} \u2014 pending ones: agentguard approvals`);
3020
+ return 1;
3021
+ }
3022
+ io.out(
3023
+ `${record.status}: ${record.tool} (${record.id}, run ${record.run_id})${record.status === "approved" ? " \u2014 the agent may now retry the identical call once" : ""}`
3024
+ );
3025
+ return 0;
3026
+ }
3027
+ async function approvalsCommand(args, io) {
3028
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
3029
+ const { guard } = createFileGuard(loaded, { alerts: false, env: io.env });
3030
+ const list = await guard.approvals.list(flagBool(args, "all") ? void 0 : "pending");
3031
+ if (flagBool(args, "json")) {
3032
+ io.out(JSON.stringify(list, null, 2));
3033
+ return 0;
3034
+ }
3035
+ if (list.length === 0) {
3036
+ io.out(flagBool(args, "all") ? "no approvals recorded" : "no pending approvals");
3037
+ return 0;
3038
+ }
3039
+ for (const r of list) {
3040
+ io.out(
3041
+ `${r.id} ${r.status.padEnd(9)} ${r.tool} run ${r.run_id}${r.agent ? ` agent ${r.agent}` : ""} expires ${r.expires_at}`
3042
+ );
3043
+ io.out(` args: ${JSON.stringify(r.args).slice(0, 300)}`);
3044
+ if (r.status === "pending")
3045
+ io.out(` \u2192 agentguard approve ${r.id} | agentguard deny ${r.id}`);
3046
+ }
3047
+ return 0;
3048
+ }
3049
+
3050
+ // src/commands/init.ts
3051
+ import { existsSync as existsSync5, writeFileSync as writeFileSync3 } from "fs";
3052
+ import { relative as relative2, resolve as resolve3 } from "path";
3053
+ import YAML from "yaml";
2861
3054
 
2862
3055
  // src/commands/tools.ts
2863
3056
  import {
@@ -2948,7 +3141,7 @@ var ProxyRuntime = class {
2948
3141
  }
2949
3142
  async connect(upstream) {
2950
3143
  const client = new Client(
2951
- { name: "agentguard", version: "0.1.0" },
3144
+ { name: "agentguard", version: version() },
2952
3145
  { capabilities: { sampling: {}, elicitation: {} } }
2953
3146
  );
2954
3147
  const conn = {
@@ -3592,7 +3785,7 @@ async function initCommand(args, io) {
3592
3785
  const explicit = flagString(args, "client");
3593
3786
  const configs = explicit ? [describeConfigPath(resolve3(io.cwd, explicit))] : detectClientConfigs(io.cwd, homeFrom(io), io.env);
3594
3787
  const rel = (p) => {
3595
- const r = relative(io.cwd, p);
3788
+ const r = relative2(io.cwd, p);
3596
3789
  return r.startsWith("..") ? p : r || ".";
3597
3790
  };
3598
3791
  if (flagBool(args, "undo")) {
@@ -3619,7 +3812,7 @@ async function initCommand(args, io) {
3619
3812
  `found ${c.path} (${c.label}, user-level) \u2014 not touched; pass --client ${JSON.stringify(c.path)} to route it through agentguard`
3620
3813
  );
3621
3814
  }
3622
- if (!chosen.every((c) => existsSync4(c.path))) {
3815
+ if (!chosen.every((c) => existsSync5(c.path))) {
3623
3816
  io.err(`no MCP config at ${chosen.map((c) => c.path).join(", ")}`);
3624
3817
  return 1;
3625
3818
  }
@@ -3638,7 +3831,7 @@ async function initCommand(args, io) {
3638
3831
  );
3639
3832
  const { upstreams, skipped } = serversToUpstreams(servers);
3640
3833
  for (const s of skipped) io.err(`skipped ${s.name}: ${s.reason}`);
3641
- if (skipped.some((s) => s.reason.includes("already")) && upstreams.length === 0 && existsSync4(policyPath)) {
3834
+ if (skipped.some((s) => s.reason.includes("already")) && upstreams.length === 0 && existsSync5(policyPath)) {
3642
3835
  io.out(
3643
3836
  `already initialized: ${rel(policyPath)} exists and ${chosen.map((c) => rel(c.path)).join(", ")} already points at agentguard`
3644
3837
  );
@@ -3665,7 +3858,7 @@ async function initCommand(args, io) {
3665
3858
  }
3666
3859
  }
3667
3860
  const mode = flagString(args, "mode") ?? "dry-run";
3668
- if (existsSync4(policyPath) && !flagBool(args, "force")) {
3861
+ if (existsSync5(policyPath) && !flagBool(args, "force")) {
3669
3862
  io.out(`kept ${rel(policyPath)} (exists; pass --force to regenerate)`);
3670
3863
  } else {
3671
3864
  writeFileSync3(policyPath, renderStarterPolicy({ upstreams, tools: probed, mode, probeErrors }));
@@ -3679,6 +3872,11 @@ async function initCommand(args, io) {
3679
3872
  }
3680
3873
  if (flagBool(args, "rewrite") !== false) {
3681
3874
  for (const c of chosen) {
3875
+ if (resolve3(c.path) === resolve3(policyPath)) {
3876
+ throw new Error(
3877
+ `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.`
3878
+ );
3879
+ }
3682
3880
  const { backup, replaced } = rewriteClientConfig(c, policyPath, {
3683
3881
  agent: flagString(args, "agent")
3684
3882
  });
@@ -3704,13 +3902,13 @@ async function initCommand(args, io) {
3704
3902
  }
3705
3903
 
3706
3904
  // src/commands/keys.ts
3707
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
3905
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
3708
3906
  import YAML2, { isMap, isSeq, YAMLSeq } from "yaml";
3709
3907
  import { generateAgentKey, hashAgentKey } from "@agentwares/agentguard-core";
3710
3908
  async function keyCommand(args, io) {
3711
3909
  const [sub, name] = args.positionals;
3712
3910
  const path = policyPathFrom(args, io);
3713
- const doc = YAML2.parseDocument(readFileSync3(path, "utf8"));
3911
+ const doc = YAML2.parseDocument(readFileSync4(path, "utf8"));
3714
3912
  let agents = doc.get("agents");
3715
3913
  if (!isSeq(agents)) {
3716
3914
  agents = new YAMLSeq();
@@ -4071,19 +4269,13 @@ async function statusCommand(args, io) {
4071
4269
  }
4072
4270
 
4073
4271
  // src/cli.ts
4074
- function version2() {
4075
- try {
4076
- return JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8")).version;
4077
- } catch {
4078
- return "0.0.0";
4079
- }
4080
- }
4081
4272
  var HELP = `agentguard \u2014 MCP policy proxy for agents that touch production
4082
4273
 
4083
4274
  npx @agentwares/agentguard init read your MCP config, write agentguard.yaml (dry-run), route every server through the proxy
4084
4275
  agentguard report [--run id|--all] what this run did / would have destroyed / spent, where it was halted
4085
4276
  agentguard diff [--run id] record-by-record mutation diff from a dry run
4086
4277
  agentguard verify [audit.jsonl] prove the hash-chained audit log was not edited
4278
+ agentguard connect <key> [--write] point this machine at your hosted proxy (paid tiers)
4087
4279
 
4088
4280
  agentguard proxy [--http --port 8788] [--agent name] [--run-id id] [--mode dry-run|enforce]
4089
4281
  agentguard tools [--json] the tools your agent will see, with class and why
@@ -4100,7 +4292,7 @@ Docs: https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli`;
4100
4292
  async function main(argv, io = defaultIo()) {
4101
4293
  const args = parseArgs(argv);
4102
4294
  if (args.flags.version) {
4103
- io.out(version2());
4295
+ io.out(version());
4104
4296
  return 0;
4105
4297
  }
4106
4298
  if (args.command === void 0 && !args.flags.help && io.tty === false) {
@@ -4128,6 +4320,8 @@ async function main(argv, io = defaultIo()) {
4128
4320
  return await diffCommand(args, io);
4129
4321
  case "verify":
4130
4322
  return await verifyCommand(args, io);
4323
+ case "connect":
4324
+ return await connectCommand(args, io);
4131
4325
  case "status":
4132
4326
  return await statusCommand(args, io);
4133
4327
  case "tools":
@@ -4174,6 +4368,6 @@ if (isMain) {
4174
4368
  export {
4175
4369
  HELP,
4176
4370
  main,
4177
- version2 as version
4371
+ version
4178
4372
  };
4179
4373
  //# sourceMappingURL=cli.js.map