@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/README.md +35 -17
- package/dist/cli.js +349 -155
- package/dist/cli.js.map +1 -1
- package/dist/fixtures/crm-server.js +92 -28
- package/dist/fixtures/crm-server.js.map +1 -1
- package/dist/fixtures/demo-agent.js +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +248 -54
- package/dist/index.js.map +1 -1
- package/package.json +16 -16
- package/server.json +2 -2
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-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
|
|
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
|
-
|
|
958
|
-
|
|
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
|
-
|
|
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:
|
|
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 ?
|
|
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: [
|
|
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({
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1528
|
+
resource,
|
|
1477
1529
|
priceUsd: opts.priceUsd,
|
|
1478
1530
|
live: opts.live,
|
|
1479
1531
|
description: opts.description,
|
|
1480
1532
|
payTo: opts.payTo,
|
|
1481
|
-
|
|
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
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
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 =
|
|
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
|
|
2359
|
-
var defaultUserAgent = `resend-node:${
|
|
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(
|
|
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/
|
|
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";
|
|
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
|
|
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(
|
|
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(
|
|
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",
|
|
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(
|
|
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:
|
|
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 =
|
|
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) =>
|
|
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 &&
|
|
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 (
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
4371
|
+
version
|
|
4178
4372
|
};
|
|
4179
4373
|
//# sourceMappingURL=cli.js.map
|