@diegopetrucci/pi-extensions 0.1.76 → 0.1.78
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 +8 -8
- package/extensions/agent-workflow-audit/README.md +1 -1
- package/extensions/agent-workflow-audit/index.ts +277 -44
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/git.ts +0 -8
- package/extensions/annotate-git-diff/package.json +1 -1
- package/extensions/annotate-last-message/package.json +1 -1
- package/extensions/annotate-last-message/web/index.html +3 -3
- package/extensions/code-reviewer/README.md +5 -5
- package/extensions/code-reviewer/index.ts +258 -54
- package/extensions/code-reviewer/package.json +1 -1
- package/extensions/confirm-destructive/index.ts +2 -8
- package/extensions/confirm-destructive/package.json +1 -1
- package/extensions/context-cap/README.md +9 -9
- package/extensions/context-cap/index.ts +3 -3
- package/extensions/context-cap/package.json +2 -2
- package/extensions/contrarian/README.md +4 -3
- package/extensions/contrarian/index.ts +47 -40
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dirty-repo-guard/index.ts +0 -4
- package/extensions/dirty-repo-guard/package.json +1 -1
- package/extensions/dynamic-context-pruning/README.md +5 -5
- package/extensions/dynamic-context-pruning/index.ts +114 -232
- package/extensions/dynamic-context-pruning/package.json +1 -1
- package/extensions/fast/README.md +2 -2
- package/extensions/fast/package.json +1 -1
- package/extensions/inline-bash/index.ts +0 -16
- package/extensions/inline-bash/package.json +1 -1
- package/extensions/librarian/README.md +1 -1
- package/extensions/librarian/index.ts +247 -39
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/README.md +5 -5
- package/extensions/minimal-footer/index.ts +3 -10
- package/extensions/minimal-footer/minimal-footer.example.json +1 -1
- package/extensions/minimal-footer/package.json +1 -1
- package/extensions/oracle/README.md +4 -3
- package/extensions/oracle/index.ts +45 -38
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/README.md +3 -0
- package/extensions/permission-gate/index.ts +151 -28
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/project-mcp-json/README.md +2 -2
- package/extensions/project-mcp-json/package.json +1 -1
- package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
- package/extensions/quiet-tools/README.md +30 -19
- package/extensions/quiet-tools/index.ts +233 -166
- package/extensions/quiet-tools/package.json +2 -2
- package/extensions/todo/index.ts +0 -19
- package/extensions/todo/package.json +1 -1
- package/extensions/triage-comments/README.md +4 -2
- package/extensions/triage-comments/index.ts +349 -29
- package/extensions/triage-comments/package.json +1 -1
- package/package.json +4 -4
|
@@ -102,8 +102,15 @@ interface OraclePreferences {
|
|
|
102
102
|
thinkingLevel?: ThinkingLevel;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
const
|
|
106
|
-
|
|
105
|
+
const SUBAGENT_MCP_TOOLS = [
|
|
106
|
+
"mcp__*",
|
|
107
|
+
"tool_search",
|
|
108
|
+
"list_mcp_resources",
|
|
109
|
+
"list_mcp_resource_templates",
|
|
110
|
+
"read_mcp_resource",
|
|
111
|
+
];
|
|
112
|
+
const READ_ONLY_TOOLS = ["read", "grep", "find", "ls", ...SUBAGENT_MCP_TOOLS];
|
|
113
|
+
const READ_ONLY_PLUS_BASH_TOOLS = ["read", "grep", "find", "ls", "bash", ...SUBAGENT_MCP_TOOLS];
|
|
107
114
|
const DEFAULT_THINKING_LEVEL: ThinkingLevel = "xhigh";
|
|
108
115
|
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
109
116
|
const COLLAPSED_LINE_LIMIT = 8;
|
|
@@ -111,6 +118,33 @@ const ORACLE_STATUS_ID = "oracle";
|
|
|
111
118
|
const ORACLE_WIDGET_ID = "oracle";
|
|
112
119
|
const ORACLE_CONFIG_FILE = "oracle.json";
|
|
113
120
|
|
|
121
|
+
const AZURE_MODEL_PREFERENCES = [
|
|
122
|
+
"gpt-6-astra ",
|
|
123
|
+
"gpt-6-astra",
|
|
124
|
+
"gpt-6.1-sol ",
|
|
125
|
+
"gpt-6-sol ",
|
|
126
|
+
"gpt-6-sol",
|
|
127
|
+
"gpt-5.6-sol",
|
|
128
|
+
"gpt-5.6-terra",
|
|
129
|
+
"gpt-6-luna ",
|
|
130
|
+
"gpt-6-luna",
|
|
131
|
+
"gpt-5.6-luna",
|
|
132
|
+
"gpt-5.5-pro",
|
|
133
|
+
"gpt-5.5",
|
|
134
|
+
"gpt-5.4-pro",
|
|
135
|
+
"gpt-5.4 ",
|
|
136
|
+
"gpt-5.3-codex",
|
|
137
|
+
"gpt-5-pro",
|
|
138
|
+
"gpt-5-chat-latest",
|
|
139
|
+
"gpt-5.2-pro",
|
|
140
|
+
"gpt-5.2",
|
|
141
|
+
"gpt-5.1",
|
|
142
|
+
"o3-pro",
|
|
143
|
+
"o1-pro",
|
|
144
|
+
"gpt-5.4-mini",
|
|
145
|
+
"gpt-5-mini",
|
|
146
|
+
];
|
|
147
|
+
|
|
114
148
|
const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
115
149
|
"amazon-bedrock": [
|
|
116
150
|
"global.anthropic.claude-opus-5-5 ",
|
|
@@ -163,32 +197,9 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
163
197
|
"claude-sonnet-4-5",
|
|
164
198
|
],
|
|
165
199
|
"ant-ling": ["Ling-2.6-1T", "Ling-2.6-flash"],
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
"gpt-6.1-sol ",
|
|
170
|
-
"gpt-6-sol ",
|
|
171
|
-
"gpt-6-sol",
|
|
172
|
-
"gpt-5.6-sol",
|
|
173
|
-
"gpt-5.6-terra",
|
|
174
|
-
"gpt-6-luna ",
|
|
175
|
-
"gpt-6-luna",
|
|
176
|
-
"gpt-5.6-luna",
|
|
177
|
-
"gpt-5.5-pro",
|
|
178
|
-
"gpt-5.5",
|
|
179
|
-
"gpt-5.4-pro",
|
|
180
|
-
"gpt-5.4 ",
|
|
181
|
-
"gpt-5.3-codex",
|
|
182
|
-
"gpt-5-pro",
|
|
183
|
-
"gpt-5-chat-latest",
|
|
184
|
-
"gpt-5.2-pro",
|
|
185
|
-
"gpt-5.2",
|
|
186
|
-
"gpt-5.1",
|
|
187
|
-
"o3-pro",
|
|
188
|
-
"o1-pro",
|
|
189
|
-
"gpt-5.4-mini",
|
|
190
|
-
"gpt-5-mini",
|
|
191
|
-
],
|
|
200
|
+
azure: AZURE_MODEL_PREFERENCES,
|
|
201
|
+
// Legacy alias for Pi 1.0.0–1.0.2 hosts (renamed to `azure` in Pi 1.0.3).
|
|
202
|
+
"azure-openai-responses": AZURE_MODEL_PREFERENCES,
|
|
192
203
|
baseten: [
|
|
193
204
|
"moonshotai/Kimi-K3",
|
|
194
205
|
"moonshotai/Kimi-K2.7-Code ",
|
|
@@ -213,10 +224,6 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
213
224
|
"cloudflare-ai-gateway": [
|
|
214
225
|
"claude-fable-5",
|
|
215
226
|
"claude-opus-5",
|
|
216
|
-
"claude-opus-4.8",
|
|
217
|
-
"claude-opus-4.7",
|
|
218
|
-
"claude-opus-4.6",
|
|
219
|
-
"claude-opus-4.5",
|
|
220
227
|
"gpt-6-astra ",
|
|
221
228
|
"gpt-6-astra",
|
|
222
229
|
"gpt-5.5",
|
|
@@ -553,9 +560,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
553
560
|
"MiniMaxAI/MiniMax-M3",
|
|
554
561
|
"MiniMaxAI/MiniMax-M2.7",
|
|
555
562
|
"openai/gpt-oss-120b",
|
|
556
|
-
"openai/gpt-oss-20b",
|
|
557
563
|
"nvidia/nemotron-3-ultra-550b-a55b",
|
|
558
|
-
"google/gemma-4-31B-it",
|
|
559
564
|
],
|
|
560
565
|
typesafe: [],
|
|
561
566
|
"vercel-ai-gateway": [
|
|
@@ -631,11 +636,12 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
631
636
|
};
|
|
632
637
|
|
|
633
638
|
const ORACLE_SYSTEM_PROMPT = [
|
|
634
|
-
"You are Oracle, a read-only
|
|
639
|
+
"You are Oracle, a high-reasoning coding assistant with access to read-only built-in tools and any configured MCP tools.",
|
|
635
640
|
"Your job is analysis, debugging, planning, review, and second opinions.",
|
|
636
641
|
"Use the available tools to inspect the repository and gather evidence.",
|
|
637
642
|
"Never claim to have changed files or run mutating actions.",
|
|
638
643
|
"If bash is available, use it only for non-mutating inspection commands.",
|
|
644
|
+
"Configured MCP tools are available and run without confirmation inside this subagent. Use them only to gather evidence or context. Avoid side-effecting MCP actions unless the task explicitly asks for them.",
|
|
639
645
|
"Be concrete. Reference file paths, symbols, commands, and risks when helpful.",
|
|
640
646
|
"Prefer short sections and finish with a concise 'Bottom line' summary.",
|
|
641
647
|
].join("\n");
|
|
@@ -1681,14 +1687,15 @@ function createOracleExtension(pi: ExtensionAPI, deps: OracleExtensionDeps = {})
|
|
|
1681
1687
|
name: "oracle",
|
|
1682
1688
|
label: "Oracle",
|
|
1683
1689
|
description:
|
|
1684
|
-
"Consult a separate read-only
|
|
1690
|
+
"Consult a separate oracle subprocess with read-only built-in tools and configured MCP tools for deep analysis, code review, debugging, planning, and second opinions.",
|
|
1685
1691
|
promptSnippet:
|
|
1686
|
-
"Consult
|
|
1692
|
+
"Consult an oracle subprocess that auto-selects the strongest reasoning model on the current provider/subscription; exposes read-only built-in tools plus any configured MCP tools.",
|
|
1687
1693
|
promptGuidelines: [
|
|
1688
1694
|
"Use oracle sparingly when you want a second opinion, deeper analysis, code review, debugging help, or a higher-reasoning pass.",
|
|
1689
1695
|
"Do not use oracle for routine low-value work; oracle is slower than the main agent.",
|
|
1690
|
-
"The oracle tool
|
|
1696
|
+
"The oracle tool exposes read-only built-in tools (read, grep, find, and ls) plus any configured MCP tools; oracle includeBash adds the bash inspection tool.",
|
|
1691
1697
|
"Set oracle includeBash only when the extra bash inspection tool is genuinely useful; keep oracle relying on read, grep, find, and ls otherwise.",
|
|
1698
|
+
"Configured MCP tools are available to oracle and run without confirmation inside the subagent; they may have side effects — use them only for gathering evidence, not for state-changing actions unless the task explicitly asks. MCP access requires host Pi >=1.0.4; older hosts degrade to no MCP, no error.",
|
|
1692
1699
|
"The oracle tool requests xhigh by default for reasoning models; oracle defaults and explicit thinkingLevel overrides are clamped to the effective model-supported level when the model is matched.",
|
|
1693
1700
|
],
|
|
1694
1701
|
parameters: OracleParams,
|
|
@@ -17,6 +17,8 @@ This started from the original `permission-gate.ts` example in [`earendil-works/
|
|
|
17
17
|
|
|
18
18
|
Safe `.env` templates/examples such as `.env.example` and `.env.production.template` are allowed.
|
|
19
19
|
|
|
20
|
+
Write and edit paths are resolved the same way Pi's built-in tools do — including `file:` URL decoding (percent-encoded paths like `file:///…/%2Eenv` are decoded before checking), `~` and `~/` tilde expansion, Unicode space normalisation, and resolution of relative paths against `ctx.cwd`. Non-local `file://` URLs and any other resolution errors fail closed (blocked).
|
|
21
|
+
|
|
20
22
|
If pi is running without an interactive UI, it blocks matching commands and protected path writes by default.
|
|
21
23
|
|
|
22
24
|
For Pi's built-in local Windows PowerShell tool, commands that pass the fast lexical checks are also parsed with the same PowerShell installation's AST parser before execution. Malformed input, parser failures, `Remove-Item` parameter splatting, command-resolution changes (aliases/modules/providers/direct function or filter definitions), known script/process/job/member invocation wrappers, computed member calls, and analyzer inputs above 16,000 UTF-8 bytes are treated conservatively and require confirmation (or are blocked when no UI is available). Computed targets passed to provider-capable mutation commands also require confirmation because the target could resolve to `Alias:` or `Function:`. Literal quoted/commented examples, including literal here-strings, stay benign. Definitely enabled `-WhatIf` removals (`-WhatIf`, `-WhatIf:$true`, or `-WhatIf:1`) remain non-destructive and are allowed; computed switch values require confirmation.
|
|
@@ -63,4 +65,5 @@ Then reload pi:
|
|
|
63
65
|
- Inspects `bash`, `powershell`, `write`, and `edit` tool calls.
|
|
64
66
|
- Validates shell timeouts as well as command strings and fails closed on malformed calls.
|
|
65
67
|
- Normalizes relative/absolute paths before matching so traversal tricks do not bypass the guard.
|
|
68
|
+
- Resolves write and edit paths like Pi's built-in tools (file URLs, `~` expansion, cwd) so percent-encoded URLs, tilde paths, and cwd-relative paths inside protected directories are all caught.
|
|
66
69
|
- Prompts with a simple `Yes` / `No` selector before allowing dangerous commands or protected path writes.
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* secret-bearing .env files (excluding example/template variants).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import * as os from "node:os";
|
|
10
11
|
import * as path from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
11
13
|
|
|
12
14
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
15
|
|
|
@@ -18,6 +20,12 @@ import {
|
|
|
18
20
|
parsePowerShellAstOutput,
|
|
19
21
|
} from "./powershell-safety.ts";
|
|
20
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Unicode space characters that Pi's path normalizer replaces with an ASCII space.
|
|
25
|
+
* Mirrors the UNICODE_SPACES constant in dist/utils/paths.js.
|
|
26
|
+
*/
|
|
27
|
+
const UNICODE_SPACES_PATTERN = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
28
|
+
|
|
21
29
|
const dangerousPatterns = [/\bsudo\b/i, /\b(chmod|chown)\b.*777/i];
|
|
22
30
|
const protectedPathSegments = new Set([".git", "node_modules"]);
|
|
23
31
|
const shellCommandSeparators = new Set([";", "&", "&&", "||", "|", "\n", "(", ")", "{", "}"]);
|
|
@@ -48,14 +56,96 @@ type ShellToken = {
|
|
|
48
56
|
quote?: '"' | "'";
|
|
49
57
|
};
|
|
50
58
|
|
|
51
|
-
type WriteInput = { path: string; content: string };
|
|
52
|
-
type EditInput = { path: string; edits: Array<{ oldText: string; newText: string }> };
|
|
53
59
|
type ShellInput = { command: string; timeout?: number };
|
|
54
60
|
|
|
55
61
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
56
62
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
57
63
|
}
|
|
58
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Converts Git Bash / MSYS / Cygwin / WSL drive paths to a form native Windows
|
|
67
|
+
* APIs accept. Mirrors normalizeWindowsShellPath from
|
|
68
|
+
* dist/utils/paths.js. No-op on non-win32 paths.
|
|
69
|
+
*/
|
|
70
|
+
function normalizeWindowsShellPathLocal(filePath: string): string {
|
|
71
|
+
if (!filePath.startsWith("/") || filePath.startsWith("//") || filePath.includes("\\"))
|
|
72
|
+
return filePath;
|
|
73
|
+
const match = filePath.match(/^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/i);
|
|
74
|
+
if (!match) return filePath;
|
|
75
|
+
const suffix = match[2]?.replaceAll("/", "\\");
|
|
76
|
+
return `${match[1].toUpperCase()}:\\${suffix ?? ""}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Mirrors Pi's normalizePath (dist/utils/paths.js).
|
|
81
|
+
*
|
|
82
|
+
* Options (all default false/true as noted):
|
|
83
|
+
* normalizeUnicodeSpaces — replace Unicode space characters with ASCII space.
|
|
84
|
+
* stripAtPrefix — strip a leading "@" path sigil.
|
|
85
|
+
* expandTilde — expand "~"/"~/" to homedir (default true).
|
|
86
|
+
*
|
|
87
|
+
* On win32, Git Bash / WSL drive-path normalisation always runs (not gated
|
|
88
|
+
* by expandTilde); only the tilde expansion itself is gated by expandTilde.
|
|
89
|
+
* Always resolves "file://" URLs via fileURLToPath; throws for non-local hosts
|
|
90
|
+
* (e.g. file://hostname/…) or invalid URLs.
|
|
91
|
+
*/
|
|
92
|
+
function normalizePath(
|
|
93
|
+
input: string,
|
|
94
|
+
opts: { normalizeUnicodeSpaces?: boolean; stripAtPrefix?: boolean; expandTilde?: boolean } = {},
|
|
95
|
+
): string {
|
|
96
|
+
const { normalizeUnicodeSpaces = false, stripAtPrefix = false, expandTilde = true } = opts;
|
|
97
|
+
let p = input;
|
|
98
|
+
if (normalizeUnicodeSpaces) p = p.replace(UNICODE_SPACES_PATTERN, " ");
|
|
99
|
+
if (stripAtPrefix && p.startsWith("@")) p = p.slice(1);
|
|
100
|
+
if (process.platform === "win32") p = normalizeWindowsShellPathLocal(p);
|
|
101
|
+
if (expandTilde) {
|
|
102
|
+
const home = os.homedir();
|
|
103
|
+
if (p === "~") p = home;
|
|
104
|
+
else if (p.startsWith("~/") || (process.platform === "win32" && p.startsWith("~\\")))
|
|
105
|
+
p = path.join(home, p.slice(2));
|
|
106
|
+
}
|
|
107
|
+
if (/^file:\/\//.test(p)) p = fileURLToPath(p);
|
|
108
|
+
return p;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Mirrors Pi's resolveToCwd (dist/core/tools/path-utils.js): normalises the
|
|
113
|
+
* raw path with { normalizeUnicodeSpaces: true, stripAtPrefix: true }, normalises
|
|
114
|
+
* cwd with default options (expandTilde: true, so win32 shell-path handling
|
|
115
|
+
* also applies to cwd as Pi does), then resolves absolute paths via
|
|
116
|
+
* path.resolve and relative paths against cwd.
|
|
117
|
+
*
|
|
118
|
+
* Exported for tests that check parity with Pi's resolveToCwd. Note: other
|
|
119
|
+
* extensions will not import this file; each extension keeps its own local copy.
|
|
120
|
+
*/
|
|
121
|
+
export function resolveToCwd(rawPath: string, cwd: string): string {
|
|
122
|
+
const normalized = normalizePath(rawPath, { normalizeUnicodeSpaces: true, stripAtPrefix: true });
|
|
123
|
+
const base = normalizePath(cwd);
|
|
124
|
+
return path.isAbsolute(normalized) ? path.resolve(normalized) : path.resolve(base, normalized);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
type PiCheckResult =
|
|
128
|
+
| { status: "protected"; resolvedPath: string }
|
|
129
|
+
| { status: "error" }
|
|
130
|
+
| { status: "allowed" };
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Resolves rawPath via the Pi-mirroring resolveToCwd, then checks whether the
|
|
134
|
+
* resulting absolute path is a protected path.
|
|
135
|
+
*/
|
|
136
|
+
function checkPiResolvedPath(rawPath: string, cwd: string): PiCheckResult {
|
|
137
|
+
try {
|
|
138
|
+
const resolved = resolveToCwd(rawPath, cwd);
|
|
139
|
+
const normalized = normalizeToolPath(resolved);
|
|
140
|
+
if (normalized !== undefined && isProtectedPath(normalized)) {
|
|
141
|
+
return { status: "protected", resolvedPath: resolved };
|
|
142
|
+
}
|
|
143
|
+
return { status: "allowed" };
|
|
144
|
+
} catch {
|
|
145
|
+
return { status: "error" };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
59
149
|
function normalizeToolPath(rawPath: string): NormalizedPath | undefined {
|
|
60
150
|
const trimmed = rawPath.trim();
|
|
61
151
|
if (!trimmed) return undefined;
|
|
@@ -636,21 +726,20 @@ async function hasDangerousPowerShellCommand(
|
|
|
636
726
|
}
|
|
637
727
|
}
|
|
638
728
|
|
|
639
|
-
function validateWriteInput(input: unknown):
|
|
729
|
+
function validateWriteInput(input: unknown): NormalizedPath | undefined {
|
|
640
730
|
if (!isRecord(input)) return undefined;
|
|
641
731
|
if (typeof input.path !== "string" || typeof input.content !== "string") return undefined;
|
|
642
|
-
|
|
643
|
-
return { path: input.path, content: input.content };
|
|
732
|
+
return normalizeToolPath(input.path);
|
|
644
733
|
}
|
|
645
734
|
|
|
646
|
-
function validateEditInput(input: unknown):
|
|
735
|
+
function validateEditInput(input: unknown): NormalizedPath | undefined {
|
|
647
736
|
if (!isRecord(input)) return undefined;
|
|
648
737
|
if (typeof input.path !== "string" || !Array.isArray(input.edits)) return undefined;
|
|
649
|
-
|
|
738
|
+
const normalizedPath = normalizeToolPath(input.path);
|
|
739
|
+
if (!normalizedPath) return undefined;
|
|
650
740
|
|
|
651
|
-
const edits = input.edits;
|
|
652
741
|
if (
|
|
653
|
-
edits.some(
|
|
742
|
+
input.edits.some(
|
|
654
743
|
(edit) =>
|
|
655
744
|
!isRecord(edit) || typeof edit.oldText !== "string" || typeof edit.newText !== "string",
|
|
656
745
|
)
|
|
@@ -658,10 +747,7 @@ function validateEditInput(input: unknown): EditInput | undefined {
|
|
|
658
747
|
return undefined;
|
|
659
748
|
}
|
|
660
749
|
|
|
661
|
-
return
|
|
662
|
-
path: input.path,
|
|
663
|
-
edits: edits as Array<{ oldText: string; newText: string }>,
|
|
664
|
-
};
|
|
750
|
+
return normalizedPath;
|
|
665
751
|
}
|
|
666
752
|
|
|
667
753
|
async function requestConfirmation(
|
|
@@ -691,17 +777,22 @@ async function confirmProtectedPathAction(
|
|
|
691
777
|
toolName: "write" | "edit",
|
|
692
778
|
normalizedPath: NormalizedPath,
|
|
693
779
|
ctx: ConfirmationContext,
|
|
780
|
+
alsoProtected = false,
|
|
781
|
+
displaySuffix?: string,
|
|
694
782
|
): Promise<GuardDecision> {
|
|
695
|
-
if (!isProtectedPath(normalizedPath)) return undefined;
|
|
783
|
+
if (!alsoProtected && !isProtectedPath(normalizedPath)) return undefined;
|
|
784
|
+
const displayLabel = displaySuffix
|
|
785
|
+
? `${normalizedPath.displayPath}${displaySuffix}`
|
|
786
|
+
: normalizedPath.displayPath;
|
|
696
787
|
if (!ctx.hasUI || !ctx.ui) {
|
|
697
788
|
return {
|
|
698
789
|
block: true,
|
|
699
|
-
reason: `Protected path blocked (${toolName} without UI confirmation): ${
|
|
790
|
+
reason: `Protected path blocked (${toolName} without UI confirmation): ${displayLabel}`,
|
|
700
791
|
};
|
|
701
792
|
}
|
|
702
793
|
|
|
703
794
|
return requestConfirmation(
|
|
704
|
-
`⚠️ Protected path ${toolName} request:\n\n ${
|
|
795
|
+
`⚠️ Protected path ${toolName} request:\n\n ${displayLabel}\n\nAllow?`,
|
|
705
796
|
ctx,
|
|
706
797
|
);
|
|
707
798
|
}
|
|
@@ -734,27 +825,59 @@ export default function (pi: ExtensionAPI) {
|
|
|
734
825
|
}
|
|
735
826
|
|
|
736
827
|
if (event.toolName === "write") {
|
|
737
|
-
const
|
|
738
|
-
if (!input) {
|
|
739
|
-
return { block: true, reason: "Malformed write input blocked" };
|
|
740
|
-
}
|
|
741
|
-
const normalizedPath = normalizeToolPath(input.path);
|
|
828
|
+
const normalizedPath = validateWriteInput(event.input);
|
|
742
829
|
if (!normalizedPath) {
|
|
743
830
|
return { block: true, reason: "Malformed write input blocked" };
|
|
744
831
|
}
|
|
745
|
-
|
|
832
|
+
// Monotonic hardening: also check via Pi-style path resolution against ctx.cwd.
|
|
833
|
+
// OR logic: protected if EITHER the lexical check OR the Pi-resolved check matches.
|
|
834
|
+
// When only the Pi-resolved check catches a path, show the resolved absolute path in
|
|
835
|
+
// the reason so the user understands why the plain-looking path is protected.
|
|
836
|
+
const lexicalProtected = isProtectedPath(normalizedPath);
|
|
837
|
+
const piResult = checkPiResolvedPath(normalizedPath.original, ctx.cwd ?? process.cwd());
|
|
838
|
+
const piAlsoProtected = piResult.status === "protected" || piResult.status === "error";
|
|
839
|
+
const displaySuffix = lexicalProtected
|
|
840
|
+
? undefined
|
|
841
|
+
: piResult.status === "protected"
|
|
842
|
+
? ` (resolves to ${piResult.resolvedPath})`
|
|
843
|
+
: piResult.status === "error"
|
|
844
|
+
? " (path could not be resolved)"
|
|
845
|
+
: undefined;
|
|
846
|
+
return confirmProtectedPathAction(
|
|
847
|
+
"write",
|
|
848
|
+
normalizedPath,
|
|
849
|
+
ctx,
|
|
850
|
+
piAlsoProtected,
|
|
851
|
+
displaySuffix,
|
|
852
|
+
);
|
|
746
853
|
}
|
|
747
854
|
|
|
748
855
|
if (event.toolName === "edit") {
|
|
749
|
-
const
|
|
750
|
-
if (!input) {
|
|
751
|
-
return { block: true, reason: "Malformed edit input blocked" };
|
|
752
|
-
}
|
|
753
|
-
const normalizedPath = normalizeToolPath(input.path);
|
|
856
|
+
const normalizedPath = validateEditInput(event.input);
|
|
754
857
|
if (!normalizedPath) {
|
|
755
858
|
return { block: true, reason: "Malformed edit input blocked" };
|
|
756
859
|
}
|
|
757
|
-
|
|
860
|
+
// Monotonic hardening: also check via Pi-style path resolution against ctx.cwd.
|
|
861
|
+
// OR logic: protected if EITHER the lexical check OR the Pi-resolved check matches.
|
|
862
|
+
// When only the Pi-resolved check catches a path, show the resolved absolute path in
|
|
863
|
+
// the reason so the user understands why the plain-looking path is protected.
|
|
864
|
+
const lexicalProtected = isProtectedPath(normalizedPath);
|
|
865
|
+
const piResult = checkPiResolvedPath(normalizedPath.original, ctx.cwd ?? process.cwd());
|
|
866
|
+
const piAlsoProtected = piResult.status === "protected" || piResult.status === "error";
|
|
867
|
+
const displaySuffix = lexicalProtected
|
|
868
|
+
? undefined
|
|
869
|
+
: piResult.status === "protected"
|
|
870
|
+
? ` (resolves to ${piResult.resolvedPath})`
|
|
871
|
+
: piResult.status === "error"
|
|
872
|
+
? " (path could not be resolved)"
|
|
873
|
+
: undefined;
|
|
874
|
+
return confirmProtectedPathAction(
|
|
875
|
+
"edit",
|
|
876
|
+
normalizedPath,
|
|
877
|
+
ctx,
|
|
878
|
+
piAlsoProtected,
|
|
879
|
+
displaySuffix,
|
|
880
|
+
);
|
|
758
881
|
}
|
|
759
882
|
|
|
760
883
|
return undefined;
|
|
@@ -89,10 +89,10 @@ if a parent directory is trusted.
|
|
|
89
89
|
Other approaches and their limitations:
|
|
90
90
|
|
|
91
91
|
- **Delete the entry (or set it to `null`)** in `trust.json`: this resumes
|
|
92
|
-
nearest-ancestor lookup (step
|
|
92
|
+
nearest-ancestor lookup (step 3), *not* a direct fallback to
|
|
93
93
|
`defaultProjectTrust`. If any ancestor directory has a saved `true`, that
|
|
94
94
|
decision is found first and loading is re-enabled. `defaultProjectTrust`
|
|
95
|
-
(step
|
|
95
|
+
(step 4) only applies when the nearest-ancestor walk finds no decision at
|
|
96
96
|
all. Deleting/nulling an entry is therefore *not* a reliable denial when
|
|
97
97
|
any ancestor path is trusted.
|
|
98
98
|
- **Set `defaultProjectTrust` to `"never"`** in `~/.pi/agent/settings.json`:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-project-mcp-json",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A pi extension that reads Claude Code-style .mcp.json files and registers the configured servers with pi.registerMcpServer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1.1.0
|
|
@@ -1,33 +1,45 @@
|
|
|
1
1
|
# quiet-tools
|
|
2
2
|
|
|
3
|
-
A pi extension that makes collapsed built-in tool rows
|
|
3
|
+
A pi extension that makes collapsed built-in tool rows and MCP tool rows quieter in the TUI.
|
|
4
4
|
|
|
5
|
-
When
|
|
5
|
+
When loaded on a supported pi host, quiet rendering is always on: each collapsed tool row shows one invocation line plus a separate `(Ctrl+O to expand)` hint line. Tool output is hidden until expanded. The global `Ctrl+O` binding, per-row expansion, and native expanded result rendering remain available.
|
|
6
6
|
|
|
7
|
-
`quiet-tools` only changes the visual renderer. It does not truncate, summarize, or rewrite the actual tool results sent to the model.
|
|
7
|
+
`quiet-tools` only changes the visual renderer. It does not truncate, summarize, or rewrite the actual tool results sent to the model. Expanded rendering delegates back to pi's renderer, preserving pi's default output for every tool.
|
|
8
|
+
|
|
9
|
+
## How it works
|
|
10
|
+
|
|
11
|
+
On pi >=1.0.1, `quiet-tools` registers a single `pi.registerToolRenderer` resolver. The resolver intercepts collapsed rows for the 7 built-in tools and any `mcp__*` tool and renders them quietly. All other tools are passed through to pi's default rendering. There is no opt-out setting, environment variable, or persistence mechanism.
|
|
12
|
+
|
|
13
|
+
On pi <1.0.1 (where `registerToolRenderer` is not available), the extension registers no renderers and shows a one-time warning notification on session start. The read-only `/quiet-tools status` command remains available, but rendering is inactive.
|
|
8
14
|
|
|
9
15
|
## Covered tools
|
|
10
16
|
|
|
11
|
-
- `bash`
|
|
12
|
-
|
|
13
|
-
- `grep`
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- `edit`
|
|
17
|
-
- `write`
|
|
17
|
+
**Built-in tools:** `bash`, `edit`, `find`, `grep`, `ls`, `read`, `write`
|
|
18
|
+
|
|
19
|
+
Each built-in has a compact one-line summary (for example, `$ echo hello`, `read ~/project/notes.txt:1-20`, or `grep /TODO/ in src/`). Collapsed error and aborted rows retain a terse `[failed]` marker; the full native result remains available when expanded.
|
|
20
|
+
|
|
21
|
+
**MCP tools:** any tool whose name starts with `mcp__`
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
MCP collapsed call lines show the tool name followed by compact JSON arguments (sanitized to a single line and truncated to the available TUI width). MCP collapsed results render empty, like built-in collapsed results.
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
For every covered tool, the collapsed invocation is truncated to a single visual line. Expanding restores pi's normal renderer.
|
|
26
|
+
|
|
27
|
+
## Command
|
|
22
28
|
|
|
23
29
|
```text
|
|
30
|
+
/quiet-tools
|
|
24
31
|
/quiet-tools status
|
|
25
|
-
/quiet-tools off
|
|
26
|
-
/quiet-tools on
|
|
27
|
-
/quiet-tools toggle
|
|
28
32
|
```
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
Both forms report the always-on status. `status` is the only completion and valid action; other arguments show `Usage: /quiet-tools status` and cannot change rendering.
|
|
35
|
+
|
|
36
|
+
## Export-style contexts
|
|
37
|
+
|
|
38
|
+
Quiet call headers are also used in export-style renderer contexts. As a result, an export may omit a native write-call preview; this is an accepted tradeoff. Native expanded result rendering remains available in the TUI, including native error text, timing, and image handling.
|
|
39
|
+
|
|
40
|
+
## Requirements
|
|
41
|
+
|
|
42
|
+
Pi >=1.0.1 is required for quiet rendering. On older pi hosts, the extension loads without error and the status command remains available, but rendering is inactive.
|
|
31
43
|
|
|
32
44
|
## Install
|
|
33
45
|
|
|
@@ -57,8 +69,7 @@ Then reload pi:
|
|
|
57
69
|
|
|
58
70
|
## Notes
|
|
59
71
|
|
|
60
|
-
-
|
|
61
|
-
- It reuses pi's built-in implementations and preserves `shellPath`, `shellCommandPrefix`, and image autoresize settings when they are available from settings files.
|
|
62
|
-
- If another extension also overrides built-in tool execution, pi's extension load order determines which override wins.
|
|
72
|
+
- `quiet-tools` uses pi's `registerToolRenderer` API to wrap only visual renderers, without touching tool execution, metadata, active-tool selection, or model-visible results.
|
|
63
73
|
- It affects assistant-invoked tool rows. User `!`/`!!` bash commands are rendered by a separate pi component and keep pi's default preview behavior.
|
|
64
74
|
- Pi renders image attachments outside tool result renderers, so inline image display for image reads is still controlled by pi's image settings.
|
|
75
|
+
- MCP tool argument lines are sanitized (ANSI and control characters) and truncated to fit the TUI width. Expand to see full output.
|