@mrciphersmith/keryx 0.3.33 → 0.3.35
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 +4 -1
- package/dist/cli.js +140 -29
- package/dist/core.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -740,7 +740,10 @@ advertised surface stays a fixed cost instead of growing with your server
|
|
|
740
740
|
list. Every call goes through the same approval prompt as `shell_exec`, a
|
|
741
741
|
server is spawned without keryx's own credentials in its environment, and
|
|
742
742
|
`--scope project` writes a `.keryx/mcp-servers.json` you can commit while
|
|
743
|
-
`keryx mcp disable` stays personal to you.
|
|
743
|
+
`keryx mcp disable` stays personal to you. In `--trust` mode you can trust one
|
|
744
|
+
exact tool for the session (never one its server marks `destructiveHint`);
|
|
745
|
+
`/mcp trust list` and `/mcp trust revoke <server__tool>|all` show and remove
|
|
746
|
+
those grants.
|
|
744
747
|
|
|
745
748
|
Remote servers work the same way — `keryx mcp add linear --transport http
|
|
746
749
|
<url> --header 'Authorization: Bearer ${LINEAR_TOKEN}'` — and if that
|
package/dist/cli.js
CHANGED
|
@@ -13447,7 +13447,7 @@ var package_default;
|
|
|
13447
13447
|
var init_package = __esm(() => {
|
|
13448
13448
|
package_default = {
|
|
13449
13449
|
name: "@mrciphersmith/keryx",
|
|
13450
|
-
version: "0.3.
|
|
13450
|
+
version: "0.3.35",
|
|
13451
13451
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
13452
13452
|
private: false,
|
|
13453
13453
|
publishConfig: {
|
|
@@ -71950,7 +71950,7 @@ ${shown.join(`
|
|
|
71950
71950
|
result = precomputedResult;
|
|
71951
71951
|
} else {
|
|
71952
71952
|
try {
|
|
71953
|
-
result = await executeCall(call, toolByName, io.requestApproval, io.permissionMode, io.readOnly, io.onAutoApproved, hasInvocationCapacity, reserveInvocation, invocationBudget.maxCalls, signal, deps.busLeases, deps.hardDeny === undefined ? undefined : { check: deps.hardDeny, onDenied: io.onUnattendedDenial }, deps.hooks, untrustedOrigin, io.trustedMcpTools, io.mcpToolFingerprint);
|
|
71953
|
+
result = await executeCall(call, toolByName, io.requestApproval, io.permissionMode, io.readOnly, io.onAutoApproved, hasInvocationCapacity, reserveInvocation, invocationBudget.maxCalls, signal, deps.busLeases, deps.hardDeny === undefined ? undefined : { check: deps.hardDeny, onDenied: io.onUnattendedDenial }, deps.hooks, untrustedOrigin, io.trustedMcpTools, io.mcpToolFingerprint, io.mcpToolDestructive);
|
|
71954
71954
|
} catch (err) {
|
|
71955
71955
|
const message = err instanceof Error ? err.message : String(err);
|
|
71956
71956
|
result = { output: `${call.name} failed: ${message}`, isError: true };
|
|
@@ -72520,7 +72520,7 @@ function composeWithHook(toolName, rawDecision, hookResult, interactive) {
|
|
|
72520
72520
|
}
|
|
72521
72521
|
return { decision, hookTightened: true, hookAsked, ...denyMessage !== undefined ? { denyMessage } : {} };
|
|
72522
72522
|
}
|
|
72523
|
-
async function executeCall(call, toolByName, requestApproval, permissionMode, readOnly, onAutoApproved, hasInvocationCapacity, reserveInvocation, maxToolCalls, signal, busLeases, hardDeny, hooks, untrustedOrigin = false, trustedMcpTools, mcpToolFingerprint) {
|
|
72523
|
+
async function executeCall(call, toolByName, requestApproval, permissionMode, readOnly, onAutoApproved, hasInvocationCapacity, reserveInvocation, maxToolCalls, signal, busLeases, hardDeny, hooks, untrustedOrigin = false, trustedMcpTools, mcpToolFingerprint, mcpToolDestructive) {
|
|
72524
72524
|
const tool = toolByName.get(call.name);
|
|
72525
72525
|
if (tool === undefined) {
|
|
72526
72526
|
return { output: `unknown tool: ${call.name}`, isError: true };
|
|
@@ -72583,11 +72583,14 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
72583
72583
|
const gated = composeWithHook(call.name, rawDecision, hookResult, hookInteractive);
|
|
72584
72584
|
const mcpFqn = call.name === "use_tool" && typeof input.tool_name === "string" ? input.tool_name : undefined;
|
|
72585
72585
|
const currentMcpFingerprint = mcpFqn !== undefined ? mcpToolFingerprint?.(mcpFqn) : undefined;
|
|
72586
|
-
|
|
72586
|
+
const mcpDestructive = mcpFqn !== undefined && mcpToolDestructive?.(mcpFqn) === true;
|
|
72587
|
+
if (mcpFqn !== undefined && trustedMcpTools?.has(mcpFqn) === true && (mcpDestructive || trustedMcpTools.get(mcpFqn) !== currentMcpFingerprint)) {
|
|
72587
72588
|
trustedMcpTools.delete(mcpFqn);
|
|
72588
72589
|
}
|
|
72589
|
-
const
|
|
72590
|
-
const
|
|
72590
|
+
const mcpGranted = mcpFqn !== undefined && trustedMcpTools?.has(mcpFqn) === true;
|
|
72591
|
+
const trustedMcp = mode === "trust" && mcpGranted;
|
|
72592
|
+
const mcpTrustPossible = mcpFqn !== undefined && mode === "trust" && !gated.hookAsked && currentMcpFingerprint !== undefined;
|
|
72593
|
+
const mcpTrustEligible = mcpTrustPossible && !mcpDestructive;
|
|
72591
72594
|
if (gated.decision === "deny") {
|
|
72592
72595
|
return {
|
|
72593
72596
|
output: gated.hookTightened ? gated.denyMessage : `tool "${call.name}" is not permitted while read-only mode (/plan) is on`,
|
|
@@ -72595,7 +72598,7 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
72595
72598
|
};
|
|
72596
72599
|
}
|
|
72597
72600
|
if (gated.decision === "auto" && !untrustedOrigin || trustedMcp && !untrustedOrigin && !gated.hookAsked && !isReadOnly) {
|
|
72598
|
-
onAutoApproved?.(call.name, call.input, { destructive, credentials });
|
|
72601
|
+
onAutoApproved?.(call.name, call.input, { destructive, credentials, ...trustedMcp ? { mcpTrusted: true } : {} });
|
|
72599
72602
|
} else {
|
|
72600
72603
|
const fingerprint = toolCallHash(call.name, call.input);
|
|
72601
72604
|
const response = requestApproval === undefined ? false : await requestApproval(call.name, call.input, {
|
|
@@ -72606,7 +72609,8 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, re
|
|
|
72606
72609
|
...publishLease && publishLeaseHolder !== undefined ? { publishLeaseDetail: `held by @${publishLeaseHolder.name} \u2014 "${publishLeaseHolder.reason}"` } : {},
|
|
72607
72610
|
...gated.hookAsked ? { hookAsk: true } : {},
|
|
72608
72611
|
...untrustedOrigin ? { untrustedOrigin: true } : {},
|
|
72609
|
-
...
|
|
72612
|
+
...mcpTrustPossible ? mcpDestructive ? { mcpTrustWithheld: true, mcpTrustWithheldReason: "destructive" } : untrustedOrigin ? { mcpTrustWithheld: true, mcpTrustWithheldReason: "untrusted-origin" } : { mcpTrustAvailable: true } : {},
|
|
72613
|
+
...mcpGranted ? { mcpTrusted: true } : {}
|
|
72610
72614
|
});
|
|
72611
72615
|
if (!isApprovalFor(response, fingerprint)) {
|
|
72612
72616
|
return { output: untrustedOrigin ? untrustedDenial : "command not approved by the user; not executed", isError: true };
|
|
@@ -81625,7 +81629,7 @@ function renderUseToolApprovalLines(description, meta) {
|
|
|
81625
81629
|
const lines = [
|
|
81626
81630
|
description.title,
|
|
81627
81631
|
` server: ${description.server}`,
|
|
81628
|
-
` tool: ${description.tool}`,
|
|
81632
|
+
` tool: ${description.tool}${meta?.mcpTrusted === true ? ` ${TRUSTED_MARKER}` : ""}`,
|
|
81629
81633
|
...description.argumentLines.map((line) => ` ${line}`)
|
|
81630
81634
|
];
|
|
81631
81635
|
if (description.argumentsTruncated) {
|
|
@@ -81637,19 +81641,25 @@ function renderUseToolApprovalLines(description, meta) {
|
|
|
81637
81641
|
for (const notice of untrustedOriginNotices(meta)) {
|
|
81638
81642
|
lines.push(` ${notice}`);
|
|
81639
81643
|
}
|
|
81644
|
+
for (const notice of destructiveTrustNotices(meta)) {
|
|
81645
|
+
lines.push(` ${notice}`);
|
|
81646
|
+
}
|
|
81640
81647
|
return lines;
|
|
81641
81648
|
}
|
|
81642
81649
|
function untrustedOriginNotices(meta) {
|
|
81643
81650
|
if (meta?.untrustedOrigin !== true)
|
|
81644
81651
|
return [];
|
|
81645
81652
|
const notices = ["\u26A0 follows untrusted external content \u2014 that content cannot authorize this call"];
|
|
81646
|
-
if (meta.mcpTrustWithheld === true) {
|
|
81653
|
+
if (meta.mcpTrustWithheld === true && meta.mcpTrustWithheldReason !== "destructive") {
|
|
81647
81654
|
notices.push("trust for this session is not offered: this turn contains external content");
|
|
81648
81655
|
}
|
|
81649
81656
|
return notices;
|
|
81650
81657
|
}
|
|
81651
|
-
function
|
|
81652
|
-
return meta?.
|
|
81658
|
+
function destructiveTrustNotices(meta) {
|
|
81659
|
+
return meta?.mcpTrustWithheld === true && meta.mcpTrustWithheldReason === "destructive" ? ["trust for this session is not offered: this tool is marked destructive (destructiveHint) by its server"] : [];
|
|
81660
|
+
}
|
|
81661
|
+
function mcpTrustOffered(meta, resolvable, destructive = false) {
|
|
81662
|
+
return meta?.mcpTrustAvailable === true && meta.untrustedOrigin !== true && meta.mcpTrustWithheldReason !== "destructive" && !destructive && meta.fingerprint !== undefined && resolvable;
|
|
81653
81663
|
}
|
|
81654
81664
|
function summariseUseToolApproval(description) {
|
|
81655
81665
|
return `${description.tool} on ${description.server}`;
|
|
@@ -81660,10 +81670,10 @@ function isMcpToolCall(tool) {
|
|
|
81660
81670
|
function isApprovalYes(answer) {
|
|
81661
81671
|
return /^y(es)?$/i.test(answer.trim());
|
|
81662
81672
|
}
|
|
81663
|
-
async function promptUseToolApproval(io, input, meta, resolve, style, gutter = "") {
|
|
81673
|
+
async function promptUseToolApproval(io, input, meta, resolve, style, gutter = "", destructive) {
|
|
81664
81674
|
const paint = style ?? { yellow: (t) => t, dim: (t) => t, green: (t) => t, red: (t) => t };
|
|
81665
81675
|
const described = describeUseToolApproval(input, resolve);
|
|
81666
|
-
const canTrust = mcpTrustOffered(meta, resolve?.(described.fqn) !== undefined);
|
|
81676
|
+
const canTrust = mcpTrustOffered(meta, resolve?.(described.fqn) !== undefined, destructive?.(described.fqn) === true);
|
|
81667
81677
|
const lines = renderUseToolApprovalLines(described, meta);
|
|
81668
81678
|
io.out(`
|
|
81669
81679
|
`);
|
|
@@ -81709,7 +81719,70 @@ function catalogFingerprintResolver(catalog) {
|
|
|
81709
81719
|
return entry === undefined ? undefined : toolDefinitionFingerprint(entry);
|
|
81710
81720
|
};
|
|
81711
81721
|
}
|
|
81712
|
-
|
|
81722
|
+
function catalogDestructiveResolver(catalog) {
|
|
81723
|
+
return (fqn) => catalog?.entries.find((candidate) => candidate.fqn === fqn)?.annotations?.destructiveHint === true;
|
|
81724
|
+
}
|
|
81725
|
+
function parseMcpTrustCommand(line) {
|
|
81726
|
+
const parts = line.trim().split(/\s+/);
|
|
81727
|
+
if (parts[0] !== "/mcp" || parts[1] !== "trust")
|
|
81728
|
+
return;
|
|
81729
|
+
const sub = parts[2];
|
|
81730
|
+
const rest = parts.slice(3);
|
|
81731
|
+
if (sub === "list") {
|
|
81732
|
+
return rest.length === 0 ? { kind: "list" } : { kind: "usage", reason: "`/mcp trust list` takes no argument" };
|
|
81733
|
+
}
|
|
81734
|
+
if (sub === "revoke") {
|
|
81735
|
+
if (rest.length === 0)
|
|
81736
|
+
return { kind: "usage", reason: "`/mcp trust revoke` needs a tool name or `all`" };
|
|
81737
|
+
if (rest.length > 1)
|
|
81738
|
+
return { kind: "usage", reason: "`/mcp trust revoke` takes ONE tool name or `all`" };
|
|
81739
|
+
const target = rest[0] ?? "";
|
|
81740
|
+
return target === "all" ? { kind: "revoke-all" } : { kind: "revoke", target };
|
|
81741
|
+
}
|
|
81742
|
+
return { kind: "usage", reason: sub === undefined ? "`/mcp trust` needs a subcommand" : `unknown subcommand \`${sanitiseIdentifier(sub, 40)}\`` };
|
|
81743
|
+
}
|
|
81744
|
+
function catalogTrustStaleness(catalog) {
|
|
81745
|
+
return (fqn, granted) => {
|
|
81746
|
+
const entry = catalog?.entries.find((candidate) => candidate.fqn === fqn);
|
|
81747
|
+
if (entry === undefined)
|
|
81748
|
+
return "gone";
|
|
81749
|
+
if (entry.annotations?.destructiveHint === true)
|
|
81750
|
+
return "destructive";
|
|
81751
|
+
return toolDefinitionFingerprint(entry) === granted ? undefined : "changed";
|
|
81752
|
+
};
|
|
81753
|
+
}
|
|
81754
|
+
function renderTrustedToolLines(trusted, resolve, staleness) {
|
|
81755
|
+
if (trusted === undefined || trusted.size === 0)
|
|
81756
|
+
return ["No MCP tools are trusted in this session."];
|
|
81757
|
+
const lines = [`Trusted MCP tools this session (${trusted.size}):`];
|
|
81758
|
+
for (const [fqn, granted] of [...trusted.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
81759
|
+
const { server } = resolve?.(fqn) ?? splitFqn(fqn);
|
|
81760
|
+
const stale = staleness?.(fqn, granted);
|
|
81761
|
+
const tail = stale === undefined ? TRUSTED_MARKER : `(will ask again: ${stale})`;
|
|
81762
|
+
lines.push(` ${sanitiseIdentifier(fqn)} server: ${sanitiseIdentifier(server)} ${tail}`);
|
|
81763
|
+
}
|
|
81764
|
+
return lines;
|
|
81765
|
+
}
|
|
81766
|
+
function runMcpTrustCommand(command, trusted, resolve, staleness) {
|
|
81767
|
+
switch (command.kind) {
|
|
81768
|
+
case "usage":
|
|
81769
|
+
return [`${command.reason}. Usage: ${MCP_TRUST_USAGE}`];
|
|
81770
|
+
case "list":
|
|
81771
|
+
return renderTrustedToolLines(trusted, resolve, staleness);
|
|
81772
|
+
case "revoke-all": {
|
|
81773
|
+
const count = trusted?.size ?? 0;
|
|
81774
|
+
trusted?.clear();
|
|
81775
|
+
return [count === 0 ? "No MCP tools were trusted; nothing to revoke." : `Revoked trust for ${count} MCP tool(s); each asks again on its next call.`];
|
|
81776
|
+
}
|
|
81777
|
+
case "revoke": {
|
|
81778
|
+
if (trusted?.delete(command.target) === true) {
|
|
81779
|
+
return [`Revoked trust for ${sanitiseIdentifier(command.target)}; it asks again on its next call.`];
|
|
81780
|
+
}
|
|
81781
|
+
return [`${sanitiseIdentifier(command.target)} is not trusted in this session; nothing changed. Use the full server__tool name (see /mcp trust list).`];
|
|
81782
|
+
}
|
|
81783
|
+
}
|
|
81784
|
+
}
|
|
81785
|
+
var TRUSTED_MARKER = "[trusted]", FQN_SEPARATOR = "__", MAX_ARGUMENT_CHARS = 400, MAX_ARGUMENT_KEYS = 40, MAX_IDENTIFIER_CHARS = 80, APPROVAL_ALLOW_ID = "allow", MCP_TRUST_USAGE = "/mcp trust list | /mcp trust revoke <server__tool> | /mcp trust revoke all";
|
|
81713
81786
|
var init_approval_render = __esm(() => {
|
|
81714
81787
|
init_catalog4();
|
|
81715
81788
|
init_config9();
|
|
@@ -92116,7 +92189,7 @@ var init_help_groups = __esm(() => {
|
|
|
92116
92189
|
kind: "slash",
|
|
92117
92190
|
name: "/mcp",
|
|
92118
92191
|
group: "External agents, ACP and MCP",
|
|
92119
|
-
summary: "MCP servers keryx is connected to \u2014 status, connect/disconnect."
|
|
92192
|
+
summary: "MCP servers keryx is connected to \u2014 status, connect/disconnect; /mcp trust list, /mcp trust revoke <server__tool> or all (session trust)."
|
|
92120
92193
|
},
|
|
92121
92194
|
{
|
|
92122
92195
|
kind: "slash",
|
|
@@ -97789,7 +97862,7 @@ var init_agent_commands = __esm(() => {
|
|
|
97789
97862
|
},
|
|
97790
97863
|
{
|
|
97791
97864
|
name: "/mcp",
|
|
97792
|
-
description: "MCP servers keryx is connected to \u2014 status, connect/disconnect",
|
|
97865
|
+
description: "MCP servers keryx is connected to \u2014 status, connect/disconnect; /mcp trust list, /mcp trust revoke <server__tool> or all",
|
|
97793
97866
|
modes: AGENT_ONLY
|
|
97794
97867
|
},
|
|
97795
97868
|
{
|
|
@@ -112869,6 +112942,7 @@ function buildConsumerModel(input) {
|
|
|
112869
112942
|
target: targetOf(server, home),
|
|
112870
112943
|
credentials: credentialNames(server),
|
|
112871
112944
|
detail,
|
|
112945
|
+
...input.trustedByServer?.get(server.name) ? { trustedTools: input.trustedByServer.get(server.name) } : {},
|
|
112872
112946
|
action: status === "needs-approval" ? `keryx mcp trust ${server.name}` : status === "needs_auth" ? "set the variable named above, then restart the session" : undefined
|
|
112873
112947
|
};
|
|
112874
112948
|
});
|
|
@@ -112880,6 +112954,9 @@ function buildConsumerModel(input) {
|
|
|
112880
112954
|
project: ${collapseHome2(input.projectFile, home)}`
|
|
112881
112955
|
};
|
|
112882
112956
|
}
|
|
112957
|
+
function trustedNote(row) {
|
|
112958
|
+
return row.trustedTools !== undefined && row.trustedTools > 0 ? ` ${TRUSTED_MARKER} ${row.trustedTools} tool(s)` : "";
|
|
112959
|
+
}
|
|
112883
112960
|
function consumerActionFor(status) {
|
|
112884
112961
|
if (status === "connected")
|
|
112885
112962
|
return "disconnect";
|
|
@@ -112915,7 +112992,7 @@ function formatConsumerModalRow(row, isSelected, status, width) {
|
|
|
112915
112992
|
action = "";
|
|
112916
112993
|
}
|
|
112917
112994
|
const creds = row.credentials.length > 0 ? ` [reads ${row.credentials.join(", ")}]` : "";
|
|
112918
|
-
return wrapHangingRow(`${mark} ${name} ${source} ${transport} ${glyph}`, `${tools}${action} ${row.target}${creds}`, width).join(`
|
|
112995
|
+
return wrapHangingRow(`${mark} ${name} ${source} ${transport} ${glyph}`, `${tools}${action} ${row.target}${creds}${trustedNote(row)}`, width).join(`
|
|
112919
112996
|
`);
|
|
112920
112997
|
}
|
|
112921
112998
|
function formatConsumerModalLines(model, selected, status) {
|
|
@@ -117525,6 +117602,7 @@ function createForegroundAgentIoFacade(owner, token, io) {
|
|
|
117525
117602
|
permissionMode: () => accepts() ? io.permissionMode?.() ?? "ask" : "ask",
|
|
117526
117603
|
...io.trustedMcpTools === undefined ? {} : { trustedMcpTools: io.trustedMcpTools },
|
|
117527
117604
|
...io.mcpToolFingerprint === undefined ? {} : { mcpToolFingerprint: io.mcpToolFingerprint },
|
|
117605
|
+
...io.mcpToolDestructive === undefined ? {} : { mcpToolDestructive: io.mcpToolDestructive },
|
|
117528
117606
|
readOnly: () => !accepts() || (io.readOnly?.() ?? false)
|
|
117529
117607
|
};
|
|
117530
117608
|
}
|
|
@@ -128462,10 +128540,11 @@ async function launchTuiAgentShell(opts) {
|
|
|
128462
128540
|
}
|
|
128463
128541
|
if (isMcpToolCall(tool)) {
|
|
128464
128542
|
const described = describeUseToolApproval(inputJson, catalogResolver(deps.mcpRuntime?.()?.catalog()));
|
|
128465
|
-
const canTrustMcp = mcpTrustOffered(meta, catalogResolver(deps.mcpRuntime?.()?.catalog())(described.fqn) !== undefined);
|
|
128543
|
+
const canTrustMcp = mcpTrustOffered(meta, catalogResolver(deps.mcpRuntime?.()?.catalog())(described.fqn) !== undefined, catalogDestructiveResolver(deps.mcpRuntime?.()?.catalog())(described.fqn));
|
|
128544
|
+
const trustedTag = meta?.mcpTrusted === true ? ` ${TRUSTED_MARKER}` : "";
|
|
128466
128545
|
transcript.add(new otui.TextRenderable(r, {
|
|
128467
128546
|
id: `ap${uid++}`,
|
|
128468
|
-
content: otui.t`${roleChunk(otui, "attention", `\u2699 ${described.server} wants to run ${described.tool}`)}`
|
|
128547
|
+
content: otui.t`${roleChunk(otui, "attention", `\u2699 ${described.server} wants to run ${described.tool}${trustedTag}`)}`
|
|
128469
128548
|
}));
|
|
128470
128549
|
for (const line of described.argumentLines) {
|
|
128471
128550
|
transcript.add(new otui.TextRenderable(r, { id: `ap${uid++}`, content: otui.t`${dimChunk(otui, ` ${line}`)}` }));
|
|
@@ -128476,7 +128555,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
128476
128555
|
content: otui.t`${dimChunk(otui, ` \u2026 arguments truncated at ${MAX_ARGUMENT_CHARS} characters`)}`
|
|
128477
128556
|
}));
|
|
128478
128557
|
}
|
|
128479
|
-
for (const notice of untrustedOriginNotices(meta)) {
|
|
128558
|
+
for (const notice of [...untrustedOriginNotices(meta), ...destructiveTrustNotices(meta)]) {
|
|
128480
128559
|
transcript.add(new otui.TextRenderable(r, { id: `ap${uid++}`, content: otui.t`${dimChunk(otui, ` ${notice}`)}` }));
|
|
128481
128560
|
}
|
|
128482
128561
|
chrome.hideMenu();
|
|
@@ -128499,7 +128578,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
128499
128578
|
setMainAgent("running", allowed ? "write" : "denied");
|
|
128500
128579
|
transcript.add(new otui.TextRenderable(r, {
|
|
128501
128580
|
id: `ap${uid++}`,
|
|
128502
|
-
content: allowed ? otui.t`${roleChunk(otui, "ok", `\u25C7 ${described.fqn} approved`)}` : otui.t`${roleChunk(otui, "error", `\u25C7 ${described.fqn} denied`)}`
|
|
128581
|
+
content: allowed ? otui.t`${roleChunk(otui, "ok", `\u25C7 ${described.fqn}${trustedTag} approved`)}` : otui.t`${roleChunk(otui, "error", `\u25C7 ${described.fqn}${trustedTag} denied`)}`
|
|
128503
128582
|
}));
|
|
128504
128583
|
return verdict;
|
|
128505
128584
|
}
|
|
@@ -128702,6 +128781,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
128702
128781
|
io.permissionMode = () => permissionMode;
|
|
128703
128782
|
io.trustedMcpTools = new Map;
|
|
128704
128783
|
io.mcpToolFingerprint = (fqn) => catalogFingerprintResolver(deps.mcpRuntime?.()?.catalog())(fqn);
|
|
128784
|
+
io.mcpToolDestructive = (fqn) => catalogDestructiveResolver(deps.mcpRuntime?.()?.catalog())(fqn);
|
|
128705
128785
|
let readOnly = false;
|
|
128706
128786
|
io.readOnly = () => readOnly;
|
|
128707
128787
|
const paintModeRow = () => {
|
|
@@ -128710,8 +128790,8 @@ async function launchTuiAgentShell(opts) {
|
|
|
128710
128790
|
};
|
|
128711
128791
|
paintModeRow();
|
|
128712
128792
|
io.onAutoApproved = (tool, input, meta) => {
|
|
128713
|
-
const preview = tool === "shell_exec" ? parseShellExecCommand(input) : tool;
|
|
128714
|
-
const label =
|
|
128793
|
+
const preview = tool === "shell_exec" ? parseShellExecCommand(input) : isMcpToolCall(tool) ? `${tool} ${describeUseToolApproval(input, catalogResolver(deps.mcpRuntime?.()?.catalog())).fqn}` : tool;
|
|
128794
|
+
const label = `\u25C7 auto-approved (${permissionMode})` + (meta.destructive ? " [destructive]" : "") + (meta.mcpTrusted === true ? ` ${TRUSTED_MARKER}` : "");
|
|
128715
128795
|
transcript.add(new otui.TextRenderable(r, {
|
|
128716
128796
|
id: `ap${uid++}`,
|
|
128717
128797
|
content: otui.t`${roleChunk(otui, "attention", label)} ${dimChunk(otui, preview)}`
|
|
@@ -129082,6 +129162,7 @@ Staying in the current session.
|
|
|
129082
129162
|
return false;
|
|
129083
129163
|
}
|
|
129084
129164
|
resetSessionSurface();
|
|
129165
|
+
io.trustedMcpTools?.clear();
|
|
129085
129166
|
liveSession = opened.handle;
|
|
129086
129167
|
history = [];
|
|
129087
129168
|
archive = [];
|
|
@@ -129403,13 +129484,32 @@ Staying in the current session.
|
|
|
129403
129484
|
client.send(parsed.toLabel, parsed.kind === "ask" ? "question" : "notice", parsed.text).then((result) => io.onSystem?.(`bus: sent (#${result.seq}) to ${parsed.toLabel}
|
|
129404
129485
|
`)).catch(reportRefusal);
|
|
129405
129486
|
};
|
|
129487
|
+
const runMcpTrustLine = (line) => {
|
|
129488
|
+
const parsed = parseMcpTrustCommand(line);
|
|
129489
|
+
if (parsed === undefined)
|
|
129490
|
+
return false;
|
|
129491
|
+
const catalog = deps.mcpRuntime?.()?.catalog();
|
|
129492
|
+
const lines = runMcpTrustCommand(parsed, io.trustedMcpTools, catalogResolver(catalog), catalogTrustStaleness(catalog));
|
|
129493
|
+
io.onSystem?.(`${lines.join(`
|
|
129494
|
+
`)}
|
|
129495
|
+
`);
|
|
129496
|
+
return true;
|
|
129497
|
+
};
|
|
129406
129498
|
const showMcpConsumer = () => {
|
|
129407
129499
|
const cwd = inspectorCwd();
|
|
129408
129500
|
const snapshot = () => {
|
|
129409
129501
|
const live = deps.mcpRuntime?.();
|
|
129410
129502
|
if (live === undefined)
|
|
129411
129503
|
return;
|
|
129504
|
+
const resolve = catalogResolver(live.catalog());
|
|
129505
|
+
const trustedByServer = new Map;
|
|
129506
|
+
for (const fqn of io.trustedMcpTools?.keys() ?? []) {
|
|
129507
|
+
const server = resolve(fqn)?.server;
|
|
129508
|
+
if (server !== undefined)
|
|
129509
|
+
trustedByServer.set(server, (trustedByServer.get(server) ?? 0) + 1);
|
|
129510
|
+
}
|
|
129412
129511
|
return buildConsumerModel({
|
|
129512
|
+
trustedByServer,
|
|
129413
129513
|
configured: live.configured(),
|
|
129414
129514
|
states: live.servers(),
|
|
129415
129515
|
problems: live.problems(),
|
|
@@ -130095,7 +130195,8 @@ Staying in the current session.
|
|
|
130095
130195
|
return;
|
|
130096
130196
|
}
|
|
130097
130197
|
case "mcp-consumer": {
|
|
130098
|
-
|
|
130198
|
+
if (!runMcpTrustLine(line))
|
|
130199
|
+
showMcpConsumer();
|
|
130099
130200
|
return;
|
|
130100
130201
|
}
|
|
130101
130202
|
case "game": {
|
|
@@ -130416,7 +130517,8 @@ Staying in the current session.
|
|
|
130416
130517
|
return;
|
|
130417
130518
|
}
|
|
130418
130519
|
if (isMcpConsumerCommand(command.name)) {
|
|
130419
|
-
|
|
130520
|
+
if (!runMcpTrustLine(line))
|
|
130521
|
+
showMcpConsumer();
|
|
130420
130522
|
return;
|
|
130421
130523
|
}
|
|
130422
130524
|
if (isMcpToolsCommand(command.name)) {
|
|
@@ -133265,7 +133367,7 @@ ${GUTTER}${style.dim("[y/N] ")}`);
|
|
|
133265
133367
|
return meta?.fingerprint !== undefined ? { approved: true, fingerprint: meta.fingerprint } : true;
|
|
133266
133368
|
}
|
|
133267
133369
|
if (isMcpToolCall(tool)) {
|
|
133268
|
-
return await promptUseToolApproval({ out, readLine: async () => await readLine() }, input, meta, catalogResolver(deps.mcpRuntime?.()?.catalog()), style, GUTTER);
|
|
133370
|
+
return await promptUseToolApproval({ out, readLine: async () => await readLine() }, input, meta, catalogResolver(deps.mcpRuntime?.()?.catalog()), style, GUTTER, catalogDestructiveResolver(deps.mcpRuntime?.()?.catalog()));
|
|
133269
133371
|
}
|
|
133270
133372
|
if (tool !== "shell_exec") {
|
|
133271
133373
|
const preview = input.length > 120 ? `${input.slice(0, 117)}\u2026` : input;
|
|
@@ -133385,14 +133487,16 @@ ${text.trim()}`;
|
|
|
133385
133487
|
agentIo.permissionMode = () => permissionMode;
|
|
133386
133488
|
agentIo.trustedMcpTools = new Map;
|
|
133387
133489
|
agentIo.mcpToolFingerprint = (fqn) => catalogFingerprintResolver(deps.mcpRuntime?.()?.catalog())(fqn);
|
|
133490
|
+
agentIo.mcpToolDestructive = (fqn) => catalogDestructiveResolver(deps.mcpRuntime?.()?.catalog())(fqn);
|
|
133388
133491
|
let reasoningSessionOverride;
|
|
133389
133492
|
let readOnly = false;
|
|
133390
133493
|
agentIo.readOnly = () => readOnly;
|
|
133391
133494
|
agentIo.onAutoApproved = (tool, input, meta) => {
|
|
133392
133495
|
stopSpinner();
|
|
133393
|
-
const preview = tool === "shell_exec" ? parseShellExecCommand(input) : tool;
|
|
133496
|
+
const preview = tool === "shell_exec" ? parseShellExecCommand(input) : isMcpToolCall(tool) ? `${tool} ${describeUseToolApproval(input, catalogResolver(deps.mcpRuntime?.()?.catalog())).fqn}` : tool;
|
|
133394
133497
|
const flag = meta.destructive ? style.yellow(" [destructive]") : "";
|
|
133395
|
-
|
|
133498
|
+
const trustedFlag = meta.mcpTrusted === true ? style.yellow(` ${TRUSTED_MARKER}`) : "";
|
|
133499
|
+
out(`${GUTTER}${style.yellow(`\u25C7 auto-approved (${permissionMode})`)}${flag}${trustedFlag} ${style.dim(preview)}
|
|
133396
133500
|
`);
|
|
133397
133501
|
};
|
|
133398
133502
|
let live;
|
|
@@ -133918,6 +134022,11 @@ ${GUTTER}${turnSeparator()}
|
|
|
133918
134022
|
agentIo.onSystem?.(`Usage: /plan [on|off]
|
|
133919
134023
|
`);
|
|
133920
134024
|
}
|
|
134025
|
+
} else if (command === "/mcp") {
|
|
134026
|
+
const trust = parseMcpTrustCommand(line);
|
|
134027
|
+
agentIo.onSystem?.(trust === undefined ? "The MCP server view is TUI-only here; `keryx mcp list` shows servers. Session trust: /mcp trust list | revoke <server__tool> | revoke all\n" : `${runMcpTrustCommand(trust, agentIo.trustedMcpTools, catalogResolver(deps.mcpRuntime?.()?.catalog()), catalogTrustStaleness(deps.mcpRuntime?.()?.catalog())).join(`
|
|
134028
|
+
`)}
|
|
134029
|
+
`);
|
|
133921
134030
|
} else if (command === "/search-provider") {
|
|
133922
134031
|
const args = parseSearchProviderArgs2(parts.slice(1));
|
|
133923
134032
|
const all = searchProviderController.configurable();
|
|
@@ -134894,6 +135003,7 @@ var init_shell = __esm(() => {
|
|
|
134894
135003
|
"/mode",
|
|
134895
135004
|
"/reasoning",
|
|
134896
135005
|
"/plan",
|
|
135006
|
+
"/mcp",
|
|
134897
135007
|
"/exit"
|
|
134898
135008
|
];
|
|
134899
135009
|
SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
@@ -183606,6 +183716,7 @@ var init_group_subcommands = __esm(() => {
|
|
|
183606
183716
|
"ac",
|
|
183607
183717
|
"check-ac",
|
|
183608
183718
|
"owner",
|
|
183719
|
+
"outcome",
|
|
183609
183720
|
"implemented",
|
|
183610
183721
|
"complete",
|
|
183611
183722
|
"confirm",
|
package/dist/core.js
CHANGED
|
@@ -38473,7 +38473,7 @@ var HELP_GROUPS = [
|
|
|
38473
38473
|
kind: "slash",
|
|
38474
38474
|
name: "/mcp",
|
|
38475
38475
|
group: "External agents, ACP and MCP",
|
|
38476
|
-
summary: "MCP servers keryx is connected to \u2014 status, connect/disconnect."
|
|
38476
|
+
summary: "MCP servers keryx is connected to \u2014 status, connect/disconnect; /mcp trust list, /mcp trust revoke <server__tool> or all (session trust)."
|
|
38477
38477
|
},
|
|
38478
38478
|
{
|
|
38479
38479
|
kind: "slash",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.35",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|