@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/LICENSE +21 -0
- package/README.md +35 -17
- package/dist/cli.js +303 -150
- package/dist/cli.js.map +1 -1
- package/dist/fixtures/crm-server.js +53 -24
- package/dist/fixtures/crm-server.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +202 -49
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
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-
|
|
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
|
|
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:
|
|
970
|
+
network: opts.network ?? DEFAULT_X402_NETWORK,
|
|
958
971
|
asset: "USDC",
|
|
959
972
|
payTo: opts.payTo,
|
|
960
973
|
maxTimeoutSeconds: 300
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
|
|
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:
|
|
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 ?
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1502
|
+
creditsPayTo: opts.creditsPayTo,
|
|
1503
|
+
signupUrl: opts.signupUrl,
|
|
1504
|
+
selfServe: opts.selfServe
|
|
1482
1505
|
});
|
|
1483
|
-
const
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
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 =
|
|
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
|
|
2359
|
-
var defaultUserAgent = `resend-node:${
|
|
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(
|
|
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/
|
|
2619
|
-
|
|
2620
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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 =
|
|
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) =>
|
|
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 &&
|
|
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 (
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
4330
|
+
version
|
|
4178
4331
|
};
|
|
4179
4332
|
//# sourceMappingURL=cli.js.map
|