@agentwares/agentguard 0.1.2 → 0.1.3

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-NF4BQXVM.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,7 @@ import {
939
949
  import { z } from "zod";
940
950
 
941
951
  // ../../packages/x402/dist/index.js
952
+ var DEFAULT_X402_NETWORK = "eip155:8453";
942
953
  var X402_HEADER = "X-Payment-Requirements";
943
954
  var X402_REQUIRED_HEADER = "PAYMENT-REQUIRED";
944
955
  function toAmount(priceUsd) {
@@ -948,42 +959,51 @@ function toAmount(priceUsd) {
948
959
  }
949
960
  function buildPaymentRequired(opts) {
950
961
  const amountUsd = toAmount(opts.priceUsd);
951
- const accepts = [
952
- {
962
+ const creditsPayTo = opts.creditsPayTo ?? opts.selfServe?.keysUrl;
963
+ const accepts = [];
964
+ if (opts.payTo) {
965
+ accepts.push({
953
966
  rail: "x402",
954
967
  amountUsd,
955
968
  resource: opts.resource,
956
969
  description: opts.description,
957
- network: "eip155:8453",
970
+ network: opts.network ?? DEFAULT_X402_NETWORK,
958
971
  asset: "USDC",
959
972
  payTo: opts.payTo,
960
973
  maxTimeoutSeconds: 300
961
- },
962
- {
963
- rail: "mpp",
974
+ });
975
+ }
976
+ if (creditsPayTo) {
977
+ accepts.push({
978
+ rail: "prepaid_credits",
964
979
  amountUsd,
965
980
  resource: opts.resource,
966
981
  description: opts.description,
967
982
  currency: "usd",
968
- payTo: opts.payTo,
969
- maxTimeoutSeconds: 300
970
- }
971
- ];
983
+ payTo: creditsPayTo,
984
+ maxTimeoutSeconds: 300,
985
+ // funding a balance ends at a hosted Stripe Checkout page, which an agent cannot fill in
986
+ humanRequired: true
987
+ });
988
+ }
972
989
  const live = opts.live === true;
990
+ const selfServe = opts.selfServe;
991
+ const liveFix = selfServe ? `Free first: retry with sample=true \u2014 no key, no payment. To pay: POST ${selfServe.keysUrl} (no auth, no signup) returns a key and a Stripe Checkout URL. Funding that key needs a person to complete the Checkout page in a browser; if you are running unattended, surface the URL to your operator rather than retrying. Once funded, send Authorization: Bearer <key>, plus Idempotency-Key so a repeated call is charged once.` : "Retry with a PAYMENT header satisfying one of `accepts`, or send an API key with credit balance.";
973
992
  const body = {
974
993
  code: "PAYMENT_REQUIRED",
975
994
  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.`,
995
+ 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
996
  retryable: true,
978
997
  priceUsd: opts.priceUsd,
979
998
  accepts,
980
999
  comingSoon: !live,
981
- alternatives: ["api_key_credits", "sample_mode"]
1000
+ alternatives: ["api_key_credits", "sample_mode"],
1001
+ ...selfServe ? { selfServe } : {}
982
1002
  };
983
1003
  const encoded = JSON.stringify({ x402Version: 2, accepts, resource: opts.resource });
984
1004
  const headers = {
985
1005
  "Content-Type": "application/json",
986
- "WWW-Authenticate": `Payment realm="agentwares", methods="x402 mpp", price="${amountUsd} USD"`,
1006
+ "WWW-Authenticate": `Payment realm="agentwares", methods="x402 prepaid_credits", price="${amountUsd} USD"`,
987
1007
  [X402_HEADER]: encoded,
988
1008
  [X402_REQUIRED_HEADER]: btoa(encoded),
989
1009
  "Cache-Control": "no-store"
@@ -1001,7 +1021,7 @@ var StubVerifier = class {
1001
1021
  }
1002
1022
  };
1003
1023
 
1004
- // ../../packages/mcp-kit/dist/chunk-AO7AV7MI.js
1024
+ // ../../packages/mcp-kit/dist/chunk-NF4BQXVM.js
1005
1025
  import { z as z2 } from "zod";
1006
1026
  import { z as z3 } from "zod";
1007
1027
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -1183,13 +1203,13 @@ function defineTool(opts) {
1183
1203
  annotations,
1184
1204
  handler,
1185
1205
  async invoke(rawInput, ctx) {
1186
- const fail = (err) => {
1206
+ const fail2 = (err) => {
1187
1207
  const result = errorResult(err, { requestId: ctx.requestId });
1188
1208
  if (output) delete result.structuredContent;
1189
1209
  return result;
1190
1210
  };
1191
1211
  const parsed = input.safeParse(rawInput ?? {});
1192
- if (!parsed.success) return fail(invalidInputError(parsed.error.issues));
1212
+ if (!parsed.success) return fail2(invalidInputError(parsed.error.issues));
1193
1213
  try {
1194
1214
  const value = await handler(parsed.data, ctx);
1195
1215
  if (output && !isCallToolResult(value)) {
@@ -1207,7 +1227,7 @@ function defineTool(opts) {
1207
1227
  }
1208
1228
  return toCallToolResult(value);
1209
1229
  } catch (err) {
1210
- return fail(err);
1230
+ return fail2(err);
1211
1231
  }
1212
1232
  }
1213
1233
  };
@@ -1448,6 +1468,7 @@ function withPayment(tool, opts) {
1448
1468
  );
1449
1469
  }
1450
1470
  const verifier = opts.verifier ?? new StubVerifier();
1471
+ const liveRail = opts.verifier !== void 0;
1451
1472
  const input = tool.input.extend({
1452
1473
  [sampleField]: z2.boolean().default(false).describe(
1453
1474
  "Set true to return an example response at no charge (sample mode). Default false."
@@ -1478,17 +1499,35 @@ function withPayment(tool, opts) {
1478
1499
  live: opts.live,
1479
1500
  description: opts.description,
1480
1501
  payTo: opts.payTo,
1481
- signupUrl: opts.signupUrl
1502
+ creditsPayTo: opts.creditsPayTo,
1503
+ signupUrl: opts.signupUrl,
1504
+ selfServe: opts.selfServe
1482
1505
  });
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;
1506
+ const advertised = required.body.accepts.find((a) => a.rail === "prepaid_credits") ?? required.body.accepts[0];
1507
+ const requirement = advertised ?? {
1508
+ rail: "prepaid_credits",
1509
+ amountUsd: String(required.body.priceUsd),
1510
+ resource: tool.name
1511
+ };
1512
+ const verified = await verifier.verify(
1513
+ new Request(`https://mcp.local/${tool.name}`, { headers: ctx.headers }),
1514
+ requirement
1515
+ );
1516
+ if (verified.ok) {
1517
+ let result;
1518
+ try {
1519
+ result = await tool.handler(record, ctx);
1520
+ } catch (error) {
1521
+ await opts.onPaidFailure?.(ctx, verified, error);
1522
+ throw error;
1523
+ }
1524
+ const settled = await verifier.settle?.(verified);
1525
+ if (settled && !settled.ok) {
1526
+ opts.onSettlementFailure?.(ctx, verified, settled.reason);
1527
+ }
1528
+ return result;
1491
1529
  }
1530
+ reason = liveRail ? verified.reason ?? reason : reason ?? verified.reason;
1492
1531
  throw paymentRequiredError(required.body, reason);
1493
1532
  }
1494
1533
  });
@@ -1881,7 +1920,7 @@ import {
1881
1920
  keyFromHeaders,
1882
1921
  resolveAgent
1883
1922
  } from "@agentwares/agentguard-core";
1884
- var PROXY_VERSION = "0.1.0";
1923
+ var PROXY_VERSION = version();
1885
1924
  function headerValue(extra, name) {
1886
1925
  const raw = extra.requestInfo?.headers?.[name] ?? extra.requestInfo?.headers?.[name.toLowerCase()];
1887
1926
  if (Array.isArray(raw)) return raw[0];
@@ -2355,8 +2394,8 @@ var MAX_HEADERS_SIZE = 2 * 1024 * 1024;
2355
2394
 
2356
2395
  // ../../node_modules/.pnpm/resend@6.25.0/node_modules/resend/dist/index.mjs
2357
2396
  var import_standardwebhooks = __toESM(require_dist(), 1);
2358
- var version = "6.25.0";
2359
- var defaultUserAgent = `resend-node:${version}`;
2397
+ var version2 = "6.25.0";
2398
+ var defaultUserAgent = `resend-node:${version2}`;
2360
2399
 
2361
2400
  // ../../packages/notify/dist/index.js
2362
2401
  var palette = {
@@ -2600,7 +2639,7 @@ function readRegistration(loaded) {
2600
2639
  const file = registrationPath(loaded.stateDir);
2601
2640
  if (!exists(file)) return void 0;
2602
2641
  try {
2603
- const reg = JSON.parse(readFileSync(file, "utf8"));
2642
+ const reg = JSON.parse(readFileSync2(file, "utf8"));
2604
2643
  return reg;
2605
2644
  } catch {
2606
2645
  return void 0;
@@ -2615,104 +2654,12 @@ function describeError(err) {
2615
2654
  return err instanceof Error ? err.message : String(err);
2616
2655
  }
2617
2656
 
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";
2657
+ // src/commands/connect.ts
2658
+ import { existsSync as existsSync4 } from "fs";
2659
+ import { relative } from "path";
2713
2660
 
2714
2661
  // src/configs.ts
2715
- import { copyFileSync, existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2662
+ import { copyFileSync, existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
2716
2663
  import { homedir as homedir2, platform } from "os";
2717
2664
  import { join as join2, resolve as resolve2 } from "path";
2718
2665
  function candidateConfigs(cwd = process.cwd(), home = homedir2(), env = process.env) {
@@ -2775,7 +2722,7 @@ function describeConfigPath(path) {
2775
2722
  if (known) return known;
2776
2723
  let serversKey = "mcpServers";
2777
2724
  try {
2778
- const parsed = JSON.parse(readFileSync2(abs, "utf8"));
2725
+ const parsed = JSON.parse(readFileSync3(abs, "utf8"));
2779
2726
  if (parsed.servers && !parsed.mcpServers) serversKey = "servers";
2780
2727
  } catch {
2781
2728
  }
@@ -2787,7 +2734,7 @@ function describeConfigPath(path) {
2787
2734
  };
2788
2735
  }
2789
2736
  function readServers(file) {
2790
- const raw = JSON.parse(readFileSync2(file.path, "utf8"));
2737
+ const raw = JSON.parse(readFileSync3(file.path, "utf8"));
2791
2738
  const servers = raw[file.serversKey];
2792
2739
  if (!servers || typeof servers !== "object") return {};
2793
2740
  return servers;
@@ -2843,7 +2790,7 @@ function proxyEntry(file, policyPath, opts = {}) {
2843
2790
  return entry;
2844
2791
  }
2845
2792
  function rewriteClientConfig(file, policyPath, opts = {}) {
2846
- const raw = JSON.parse(readFileSync2(file.path, "utf8"));
2793
+ const raw = JSON.parse(readFileSync3(file.path, "utf8"));
2847
2794
  const servers = raw[file.serversKey] ?? {};
2848
2795
  const replaced = Object.keys(servers).filter((n) => !isProxyEntry(servers[n]));
2849
2796
  const backup = backupPath(file);
@@ -2858,6 +2805,216 @@ function restoreClientConfig(file) {
2858
2805
  copyFileSync(backup, file.path);
2859
2806
  return true;
2860
2807
  }
2808
+ function mergeServerIntoConfig(file, name, entry) {
2809
+ const raw = JSON.parse(readFileSync3(file.path, "utf8"));
2810
+ const servers = raw[file.serversKey] ?? {};
2811
+ const replaced = name in servers;
2812
+ const backup = backupPath(file);
2813
+ if (!existsSync3(backup)) copyFileSync(file.path, backup);
2814
+ raw[file.serversKey] = { ...servers, [name]: entry };
2815
+ writeFileSync2(file.path, JSON.stringify(raw, null, 2) + "\n");
2816
+ return { backup, replaced };
2817
+ }
2818
+
2819
+ // src/commands/connect.ts
2820
+ var DEFAULT_PROXY_URL = "https://agentwares-agentguard-proxy.vercel.app";
2821
+ function fail(e) {
2822
+ throw e;
2823
+ }
2824
+ async function connectCommand(args, io, fetchImpl = fetch) {
2825
+ const key = args.positionals[0] ?? io.env.AGENTGUARD_KEY;
2826
+ if (!key) {
2827
+ io.err("usage: agentguard connect <key> [--write] [--client <path>] [--url <proxy-url>]");
2828
+ io.err(" the key is on your dashboard; it starts with agk_");
2829
+ return 2;
2830
+ }
2831
+ const base = (flagString(args, "url") ?? io.env.AGENTGUARD_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\/+$/, "");
2832
+ let res;
2833
+ try {
2834
+ res = await fetchImpl(`${base}/connect`, {
2835
+ method: "POST",
2836
+ headers: { "content-type": "application/json" },
2837
+ body: JSON.stringify({ key })
2838
+ });
2839
+ } catch (err) {
2840
+ fail({
2841
+ code: "PROXY_UNREACHABLE",
2842
+ cause: `could not reach ${base}: ${err instanceof Error ? err.message : String(err)}`,
2843
+ fix: "check the network, or pass --url if you run your own proxy",
2844
+ retryable: true
2845
+ });
2846
+ }
2847
+ const text = await res.text();
2848
+ let body;
2849
+ try {
2850
+ body = JSON.parse(text);
2851
+ } catch {
2852
+ fail({
2853
+ code: "PROXY_BAD_RESPONSE",
2854
+ cause: `${base}/connect returned ${res.status} that was not JSON: ${text.slice(0, 200)}`,
2855
+ fix: "check --url points at an agentguard proxy, not at the dashboard",
2856
+ retryable: false
2857
+ });
2858
+ }
2859
+ if (!res.ok) {
2860
+ const e = body;
2861
+ fail({
2862
+ code: e.code ?? "CONNECT_REFUSED",
2863
+ cause: e.cause ?? `${base}/connect returned ${res.status}`,
2864
+ fix: e.fix ?? "check the key on your dashboard; revoked keys stop working immediately",
2865
+ retryable: e.retryable ?? false
2866
+ });
2867
+ }
2868
+ const conn = body;
2869
+ if (flagBool(args, "json")) {
2870
+ io.out(JSON.stringify(conn, null, 2));
2871
+ return 0;
2872
+ }
2873
+ const rel = (p) => p.startsWith(io.cwd + "/") ? relative(io.cwd, p) : p;
2874
+ const entry = conn.mcpServers[PROXY_SERVER_NAME] ?? {
2875
+ type: "http",
2876
+ url: conn.mcpUrl,
2877
+ headers: conn.headers
2878
+ };
2879
+ if (!flagBool(args, "write")) {
2880
+ io.out(JSON.stringify({ mcpServers: { [PROXY_SERVER_NAME]: entry } }, null, 2));
2881
+ } else {
2882
+ const explicit = flagString(args, "client");
2883
+ const configs = explicit ? [describeConfigPath(explicit)] : detectClientConfigs(io.cwd, homeFrom(io), io.env);
2884
+ const projectLevel = configs.filter((c) => c.path.startsWith(io.cwd + "/"));
2885
+ let chosen;
2886
+ if (explicit || flagBool(args, "all")) chosen = configs;
2887
+ else chosen = projectLevel.slice(0, 1);
2888
+ if (chosen.length === 0) {
2889
+ io.err(
2890
+ "no MCP config found here (.mcp.json, .cursor/mcp.json, .vscode/mcp.json). Create one, or paste this into your client:"
2891
+ );
2892
+ io.out(JSON.stringify({ mcpServers: { [PROXY_SERVER_NAME]: entry } }, null, 2));
2893
+ return 1;
2894
+ }
2895
+ for (const c of chosen) {
2896
+ if (!existsSync4(c.path)) {
2897
+ io.err(`no MCP config at ${c.path}`);
2898
+ return 1;
2899
+ }
2900
+ const { backup, replaced } = mergeServerIntoConfig(c, PROXY_SERVER_NAME, entry);
2901
+ io.out(
2902
+ `${replaced ? "updated" : "wrote "} ${rel(c.path)} (${c.label}) \u2192 ${PROXY_SERVER_NAME} = ${conn.mcpUrl}`
2903
+ );
2904
+ io.out(`backup ${rel(backup)}`);
2905
+ }
2906
+ for (const c of configs.filter((c2) => !chosen.includes(c2)))
2907
+ io.out(
2908
+ `found ${c.path} (${c.label}, user-level) \u2014 not touched; pass --client ${JSON.stringify(c.path)} to write it too`
2909
+ );
2910
+ }
2911
+ const band = conn.band ? ` band=${conn.band.tier} (${conn.band.quantity} calls/mo)` : "";
2912
+ io.out(`
2913
+ proxy ${conn.slug} (${conn.proxyId}) mode=${conn.mode}${band}`);
2914
+ if (conn.upstreams?.length) io.out(`upstreams: ${conn.upstreams.join(", ")}`);
2915
+ if (conn.dashboardUrl) io.out(`dashboard: ${conn.dashboardUrl}`);
2916
+ if (conn.claudeCode) io.out(`claude code: ${conn.claudeCode}`);
2917
+ if (!flagBool(args, "write"))
2918
+ io.out(`
2919
+ run again with --write to add this to your MCP client config`);
2920
+ return 0;
2921
+ }
2922
+
2923
+ // src/commands/control.ts
2924
+ async function pokeHttp(io, reg, path, body) {
2925
+ if (!reg) return;
2926
+ try {
2927
+ const res = await fetch(`${reg.baseUrl}${path}`, {
2928
+ method: "POST",
2929
+ headers: { Authorization: `Bearer ${reg.token}`, "Content-Type": "application/json" },
2930
+ body: body === void 0 ? void 0 : JSON.stringify(body),
2931
+ signal: AbortSignal.timeout(2e3)
2932
+ });
2933
+ if (!res.ok)
2934
+ io.err(
2935
+ `running proxy at ${reg.baseUrl} answered ${res.status} (the file switch still applies)`
2936
+ );
2937
+ } catch {
2938
+ io.err(
2939
+ `no proxy answering at ${reg.baseUrl} (stale ${reg.pid}?) \u2014 the file switch still applies`
2940
+ );
2941
+ }
2942
+ }
2943
+ async function killCommand(args, io) {
2944
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
2945
+ const { kill } = createFileGuard(loaded, { alerts: false, env: io.env });
2946
+ const reason = args.positionals.join(" ") || flagString(args, "reason") || `agentguard kill by ${io.env.USER ?? "operator"}`;
2947
+ kill.kill(reason);
2948
+ await pokeHttp(io, readRegistration(loaded), "/kill", { reason });
2949
+ io.out(
2950
+ `KILLED: ${kill.path} written \u2014 every tool call through this policy now returns KILLED. Clear with: agentguard resume`
2951
+ );
2952
+ return 0;
2953
+ }
2954
+ async function resumeCommand(args, io) {
2955
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
2956
+ const { kill } = createFileGuard(loaded, { alerts: false, env: io.env });
2957
+ kill.resume();
2958
+ await pokeHttp(io, readRegistration(loaded), "/resume");
2959
+ io.out(`resumed: ${kill.path} removed`);
2960
+ if (io.env[loaded.policy.kill.env])
2961
+ io.err(
2962
+ `note: ${loaded.policy.kill.env} is set in this shell's environment; proxies started from it stay killed`
2963
+ );
2964
+ return 0;
2965
+ }
2966
+ async function decideCommand(decision, args, io) {
2967
+ const id = args.positionals[0];
2968
+ if (!id) {
2969
+ io.err(
2970
+ `usage: agentguard ${decision === "approved" ? "approve" : "deny"} <approval-id> [--by name] [--note text]`
2971
+ );
2972
+ return 1;
2973
+ }
2974
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
2975
+ const { guard } = createFileGuard(loaded, { alerts: false, env: io.env });
2976
+ const record = await guard.decide(
2977
+ id,
2978
+ decision,
2979
+ flagString(args, "by") ?? io.env.USER ?? "cli",
2980
+ flagString(args, "note")
2981
+ );
2982
+ if (!record) {
2983
+ io.err(`no approval ${id} \u2014 pending ones: agentguard approvals`);
2984
+ return 1;
2985
+ }
2986
+ io.out(
2987
+ `${record.status}: ${record.tool} (${record.id}, run ${record.run_id})${record.status === "approved" ? " \u2014 the agent may now retry the identical call once" : ""}`
2988
+ );
2989
+ return 0;
2990
+ }
2991
+ async function approvalsCommand(args, io) {
2992
+ const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
2993
+ const { guard } = createFileGuard(loaded, { alerts: false, env: io.env });
2994
+ const list = await guard.approvals.list(flagBool(args, "all") ? void 0 : "pending");
2995
+ if (flagBool(args, "json")) {
2996
+ io.out(JSON.stringify(list, null, 2));
2997
+ return 0;
2998
+ }
2999
+ if (list.length === 0) {
3000
+ io.out(flagBool(args, "all") ? "no approvals recorded" : "no pending approvals");
3001
+ return 0;
3002
+ }
3003
+ for (const r of list) {
3004
+ io.out(
3005
+ `${r.id} ${r.status.padEnd(9)} ${r.tool} run ${r.run_id}${r.agent ? ` agent ${r.agent}` : ""} expires ${r.expires_at}`
3006
+ );
3007
+ io.out(` args: ${JSON.stringify(r.args).slice(0, 300)}`);
3008
+ if (r.status === "pending")
3009
+ io.out(` \u2192 agentguard approve ${r.id} | agentguard deny ${r.id}`);
3010
+ }
3011
+ return 0;
3012
+ }
3013
+
3014
+ // src/commands/init.ts
3015
+ import { existsSync as existsSync5, writeFileSync as writeFileSync3 } from "fs";
3016
+ import { relative as relative2, resolve as resolve3 } from "path";
3017
+ import YAML from "yaml";
2861
3018
 
2862
3019
  // src/commands/tools.ts
2863
3020
  import {
@@ -2948,7 +3105,7 @@ var ProxyRuntime = class {
2948
3105
  }
2949
3106
  async connect(upstream) {
2950
3107
  const client = new Client(
2951
- { name: "agentguard", version: "0.1.0" },
3108
+ { name: "agentguard", version: version() },
2952
3109
  { capabilities: { sampling: {}, elicitation: {} } }
2953
3110
  );
2954
3111
  const conn = {
@@ -3592,7 +3749,7 @@ async function initCommand(args, io) {
3592
3749
  const explicit = flagString(args, "client");
3593
3750
  const configs = explicit ? [describeConfigPath(resolve3(io.cwd, explicit))] : detectClientConfigs(io.cwd, homeFrom(io), io.env);
3594
3751
  const rel = (p) => {
3595
- const r = relative(io.cwd, p);
3752
+ const r = relative2(io.cwd, p);
3596
3753
  return r.startsWith("..") ? p : r || ".";
3597
3754
  };
3598
3755
  if (flagBool(args, "undo")) {
@@ -3619,7 +3776,7 @@ async function initCommand(args, io) {
3619
3776
  `found ${c.path} (${c.label}, user-level) \u2014 not touched; pass --client ${JSON.stringify(c.path)} to route it through agentguard`
3620
3777
  );
3621
3778
  }
3622
- if (!chosen.every((c) => existsSync4(c.path))) {
3779
+ if (!chosen.every((c) => existsSync5(c.path))) {
3623
3780
  io.err(`no MCP config at ${chosen.map((c) => c.path).join(", ")}`);
3624
3781
  return 1;
3625
3782
  }
@@ -3638,7 +3795,7 @@ async function initCommand(args, io) {
3638
3795
  );
3639
3796
  const { upstreams, skipped } = serversToUpstreams(servers);
3640
3797
  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)) {
3798
+ if (skipped.some((s) => s.reason.includes("already")) && upstreams.length === 0 && existsSync5(policyPath)) {
3642
3799
  io.out(
3643
3800
  `already initialized: ${rel(policyPath)} exists and ${chosen.map((c) => rel(c.path)).join(", ")} already points at agentguard`
3644
3801
  );
@@ -3665,7 +3822,7 @@ async function initCommand(args, io) {
3665
3822
  }
3666
3823
  }
3667
3824
  const mode = flagString(args, "mode") ?? "dry-run";
3668
- if (existsSync4(policyPath) && !flagBool(args, "force")) {
3825
+ if (existsSync5(policyPath) && !flagBool(args, "force")) {
3669
3826
  io.out(`kept ${rel(policyPath)} (exists; pass --force to regenerate)`);
3670
3827
  } else {
3671
3828
  writeFileSync3(policyPath, renderStarterPolicy({ upstreams, tools: probed, mode, probeErrors }));
@@ -3704,13 +3861,13 @@ async function initCommand(args, io) {
3704
3861
  }
3705
3862
 
3706
3863
  // src/commands/keys.ts
3707
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
3864
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
3708
3865
  import YAML2, { isMap, isSeq, YAMLSeq } from "yaml";
3709
3866
  import { generateAgentKey, hashAgentKey } from "@agentwares/agentguard-core";
3710
3867
  async function keyCommand(args, io) {
3711
3868
  const [sub, name] = args.positionals;
3712
3869
  const path = policyPathFrom(args, io);
3713
- const doc = YAML2.parseDocument(readFileSync3(path, "utf8"));
3870
+ const doc = YAML2.parseDocument(readFileSync4(path, "utf8"));
3714
3871
  let agents = doc.get("agents");
3715
3872
  if (!isSeq(agents)) {
3716
3873
  agents = new YAMLSeq();
@@ -4071,19 +4228,13 @@ async function statusCommand(args, io) {
4071
4228
  }
4072
4229
 
4073
4230
  // 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
4231
  var HELP = `agentguard \u2014 MCP policy proxy for agents that touch production
4082
4232
 
4083
4233
  npx @agentwares/agentguard init read your MCP config, write agentguard.yaml (dry-run), route every server through the proxy
4084
4234
  agentguard report [--run id|--all] what this run did / would have destroyed / spent, where it was halted
4085
4235
  agentguard diff [--run id] record-by-record mutation diff from a dry run
4086
4236
  agentguard verify [audit.jsonl] prove the hash-chained audit log was not edited
4237
+ agentguard connect <key> [--write] point this machine at your hosted proxy (paid tiers)
4087
4238
 
4088
4239
  agentguard proxy [--http --port 8788] [--agent name] [--run-id id] [--mode dry-run|enforce]
4089
4240
  agentguard tools [--json] the tools your agent will see, with class and why
@@ -4100,7 +4251,7 @@ Docs: https://github.com/agentwares/agentguard/tree/main/apps/agentguard-cli`;
4100
4251
  async function main(argv, io = defaultIo()) {
4101
4252
  const args = parseArgs(argv);
4102
4253
  if (args.flags.version) {
4103
- io.out(version2());
4254
+ io.out(version());
4104
4255
  return 0;
4105
4256
  }
4106
4257
  if (args.command === void 0 && !args.flags.help && io.tty === false) {
@@ -4128,6 +4279,8 @@ async function main(argv, io = defaultIo()) {
4128
4279
  return await diffCommand(args, io);
4129
4280
  case "verify":
4130
4281
  return await verifyCommand(args, io);
4282
+ case "connect":
4283
+ return await connectCommand(args, io);
4131
4284
  case "status":
4132
4285
  return await statusCommand(args, io);
4133
4286
  case "tools":
@@ -4174,6 +4327,6 @@ if (isMain) {
4174
4327
  export {
4175
4328
  HELP,
4176
4329
  main,
4177
- version2 as version
4330
+ version
4178
4331
  };
4179
4332
  //# sourceMappingURL=cli.js.map