@mrciphersmith/keryx 0.3.34 → 0.3.36
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 +146 -30
- 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.36",
|
|
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();
|
|
@@ -87658,7 +87731,13 @@ var init_codex_cli = __esm(() => {
|
|
|
87658
87731
|
/unknown option/i,
|
|
87659
87732
|
/^\s*usage:\s*codex/i
|
|
87660
87733
|
];
|
|
87661
|
-
AUTH_PATTERNS2 = [
|
|
87734
|
+
AUTH_PATTERNS2 = [
|
|
87735
|
+
/\b401\b/,
|
|
87736
|
+
/unauthorized/i,
|
|
87737
|
+
/not logged/i,
|
|
87738
|
+
/access token could not be refreshed/i,
|
|
87739
|
+
/log out and sign in again/i
|
|
87740
|
+
];
|
|
87662
87741
|
LIMIT_PATTERNS2 = [/rate limit/i, /usage limit/i, /quota/i];
|
|
87663
87742
|
codexCliCodec = {
|
|
87664
87743
|
id: "codex-cli",
|
|
@@ -92116,7 +92195,7 @@ var init_help_groups = __esm(() => {
|
|
|
92116
92195
|
kind: "slash",
|
|
92117
92196
|
name: "/mcp",
|
|
92118
92197
|
group: "External agents, ACP and MCP",
|
|
92119
|
-
summary: "MCP servers keryx is connected to \u2014 status, connect/disconnect."
|
|
92198
|
+
summary: "MCP servers keryx is connected to \u2014 status, connect/disconnect; /mcp trust list, /mcp trust revoke <server__tool> or all (session trust)."
|
|
92120
92199
|
},
|
|
92121
92200
|
{
|
|
92122
92201
|
kind: "slash",
|
|
@@ -97789,7 +97868,7 @@ var init_agent_commands = __esm(() => {
|
|
|
97789
97868
|
},
|
|
97790
97869
|
{
|
|
97791
97870
|
name: "/mcp",
|
|
97792
|
-
description: "MCP servers keryx is connected to \u2014 status, connect/disconnect",
|
|
97871
|
+
description: "MCP servers keryx is connected to \u2014 status, connect/disconnect; /mcp trust list, /mcp trust revoke <server__tool> or all",
|
|
97793
97872
|
modes: AGENT_ONLY
|
|
97794
97873
|
},
|
|
97795
97874
|
{
|
|
@@ -112869,6 +112948,7 @@ function buildConsumerModel(input) {
|
|
|
112869
112948
|
target: targetOf(server, home),
|
|
112870
112949
|
credentials: credentialNames(server),
|
|
112871
112950
|
detail,
|
|
112951
|
+
...input.trustedByServer?.get(server.name) ? { trustedTools: input.trustedByServer.get(server.name) } : {},
|
|
112872
112952
|
action: status === "needs-approval" ? `keryx mcp trust ${server.name}` : status === "needs_auth" ? "set the variable named above, then restart the session" : undefined
|
|
112873
112953
|
};
|
|
112874
112954
|
});
|
|
@@ -112880,6 +112960,9 @@ function buildConsumerModel(input) {
|
|
|
112880
112960
|
project: ${collapseHome2(input.projectFile, home)}`
|
|
112881
112961
|
};
|
|
112882
112962
|
}
|
|
112963
|
+
function trustedNote(row) {
|
|
112964
|
+
return row.trustedTools !== undefined && row.trustedTools > 0 ? ` ${TRUSTED_MARKER} ${row.trustedTools} tool(s)` : "";
|
|
112965
|
+
}
|
|
112883
112966
|
function consumerActionFor(status) {
|
|
112884
112967
|
if (status === "connected")
|
|
112885
112968
|
return "disconnect";
|
|
@@ -112915,7 +112998,7 @@ function formatConsumerModalRow(row, isSelected, status, width) {
|
|
|
112915
112998
|
action = "";
|
|
112916
112999
|
}
|
|
112917
113000
|
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(`
|
|
113001
|
+
return wrapHangingRow(`${mark} ${name} ${source} ${transport} ${glyph}`, `${tools}${action} ${row.target}${creds}${trustedNote(row)}`, width).join(`
|
|
112919
113002
|
`);
|
|
112920
113003
|
}
|
|
112921
113004
|
function formatConsumerModalLines(model, selected, status) {
|
|
@@ -117525,6 +117608,7 @@ function createForegroundAgentIoFacade(owner, token, io) {
|
|
|
117525
117608
|
permissionMode: () => accepts() ? io.permissionMode?.() ?? "ask" : "ask",
|
|
117526
117609
|
...io.trustedMcpTools === undefined ? {} : { trustedMcpTools: io.trustedMcpTools },
|
|
117527
117610
|
...io.mcpToolFingerprint === undefined ? {} : { mcpToolFingerprint: io.mcpToolFingerprint },
|
|
117611
|
+
...io.mcpToolDestructive === undefined ? {} : { mcpToolDestructive: io.mcpToolDestructive },
|
|
117528
117612
|
readOnly: () => !accepts() || (io.readOnly?.() ?? false)
|
|
117529
117613
|
};
|
|
117530
117614
|
}
|
|
@@ -128462,10 +128546,11 @@ async function launchTuiAgentShell(opts) {
|
|
|
128462
128546
|
}
|
|
128463
128547
|
if (isMcpToolCall(tool)) {
|
|
128464
128548
|
const described = describeUseToolApproval(inputJson, catalogResolver(deps.mcpRuntime?.()?.catalog()));
|
|
128465
|
-
const canTrustMcp = mcpTrustOffered(meta, catalogResolver(deps.mcpRuntime?.()?.catalog())(described.fqn) !== undefined);
|
|
128549
|
+
const canTrustMcp = mcpTrustOffered(meta, catalogResolver(deps.mcpRuntime?.()?.catalog())(described.fqn) !== undefined, catalogDestructiveResolver(deps.mcpRuntime?.()?.catalog())(described.fqn));
|
|
128550
|
+
const trustedTag = meta?.mcpTrusted === true ? ` ${TRUSTED_MARKER}` : "";
|
|
128466
128551
|
transcript.add(new otui.TextRenderable(r, {
|
|
128467
128552
|
id: `ap${uid++}`,
|
|
128468
|
-
content: otui.t`${roleChunk(otui, "attention", `\u2699 ${described.server} wants to run ${described.tool}`)}`
|
|
128553
|
+
content: otui.t`${roleChunk(otui, "attention", `\u2699 ${described.server} wants to run ${described.tool}${trustedTag}`)}`
|
|
128469
128554
|
}));
|
|
128470
128555
|
for (const line of described.argumentLines) {
|
|
128471
128556
|
transcript.add(new otui.TextRenderable(r, { id: `ap${uid++}`, content: otui.t`${dimChunk(otui, ` ${line}`)}` }));
|
|
@@ -128476,7 +128561,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
128476
128561
|
content: otui.t`${dimChunk(otui, ` \u2026 arguments truncated at ${MAX_ARGUMENT_CHARS} characters`)}`
|
|
128477
128562
|
}));
|
|
128478
128563
|
}
|
|
128479
|
-
for (const notice of untrustedOriginNotices(meta)) {
|
|
128564
|
+
for (const notice of [...untrustedOriginNotices(meta), ...destructiveTrustNotices(meta)]) {
|
|
128480
128565
|
transcript.add(new otui.TextRenderable(r, { id: `ap${uid++}`, content: otui.t`${dimChunk(otui, ` ${notice}`)}` }));
|
|
128481
128566
|
}
|
|
128482
128567
|
chrome.hideMenu();
|
|
@@ -128499,7 +128584,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
128499
128584
|
setMainAgent("running", allowed ? "write" : "denied");
|
|
128500
128585
|
transcript.add(new otui.TextRenderable(r, {
|
|
128501
128586
|
id: `ap${uid++}`,
|
|
128502
|
-
content: allowed ? otui.t`${roleChunk(otui, "ok", `\u25C7 ${described.fqn} approved`)}` : otui.t`${roleChunk(otui, "error", `\u25C7 ${described.fqn} denied`)}`
|
|
128587
|
+
content: allowed ? otui.t`${roleChunk(otui, "ok", `\u25C7 ${described.fqn}${trustedTag} approved`)}` : otui.t`${roleChunk(otui, "error", `\u25C7 ${described.fqn}${trustedTag} denied`)}`
|
|
128503
128588
|
}));
|
|
128504
128589
|
return verdict;
|
|
128505
128590
|
}
|
|
@@ -128702,6 +128787,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
128702
128787
|
io.permissionMode = () => permissionMode;
|
|
128703
128788
|
io.trustedMcpTools = new Map;
|
|
128704
128789
|
io.mcpToolFingerprint = (fqn) => catalogFingerprintResolver(deps.mcpRuntime?.()?.catalog())(fqn);
|
|
128790
|
+
io.mcpToolDestructive = (fqn) => catalogDestructiveResolver(deps.mcpRuntime?.()?.catalog())(fqn);
|
|
128705
128791
|
let readOnly = false;
|
|
128706
128792
|
io.readOnly = () => readOnly;
|
|
128707
128793
|
const paintModeRow = () => {
|
|
@@ -128710,8 +128796,8 @@ async function launchTuiAgentShell(opts) {
|
|
|
128710
128796
|
};
|
|
128711
128797
|
paintModeRow();
|
|
128712
128798
|
io.onAutoApproved = (tool, input, meta) => {
|
|
128713
|
-
const preview = tool === "shell_exec" ? parseShellExecCommand(input) : tool;
|
|
128714
|
-
const label =
|
|
128799
|
+
const preview = tool === "shell_exec" ? parseShellExecCommand(input) : isMcpToolCall(tool) ? `${tool} ${describeUseToolApproval(input, catalogResolver(deps.mcpRuntime?.()?.catalog())).fqn}` : tool;
|
|
128800
|
+
const label = `\u25C7 auto-approved (${permissionMode})` + (meta.destructive ? " [destructive]" : "") + (meta.mcpTrusted === true ? ` ${TRUSTED_MARKER}` : "");
|
|
128715
128801
|
transcript.add(new otui.TextRenderable(r, {
|
|
128716
128802
|
id: `ap${uid++}`,
|
|
128717
128803
|
content: otui.t`${roleChunk(otui, "attention", label)} ${dimChunk(otui, preview)}`
|
|
@@ -129082,6 +129168,7 @@ Staying in the current session.
|
|
|
129082
129168
|
return false;
|
|
129083
129169
|
}
|
|
129084
129170
|
resetSessionSurface();
|
|
129171
|
+
io.trustedMcpTools?.clear();
|
|
129085
129172
|
liveSession = opened.handle;
|
|
129086
129173
|
history = [];
|
|
129087
129174
|
archive = [];
|
|
@@ -129403,13 +129490,32 @@ Staying in the current session.
|
|
|
129403
129490
|
client.send(parsed.toLabel, parsed.kind === "ask" ? "question" : "notice", parsed.text).then((result) => io.onSystem?.(`bus: sent (#${result.seq}) to ${parsed.toLabel}
|
|
129404
129491
|
`)).catch(reportRefusal);
|
|
129405
129492
|
};
|
|
129493
|
+
const runMcpTrustLine = (line) => {
|
|
129494
|
+
const parsed = parseMcpTrustCommand(line);
|
|
129495
|
+
if (parsed === undefined)
|
|
129496
|
+
return false;
|
|
129497
|
+
const catalog = deps.mcpRuntime?.()?.catalog();
|
|
129498
|
+
const lines = runMcpTrustCommand(parsed, io.trustedMcpTools, catalogResolver(catalog), catalogTrustStaleness(catalog));
|
|
129499
|
+
io.onSystem?.(`${lines.join(`
|
|
129500
|
+
`)}
|
|
129501
|
+
`);
|
|
129502
|
+
return true;
|
|
129503
|
+
};
|
|
129406
129504
|
const showMcpConsumer = () => {
|
|
129407
129505
|
const cwd = inspectorCwd();
|
|
129408
129506
|
const snapshot = () => {
|
|
129409
129507
|
const live = deps.mcpRuntime?.();
|
|
129410
129508
|
if (live === undefined)
|
|
129411
129509
|
return;
|
|
129510
|
+
const resolve = catalogResolver(live.catalog());
|
|
129511
|
+
const trustedByServer = new Map;
|
|
129512
|
+
for (const fqn of io.trustedMcpTools?.keys() ?? []) {
|
|
129513
|
+
const server = resolve(fqn)?.server;
|
|
129514
|
+
if (server !== undefined)
|
|
129515
|
+
trustedByServer.set(server, (trustedByServer.get(server) ?? 0) + 1);
|
|
129516
|
+
}
|
|
129412
129517
|
return buildConsumerModel({
|
|
129518
|
+
trustedByServer,
|
|
129413
129519
|
configured: live.configured(),
|
|
129414
129520
|
states: live.servers(),
|
|
129415
129521
|
problems: live.problems(),
|
|
@@ -130095,7 +130201,8 @@ Staying in the current session.
|
|
|
130095
130201
|
return;
|
|
130096
130202
|
}
|
|
130097
130203
|
case "mcp-consumer": {
|
|
130098
|
-
|
|
130204
|
+
if (!runMcpTrustLine(line))
|
|
130205
|
+
showMcpConsumer();
|
|
130099
130206
|
return;
|
|
130100
130207
|
}
|
|
130101
130208
|
case "game": {
|
|
@@ -130416,7 +130523,8 @@ Staying in the current session.
|
|
|
130416
130523
|
return;
|
|
130417
130524
|
}
|
|
130418
130525
|
if (isMcpConsumerCommand(command.name)) {
|
|
130419
|
-
|
|
130526
|
+
if (!runMcpTrustLine(line))
|
|
130527
|
+
showMcpConsumer();
|
|
130420
130528
|
return;
|
|
130421
130529
|
}
|
|
130422
130530
|
if (isMcpToolsCommand(command.name)) {
|
|
@@ -133265,7 +133373,7 @@ ${GUTTER}${style.dim("[y/N] ")}`);
|
|
|
133265
133373
|
return meta?.fingerprint !== undefined ? { approved: true, fingerprint: meta.fingerprint } : true;
|
|
133266
133374
|
}
|
|
133267
133375
|
if (isMcpToolCall(tool)) {
|
|
133268
|
-
return await promptUseToolApproval({ out, readLine: async () => await readLine() }, input, meta, catalogResolver(deps.mcpRuntime?.()?.catalog()), style, GUTTER);
|
|
133376
|
+
return await promptUseToolApproval({ out, readLine: async () => await readLine() }, input, meta, catalogResolver(deps.mcpRuntime?.()?.catalog()), style, GUTTER, catalogDestructiveResolver(deps.mcpRuntime?.()?.catalog()));
|
|
133269
133377
|
}
|
|
133270
133378
|
if (tool !== "shell_exec") {
|
|
133271
133379
|
const preview = input.length > 120 ? `${input.slice(0, 117)}\u2026` : input;
|
|
@@ -133385,14 +133493,16 @@ ${text.trim()}`;
|
|
|
133385
133493
|
agentIo.permissionMode = () => permissionMode;
|
|
133386
133494
|
agentIo.trustedMcpTools = new Map;
|
|
133387
133495
|
agentIo.mcpToolFingerprint = (fqn) => catalogFingerprintResolver(deps.mcpRuntime?.()?.catalog())(fqn);
|
|
133496
|
+
agentIo.mcpToolDestructive = (fqn) => catalogDestructiveResolver(deps.mcpRuntime?.()?.catalog())(fqn);
|
|
133388
133497
|
let reasoningSessionOverride;
|
|
133389
133498
|
let readOnly = false;
|
|
133390
133499
|
agentIo.readOnly = () => readOnly;
|
|
133391
133500
|
agentIo.onAutoApproved = (tool, input, meta) => {
|
|
133392
133501
|
stopSpinner();
|
|
133393
|
-
const preview = tool === "shell_exec" ? parseShellExecCommand(input) : tool;
|
|
133502
|
+
const preview = tool === "shell_exec" ? parseShellExecCommand(input) : isMcpToolCall(tool) ? `${tool} ${describeUseToolApproval(input, catalogResolver(deps.mcpRuntime?.()?.catalog())).fqn}` : tool;
|
|
133394
133503
|
const flag = meta.destructive ? style.yellow(" [destructive]") : "";
|
|
133395
|
-
|
|
133504
|
+
const trustedFlag = meta.mcpTrusted === true ? style.yellow(` ${TRUSTED_MARKER}`) : "";
|
|
133505
|
+
out(`${GUTTER}${style.yellow(`\u25C7 auto-approved (${permissionMode})`)}${flag}${trustedFlag} ${style.dim(preview)}
|
|
133396
133506
|
`);
|
|
133397
133507
|
};
|
|
133398
133508
|
let live;
|
|
@@ -133918,6 +134028,11 @@ ${GUTTER}${turnSeparator()}
|
|
|
133918
134028
|
agentIo.onSystem?.(`Usage: /plan [on|off]
|
|
133919
134029
|
`);
|
|
133920
134030
|
}
|
|
134031
|
+
} else if (command === "/mcp") {
|
|
134032
|
+
const trust = parseMcpTrustCommand(line);
|
|
134033
|
+
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(`
|
|
134034
|
+
`)}
|
|
134035
|
+
`);
|
|
133921
134036
|
} else if (command === "/search-provider") {
|
|
133922
134037
|
const args = parseSearchProviderArgs2(parts.slice(1));
|
|
133923
134038
|
const all = searchProviderController.configurable();
|
|
@@ -134894,6 +135009,7 @@ var init_shell = __esm(() => {
|
|
|
134894
135009
|
"/mode",
|
|
134895
135010
|
"/reasoning",
|
|
134896
135011
|
"/plan",
|
|
135012
|
+
"/mcp",
|
|
134897
135013
|
"/exit"
|
|
134898
135014
|
];
|
|
134899
135015
|
SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
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.36",
|
|
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": {
|