@agentwares/agentguard 0.1.5 → 0.1.6
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/CHANGELOG.md +69 -0
- package/README.md +69 -19
- package/dist/cli.js +417 -56
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +819 -458
- package/dist/index.js.map +1 -1
- package/llms.txt +4 -0
- package/package.json +7 -2
- package/server.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -925,7 +925,7 @@ function version() {
|
|
|
925
925
|
}
|
|
926
926
|
|
|
927
927
|
// src/context.ts
|
|
928
|
-
import { existsSync as
|
|
928
|
+
import { existsSync as existsSync3 } from "fs";
|
|
929
929
|
import { resolve } from "path";
|
|
930
930
|
import { Guard } from "@agentwares/agentguard-core";
|
|
931
931
|
import {
|
|
@@ -935,7 +935,7 @@ import {
|
|
|
935
935
|
FileStateStore,
|
|
936
936
|
loadPolicyFile
|
|
937
937
|
} from "@agentwares/agentguard-core/node";
|
|
938
|
-
import { existsSync as exists, readFileSync as
|
|
938
|
+
import { existsSync as exists, readFileSync as readFileSync3 } from "fs";
|
|
939
939
|
|
|
940
940
|
// ../../packages/mcp-kit/dist/chunk-7LRFDQNC.js
|
|
941
941
|
import { completable } from "@modelcontextprotocol/sdk/server/completable.js";
|
|
@@ -1161,6 +1161,10 @@ function errorResult(err, opts = {}) {
|
|
|
1161
1161
|
_meta: meta
|
|
1162
1162
|
};
|
|
1163
1163
|
}
|
|
1164
|
+
function createToolContext(init = {}) {
|
|
1165
|
+
const { headers, ...rest } = init;
|
|
1166
|
+
return { headers: headers ?? new Headers(), ...rest };
|
|
1167
|
+
}
|
|
1164
1168
|
var TOOL_NAME_PATTERN = /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/;
|
|
1165
1169
|
var MIN_NAMESPACE_LENGTH = 3;
|
|
1166
1170
|
var MIN_DESCRIPTION_LENGTH = 20;
|
|
@@ -1931,7 +1935,7 @@ async function serveStdio(serverOrOpts) {
|
|
|
1931
1935
|
}
|
|
1932
1936
|
|
|
1933
1937
|
// src/proxy/http.ts
|
|
1934
|
-
import { writeFileSync, unlinkSync, existsSync, mkdirSync } from "fs";
|
|
1938
|
+
import { writeFileSync, unlinkSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
1935
1939
|
import { createServer } from "http";
|
|
1936
1940
|
import { join } from "path";
|
|
1937
1941
|
import { FileKillSwitch } from "@agentwares/agentguard-core/node";
|
|
@@ -1955,6 +1959,278 @@ import {
|
|
|
1955
1959
|
keyFromHeaders,
|
|
1956
1960
|
resolveAgent
|
|
1957
1961
|
} from "@agentwares/agentguard-core";
|
|
1962
|
+
|
|
1963
|
+
// src/proxy/own-tools.ts
|
|
1964
|
+
import {
|
|
1965
|
+
buildReport,
|
|
1966
|
+
latestRunId,
|
|
1967
|
+
listRuns,
|
|
1968
|
+
parseAuditJsonl,
|
|
1969
|
+
renderReportMarkdown,
|
|
1970
|
+
verifyChain
|
|
1971
|
+
} from "@agentwares/agentguard-core";
|
|
1972
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
1973
|
+
import { z as z5 } from "zod";
|
|
1974
|
+
var INIT_COMMAND = "npx -y @agentwares/agentguard init";
|
|
1975
|
+
var READ_ONLY = {
|
|
1976
|
+
readOnlyHint: true,
|
|
1977
|
+
destructiveHint: false,
|
|
1978
|
+
idempotentHint: true,
|
|
1979
|
+
openWorldHint: false
|
|
1980
|
+
};
|
|
1981
|
+
var runIdInput = z5.string().min(1).max(128).optional().describe(
|
|
1982
|
+
"Run to read, as it appears in the audit log (for example run_20261007093015_ab12, or the X-Run-Id an HTTP client sent). Omit it for the most recent run."
|
|
1983
|
+
);
|
|
1984
|
+
var counters = z5.record(z5.string(), z5.number()).describe(
|
|
1985
|
+
"counter name (tool_calls, writes, deletes, emails, spend_usd or a custom one) \u2192 value"
|
|
1986
|
+
);
|
|
1987
|
+
function readAudit(path) {
|
|
1988
|
+
if (!existsSync(path)) return { exists: false, entries: [] };
|
|
1989
|
+
try {
|
|
1990
|
+
return { exists: true, entries: parseAuditJsonl(readFileSync2(path, "utf8")) };
|
|
1991
|
+
} catch (err) {
|
|
1992
|
+
return { exists: true, entries: [], error: err instanceof Error ? err.message : String(err) };
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
function unreadable(path, error) {
|
|
1996
|
+
return toolError({
|
|
1997
|
+
code: "AUDIT_UNREADABLE",
|
|
1998
|
+
cause: `the audit log at ${path} could not be read: ${error}`,
|
|
1999
|
+
fix: "call agentguard_verify_audit_log to see where it breaks; a hand-edited or truncated line has to be restored from a copy",
|
|
2000
|
+
retryable: false
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
function numbers(caps) {
|
|
2004
|
+
const out = {};
|
|
2005
|
+
for (const [k, v] of Object.entries(caps)) if (typeof v === "number") out[k] = v;
|
|
2006
|
+
return out;
|
|
2007
|
+
}
|
|
2008
|
+
function nextStep(runtime) {
|
|
2009
|
+
if (!runtime.configured)
|
|
2010
|
+
return `agentguard is guarding nothing: no policy file at ${runtime.loaded.path}. Run \`${INIT_COMMAND}\` in the project directory: it writes agentguard.yaml in dry-run mode and routes the project's MCP servers through agentguard (backup kept, \`--undo\` restores). Then restart the MCP client.`;
|
|
2011
|
+
if (runtime.policy.upstreams.length === 0)
|
|
2012
|
+
return `${runtime.loaded.path} lists no upstream servers, so agentguard is guarding nothing. Add the MCP servers your agent uses under \`upstreams:\` in that file (it has commented examples), then restart the MCP client.`;
|
|
2013
|
+
return "agentguard is running; run your agent, then read agentguard_get_report.";
|
|
2014
|
+
}
|
|
2015
|
+
var StatusOutput = z5.object({
|
|
2016
|
+
configured: z5.boolean().describe(
|
|
2017
|
+
"true when a policy file was found and loaded; false means agentguard guards nothing"
|
|
2018
|
+
),
|
|
2019
|
+
policy_path: z5.string().describe("absolute path of the policy file read, or looked for"),
|
|
2020
|
+
mode: z5.enum(["dry-run", "enforce"]).describe("dry-run fakes writes and spends and records them; enforce lets allowed calls run"),
|
|
2021
|
+
upstreams: z5.array(
|
|
2022
|
+
z5.object({
|
|
2023
|
+
name: z5.string().describe("upstream name from the policy"),
|
|
2024
|
+
connected: z5.boolean().describe("whether agentguard is connected to it"),
|
|
2025
|
+
tools: z5.number().int().describe("tools it exposes"),
|
|
2026
|
+
transport: z5.enum(["stdio", "http"]).describe("how agentguard reaches it"),
|
|
2027
|
+
error: z5.string().optional().describe("why it is not connected")
|
|
2028
|
+
})
|
|
2029
|
+
).describe("the MCP servers this proxy fronts; empty means none are configured"),
|
|
2030
|
+
run_id: z5.string().nullable().describe(
|
|
2031
|
+
"run the usage is for: the requested run_id, else the latest run in the log, else null"
|
|
2032
|
+
),
|
|
2033
|
+
caps: z5.object({ per_run: counters, per_day: counters }).describe("limits from the policy; a counter that is absent has no cap"),
|
|
2034
|
+
used: z5.object({ per_run: counters, per_day: counters }).describe("what that run, and today, have used against the caps"),
|
|
2035
|
+
remaining: z5.record(
|
|
2036
|
+
z5.string(),
|
|
2037
|
+
z5.object({ per_run: z5.number().optional(), per_day: z5.number().optional() })
|
|
2038
|
+
).describe("budget left per capped counter, per run and per day"),
|
|
2039
|
+
kill_switch: z5.object({
|
|
2040
|
+
on: z5.boolean().describe("true halts every tool call with KILLED"),
|
|
2041
|
+
reason: z5.string().optional().describe("why it was pulled"),
|
|
2042
|
+
source: z5.string().optional().describe("file, env or memory")
|
|
2043
|
+
}).describe("the kill switch state"),
|
|
2044
|
+
pending_approvals: z5.number().int().describe("destructive calls waiting for a human decision"),
|
|
2045
|
+
audit_log: z5.object({
|
|
2046
|
+
path: z5.string().describe("absolute path of the hash-chained JSONL audit log"),
|
|
2047
|
+
entries: z5.number().int().describe("entries in it; 0 when it does not exist yet")
|
|
2048
|
+
}).describe("where every tool call is recorded"),
|
|
2049
|
+
next_step: z5.string().describe("the one thing to do next, with the command when there is one")
|
|
2050
|
+
});
|
|
2051
|
+
var ReportOutput = z5.object({
|
|
2052
|
+
configured: z5.boolean().describe("false when no policy file was found (agentguard guards nothing)"),
|
|
2053
|
+
audit_log_path: z5.string().describe("absolute path of the audit log the report was built from"),
|
|
2054
|
+
run_id: z5.string().nullable().describe("the run reported on; null when the log has no runs"),
|
|
2055
|
+
runs_in_log: z5.number().int().describe("how many runs the audit log holds"),
|
|
2056
|
+
tool_calls: z5.number().int().describe("tool calls in this run"),
|
|
2057
|
+
would_have: z5.object({
|
|
2058
|
+
creates: z5.number().int(),
|
|
2059
|
+
updates: z5.number().int(),
|
|
2060
|
+
deletes: z5.number().int(),
|
|
2061
|
+
sends: z5.number().int(),
|
|
2062
|
+
executes: z5.number().int(),
|
|
2063
|
+
spends: z5.number().int(),
|
|
2064
|
+
spend_usd: z5.number()
|
|
2065
|
+
}).describe("what dry-run mode faked: what the run would have done had the calls executed"),
|
|
2066
|
+
did: z5.object({
|
|
2067
|
+
writes: z5.number().int(),
|
|
2068
|
+
deletes: z5.number().int(),
|
|
2069
|
+
sends: z5.number().int(),
|
|
2070
|
+
spend_usd: z5.number()
|
|
2071
|
+
}).describe("what actually executed upstream"),
|
|
2072
|
+
halts: z5.array(
|
|
2073
|
+
z5.object({
|
|
2074
|
+
seq: z5.number().int().describe("audit entry number"),
|
|
2075
|
+
ts: z5.string().describe("ISO time of the call"),
|
|
2076
|
+
code: z5.string().describe("CAP_EXCEEDED, LOOP_DETECTED, KILLED, APPROVAL_REQUIRED, \u2026"),
|
|
2077
|
+
tool: z5.string().describe("the tool that was stopped"),
|
|
2078
|
+
cause: z5.string().describe("why")
|
|
2079
|
+
})
|
|
2080
|
+
).describe("every call agentguard stopped, in order"),
|
|
2081
|
+
top_tools: z5.array(z5.object({ tool: z5.string(), calls: z5.number().int(), class: z5.string() })).describe("the most-called tools with their class (read, write, spend)"),
|
|
2082
|
+
markdown: z5.string().describe("the same report `agentguard report` prints, as Markdown")
|
|
2083
|
+
});
|
|
2084
|
+
var VerifyOutput = z5.object({
|
|
2085
|
+
configured: z5.boolean().describe("false when no policy file was found (agentguard guards nothing)"),
|
|
2086
|
+
path: z5.string().describe("absolute path of the audit log checked"),
|
|
2087
|
+
exists: z5.boolean().describe("false when no call has been recorded yet; an absent log verifies"),
|
|
2088
|
+
ok: z5.boolean().describe("true when every entry's hash and prev_hash recompute"),
|
|
2089
|
+
entries: z5.number().int().describe("entries checked"),
|
|
2090
|
+
head_hash: z5.string().nullable().describe(
|
|
2091
|
+
"hash of the last entry; record it with the count to detect entries cut from the end"
|
|
2092
|
+
),
|
|
2093
|
+
broken_at: z5.number().int().nullable().describe("seq of the first entry that does not verify"),
|
|
2094
|
+
reason: z5.string().nullable().describe("why verification failed")
|
|
2095
|
+
});
|
|
2096
|
+
function createOwnTools(runtime) {
|
|
2097
|
+
const status = defineTool({
|
|
2098
|
+
name: "agentguard_get_status",
|
|
2099
|
+
title: "agentguard status",
|
|
2100
|
+
description: "Report what agentguard is guarding right now: the policy file it loaded (or that none was found), dry-run or enforce mode, each upstream MCP server and whether it connected, a run's cap usage and remaining budget, the kill switch, and pending approvals. Call it first to learn whether agentguard is set up; next_step says what to do when it is not. For what a run did, use agentguard_get_report; to check the log was not edited, agentguard_verify_audit_log. Reads local files only and changes nothing.",
|
|
2101
|
+
input: z5.strictObject({ run_id: runIdInput }),
|
|
2102
|
+
output: StatusOutput,
|
|
2103
|
+
annotations: { title: "agentguard status", ...READ_ONLY },
|
|
2104
|
+
async handler({ run_id }) {
|
|
2105
|
+
const { loaded, guard } = runtime;
|
|
2106
|
+
const audit = readAudit(loaded.auditPath);
|
|
2107
|
+
if (audit.error) throw unreadable(loaded.auditPath, audit.error);
|
|
2108
|
+
const runId = run_id ?? latestRunId(audit.entries) ?? null;
|
|
2109
|
+
const state = await guard.status(runId ?? "(none yet)");
|
|
2110
|
+
const pending = await guard.approvals.list("pending");
|
|
2111
|
+
return {
|
|
2112
|
+
configured: runtime.configured,
|
|
2113
|
+
policy_path: loaded.path,
|
|
2114
|
+
mode: runtime.policy.mode,
|
|
2115
|
+
upstreams: runtime.upstreamStatus(),
|
|
2116
|
+
run_id: runId,
|
|
2117
|
+
caps: { per_run: numbers(state.caps.per_run), per_day: numbers(state.caps.per_day) },
|
|
2118
|
+
used: state.usage,
|
|
2119
|
+
remaining: state.remaining,
|
|
2120
|
+
kill_switch: {
|
|
2121
|
+
on: state.killed.killed,
|
|
2122
|
+
...state.killed.reason ? { reason: state.killed.reason } : {},
|
|
2123
|
+
...state.killed.source ? { source: state.killed.source } : {}
|
|
2124
|
+
},
|
|
2125
|
+
pending_approvals: pending.length,
|
|
2126
|
+
audit_log: { path: loaded.auditPath, entries: audit.entries.length },
|
|
2127
|
+
next_step: nextStep(runtime)
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
});
|
|
2131
|
+
const report = defineTool({
|
|
2132
|
+
name: "agentguard_get_report",
|
|
2133
|
+
title: "agentguard run report",
|
|
2134
|
+
description: "Summarize one run from agentguard's audit log: how many tool calls, what dry-run mode faked (records it would have created, updated or deleted, messages sent, dollars spent), what really ran upstream, and every call agentguard halted with its code and cause. Defaults to the most recent run. Use it after an agent run, before switching a policy from dry-run to enforce; for live caps and the kill switch use agentguard_get_status. Read-only: reads the audit file and does not record this call.",
|
|
2135
|
+
input: z5.strictObject({ run_id: runIdInput }),
|
|
2136
|
+
output: ReportOutput,
|
|
2137
|
+
annotations: { title: "agentguard run report", ...READ_ONLY },
|
|
2138
|
+
handler({ run_id }) {
|
|
2139
|
+
const { loaded } = runtime;
|
|
2140
|
+
const audit = readAudit(loaded.auditPath);
|
|
2141
|
+
if (audit.error) throw unreadable(loaded.auditPath, audit.error);
|
|
2142
|
+
const runs = listRuns(audit.entries);
|
|
2143
|
+
if (run_id && !runs.some((r) => r.runId === run_id)) {
|
|
2144
|
+
throw toolError({
|
|
2145
|
+
code: "NOT_FOUND",
|
|
2146
|
+
cause: `no run "${run_id}" in the audit log at ${loaded.auditPath}`,
|
|
2147
|
+
fix: `call again without run_id for the latest run${runs.length ? `; runs in the log: ${runs.slice(0, 5).map((r) => r.runId).join(", ")}` : "; the log has no runs yet"}`,
|
|
2148
|
+
retryable: false
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
const runId = run_id ?? latestRunId(audit.entries) ?? null;
|
|
2152
|
+
const built = buildReport(audit.entries, { runId: runId ?? void 0 });
|
|
2153
|
+
const markdown = runId ? renderReportMarkdown(built) : `# agentguard report
|
|
2154
|
+
|
|
2155
|
+
No runs recorded in ${loaded.auditPath} yet. ${nextStep(runtime)}`;
|
|
2156
|
+
return {
|
|
2157
|
+
configured: runtime.configured,
|
|
2158
|
+
audit_log_path: loaded.auditPath,
|
|
2159
|
+
run_id: runId,
|
|
2160
|
+
runs_in_log: runs.length,
|
|
2161
|
+
tool_calls: runId ? audit.entries.filter((e) => e.run_id === runId).length : 0,
|
|
2162
|
+
would_have: {
|
|
2163
|
+
creates: built.wouldHave.creates,
|
|
2164
|
+
updates: built.wouldHave.updates,
|
|
2165
|
+
deletes: built.wouldHave.deletes,
|
|
2166
|
+
sends: built.wouldHave.sends,
|
|
2167
|
+
executes: built.wouldHave.executes,
|
|
2168
|
+
spends: built.wouldHave.spends,
|
|
2169
|
+
spend_usd: built.wouldHave.spendUsd
|
|
2170
|
+
},
|
|
2171
|
+
did: {
|
|
2172
|
+
writes: built.did.writes,
|
|
2173
|
+
deletes: built.did.deletes,
|
|
2174
|
+
sends: built.did.sends,
|
|
2175
|
+
spend_usd: built.did.spendUsd
|
|
2176
|
+
},
|
|
2177
|
+
halts: built.halts,
|
|
2178
|
+
top_tools: built.topTools,
|
|
2179
|
+
markdown
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2183
|
+
const verify = defineTool({
|
|
2184
|
+
name: "agentguard_verify_audit_log",
|
|
2185
|
+
title: "Verify agentguard audit log",
|
|
2186
|
+
description: "Recompute the hash chain of agentguard's audit log (JSONL, one entry per tool call) and say whether any entry was edited, removed from the middle or reordered, naming the first broken entry. Returns the entry count and head hash; record both elsewhere, because entries cut from the end of the file still verify. Use it before trusting agentguard_get_report. Takes no arguments: it checks the log the loaded policy names. Read-only; a log that does not exist yet verifies as empty.",
|
|
2187
|
+
input: z5.strictObject({}),
|
|
2188
|
+
output: VerifyOutput,
|
|
2189
|
+
annotations: { title: "Verify agentguard audit log", ...READ_ONLY },
|
|
2190
|
+
async handler() {
|
|
2191
|
+
const { loaded } = runtime;
|
|
2192
|
+
const audit = readAudit(loaded.auditPath);
|
|
2193
|
+
if (audit.error) {
|
|
2194
|
+
return {
|
|
2195
|
+
configured: runtime.configured,
|
|
2196
|
+
path: loaded.auditPath,
|
|
2197
|
+
exists: true,
|
|
2198
|
+
ok: false,
|
|
2199
|
+
entries: 0,
|
|
2200
|
+
head_hash: null,
|
|
2201
|
+
broken_at: null,
|
|
2202
|
+
reason: audit.error
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
const result = await verifyChain(audit.entries);
|
|
2206
|
+
return {
|
|
2207
|
+
configured: runtime.configured,
|
|
2208
|
+
path: loaded.auditPath,
|
|
2209
|
+
exists: audit.exists,
|
|
2210
|
+
ok: result.ok,
|
|
2211
|
+
entries: result.entries,
|
|
2212
|
+
// an empty chain's head is the genesis hash, which is not an entry anyone can record
|
|
2213
|
+
head_hash: result.entries > 0 ? result.head ?? null : null,
|
|
2214
|
+
broken_at: result.brokenAt ?? null,
|
|
2215
|
+
reason: result.reason ?? null
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
});
|
|
2219
|
+
return [status, report, verify];
|
|
2220
|
+
}
|
|
2221
|
+
function ownToolsActive(runtime) {
|
|
2222
|
+
return runtime.upstreamStatus().length === 0;
|
|
2223
|
+
}
|
|
2224
|
+
function ownToolList(tools) {
|
|
2225
|
+
return listToolManifest(tools);
|
|
2226
|
+
}
|
|
2227
|
+
async function callOwnTool(tools, name, args, requestId) {
|
|
2228
|
+
const tool = tools.find((t) => t.name === name);
|
|
2229
|
+
if (!tool) return void 0;
|
|
2230
|
+
return tool.invoke(args, createToolContext({ requestId }));
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
// src/proxy/server.ts
|
|
1958
2234
|
var PROXY_VERSION = version();
|
|
1959
2235
|
function headerValue(extra, name) {
|
|
1960
2236
|
const raw = extra.requestInfo?.headers?.[name] ?? extra.requestInfo?.headers?.[name.toLowerCase()];
|
|
@@ -2055,6 +2331,11 @@ function killedError(state) {
|
|
|
2055
2331
|
{ code: -32e3 }
|
|
2056
2332
|
);
|
|
2057
2333
|
}
|
|
2334
|
+
function serverInstructions(runtime) {
|
|
2335
|
+
if (ownToolsActive(runtime))
|
|
2336
|
+
return `agentguard is an MCP policy proxy, and this one fronts no upstream MCP servers${runtime.configured ? "" : ` (no policy file at ${runtime.loaded.path})`}, so it serves only its own read-only tools: agentguard_get_status (what it guards, caps, kill switch), agentguard_get_report (what a run did or would have done) and agentguard_verify_audit_log. To guard the user's MCP servers, run \`${INIT_COMMAND}\` in the project directory and restart the client.`;
|
|
2337
|
+
return `Tool calls pass through agentguard (${runtime.policy.mode} mode). Errors carry { code, cause, fix, retryable }; follow "fix". CAP_EXCEEDED, LOOP_DETECTED and KILLED are not retryable: stop and report to the user. APPROVAL_REQUIRED: relay the approval command to the user, then retry the identical call once.`;
|
|
2338
|
+
}
|
|
2058
2339
|
function createDownstreamServer(runtime, opts = {}) {
|
|
2059
2340
|
const server = new McpServer3(
|
|
2060
2341
|
{ name: "agentguard", version: PROXY_VERSION, title: "agentguard policy proxy" },
|
|
@@ -2066,9 +2347,10 @@ function createDownstreamServer(runtime, opts = {}) {
|
|
|
2066
2347
|
logging: {},
|
|
2067
2348
|
completions: {}
|
|
2068
2349
|
},
|
|
2069
|
-
instructions:
|
|
2350
|
+
instructions: serverInstructions(runtime)
|
|
2070
2351
|
}
|
|
2071
2352
|
);
|
|
2353
|
+
const ownTools = createOwnTools(runtime);
|
|
2072
2354
|
const session = {
|
|
2073
2355
|
id: crypto.randomUUID(),
|
|
2074
2356
|
server,
|
|
@@ -2106,12 +2388,22 @@ function createDownstreamServer(runtime, opts = {}) {
|
|
|
2106
2388
|
touch();
|
|
2107
2389
|
const { agent } = await agentFor(extra);
|
|
2108
2390
|
const scope = agent === void 0 ? runtime.agent : agent;
|
|
2391
|
+
if (ownToolsActive(runtime)) return { tools: ownToolList(ownTools) };
|
|
2109
2392
|
return {
|
|
2110
2393
|
tools: runtime.listTools(scope).map((t) => annotateTool(t, scope?.mode ?? runtime.policy.mode))
|
|
2111
2394
|
};
|
|
2112
2395
|
});
|
|
2113
2396
|
low.setRequestHandler(CallToolRequestSchema2, async (req, extra) => {
|
|
2114
2397
|
touch();
|
|
2398
|
+
if (ownToolsActive(runtime)) {
|
|
2399
|
+
const own = await callOwnTool(
|
|
2400
|
+
ownTools,
|
|
2401
|
+
req.params.name,
|
|
2402
|
+
req.params.arguments ?? {},
|
|
2403
|
+
String(extra.requestId)
|
|
2404
|
+
);
|
|
2405
|
+
if (own) return own;
|
|
2406
|
+
}
|
|
2115
2407
|
const { agent, error } = await agentFor(extra);
|
|
2116
2408
|
const runId = runIdFor(extra, req.params._meta, opts.defaultRunId ?? runtime.defaultRunId);
|
|
2117
2409
|
if (error) {
|
|
@@ -2378,7 +2670,7 @@ async function startHttpProxy(opts) {
|
|
|
2378
2670
|
port,
|
|
2379
2671
|
token,
|
|
2380
2672
|
close: () => new Promise((resolve5, reject) => {
|
|
2381
|
-
if (opts.registerFile !== false &&
|
|
2673
|
+
if (opts.registerFile !== false && existsSync2(regFile)) {
|
|
2382
2674
|
try {
|
|
2383
2675
|
unlinkSync(regFile);
|
|
2384
2676
|
} catch {
|
|
@@ -2674,7 +2966,7 @@ function readRegistration(loaded) {
|
|
|
2674
2966
|
const file = registrationPath(loaded.stateDir);
|
|
2675
2967
|
if (!exists(file)) return void 0;
|
|
2676
2968
|
try {
|
|
2677
|
-
const reg = JSON.parse(
|
|
2969
|
+
const reg = JSON.parse(readFileSync3(file, "utf8"));
|
|
2678
2970
|
return reg;
|
|
2679
2971
|
} catch {
|
|
2680
2972
|
return void 0;
|
|
@@ -2690,11 +2982,11 @@ function describeError(err) {
|
|
|
2690
2982
|
}
|
|
2691
2983
|
|
|
2692
2984
|
// src/commands/connect.ts
|
|
2693
|
-
import { existsSync as
|
|
2985
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2694
2986
|
import { relative } from "path";
|
|
2695
2987
|
|
|
2696
2988
|
// src/configs.ts
|
|
2697
|
-
import { copyFileSync, existsSync as
|
|
2989
|
+
import { copyFileSync, existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
2698
2990
|
import { homedir as homedir2, platform } from "os";
|
|
2699
2991
|
import { join as join2, resolve as resolve2 } from "path";
|
|
2700
2992
|
function candidateConfigs(cwd = process.cwd(), home = homedir2(), env = process.env) {
|
|
@@ -2727,6 +3019,12 @@ function candidateConfigs(cwd = process.cwd(), home = homedir2(), env = process.
|
|
|
2727
3019
|
serversKey: "servers"
|
|
2728
3020
|
},
|
|
2729
3021
|
{ kind: "generic", label: "mcp.json", path: join2(cwd, "mcp.json"), serversKey: "mcpServers" },
|
|
3022
|
+
{
|
|
3023
|
+
kind: "gemini-project",
|
|
3024
|
+
label: "Gemini CLI (project .gemini/settings.json)",
|
|
3025
|
+
path: join2(cwd, ".gemini", "settings.json"),
|
|
3026
|
+
serversKey: "mcpServers"
|
|
3027
|
+
},
|
|
2730
3028
|
{ kind: "claude-desktop", label: "Claude Desktop", path: desktop, serversKey: "mcpServers" },
|
|
2731
3029
|
{
|
|
2732
3030
|
kind: "cursor-user",
|
|
@@ -2745,11 +3043,20 @@ function candidateConfigs(cwd = process.cwd(), home = homedir2(), env = process.
|
|
|
2745
3043
|
label: "Claude Code (user ~/.claude.json)",
|
|
2746
3044
|
path: join2(home, ".claude.json"),
|
|
2747
3045
|
serversKey: "mcpServers"
|
|
3046
|
+
},
|
|
3047
|
+
{
|
|
3048
|
+
kind: "gemini-user",
|
|
3049
|
+
label: "Gemini CLI (user ~/.gemini/settings.json)",
|
|
3050
|
+
path: join2(home, ".gemini", "settings.json"),
|
|
3051
|
+
serversKey: "mcpServers"
|
|
2748
3052
|
}
|
|
2749
3053
|
];
|
|
2750
3054
|
}
|
|
3055
|
+
function isGeminiConfig(file) {
|
|
3056
|
+
return file.kind === "gemini-project" || file.kind === "gemini-user" || /[/\\]\.gemini[/\\]settings\.json$/.test(file.path);
|
|
3057
|
+
}
|
|
2751
3058
|
function detectClientConfigs(cwd, home, env) {
|
|
2752
|
-
return candidateConfigs(cwd, home, env).filter((c) =>
|
|
3059
|
+
return candidateConfigs(cwd, home, env).filter((c) => existsSync4(c.path));
|
|
2753
3060
|
}
|
|
2754
3061
|
function describeConfigPath(path) {
|
|
2755
3062
|
const abs = resolve2(path);
|
|
@@ -2757,7 +3064,7 @@ function describeConfigPath(path) {
|
|
|
2757
3064
|
if (known) return known;
|
|
2758
3065
|
let serversKey = "mcpServers";
|
|
2759
3066
|
try {
|
|
2760
|
-
const parsed = JSON.parse(
|
|
3067
|
+
const parsed = JSON.parse(readFileSync4(abs, "utf8"));
|
|
2761
3068
|
if (parsed.servers && !parsed.mcpServers) serversKey = "servers";
|
|
2762
3069
|
} catch {
|
|
2763
3070
|
}
|
|
@@ -2769,7 +3076,7 @@ function describeConfigPath(path) {
|
|
|
2769
3076
|
};
|
|
2770
3077
|
}
|
|
2771
3078
|
function readServers(file) {
|
|
2772
|
-
const raw = JSON.parse(
|
|
3079
|
+
const raw = JSON.parse(readFileSync4(file.path, "utf8"));
|
|
2773
3080
|
const servers = raw[file.serversKey];
|
|
2774
3081
|
if (!servers || typeof servers !== "object") return {};
|
|
2775
3082
|
return servers;
|
|
@@ -2788,10 +3095,11 @@ function serversToUpstreams(servers) {
|
|
|
2788
3095
|
continue;
|
|
2789
3096
|
}
|
|
2790
3097
|
const name = rawName.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/^[^a-zA-Z0-9]+/, "") || "upstream";
|
|
2791
|
-
|
|
3098
|
+
const url = typeof entry.httpUrl === "string" ? entry.httpUrl : entry.url;
|
|
3099
|
+
if (url) {
|
|
2792
3100
|
upstreams.push({
|
|
2793
3101
|
name,
|
|
2794
|
-
url
|
|
3102
|
+
url,
|
|
2795
3103
|
headers: entry.headers ?? {},
|
|
2796
3104
|
args: [],
|
|
2797
3105
|
env: {},
|
|
@@ -2826,27 +3134,27 @@ function proxyEntry(file, policyPath, opts = {}) {
|
|
|
2826
3134
|
return entry;
|
|
2827
3135
|
}
|
|
2828
3136
|
function rewriteClientConfig(file, policyPath, opts = {}) {
|
|
2829
|
-
const raw = JSON.parse(
|
|
3137
|
+
const raw = JSON.parse(readFileSync4(file.path, "utf8"));
|
|
2830
3138
|
const servers = raw[file.serversKey] ?? {};
|
|
2831
3139
|
const replaced = Object.keys(servers).filter((n) => !isProxyEntry(servers[n]));
|
|
2832
3140
|
const backup = backupPath(file);
|
|
2833
|
-
if (!
|
|
3141
|
+
if (!existsSync4(backup)) copyFileSync(file.path, backup);
|
|
2834
3142
|
raw[file.serversKey] = { [PROXY_SERVER_NAME]: proxyEntry(file, policyPath, opts) };
|
|
2835
3143
|
writeFileSync2(file.path, JSON.stringify(raw, null, 2) + "\n");
|
|
2836
3144
|
return { backup, replaced };
|
|
2837
3145
|
}
|
|
2838
3146
|
function restoreClientConfig(file) {
|
|
2839
3147
|
const backup = backupPath(file);
|
|
2840
|
-
if (!
|
|
3148
|
+
if (!existsSync4(backup)) return false;
|
|
2841
3149
|
copyFileSync(backup, file.path);
|
|
2842
3150
|
return true;
|
|
2843
3151
|
}
|
|
2844
3152
|
function mergeServerIntoConfig(file, name, entry) {
|
|
2845
|
-
const raw = JSON.parse(
|
|
3153
|
+
const raw = JSON.parse(readFileSync4(file.path, "utf8"));
|
|
2846
3154
|
const servers = raw[file.serversKey] ?? {};
|
|
2847
3155
|
const replaced = name in servers;
|
|
2848
3156
|
const backup = backupPath(file);
|
|
2849
|
-
if (!
|
|
3157
|
+
if (!existsSync4(backup)) copyFileSync(file.path, backup);
|
|
2850
3158
|
raw[file.serversKey] = { ...servers, [name]: entry };
|
|
2851
3159
|
writeFileSync2(file.path, JSON.stringify(raw, null, 2) + "\n");
|
|
2852
3160
|
return { backup, replaced };
|
|
@@ -2929,11 +3237,12 @@ async function connectCommand(args, io, fetchImpl = fetch) {
|
|
|
2929
3237
|
return 1;
|
|
2930
3238
|
}
|
|
2931
3239
|
for (const c of chosen) {
|
|
2932
|
-
if (!
|
|
3240
|
+
if (!existsSync5(c.path)) {
|
|
2933
3241
|
io.err(`no MCP config at ${c.path}`);
|
|
2934
3242
|
return 1;
|
|
2935
3243
|
}
|
|
2936
|
-
const {
|
|
3244
|
+
const forClient = isGeminiConfig(c) ? { httpUrl: entry.url, ...entry.headers ? { headers: entry.headers } : {} } : entry;
|
|
3245
|
+
const { backup, replaced } = mergeServerIntoConfig(c, PROXY_SERVER_NAME, forClient);
|
|
2937
3246
|
io.out(
|
|
2938
3247
|
`${replaced ? "updated" : "wrote "} ${rel(c.path)} (${c.label}) \u2192 ${PROXY_SERVER_NAME} = ${conn.mcpUrl}`
|
|
2939
3248
|
);
|
|
@@ -3048,7 +3357,7 @@ async function approvalsCommand(args, io) {
|
|
|
3048
3357
|
}
|
|
3049
3358
|
|
|
3050
3359
|
// src/commands/init.ts
|
|
3051
|
-
import { existsSync as
|
|
3360
|
+
import { existsSync as existsSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
3052
3361
|
import { relative as relative2, resolve as resolve3 } from "path";
|
|
3053
3362
|
import YAML from "yaml";
|
|
3054
3363
|
|
|
@@ -3085,6 +3394,7 @@ var ProxyRuntime = class {
|
|
|
3085
3394
|
guard;
|
|
3086
3395
|
agent;
|
|
3087
3396
|
defaultRunId;
|
|
3397
|
+
configured;
|
|
3088
3398
|
sessions = /* @__PURE__ */ new Set();
|
|
3089
3399
|
connections = /* @__PURE__ */ new Map();
|
|
3090
3400
|
exposed = [];
|
|
@@ -3101,6 +3411,7 @@ var ProxyRuntime = class {
|
|
|
3101
3411
|
this.guard = opts.guard;
|
|
3102
3412
|
this.agent = opts.agent;
|
|
3103
3413
|
this.defaultRunId = opts.defaultRunId;
|
|
3414
|
+
this.configured = opts.configured ?? true;
|
|
3104
3415
|
this.log = opts.log ?? ((line) => process.stderr.write(`[agentguard] ${line}
|
|
3105
3416
|
`));
|
|
3106
3417
|
this.connectTimeoutMs = opts.connectTimeoutMs ?? 15e3;
|
|
@@ -3645,6 +3956,10 @@ async function toolsCommand(args, io) {
|
|
|
3645
3956
|
io.err(
|
|
3646
3957
|
`${u.connected ? "ok " : "down"} ${u.name}: ${u.connected ? `${u.tools} tools` : u.error}`
|
|
3647
3958
|
);
|
|
3959
|
+
if (result.upstreams.length === 0)
|
|
3960
|
+
io.err(
|
|
3961
|
+
"no upstreams in the policy: an MCP client sees agentguard's own read-only tools instead (agentguard_get_status, agentguard_get_report, agentguard_verify_audit_log)"
|
|
3962
|
+
);
|
|
3648
3963
|
for (const line of formatToolTable(result.tools)) io.out(line);
|
|
3649
3964
|
const counts = result.tools.reduce(
|
|
3650
3965
|
(acc, t) => ({ ...acc, [t.classification.class]: (acc[t.classification.class] ?? 0) + 1 }),
|
|
@@ -3812,7 +4127,7 @@ async function initCommand(args, io) {
|
|
|
3812
4127
|
`found ${c.path} (${c.label}, user-level) \u2014 not touched; pass --client ${JSON.stringify(c.path)} to route it through agentguard`
|
|
3813
4128
|
);
|
|
3814
4129
|
}
|
|
3815
|
-
if (!chosen.every((c) =>
|
|
4130
|
+
if (!chosen.every((c) => existsSync6(c.path))) {
|
|
3816
4131
|
io.err(`no MCP config at ${chosen.map((c) => c.path).join(", ")}`);
|
|
3817
4132
|
return 1;
|
|
3818
4133
|
}
|
|
@@ -3827,11 +4142,11 @@ async function initCommand(args, io) {
|
|
|
3827
4142
|
}
|
|
3828
4143
|
if (chosen.length === 0)
|
|
3829
4144
|
io.out(
|
|
3830
|
-
"found no MCP config (Claude Code .mcp.json, Cursor, VS Code, Claude Desktop, Windsurf) \u2014 writing a policy with no upstreams"
|
|
4145
|
+
"found no MCP config (Claude Code .mcp.json, Cursor, VS Code, Claude Desktop, Windsurf, Gemini CLI) \u2014 writing a policy with no upstreams"
|
|
3831
4146
|
);
|
|
3832
4147
|
const { upstreams, skipped } = serversToUpstreams(servers);
|
|
3833
4148
|
for (const s of skipped) io.err(`skipped ${s.name}: ${s.reason}`);
|
|
3834
|
-
if (skipped.some((s) => s.reason.includes("already")) && upstreams.length === 0 &&
|
|
4149
|
+
if (skipped.some((s) => s.reason.includes("already")) && upstreams.length === 0 && existsSync6(policyPath)) {
|
|
3835
4150
|
io.out(
|
|
3836
4151
|
`already initialized: ${rel(policyPath)} exists and ${chosen.map((c) => rel(c.path)).join(", ")} already points at agentguard`
|
|
3837
4152
|
);
|
|
@@ -3858,7 +4173,7 @@ async function initCommand(args, io) {
|
|
|
3858
4173
|
}
|
|
3859
4174
|
}
|
|
3860
4175
|
const mode = flagString(args, "mode") ?? "dry-run";
|
|
3861
|
-
if (
|
|
4176
|
+
if (existsSync6(policyPath) && !flagBool(args, "force")) {
|
|
3862
4177
|
io.out(`kept ${rel(policyPath)} (exists; pass --force to regenerate)`);
|
|
3863
4178
|
} else {
|
|
3864
4179
|
writeFileSync3(policyPath, renderStarterPolicy({ upstreams, tools: probed, mode, probeErrors }));
|
|
@@ -3902,13 +4217,13 @@ async function initCommand(args, io) {
|
|
|
3902
4217
|
}
|
|
3903
4218
|
|
|
3904
4219
|
// src/commands/keys.ts
|
|
3905
|
-
import { readFileSync as
|
|
4220
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
3906
4221
|
import YAML2, { isMap, isSeq, YAMLSeq } from "yaml";
|
|
3907
4222
|
import { generateAgentKey, hashAgentKey } from "@agentwares/agentguard-core";
|
|
3908
4223
|
async function keyCommand(args, io) {
|
|
3909
4224
|
const [sub, name] = args.positionals;
|
|
3910
4225
|
const path = policyPathFrom(args, io);
|
|
3911
|
-
const doc = YAML2.parseDocument(
|
|
4226
|
+
const doc = YAML2.parseDocument(readFileSync5(path, "utf8"));
|
|
3912
4227
|
let agents = doc.get("agents");
|
|
3913
4228
|
if (!isSeq(agents)) {
|
|
3914
4229
|
agents = new YAMLSeq();
|
|
@@ -4075,14 +4390,65 @@ async function permissionDiffCommand(args, io) {
|
|
|
4075
4390
|
}
|
|
4076
4391
|
|
|
4077
4392
|
// src/commands/proxy.ts
|
|
4078
|
-
import {
|
|
4393
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4394
|
+
import { dirname } from "path";
|
|
4395
|
+
import { Guard as Guard3, defaultPolicy, resolveAgent as resolveAgent2 } from "@agentwares/agentguard-core";
|
|
4396
|
+
import { FileKillSwitch as FileKillSwitch3, resolvePath } from "@agentwares/agentguard-core/node";
|
|
4079
4397
|
function newRunId(now = /* @__PURE__ */ new Date()) {
|
|
4080
4398
|
return `run_${now.toISOString().replace(/[-:.TZ]/g, "").slice(0, 14)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
4081
4399
|
}
|
|
4400
|
+
function unconfiguredPolicy(args, io) {
|
|
4401
|
+
if (flagString(args, "config") !== void 0 || io.env.AGENTGUARD_CONFIG) return void 0;
|
|
4402
|
+
const path = policyPathFrom(args, io);
|
|
4403
|
+
if (existsSync7(path)) return void 0;
|
|
4404
|
+
const policy = defaultPolicy();
|
|
4405
|
+
const baseDir = dirname(path);
|
|
4406
|
+
return {
|
|
4407
|
+
policy,
|
|
4408
|
+
path,
|
|
4409
|
+
baseDir,
|
|
4410
|
+
stateDir: resolvePath(policy.state.dir, baseDir),
|
|
4411
|
+
auditPath: resolvePath(policy.audit.path, baseDir),
|
|
4412
|
+
killPath: resolvePath(policy.kill.file, baseDir)
|
|
4413
|
+
};
|
|
4414
|
+
}
|
|
4415
|
+
async function serveOverStdio(runtime) {
|
|
4416
|
+
const server = createDownstreamServer(runtime);
|
|
4417
|
+
const stop = async () => {
|
|
4418
|
+
await runtime.close();
|
|
4419
|
+
};
|
|
4420
|
+
process.once("SIGINT", () => void stop().then(() => process.exit(0)));
|
|
4421
|
+
process.once("SIGTERM", () => void stop().then(() => process.exit(0)));
|
|
4422
|
+
await serveStdio(server);
|
|
4423
|
+
await stop();
|
|
4424
|
+
return 0;
|
|
4425
|
+
}
|
|
4082
4426
|
async function proxyCommand(args, io) {
|
|
4083
|
-
const loaded = loadPolicyFor(args, io);
|
|
4084
4427
|
const log = (line) => io.err(`[agentguard] ${line}`);
|
|
4085
4428
|
const modeOverride = flagString(args, "mode");
|
|
4429
|
+
const http = flagBool(args, "http") === true;
|
|
4430
|
+
const unconfigured = http ? void 0 : unconfiguredPolicy(args, io);
|
|
4431
|
+
if (unconfigured) {
|
|
4432
|
+
if (modeOverride === "dry-run" || modeOverride === "enforce")
|
|
4433
|
+
unconfigured.policy.mode = modeOverride;
|
|
4434
|
+
const runtime2 = new ProxyRuntime({
|
|
4435
|
+
loaded: unconfigured,
|
|
4436
|
+
guard: new Guard3({
|
|
4437
|
+
policy: unconfigured.policy,
|
|
4438
|
+
env: io.env,
|
|
4439
|
+
kill: new FileKillSwitch3(unconfigured.killPath)
|
|
4440
|
+
}),
|
|
4441
|
+
configured: false,
|
|
4442
|
+
defaultRunId: flagString(args, "run-id") ?? newRunId(),
|
|
4443
|
+
log
|
|
4444
|
+
});
|
|
4445
|
+
await runtime2.start();
|
|
4446
|
+
log(
|
|
4447
|
+
`no policy file at ${unconfigured.path}: serving agentguard's own read-only tools only. To guard your MCP servers, run \`${INIT_COMMAND}\` in the project directory.`
|
|
4448
|
+
);
|
|
4449
|
+
return serveOverStdio(runtime2);
|
|
4450
|
+
}
|
|
4451
|
+
const loaded = loadPolicyFor(args, io);
|
|
4086
4452
|
if (modeOverride === "dry-run" || modeOverride === "enforce") loaded.policy.mode = modeOverride;
|
|
4087
4453
|
const agentName = flagString(args, "agent");
|
|
4088
4454
|
let agent = void 0;
|
|
@@ -4096,7 +4462,6 @@ async function proxyCommand(args, io) {
|
|
|
4096
4462
|
}
|
|
4097
4463
|
agent = resolved.agent;
|
|
4098
4464
|
}
|
|
4099
|
-
const http = flagBool(args, "http") === true;
|
|
4100
4465
|
let approvalBase;
|
|
4101
4466
|
const fileGuard = createFileGuard(loaded, {
|
|
4102
4467
|
log,
|
|
@@ -4122,59 +4487,55 @@ async function proxyCommand(args, io) {
|
|
|
4122
4487
|
log(
|
|
4123
4488
|
`kill switch is ON (${killed.reason}); every call returns KILLED until \`agentguard resume\``
|
|
4124
4489
|
);
|
|
4490
|
+
if (runtime.upstreamStatus().length === 0)
|
|
4491
|
+
log(
|
|
4492
|
+
`${loaded.path} lists no upstreams: serving agentguard's own read-only tools only. Add servers under \`upstreams:\` to guard them.`
|
|
4493
|
+
);
|
|
4125
4494
|
if (http) {
|
|
4126
|
-
const
|
|
4495
|
+
const server = await startHttpProxy({
|
|
4127
4496
|
runtime,
|
|
4128
4497
|
port: flagNumber(args, "port") ?? 8788,
|
|
4129
4498
|
host: flagString(args, "host") ?? "127.0.0.1",
|
|
4130
4499
|
token: io.env.AGENTGUARD_HTTP_TOKEN
|
|
4131
4500
|
});
|
|
4132
|
-
approvalBase = { baseUrl:
|
|
4501
|
+
approvalBase = { baseUrl: server.url.replace(/\/mcp$/, ""), token: server.token };
|
|
4133
4502
|
log(
|
|
4134
|
-
`Streamable HTTP at ${
|
|
4503
|
+
`Streamable HTTP at ${server.url} \xB7 GET ${approvalBase.baseUrl}/health \xB7 control token in ${loaded.stateDir}/http.json`
|
|
4135
4504
|
);
|
|
4136
4505
|
log(
|
|
4137
|
-
`point your agent at ${
|
|
4506
|
+
`point your agent at ${server.url} (send X-Run-Id per run; Authorization: Bearer <agent key> for scoped agents)`
|
|
4138
4507
|
);
|
|
4139
|
-
const
|
|
4140
|
-
await
|
|
4508
|
+
const stop = async () => {
|
|
4509
|
+
await server.close();
|
|
4141
4510
|
await runtime.close();
|
|
4142
4511
|
process.exit(0);
|
|
4143
4512
|
};
|
|
4144
|
-
process.once("SIGINT", () => void
|
|
4145
|
-
process.once("SIGTERM", () => void
|
|
4513
|
+
process.once("SIGINT", () => void stop());
|
|
4514
|
+
process.once("SIGTERM", () => void stop());
|
|
4146
4515
|
await new Promise(() => void 0);
|
|
4147
4516
|
}
|
|
4148
|
-
|
|
4149
|
-
const stop = async () => {
|
|
4150
|
-
await runtime.close();
|
|
4151
|
-
};
|
|
4152
|
-
process.once("SIGINT", () => void stop().then(() => process.exit(0)));
|
|
4153
|
-
process.once("SIGTERM", () => void stop().then(() => process.exit(0)));
|
|
4154
|
-
await serveStdio(server);
|
|
4155
|
-
await stop();
|
|
4156
|
-
return 0;
|
|
4517
|
+
return serveOverStdio(runtime);
|
|
4157
4518
|
}
|
|
4158
4519
|
|
|
4159
4520
|
// src/commands/report.ts
|
|
4160
4521
|
import { resolve as resolve4 } from "path";
|
|
4161
4522
|
import {
|
|
4162
|
-
buildReport,
|
|
4163
|
-
latestRunId,
|
|
4164
|
-
listRuns,
|
|
4523
|
+
buildReport as buildReport2,
|
|
4524
|
+
latestRunId as latestRunId2,
|
|
4525
|
+
listRuns as listRuns2,
|
|
4165
4526
|
renderMutationDiff,
|
|
4166
|
-
renderReportMarkdown
|
|
4527
|
+
renderReportMarkdown as renderReportMarkdown2
|
|
4167
4528
|
} from "@agentwares/agentguard-core";
|
|
4168
4529
|
import { readAuditFile, verifyAuditFile } from "@agentwares/agentguard-core/node";
|
|
4169
4530
|
function pickRun(args, entries) {
|
|
4170
4531
|
if (flagBool(args, "all")) return void 0;
|
|
4171
|
-
return flagString(args, "run") ??
|
|
4532
|
+
return flagString(args, "run") ?? latestRunId2(entries);
|
|
4172
4533
|
}
|
|
4173
4534
|
async function reportCommand(args, io) {
|
|
4174
4535
|
const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
|
|
4175
4536
|
const entries = readAuditFile(loaded.auditPath);
|
|
4176
4537
|
const runId = pickRun(args, entries);
|
|
4177
|
-
const report =
|
|
4538
|
+
const report = buildReport2(entries, { runId });
|
|
4178
4539
|
if (flagBool(args, "json")) {
|
|
4179
4540
|
io.out(
|
|
4180
4541
|
JSON.stringify(
|
|
@@ -4193,12 +4554,12 @@ async function reportCommand(args, io) {
|
|
|
4193
4554
|
);
|
|
4194
4555
|
return 0;
|
|
4195
4556
|
}
|
|
4196
|
-
io.out(
|
|
4557
|
+
io.out(renderReportMarkdown2(report));
|
|
4197
4558
|
const chain = await verifyAuditFile(loaded.auditPath);
|
|
4198
4559
|
io.out(
|
|
4199
4560
|
chain.ok ? `Audit chain: ${chain.entries} entries, verified (${loaded.auditPath}).` : `Audit chain BROKEN at entry ${chain.brokenAt}: ${chain.reason}`
|
|
4200
4561
|
);
|
|
4201
|
-
if (runId &&
|
|
4562
|
+
if (runId && listRuns2(entries).length > 1) io.out(`Other runs: agentguard report --all`);
|
|
4202
4563
|
return 0;
|
|
4203
4564
|
}
|
|
4204
4565
|
async function diffCommand(args, io) {
|
|
@@ -4225,7 +4586,7 @@ async function statusCommand(args, io) {
|
|
|
4225
4586
|
const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
|
|
4226
4587
|
const { guard, kill } = createFileGuard(loaded, { alerts: false, env: io.env });
|
|
4227
4588
|
const entries = readAuditFile(loaded.auditPath);
|
|
4228
|
-
const runId = flagString(args, "run") ??
|
|
4589
|
+
const runId = flagString(args, "run") ?? latestRunId2(entries) ?? "(none yet)";
|
|
4229
4590
|
const status = await guard.status(runId);
|
|
4230
4591
|
const killed = kill.check();
|
|
4231
4592
|
const reg = readRegistration(loaded);
|