/** * Minimal Mode Example - Demonstrates a "minimal" tool display mode * * This extension overrides built-in tools to provide custom rendering: * - Collapsed mode: Only shows the tool call (command/path), no output * - Expanded mode: Shows full output like the built-in renderers * * This demonstrates how a "minimal mode" could work, where ctrl+o cycles through: * - Standard: Shows truncated output (current default) * - Expanded: Shows full output (current expanded) * - Minimal: Shows only tool call, no output (this extension's collapsed mode) * * Usage: * pi -e ./minimal-mode.ts * * Then use ctrl+o to toggle between minimal (collapsed) and full (expanded) views. */ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, } from "@earendil-works/pi-coding-agent"; import { Text } from "@earendil-works/pi-tui"; import { homedir } from "os"; /** * Shorten a path by replacing home directory with ~ */ function shortenPath(path: string): string { const home = homedir(); if (path.startsWith(home)) { return `~${path.slice(home.length)}`; } return path; } function createBuiltInTools(cwd: string) { return { read: createReadToolDefinition(cwd), bash: createBashToolDefinition(cwd), edit: createEditToolDefinition(cwd), write: createWriteToolDefinition(cwd), find: createFindToolDefinition(cwd), grep: createGrepToolDefinition(cwd), ls: createLsToolDefinition(cwd), }; } export default function (pi: ExtensionAPI) { const tools = createBuiltInTools(process.cwd()); // ========================================================================= // Read Tool // ========================================================================= pi.registerTool({ ...tools.read, renderCall(args, theme, _context) { const path = shortenPath(args.path || ""); let pathDisplay = path ? theme.fg("accent", path) : theme.fg("toolOutput", "..."); // Show line range if specified if (args.offset !== undefined || args.limit !== undefined) { const startLine = args.offset ?? 1; const endLine = args.limit !== undefined ? startLine + args.limit - 1 : ""; pathDisplay += theme.fg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`); } return new Text(`${theme.fg("toolTitle", theme.bold("read"))} ${pathDisplay}`, 0, 0); }, renderResult(result, { expanded }, theme, _context) { // Minimal mode: show nothing in collapsed state if (!expanded) { return new Text("", 0, 0); } // Expanded mode: show full output const textContent = result.content.find((c) => c.type === "text"); if (!textContent || textContent.type !== "text") { return new Text("", 0, 0); } const lines = textContent.text.split("\n"); const output = lines.map((line) => theme.fg("toolOutput", line)).join("\n"); return new Text(`\n${output}`, 0, 0); }, }); // ========================================================================= // Bash Tool // ========================================================================= pi.registerTool({ ...tools.bash, renderCall(args, theme, _context) { const command = args.command || "..."; const timeout = args.timeout as number | undefined; const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : ""; return new Text(theme.fg("toolTitle", theme.bold(`$ ${command}`)) + timeoutSuffix, 0, 0); }, renderResult(result, { expanded }, theme, _context) { // Minimal mode: show nothing in collapsed state if (!expanded) { return new Text("", 0, 0); } // Expanded mode: show full output const textContent = result.content.find((c) => c.type === "text"); if (!textContent || textContent.type !== "text") { return new Text("", 0, 0); } const output = textContent.text .trim() .split("\n") .map((line) => theme.fg("toolOutput", line)) .join("\n"); if (!output) { return new Text("", 0, 0); } return new Text(`\n${output}`, 0, 0); }, }); // ========================================================================= // Write Tool // ========================================================================= pi.registerTool({ ...tools.write, renderCall(args, theme, _context) { const path = shortenPath(args.path || ""); const pathDisplay = path ? theme.fg("accent", path) : theme.fg("toolOutput", "..."); const lineCount = args.content ? args.content.split("\n").length : 0; const lineInfo = lineCount > 0 ? theme.fg("muted", ` (${lineCount} lines)`) : ""; return new Text(`${theme.fg("toolTitle", theme.bold("write"))} ${pathDisplay}${lineInfo}`, 0, 0); }, renderResult(result, { expanded }, theme, _context) { // Minimal mode: show nothing (file was written) if (!expanded) { return new Text("", 0, 0); } // Expanded mode: show error if any if (result.content.some((c) => c.type === "text" && c.text)) { const textContent = result.content.find((c) => c.type === "text"); if (textContent?.type === "text" && textContent.text) { return new Text(`\n${theme.fg("error", textContent.text)}`, 0, 0); } } return new Text("", 0, 0); }, }); // ========================================================================= // Edit Tool // ========================================================================= pi.registerTool({ ...tools.edit, renderShell: "default", renderCall(args, theme, _context) { const path = shortenPath(args.path || ""); const pathDisplay = path ? theme.fg("accent", path) : theme.fg("toolOutput", "..."); return new Text(`${theme.fg("toolTitle", theme.bold("edit"))} ${pathDisplay}`, 0, 0); }, renderResult(result, { expanded }, theme, _context) { // Minimal mode: show nothing in collapsed state if (!expanded) { return new Text("", 0, 0); } // Expanded mode: show diff or error const textContent = result.content.find((c) => c.type === "text"); if (!textContent || textContent.type !== "text") { return new Text("", 0, 0); } // For errors, show the error message const text = textContent.text; if (text.includes("Error") || text.includes("error")) { return new Text(`\n${theme.fg("error", text)}`, 0, 0); } // Otherwise show the text (would be nice to show actual diff here) return new Text(`\n${theme.fg("toolOutput", text)}`, 0, 0); }, }); // ========================================================================= // Find Tool // ========================================================================= pi.registerTool({ ...tools.find, renderCall(args, theme, _context) { const pattern = args.pattern || ""; const path = shortenPath(args.path || "."); const limit = args.limit; let text = `${theme.fg("toolTitle", theme.bold("find"))} ${theme.fg("accent", pattern)}`; text += theme.fg("toolOutput", ` in ${path}`); if (limit !== undefined) { text += theme.fg("toolOutput", ` (limit ${limit})`); } return new Text(text, 0, 0); }, renderResult(result, { expanded }, theme, _context) { if (!expanded) { // Minimal: just show count const textContent = result.content.find((c) => c.type === "text"); if (textContent?.type === "text") { const count = textContent.text.trim().split("\n").filter(Boolean).length; if (count > 0) { return new Text(theme.fg("muted", ` → ${count} files`), 0, 0); } } return new Text("", 0, 0); } // Expanded: show full results const textContent = result.content.find((c) => c.type === "text"); if (!textContent || textContent.type !== "text") { return new Text("", 0, 0); } const output = textContent.text .trim() .split("\n") .map((line) => theme.fg("toolOutput", line)) .join("\n"); return new Text(`\n${output}`, 0, 0); }, }); // ========================================================================= // Grep Tool // ========================================================================= pi.registerTool({ ...tools.grep, renderCall(args, theme, _context) { const pattern = args.pattern || ""; const path = shortenPath(args.path || "."); const glob = args.glob; const limit = args.limit; let text = `${theme.fg("toolTitle", theme.bold("grep"))} ${theme.fg("accent", `/${pattern}/`)}`; text += theme.fg("toolOutput", ` in ${path}`); if (glob) { text += theme.fg("toolOutput", ` (${glob})`); } if (limit !== undefined) { text += theme.fg("toolOutput", ` limit ${limit}`); } return new Text(text, 0, 0); }, renderResult(result, { expanded }, theme, _context) { if (!expanded) { // Minimal: just show match count const textContent = result.content.find((c) => c.type === "text"); if (textContent?.type === "text") { const count = textContent.text.trim().split("\n").filter(Boolean).length; if (count > 0) { return new Text(theme.fg("muted", ` → ${count} matches`), 0, 0); } } return new Text("", 0, 0); } // Expanded: show full results const textContent = result.content.find((c) => c.type === "text"); if (!textContent || textContent.type !== "text") { return new Text("", 0, 0); } const output = textContent.text .trim() .split("\n") .map((line) => theme.fg("toolOutput", line)) .join("\n"); return new Text(`\n${output}`, 0, 0); }, }); // ========================================================================= // Ls Tool // ========================================================================= pi.registerTool({ ...tools.ls, renderCall(args, theme, _context) { const path = shortenPath(args.path || "."); const limit = args.limit; let text = `${theme.fg("toolTitle", theme.bold("ls"))} ${theme.fg("accent", path)}`; if (limit !== undefined) { text += theme.fg("toolOutput", ` (limit ${limit})`); } return new Text(text, 0, 0); }, renderResult(result, { expanded }, theme, _context) { if (!expanded) { // Minimal: just show entry count const textContent = result.content.find((c) => c.type === "text"); if (textContent?.type === "text") { const count = textContent.text.trim().split("\n").filter(Boolean).length; if (count > 0) { return new Text(theme.fg("muted", ` → ${count} entries`), 0, 0); } } return new Text("", 0, 0); } // Expanded: show full listing const textContent = result.content.find((c) => c.type === "text"); if (!textContent || textContent.type !== "text") { return new Text("", 0, 0); } const output = textContent.text .trim() .split("\n") .map((line) => theme.fg("toolOutput", line)) .join("\n"); return new Text(`\n${output}`, 0, 0); }, }); }